Passed
Push — master ( d43caa...2694be )
by Alec
03:13
created

Spinner::spin()   B

Complexity

Conditions 8
Paths 16

Size

Total Lines 30
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 8

Importance

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