Completed
Push — master ( 61e87b...b8e375 )
by Harry
01:35
created

DiffConsoleOutput::reWrite()   B

Complexity

Conditions 6
Paths 11

Size

Total Lines 30
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 30
rs 8.439
c 0
b 0
f 0
cc 6
eloc 18
nc 11
nop 3
1
<?php
2
3
/**
4
 * This file is part of graze/console-diff-renderer.
5
 *
6
 * Copyright (c) 2017 Nature Delivered Ltd. <https://www.graze.com>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 *
11
 * @license https://github.com/graze/console-diff-renderer/blob/master/LICENSE.md
12
 * @link    https://github.com/graze/console-diff-renderer
13
 */
14
15
namespace Graze\DiffRenderer;
16
17
use Graze\DiffRenderer\Diff\ConsoleDiff;
18
use Graze\DiffRenderer\Terminal\Terminal;
19
use Graze\DiffRenderer\Terminal\TerminalInterface;
20
use Graze\DiffRenderer\Wrap\Wrapper;
21
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
22
use Symfony\Component\Console\Output\OutputInterface;
23
24
/**
25
 * This takes an array of lines to write to the console, does a different and only over-writes what has changed to the
26
 * console
27
 */
28
class DiffConsoleOutput implements OutputInterface
29
{
30
    /** @var string[] */
31
    private $buffer = [];
32
    /** @var ConsoleDiff */
33
    private $diff;
34
    /** @var TerminalInterface */
35
    private $terminal;
36
    /** @var OutputInterface */
37
    private $output;
38
    /** @var Wrapper */
39
    private $wrapper;
40
    /** @var bool */
41
    private $trim = false;
42
43
    /**
44
     * Constructor.
45
     *
46
     * @param OutputInterface   $output
47
     * @param TerminalInterface $terminal
48
     * @param Wrapper           $wrapper
49
     */
50
    public function __construct(
51
        OutputInterface $output,
52
        TerminalInterface $terminal = null,
53
        Wrapper $wrapper = null
54
    ) {
55
        $this->output = $output;
56
        $this->terminal = $terminal ?: new Terminal();
57
        $this->diff = new ConsoleDiff($terminal);
58
        $this->wrapper = $wrapper ?: new Wrapper($this->terminal);
59
    }
60
61
    /**
62
     * @param string|array $messages The message as an array of lines or a single string
63
     * @param bool         $newline  Whether to add a newline
64
     * @param int          $options  A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered
65
     *                               the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL
66
     */
67
    public function write($messages, $newline = false, $options = 0)
68
    {
69
        $this->buffer = [];
70
        $this->output->write($messages, $newline, $options);
71
    }
72
73
    /**
74
     * @param string|string[] $messages The message as an array of lines or a single string
75
     * @param bool            $newline  Whether to add a newline
76
     * @param int             $options  A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered
77
     *                                  the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL
78
     */
79
    public function reWrite($messages, $newline = false, $options = 0)
80
    {
81
        $messages = $this->format($messages, $options);
82
        if (count($messages) === 0) {
83
            return;
84
        }
85
86
        $messages = $this->splitNewLines($messages);
87
        $messages = ($this->trim) ? $this->wrapper->trim($messages) : $this->wrapper->wrap($messages);
88
89
        $outputOptions = self::OUTPUT_RAW | $this->output->getVerbosity();
90
91
        if (count($this->buffer) === 0) {
92
            $this->buffer = $messages;
93
            $this->output->write($messages, $newline, $outputOptions);
94
            return;
95
        }
96
97
        $sizeDiff = ($newline ? 1 : 0);
98
        if (count($messages) + $sizeDiff > $this->terminal->getHeight()) {
99
            $messages = array_slice($messages, count($messages) + $sizeDiff - $this->terminal->getHeight());
100
        }
101
102
        $diff = $this->diff->lines($this->buffer, $messages);
103
104
        $output = $this->buildOutput($diff, $newline);
105
        $this->buffer = $messages;
106
107
        $this->output->write($output, $newline, $outputOptions);
108
    }
109
110
    /**
111
     * @param string|string[] $messages The message as an array of lines or a single string
112
     * @param int             $options  A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered
113
     *                                  the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL
114
     *
115
     * @return \string[]
116
     */
117
    private function format($messages, $options = 0)
118
    {
119
        $messages = (array) $messages;
120
121
        $types = self::OUTPUT_NORMAL | self::OUTPUT_RAW | self::OUTPUT_PLAIN;
122
        $type = $types & $options ?: self::OUTPUT_NORMAL;
123
124
        $verbosities = self::VERBOSITY_QUIET | self::VERBOSITY_NORMAL | self::VERBOSITY_VERBOSE | self::VERBOSITY_VERY_VERBOSE | self::VERBOSITY_DEBUG;
125
        $verbosity = $verbosities & $options ?: self::VERBOSITY_NORMAL;
126
127
        if ($verbosity > $this->getVerbosity()) {
128
            return [];
129
        }
130
131
        $formatter = $this->output->getFormatter();
132
133
        return array_map(function ($message) use ($type, $formatter) {
134
            switch ($type) {
135
                case OutputInterface::OUTPUT_NORMAL:
136
                    return $formatter->format($message);
137
                case OutputInterface::OUTPUT_PLAIN:
138
                    return strip_tags($formatter->format($message));
139
                case OutputInterface::OUTPUT_RAW:
140
                default:
141
                    return $message;
142
            }
143
        }, $messages);
144
    }
145
146
    /**
147
     * @param string|string[] $messages
148
     *
149
     * @return string[]
150
     */
151
    private function splitNewLines($messages)
152
    {
153
        $exploded = array_map(function ($line) {
154
            return explode("\n", $line);
155
        }, (array) $messages);
156
        $out = [];
157
        array_walk_recursive($exploded, function ($a) use (&$out) {
158
            $out[] = $a;
159
        });
160
        return $out;
161
    }
162
163
    /**
164
     * @return TerminalInterface
165
     */
166
    public function getTerminal()
167
    {
168
        return $this->terminal;
169
    }
170
171
    /**
172
     * @param array $diff
173
     * @param bool  $newline
174
     *
175
     * @return string
176
     */
177
    private function buildOutput(array $diff, $newline = false)
178
    {
179
        $buffer = '';
180
181
        // reset cursor position
182
        $count = count($this->buffer);
183
        $mod = ($newline ? 1 : 0);
184
        $up = ($count > 0 ? $count - 1 + $mod : $mod);
185
        if ($up > 0) {
186
            $buffer .= $this->terminal->moveUp($up);
187
        }
188
        $buffer .= "\r";
189
190
        $diffSize = count($diff);
191
192
        for ($i = 0; $i < $diffSize; $i++) {
193
            $d = $diff[$i];
194
            if ($i !== 0) {
195
                $buffer .= PHP_EOL . "\r";
196
            }
197
            if (!is_null($d)) {
198
                if ($d['col'] > 0) {
199
                    $buffer .= $this->terminal->moveRight($d['col']);
200
                }
201
                $buffer .= $this->terminal->eraseToEnd() . $d['str'];
202
            }
203
        }
204
205
        return $buffer;
206
    }
207
208
    /**
209
     * Writes a message to the output and adds a newline at the end.
210
     *
211
     * @param string|array $messages The message as an array of lines of a single string
212
     * @param int          $options  A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered
213
     *                               the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL
214
     */
215
    public function writeln($messages, $options = 0)
216
    {
217
        $this->write($messages, true, $options);
218
    }
219
220
    /**
221
     * Sets the verbosity of the output.
222
     *
223
     * @param int $level The level of verbosity (one of the VERBOSITY constants)
224
     */
225
    public function setVerbosity($level)
226
    {
227
        $this->output->setVerbosity($level);
228
    }
229
230
    /**
231
     * Gets the current verbosity of the output.
232
     *
233
     * @return int The current level of verbosity (one of the VERBOSITY constants)
234
     */
235
    public function getVerbosity()
236
    {
237
        return $this->output->getVerbosity();
238
    }
239
240
    /**
241
     * Returns whether verbosity is quiet (-q).
242
     *
243
     * @return bool true if verbosity is set to VERBOSITY_QUIET, false otherwise
244
     */
245
    public function isQuiet()
246
    {
247
        return $this->output->isQuiet();
248
    }
249
250
    /**
251
     * Returns whether verbosity is verbose (-v).
252
     *
253
     * @return bool true if verbosity is set to VERBOSITY_VERBOSE, false otherwise
254
     */
255
    public function isVerbose()
256
    {
257
        return $this->output->isVerbose();
258
    }
259
260
    /**
261
     * Returns whether verbosity is very verbose (-vv).
262
     *
263
     * @return bool true if verbosity is set to VERBOSITY_VERY_VERBOSE, false otherwise
264
     */
265
    public function isVeryVerbose()
266
    {
267
        return $this->output->isVeryVerbose();
268
    }
269
270
    /**
271
     * Returns whether verbosity is debug (-vvv).
272
     *
273
     * @return bool true if verbosity is set to VERBOSITY_DEBUG, false otherwise
274
     */
275
    public function isDebug()
276
    {
277
        return $this->output->isDebug();
278
    }
279
280
    /**
281
     * Sets the decorated flag.
282
     *
283
     * @param bool $decorated Whether to decorate the messages
284
     */
285
    public function setDecorated($decorated)
286
    {
287
        $this->output->setDecorated($decorated);
288
    }
289
290
    /**
291
     * Gets the decorated flag.
292
     *
293
     * @return bool true if the output will decorate messages, false otherwise
294
     */
295
    public function isDecorated()
296
    {
297
        return $this->output->isDecorated();
298
    }
299
300
    /**
301
     * Sets output formatter.
302
     *
303
     * @param OutputFormatterInterface $formatter
304
     */
305
    public function setFormatter(OutputFormatterInterface $formatter)
306
    {
307
        $this->output->setFormatter($formatter);
308
    }
309
310
    /**
311
     * Returns current output formatter instance.
312
     *
313
     * @return OutputFormatterInterface
314
     */
315
    public function getFormatter()
316
    {
317
        return $this->output->getFormatter();
318
    }
319
320
    /**
321
     * @return bool
322
     */
323
    public function isTrim()
324
    {
325
        return $this->trim;
326
    }
327
328
    /**
329
     * Should we wrap the input or not, if this is set to false, it will trim each line
330
     *
331
     * @param bool $trim
332
     *
333
     * @return $this
334
     */
335
    public function setTrim($trim)
336
    {
337
        $this->trim = $trim;
338
        return $this;
339
    }
340
}
341