Passed
Push — master ( 80875d...eb7214 )
by Alec
02:41
created

Spinner::message()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

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