Passed
Push — master ( d08e35...139360 )
by Alec
02:47
created

Spinner::refineSettings()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

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