Passed
Push — master ( 640636...84e31b )
by Alec
05:01 queued 47s
created

Spinner::last()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

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