Passed
Push — master ( a0dc40...285a03 )
by Alec
03:52
created

Spinner::spin()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 6
Bugs 0 Features 0
Metric Value
eloc 6
c 6
b 0
f 0
dl 0
loc 9
ccs 6
cts 6
cp 1
rs 10
cc 2
nc 2
nop 2
crap 2
1
<?php declare(strict_types=1);
2
3
namespace AlecRabbit\Spinner\Core;
4
5
use AlecRabbit\Accessories\Circular;
6
use AlecRabbit\Cli\Tools\Cursor;
7
use AlecRabbit\Spinner\Core\Adapters\EchoOutputAdapter;
8
use AlecRabbit\Spinner\Core\Contracts\Frames;
9
use AlecRabbit\Spinner\Core\Contracts\SettingsInterface;
10
use AlecRabbit\Spinner\Core\Contracts\SpinnerInterface;
11
use AlecRabbit\Spinner\Core\Contracts\SpinnerOutputInterface;
12
use function AlecRabbit\typeOf;
13
use const AlecRabbit\ESC;
14
15
abstract class Spinner implements SpinnerInterface
16
{
17
    protected const INTERVAL = SettingsInterface::DEFAULT_INTERVAL;
18
    protected const FRAMES = Frames::DIAMOND;
19
    protected const STYLES = [];
20
21
    /** @var string */
22
    protected $messageStr;
23
    /** @var string */
24
    protected $currentMessage;
25
    /** @var string */
26
    protected $currentMessagePrefix;
27
    /** @var string */
28
    protected $currentMessageSuffix;
29
    /** @var string */
30
    protected $percentStr = '';
31
    /** @var int */
32
    protected $percentStrLen = 0;
33
    /** @var string */
34
    protected $percentSpacer;
35
    /** @var string */
36
    protected $moveBackSequenceStr;
37
    /** @var string */
38
    protected $inlinePaddingStr;
39
    /** @var string */
40
    protected $eraseBySpacesStr;
41
    /** @var Style */
42
    protected $style;
43
    /** @var float */
44
    protected $interval;
45
    /** @var int */
46
    protected $frameErasingShift;
47
    /** @var Circular */
48
    protected $symbols;
49
    /** @var null|SpinnerOutputInterface */
50
    protected $output;
51
    /** @var int */
52
    protected $messageErasingLen;
53
    /** @var string */
54
    protected $spacer;
55
    /** @var SettingsInterface */
56
    protected $settings;
57
    /** @var int */
58
    protected $currentMessagePrefixLen;
59
    /** @var int */
60
    protected $currentMessageSuffixLen;
61
    /** @var int */
62
    protected $inlinePaddingStrLen;
63
64
    /**
65
     * AbstractSpinner constructor.
66
     *
67
     * @param mixed $settings
68
     * @param null|false|SpinnerOutputInterface $output
69
     * @param mixed $color
70
     */
71 24
    public function __construct($settings = null, $output = null, $color = null)
72
    {
73 24
        $this->output = $this->refineOutput($output);
74 22
        $this->settings = $this->refineSettings($settings);
75 21
        $this->loadSettings($color);
76 20
    }
77
78
    /**
79
     * @param null|false|SpinnerOutputInterface $output
80
     * @return null|SpinnerOutputInterface
81
     */
82 24
    protected function refineOutput($output): ?SpinnerOutputInterface
83
    {
84 24
        $this->assertOutput($output);
85 22
        if (false === $output) {
86 13
            return null;
87
        }
88 9
        return $output ?? new EchoOutputAdapter();
89
    }
90
91
    /**
92
     * @param mixed $output
93
     */
94 24
    protected function assertOutput($output): void
95
    {
96 24
        if (null !== $output && false !== $output && !$output instanceof SpinnerOutputInterface) {
97 2
            $typeOrValue = true === $output ? 'true' : typeOf($output);
98 2
            throw new \InvalidArgumentException(
99
                'Incorrect $output param' .
100
                ' [null|false|SpinnerOutputInterface] expected'
101 2
                . ' "' . $typeOrValue . '" given.'
102
            );
103
        }
104 22
    }
105
106
    /**
107
     * @param mixed $settings
108
     * @return SettingsInterface
109
     */
110 22
    protected function refineSettings($settings): SettingsInterface
111
    {
112 22
        $this->assertSettings($settings);
113 21
        if (\is_string($settings)) {
114
            return
115 15
                $this->defaultSettings()->setMessage($settings);
116
        }
117
        return
118 6
            $settings ?? $this->defaultSettings();
119
    }
120
121
    /**
122
     * @param mixed $settings
123
     */
124 22
    protected function assertSettings($settings): void
125
    {
126 22
        if (null !== $settings && !\is_string($settings) && !$settings instanceof SettingsInterface) {
127 1
            throw new \InvalidArgumentException(
128 1
                'Instance of SettingsInterface or string expected ' . typeOf($settings) . ' given.'
129
            );
130
        }
131 21
    }
132
133
    /**
134
     * @return SettingsInterface
135
     */
136 17
    protected function defaultSettings(): SettingsInterface
137
    {
138
        return
139 17
            (new Settings())
140 17
                ->setInterval(static::INTERVAL)
141 17
                ->setSymbols(static::FRAMES)
142 17
                ->setStyles(static::STYLES);
143
    }
144
145
    /**
146
     * @param mixed $color
147
     */
148 21
    protected function loadSettings($color): void
149
    {
150 21
        $this->interval = $this->settings->getInterval();
151 21
        $this->frameErasingShift = $this->settings->getErasingShift();
152 21
        $this->inlinePaddingStr = $this->settings->getInlinePaddingStr();
153 21
        $this->currentMessage = $this->settings->getMessage();
154 21
        $this->messageErasingLen = $this->settings->getMessageErasingLen();
155 21
        $this->currentMessagePrefix = $this->settings->getMessagePrefix();
156 21
        $this->currentMessageSuffix = $this->settings->getMessageSuffix();
157 21
        $this->spacer = $this->settings->getSpacer();
158 21
        $this->symbols = new Circular($this->settings->getSymbols());
159
160
        try {
161 21
            $this->style = new Style($this->settings->getStyles(), $color);
162 1
        } catch (\Throwable $e) {
163 1
            throw new \InvalidArgumentException(
164 1
                '[' . static::class . '] ' . $e->getMessage(),
165 1
                (int)$e->getCode(),
166 1
                $e
167
            );
168
        }
169 20
        $this->inlinePaddingStrLen = strlen($this->inlinePaddingStr); // TODO fix code duplicate?
170 20
        $this->currentMessagePrefixLen = strlen($this->currentMessagePrefix);
171 20
        $this->currentMessageSuffixLen = strlen($this->currentMessageSuffix);
172 20
        $this->messageStr = $this->prepareMessageStr();
173 20
        $this->updateProperties();
174 20
    }
175
176 20
    protected function prepareMessageStr(): string
177
    {
178
        return
179 20
            $this->spacer .
180 20
            $this->currentMessagePrefix .
181 20
            ucfirst($this->currentMessage) .
182 20
            $this->currentMessageSuffix;
183
    }
184
185 20
    protected function updateProperties(): void
186
    {
187 20
        $this->percentSpacer = $this->getPercentSpacer(); // TODO move to other location - optimize performance
188
        $strLen =
189 20
            $this->currentMessagePrefixLen +
190 20
            $this->messageErasingLen +
191 20
            $this->currentMessageSuffixLen +
192 20
            $this->percentStrLen +
193 20
            $this->inlinePaddingStrLen +
194 20
            $this->frameErasingShift;
195 20
        $this->moveBackSequenceStr = ESC . "[{$strLen}D";
196 20
        $this->eraseBySpacesStr = str_repeat(SettingsInterface::ONE_SPACE_SYMBOL, $strLen);
197 20
    }
198
199
    /**
200
     * @return string
201
     */
202 20
    protected function getPercentSpacer(): string
203
    {
204 20
        if (strpos($this->messageStr, SettingsInterface::DEFAULT_SUFFIX)) {
205 17
            return SettingsInterface::ONE_SPACE_SYMBOL;
206
        }
207 3
        return SettingsInterface::EMPTY;
208
    }
209
210
    /** {@inheritDoc} */
211 3
    public function getOutput(): ?SpinnerOutputInterface
212
    {
213 3
        return $this->output;
214
    }
215
216 3
    public function interval(): float
217
    {
218 3
        return $this->interval;
219
    }
220
221 4
    public function inline(bool $inline): SpinnerInterface
222
    {
223 4
        $this->inlinePaddingStr = $inline ? SettingsInterface::ONE_SPACE_SYMBOL : SettingsInterface::EMPTY;
224 4
        $this->inlinePaddingStrLen = strlen($this->inlinePaddingStr);
225 4
        $this->updateProperties();
226 4
        return $this;
227
    }
228
229
    /** {@inheritDoc} */
230 16
    public function begin(?float $percent = null): string
231
    {
232 16
        if ($this->output) {
233 3
            $this->output->write(Cursor::hide());
234 3
            $this->spin($percent);
235 3
            return '';
236
        }
237 13
        return Cursor::hide() . $this->spin($percent);
238
    }
239
240
    /** {@inheritDoc} */
241 16
    public function spin(?float $percent = null, ?string $message = null): string
242
    {
243 16
        $this->update($percent, $message);
244 16
        if ($this->output) {
245 3
            $this->output->write($this->preparedStr());
246 3
            return '';
247
        }
248
        return
249 13
            $this->preparedStr();
250
    }
251
252
    /**
253
     * @param null|float $percent
254
     * @param null|string $message
255
     */
256 16
    protected function update(?float $percent, ?string $message): void
257
    {
258 16
        if ((null !== $percent) && 0 === ($percentVal = (int)($percent * 1000)) % 10) {
259 4
            $this->percentStr = $this->percentSpacer . ($percentVal / 10) . '%';
260 4
            $this->percentStrLen = strlen($this->percentStr);
261
        }
262 16
        if ((null !== $message) && $this->currentMessage !== $message) {
263 1
            $this->currentMessage = $message;
264 1
            $this->messageErasingLen = strlen($message);
265 1
            $this->messageStr = $this->prepareMessageStr();
266
        }
267 16
        if (null !== $percent || null !== $message) {
268 5
            $this->updateProperties();
269
        }
270 16
    }
271
272
    /**
273
     * @return string
274
     */
275 16
    protected function preparedStr(): string
276
    {
277
        return
278 16
            $this->inlinePaddingStr .
279 16
            $this->style->spinner((string)$this->symbols->value()) .
280 16
            $this->style->message(
281 16
                $this->message()
282
            ) .
283 16
            $this->style->percent(
284 16
                $this->percent()
285
            ) .
286 16
            $this->moveBackSequenceStr;
287
    }
288
289
    /**
290
     * @return string
291
     */
292 16
    protected function message(): string
293
    {
294 16
        return $this->messageStr;
295
    }
296
297
    /**
298
     * @return string
299
     */
300 16
    protected function percent(): string
301
    {
302 16
        return $this->percentStr;
303
    }
304
305
    /** {@inheritDoc} */
306 16
    public function end(): string
307
    {
308 16
        if ($this->output) {
309 3
            $this->erase();
310 3
            $this->output->write(Cursor::show());
311 3
            return '';
312
        }
313 13
        return $this->erase() . Cursor::show();
314
    }
315
316
    /** {@inheritDoc} */
317 16
    public function erase(): string
318
    {
319 16
        $str = $this->eraseBySpacesStr . $this->moveBackSequenceStr;
320 16
        if ($this->output) {
321 3
            $this->output->write($str);
322 3
            return '';
323
        }
324 13
        return $str;
325
    }
326
327
    /** {@inheritDoc} */
328
    public function getSettings(): SettingsInterface
329
    {
330
        throw new \RuntimeException(static::class . ': Call to unimplemented functionality ' . __METHOD__);
331
        return $this->settings;
0 ignored issues
show
Unused Code introduced by
return $this->settings is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
332
    }
333
}
334