Passed
Push — master ( 84e31b...82e47b )
by Alec
02:28
created

Spinner::calcEraseSequence()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 9
c 1
b 0
f 0
dl 0
loc 15
ccs 10
cts 10
cp 1
rs 9.9666
cc 2
nc 2
nop 1
crap 2
1
<?php declare(strict_types=1);
2
3
namespace AlecRabbit\Spinner\Core;
4
5
use AlecRabbit\Cli\Tools\Core\Terminal;
6
use AlecRabbit\Cli\Tools\Cursor;
7
use AlecRabbit\Spinner\Core\Coloring\Colors;
8
use AlecRabbit\Spinner\Core\Contracts\OutputInterface;
9
use AlecRabbit\Spinner\Core\Contracts\SpinnerInterface;
10
use AlecRabbit\Spinner\Core\Jugglers\Contracts\JugglerInterface;
11
use AlecRabbit\Spinner\Core\Jugglers\FrameJuggler;
12
use AlecRabbit\Spinner\Core\Jugglers\MessageJuggler;
13
use AlecRabbit\Spinner\Core\Jugglers\ProgressJuggler;
14
use AlecRabbit\Spinner\Settings\Contracts\Defaults;
15
use AlecRabbit\Spinner\Settings\Settings;
16
use const AlecRabbit\ESC;
17
use const AlecRabbit\NO_COLOR_TERMINAL;
18
19
abstract class Spinner extends SpinnerCore
20
{
21
    /** @var Settings */
22
    protected $settings;
23
    /** @var bool */
24
    protected $inline = false;
25
    /** @var null|MessageJuggler */
26
    protected $messageJuggler;
27
    /** @var null|FrameJuggler */
28
    protected $frameJuggler;
29
    /** @var null|ProgressJuggler */
30
    protected $progressJuggler;
31
    /** @var string */
32
    protected $moveCursorBackSequence = self::EMPTY_STRING;
33
    /** @var string */
34
    protected $eraseBySpacesSequence = self::EMPTY_STRING;
35
    /** @var int */
36
    protected $previousErasingLength = 0;
37
    /** @var Colors */
38
    protected $coloring;
39
    /** @var null[]|JugglerInterface[] */
40
    protected $jugglers = [];
41
    /** @var string */
42
    protected $lastSpinnerString = self::EMPTY_STRING;
43
    /** @var string */
44
    protected $inlinePaddingStr = self::EMPTY_STRING;
45
    /** @var bool */
46
    protected $hideCursor;
47
    /** @var int */
48
    protected $inlinePaddingStrLength;
49
    /** @var string */
50
    protected $inlinePaddingStrValue;
51
52
    /**
53
     * Spinner constructor.
54
     *
55
     * @param null|string|Settings $messageOrSettings
56
     * @param null|false|OutputInterface $output
57
     * @param null|int $color
58
     */
59 30
    public function __construct($messageOrSettings = null, $output = null, ?int $color = null)
60
    {
61 30
        $this->output = $this->refineOutput($output);
62 28
        $this->settings = $this->refineSettings($messageOrSettings);
63 27
        $this->interval = $this->settings->getInterval();
64 27
        $this->hideCursor = $this->settings->getHideCursor();
65 27
        $this->enabled = $this->settings->isEnabled();
66 27
        $this->inlinePaddingStrValue = $this->settings->getInlinePaddingStr();
67 27
        $this->updateInlinePaddingStrProperties();
68 27
        $this->coloring = new Colors($this->settings->getStyles(), $this->refineColor($color));
69 27
        $jugglerOrder = $this->settings->getJugglersOrder();
70 27
        $this->jugglers = [
71 27
            $jugglerOrder[0] => &$this->frameJuggler,
72 27
            $jugglerOrder[1] => &$this->messageJuggler,
73 27
            $jugglerOrder[2] => &$this->progressJuggler,
74
        ];
75 27
        ksort($this->jugglers);
76 27
        $this->initJugglers();
77 27
    }
78
79
    /**
80
     * @param null|int $color
81
     * @return int
82
     */
83 27
    protected function refineColor(?int $color): int
84
    {
85 27
        if (null === $color && $this->output instanceof OutputInterface) {
86 1
            $color = Terminal::colorSupport($this->output->getStream());
87
        }
88 27
        return $color ?? NO_COLOR_TERMINAL;
89
    }
90
91 27
    protected function initJugglers(): void
92
    {
93 27
        $frames = $this->settings->getFrames();
94 27
        if (!empty($frames)) {
95 21
            $this->frameJuggler =
96 21
                new FrameJuggler($this->settings, $this->coloring->getFrameStyles());
97
        }
98
99 27
        $message = $this->settings->getMessage();
100 27
        if (self::EMPTY_STRING !== $message) {
101 23
            $this->setMessage($message);
102
        }
103 27
    }
104
105
    /**
106
     * @param null|string $message
107
     */
108 24
    protected function setMessage(?string $message): void
109
    {
110 24
        $this->settings->setMessage($message);
111
112 24
        if ($this->messageJuggler instanceof MessageJuggler) {
113 6
            if (null === $message) {
114 3
                $this->messageJuggler = null;
115
            } else {
116 6
                $this->messageJuggler->setMessage($message);
117
            }
118
        } else {
119 24
            $this->messageJuggler =
120 24
                null === $message ?
121 1
                    null :
122 24
                    new MessageJuggler($this->settings, $this->coloring->getMessageStyles());
123
        }
124 24
    }
125
126
    /** {@inheritDoc} */
127 23
    public function end(?string $finalMessage = null): string
128
    {
129 23
        if (!$this->enabled) {
130 1
            return self::EMPTY_STRING;
131
        }
132 23
        $finalMessage = (string)$finalMessage;
133 23
        $showCursor = $this->hideCursor ? Cursor::show() : self::EMPTY_STRING;
134 23
        if ($this->output instanceof OutputInterface) {
135 3
            $this->erase();
136 3
            $this->output->write($showCursor . $this->inlinePaddingStr . $finalMessage);
137 3
            return self::EMPTY_STRING;
138
        }
139 20
        return $this->erase() . $showCursor . $this->inlinePaddingStr . $finalMessage;
140
    }
141
142
    /** {@inheritDoc} */
143 23
    public function erase(): string
144
    {
145 23
        if (!$this->enabled) {
146 1
            return self::EMPTY_STRING;
147
        }
148 23
        $str = $this->eraseBySpacesSequence . $this->moveCursorBackSequence;
149 23
        if ($this->output instanceof OutputInterface) {
150 3
            $this->output->write($str);
151 3
            return self::EMPTY_STRING;
152
        }
153 20
        return $str;
154
    }
155
156
    /** {@inheritDoc} */
157 5
    public function getOutput(): ?OutputInterface
158
    {
159 5
        return $this->output;
160
    }
161
162 8
    public function inline(bool $inline): SpinnerInterface
163
    {
164 8
        $this->inline = $inline;
165 8
        $this->updateInlinePaddingStrProperties();
166 8
        return $this;
167
    }
168
169
    /** {@inheritDoc} */
170 6
    public function message(?string $message): self
171
    {
172 6
        $this->setMessage($message);
173 6
        return $this;
174
    }
175
176 11
    public function progress(?float $percent): self
177
    {
178 11
        $this->setProgress($percent);
179 11
        return $this;
180
    }
181
182
    /**
183
     * @param null|float $percent
184
     */
185 12
    protected function setProgress(?float $percent = null): void
186
    {
187 12
        $this->settings->setInitialPercent($percent);
188 12
        if ($this->progressJuggler instanceof ProgressJuggler) {
189 11
            if (null === $percent) {
190 2
                $this->progressJuggler = null;
191
            } else {
192 11
                $this->progressJuggler->setProgress($percent);
193
            }
194
        } else {
195 12
            $this->progressJuggler =
196 12
                null === $percent ? null : new ProgressJuggler($this->settings, $this->coloring->getProgressStyles());
197
        }
198 12
    }
199
200
    /** {@inheritDoc} */
201 26
    public function begin(?float $percent = null): string
202
    {
203 26
        if (!$this->enabled) {
204 1
            return self::EMPTY_STRING;
205
        }
206 26
        if (null === $percent) {
207 22
            $this->progressJuggler = null;
208
        } else {
209 5
            $this->setProgress($percent);
210
        }
211 26
        $hideCursor = $this->hideCursor ? Cursor::hide() : self::EMPTY_STRING;
212 26
        if ($this->output instanceof OutputInterface) {
213 3
            $this->output->write($hideCursor);
214 3
            $this->spin();
215 3
            return self::EMPTY_STRING;
216
        }
217 23
        return $hideCursor . $this->spin();
218
    }
219
220
    /** {@inheritDoc} */
221 26
    public function spin(): string
222
    {
223 26
        if (!$this->enabled) {
224 1
            return self::EMPTY_STRING;
225
        }
226 26
        $this->lastSpinnerString = $this->prepareLastSpinnerString();
227
        return
228 26
            $this->last();
229
    }
230
231 26
    protected function prepareLastSpinnerString(): string
232
    {
233
//        $start = hrtime(true);
234 26
        $str = '';
235 26
        $erasingLength = 0;
236 26
        foreach ($this->jugglers as $juggler) {
237 26
            if ($juggler instanceof JugglerInterface) {
238 25
                $str .= $juggler->getStyledFrame();
239 25
                $erasingLength += $juggler->getFrameErasingLength();
240
            }
241
        }
242 26
        $erasingLength += $this->inlinePaddingStrLength;
243 26
        $eraseTailBySpacesSequence = $this->calcEraseSequence($erasingLength);
244
245 26
        $str = $this->inlinePaddingStr . $str . $eraseTailBySpacesSequence . $this->moveCursorBackSequence;
246
//        dump(hrtime(true) - $start);
247 26
        return $str;
248
    }
249
250
    /** {@inheritDoc} */
251 26
    public function last(): string
252
    {
253 26
        if (!$this->enabled) {
254 1
            return self::EMPTY_STRING;
255
        }
256 26
        if ($this->output instanceof OutputInterface) {
257 3
            $this->output->write($this->lastSpinnerString);
258 3
            return self::EMPTY_STRING;
259
        }
260
        return
261 23
            $this->lastSpinnerString;
262
    }
263
264 27
    protected function updateInlinePaddingStrProperties(): void
265
    {
266 27
        $this->inlinePaddingStr = $this->inline ? $this->inlinePaddingStrValue : self::EMPTY_STRING;
267 27
        $this->inlinePaddingStrLength = $this->inline ? strlen($this->inlinePaddingStr) : 0;
268 27
    }
269
270
    /**
271
     * @param int $erasingLength
272
     * @return string
273
     */
274 26
    protected function calcEraseSequence(int $erasingLength): string
275
    {
276 26
        $eraseTailBySpacesSequence = '';
277
278 26
        $erasingLengthDelta = $this->previousErasingLength - $erasingLength;
279 26
        $this->previousErasingLength = $erasingLength;
280
281 26
        if ($erasingLengthDelta > 0) {
282 7
            $erasingLength += $erasingLengthDelta;
283 7
            $eraseTailBySpacesSequence = str_repeat(Defaults::ONE_SPACE_SYMBOL, $erasingLengthDelta);
284
        }
285 26
        $this->eraseBySpacesSequence = str_repeat(Defaults::ONE_SPACE_SYMBOL, $erasingLength);
286 26
        $this->moveCursorBackSequence = ESC . "[{$erasingLength}D";
287
288 26
        return $eraseTailBySpacesSequence;
289
    }
290
}
291