Passed
Push — master ( eb7214...99cc8c )
by Alec
02:56 queued 10s
created

Spinner::setProgress()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 12
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4

Importance

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