Passed
Push — master ( 472599...1fbb84 )
by Alec
03:04
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 float */
26
    protected $interval;
27
    /** @var null|MessageJuggler */
28
    protected $messageJuggler;
29
    /** @var null|FrameJuggler */
30
    protected $frameJuggler;
31
    /** @var null|ProgressJuggler */
32
    protected $progressJuggler;
33
    /** @var string */
34
    protected $moveCursorBackSequence = self::EMPTY_STRING;
35
    /** @var string */
36
    protected $eraseBySpacesSequence = self::EMPTY_STRING;
37
    /** @var int */
38
    protected $previousErasingLength = 0;
39
    /** @var Colors */
40
    protected $coloring;
41
    /** @var null[]|JugglerInterface[] */
42
    protected $jugglers = [];
43
    /** @var string */
44
    protected $lastSpinnerString = self::EMPTY_STRING;
45
    /** @var string */
46
    protected $inlinePaddingStr = self::EMPTY_STRING;
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->enabled = $this->settings->isEnabled();
61 26
        $this->inlinePaddingStr = $this->settings->getInlinePaddingStr();
62 26
        $this->coloring = new Colors($this->settings->getStyles(), $this->refineColor($color));
63 26
        $jugglerOrder = $this->settings->getJugglersOrder();
64 26
        $this->jugglers = [
65 26
            $jugglerOrder[0] => &$this->frameJuggler,
66 26
            $jugglerOrder[1] => &$this->messageJuggler,
67 26
            $jugglerOrder[2] => &$this->progressJuggler,
68
        ];
69 26
        ksort($this->jugglers);
70 26
        $this->initJugglers();
71 26
    }
72
73
    /**
74
     * @param null|string|Settings $settings
75
     * @return Settings
76
     */
77 27
    protected function refineSettings($settings): Settings
78
    {
79 27
        Sentinel::assertSettings($settings);
80 26
        if (\is_string($settings)) {
81
            return
82 19
                $this->defaultSettings()->setMessage($settings);
83
        }
84 7
        if ($settings instanceof Settings) {
85 4
            return $this->defaultSettings()->merge($settings);
86
        }
87
        return
88 3
            $this->defaultSettings();
89
    }
90
91
    /**
92
     * @return Settings
93
     */
94 26
    protected function defaultSettings(): Settings
95
    {
96
        return
97 26
            (new Settings())
98 26
                ->setInterval(static::INTERVAL)
99 26
                ->setFrames(static::FRAMES)
100 26
                ->setStyles(static::STYLES);
101
    }
102
103
    /**
104
     * @param null|int $color
105
     * @return int
106
     */
107 26
    protected function refineColor(?int $color): int
108
    {
109 26
        if (null === $color && $this->output instanceof OutputInterface) {
110 1
            $color = Terminal::colorSupport($this->output->getStream());
111
        }
112 26
        return $color ?? NO_COLOR_TERMINAL;
113
    }
114
115 26
    protected function initJugglers(): void
116
    {
117 26
        $frames = $this->settings->getFrames();
118 26
        if (!empty($frames)) {
119 20
            $this->frameJuggler =
120 20
                new FrameJuggler($this->settings, $this->coloring->getFrameStyles());
121
        }
122
123 26
        $message = $this->settings->getMessage();
124 26
        if (self::EMPTY_STRING !== $message) {
125 22
            $this->setMessage($message);
126
        }
127 26
    }
128
129
    /**
130
     * @param null|string $message
131
     */
132 23
    protected function setMessage(?string $message): void
133
    {
134 23
        $this->settings->setMessage($message);
135
136 23
        if ($this->messageJuggler instanceof MessageJuggler) {
137 5
            if (null === $message) {
138 2
                $this->messageJuggler = null;
139
            } else {
140 5
                $this->messageJuggler->setMessage($message);
141
            }
142
        } else {
143 23
            $this->messageJuggler =
144 23
                null === $message ?
145 1
                    null :
146 23
                    new MessageJuggler($this->settings, $this->coloring->getMessageStyles());
147
        }
148 23
    }
149
150
    /** {@inheritDoc} */
151 22
    public function end(?string $finalMessage = null): string
152
    {
153 22
        if (!$this->enabled) {
154 1
            return self::EMPTY_STRING;
155
        }
156 22
        $finalMessage = (string)$finalMessage;
157 22
        if ($this->output instanceof OutputInterface) {
158 3
            $this->erase();
159 3
            $this->output->write(Cursor::show() . $this->inlinePaddingStr . $finalMessage);
160 3
            return self::EMPTY_STRING;
161
        }
162 19
        return $this->erase() . Cursor::show() . $this->inlinePaddingStr . $finalMessage;
163
    }
164
165
    /** {@inheritDoc} */
166 22
    public function erase(): string
167
    {
168 22
        if (!$this->enabled) {
169 1
            return self::EMPTY_STRING;
170
        }
171 22
        $str = $this->eraseBySpacesSequence . $this->moveCursorBackSequence;
172 22
        if ($this->output instanceof OutputInterface) {
173 3
            $this->output->write($str);
174 3
            return self::EMPTY_STRING;
175
        }
176 19
        return $str;
177
    }
178
179
    /** {@inheritDoc} */
180 4
    public function getOutput(): ?OutputInterface
181
    {
182 4
        return $this->output;
183
    }
184
185 8
    public function inline(bool $inline): SpinnerInterface
186
    {
187 8
        $this->inline = $inline;
188 8
        $this->inlinePaddingStr = $this->inline ? Defaults::ONE_SPACE_SYMBOL : self::EMPTY_STRING;
189 8
        return $this;
190
    }
191
192 6
    public function interval(): float
193
    {
194 6
        return $this->interval;
195
    }
196
197
    /** {@inheritDoc} */
198 5
    public function message(?string $message = null): self
199
    {
200 5
        $this->setMessage($message);
201 5
        return $this;
202
    }
203
204 10
    public function progress(?float $percent): self
205
    {
206 10
        $this->setProgress($percent);
207 10
        return $this;
208
    }
209
210
    /**
211
     * @param null|float $percent
212
     */
213 11
    protected function setProgress(?float $percent = null): void
214
    {
215 11
        $this->settings->setInitialPercent($percent);
216 11
        if ($this->progressJuggler instanceof ProgressJuggler) {
217 10
            if (null === $percent) {
218 1
                $this->progressJuggler = null;
219
            } else {
220 10
                $this->progressJuggler->setProgress($percent);
221
            }
222
        } else {
223 11
            $this->progressJuggler =
224 11
                null === $percent ? null : new ProgressJuggler($this->settings, $this->coloring->getProgressStyles());
225
        }
226 11
    }
227
228
    /** {@inheritDoc} */
229 25
    public function begin(?float $percent = null): string
230
    {
231 25
        if (!$this->enabled) {
232 1
            return self::EMPTY_STRING;
233
        }
234 25
        if (null === $percent) {
235 21
            $this->progressJuggler = null;
236
        } else {
237 5
            $this->setProgress($percent);
238
        }
239 25
        if ($this->output instanceof OutputInterface) {
240 3
            $this->output->write(Cursor::hide());
241 3
            $this->spin();
242 3
            return self::EMPTY_STRING;
243
        }
244 22
        return Cursor::hide() . $this->spin();
245
    }
246
247
    /** {@inheritDoc} */
248 25
    public function spin(): string
249
    {
250 25
        if (!$this->enabled) {
251 1
            return self::EMPTY_STRING;
252
        }
253 25
        $this->lastSpinnerString = $this->prepareLastSpinnerString();
254
        return
255 25
            $this->last();
256
    }
257
258 25
    protected function prepareLastSpinnerString(): string
259
    {
260
//        $start = hrtime(true);
261 25
        $str = '';
262 25
        $erasingLength = 0;
263 25
        $eraseTailBySpacesSequence = '';
264 25
        foreach ($this->jugglers as $juggler) {
265 25
            if ($juggler instanceof JugglerInterface) {
266 24
                $str .= $juggler->getStyledFrame();
267 24
                $erasingLength += $juggler->getFrameErasingLength();
268
            }
269
        }
270 25
        $erasingLength += $this->inline ? strlen($this->inlinePaddingStr) : 0;
271 25
        $erasingLengthDelta = $this->previousErasingLength - $erasingLength;
272 25
        $this->previousErasingLength = $erasingLength;
273
274 25
        if ($erasingLengthDelta > 0) {
275 6
            $erasingLength += $erasingLengthDelta;
276 6
            $eraseTailBySpacesSequence = str_repeat(Defaults::ONE_SPACE_SYMBOL, $erasingLengthDelta);
277
        }
278 25
        $this->moveCursorBackSequence = ESC . "[{$erasingLength}D";
279 25
        $this->eraseBySpacesSequence = str_repeat(Defaults::ONE_SPACE_SYMBOL, $erasingLength);
280
281 25
        $str = $this->inlinePaddingStr . $str . $eraseTailBySpacesSequence . $this->moveCursorBackSequence;
282
//        dump(hrtime(true) - $start);
283 25
        return $str;
284
    }
285
286
    /** {@inheritDoc} */
287 25
    public function last(): string
288
    {
289 25
        if (!$this->enabled) {
290 1
            return self::EMPTY_STRING;
291
        }
292 25
        if ($this->output instanceof OutputInterface) {
293 3
            $this->output->write($this->lastSpinnerString);
294 3
            return self::EMPTY_STRING;
295
        }
296
        return
297 22
            $this->lastSpinnerString;
298
    }
299
}
300