Test Setup Failed
Push — master ( 016bc5...505cdc )
by Alec
02:25
created

Spinner::message()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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