Subversion-Projekte lars-tiefland.laravel_shop

Revision

Zur aktuellen Revision | Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
/*
4
 * This file is part of the Symfony package.
5
 *
6
 * (c) Fabien Potencier <fabien@symfony.com>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
 
12
namespace Symfony\Component\Console\Helper;
13
 
14
use Symfony\Component\Console\Exception\InvalidArgumentException;
15
use Symfony\Component\Console\Exception\LogicException;
16
use Symfony\Component\Console\Output\OutputInterface;
17
 
18
/**
19
 * @author Kevin Bond <kevinbond@gmail.com>
20
 */
21
class ProgressIndicator
22
{
23
    private const FORMATS = [
24
        'normal' => ' %indicator% %message%',
25
        'normal_no_ansi' => ' %message%',
26
 
27
        'verbose' => ' %indicator% %message% (%elapsed:6s%)',
28
        'verbose_no_ansi' => ' %message% (%elapsed:6s%)',
29
 
30
        'very_verbose' => ' %indicator% %message% (%elapsed:6s%, %memory:6s%)',
31
        'very_verbose_no_ansi' => ' %message% (%elapsed:6s%, %memory:6s%)',
32
    ];
33
 
34
    private OutputInterface $output;
35
    private int $startTime;
36
    private ?string $format = null;
37
    private ?string $message = null;
38
    private array $indicatorValues;
39
    private int $indicatorCurrent;
40
    private int $indicatorChangeInterval;
41
    private float $indicatorUpdateTime;
42
    private bool $started = false;
43
 
44
    /**
45
     * @var array<string, callable>
46
     */
47
    private static array $formatters;
48
 
49
    /**
50
     * @param int        $indicatorChangeInterval Change interval in milliseconds
51
     * @param array|null $indicatorValues         Animated indicator characters
52
     */
53
    public function __construct(OutputInterface $output, string $format = null, int $indicatorChangeInterval = 100, array $indicatorValues = null)
54
    {
55
        $this->output = $output;
56
 
57
        $format ??= $this->determineBestFormat();
58
        $indicatorValues ??= ['-', '\\', '|', '/'];
59
        $indicatorValues = array_values($indicatorValues);
60
 
61
        if (2 > \count($indicatorValues)) {
62
            throw new InvalidArgumentException('Must have at least 2 indicator value characters.');
63
        }
64
 
65
        $this->format = self::getFormatDefinition($format);
66
        $this->indicatorChangeInterval = $indicatorChangeInterval;
67
        $this->indicatorValues = $indicatorValues;
68
        $this->startTime = time();
69
    }
70
 
71
    /**
72
     * Sets the current indicator message.
73
     */
74
    public function setMessage(?string $message)
75
    {
76
        $this->message = $message;
77
 
78
        $this->display();
79
    }
80
 
81
    /**
82
     * Starts the indicator output.
83
     */
84
    public function start(string $message)
85
    {
86
        if ($this->started) {
87
            throw new LogicException('Progress indicator already started.');
88
        }
89
 
90
        $this->message = $message;
91
        $this->started = true;
92
        $this->startTime = time();
93
        $this->indicatorUpdateTime = $this->getCurrentTimeInMilliseconds() + $this->indicatorChangeInterval;
94
        $this->indicatorCurrent = 0;
95
 
96
        $this->display();
97
    }
98
 
99
    /**
100
     * Advances the indicator.
101
     */
102
    public function advance()
103
    {
104
        if (!$this->started) {
105
            throw new LogicException('Progress indicator has not yet been started.');
106
        }
107
 
108
        if (!$this->output->isDecorated()) {
109
            return;
110
        }
111
 
112
        $currentTime = $this->getCurrentTimeInMilliseconds();
113
 
114
        if ($currentTime < $this->indicatorUpdateTime) {
115
            return;
116
        }
117
 
118
        $this->indicatorUpdateTime = $currentTime + $this->indicatorChangeInterval;
119
        ++$this->indicatorCurrent;
120
 
121
        $this->display();
122
    }
123
 
124
    /**
125
     * Finish the indicator with message.
126
     *
127
     * @param $message
128
     */
129
    public function finish(string $message)
130
    {
131
        if (!$this->started) {
132
            throw new LogicException('Progress indicator has not yet been started.');
133
        }
134
 
135
        $this->message = $message;
136
        $this->display();
137
        $this->output->writeln('');
138
        $this->started = false;
139
    }
140
 
141
    /**
142
     * Gets the format for a given name.
143
     */
144
    public static function getFormatDefinition(string $name): ?string
145
    {
146
        return self::FORMATS[$name] ?? null;
147
    }
148
 
149
    /**
150
     * Sets a placeholder formatter for a given name.
151
     *
152
     * This method also allow you to override an existing placeholder.
153
     */
154
    public static function setPlaceholderFormatterDefinition(string $name, callable $callable)
155
    {
156
        self::$formatters ??= self::initPlaceholderFormatters();
157
 
158
        self::$formatters[$name] = $callable;
159
    }
160
 
161
    /**
162
     * Gets the placeholder formatter for a given name (including the delimiter char like %).
163
     */
164
    public static function getPlaceholderFormatterDefinition(string $name): ?callable
165
    {
166
        self::$formatters ??= self::initPlaceholderFormatters();
167
 
168
        return self::$formatters[$name] ?? null;
169
    }
170
 
171
    private function display()
172
    {
173
        if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) {
174
            return;
175
        }
176
 
177
        $this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) {
178
            if ($formatter = self::getPlaceholderFormatterDefinition($matches[1])) {
179
                return $formatter($this);
180
            }
181
 
182
            return $matches[0];
183
        }, $this->format ?? ''));
184
    }
185
 
186
    private function determineBestFormat(): string
187
    {
188
        return match ($this->output->getVerbosity()) {
189
            // OutputInterface::VERBOSITY_QUIET: display is disabled anyway
190
            OutputInterface::VERBOSITY_VERBOSE => $this->output->isDecorated() ? 'verbose' : 'verbose_no_ansi',
191
            OutputInterface::VERBOSITY_VERY_VERBOSE,
192
            OutputInterface::VERBOSITY_DEBUG => $this->output->isDecorated() ? 'very_verbose' : 'very_verbose_no_ansi',
193
            default => $this->output->isDecorated() ? 'normal' : 'normal_no_ansi',
194
        };
195
    }
196
 
197
    /**
198
     * Overwrites a previous message to the output.
199
     */
200
    private function overwrite(string $message)
201
    {
202
        if ($this->output->isDecorated()) {
203
            $this->output->write("\x0D\x1B[2K");
204
            $this->output->write($message);
205
        } else {
206
            $this->output->writeln($message);
207
        }
208
    }
209
 
210
    private function getCurrentTimeInMilliseconds(): float
211
    {
212
        return round(microtime(true) * 1000);
213
    }
214
 
215
    /**
216
     * @return array<string, \Closure>
217
     */
218
    private static function initPlaceholderFormatters(): array
219
    {
220
        return [
221
            'indicator' => function (self $indicator) {
222
                return $indicator->indicatorValues[$indicator->indicatorCurrent % \count($indicator->indicatorValues)];
223
            },
224
            'message' => function (self $indicator) {
225
                return $indicator->message;
226
            },
227
            'elapsed' => function (self $indicator) {
228
                return Helper::formatTime(time() - $indicator->startTime);
229
            },
230
            'memory' => function () {
231
                return Helper::formatMemory(memory_get_usage(true));
232
            },
233
        ];
234
    }
235
}