Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 148 | Details | Vergleich mit vorheriger | 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
    public function finish(string $message)
128
    {
129
        if (!$this->started) {
130
            throw new LogicException('Progress indicator has not yet been started.');
131
        }
132
 
133
        $this->message = $message;
134
        $this->display();
135
        $this->output->writeln('');
136
        $this->started = false;
137
    }
138
 
139
    /**
140
     * Gets the format for a given name.
141
     */
142
    public static function getFormatDefinition(string $name): ?string
143
    {
144
        return self::FORMATS[$name] ?? null;
145
    }
146
 
147
    /**
148
     * Sets a placeholder formatter for a given name.
149
     *
150
     * This method also allow you to override an existing placeholder.
151
     */
152
    public static function setPlaceholderFormatterDefinition(string $name, callable $callable)
153
    {
154
        self::$formatters ??= self::initPlaceholderFormatters();
155
 
156
        self::$formatters[$name] = $callable;
157
    }
158
 
159
    /**
160
     * Gets the placeholder formatter for a given name (including the delimiter char like %).
161
     */
162
    public static function getPlaceholderFormatterDefinition(string $name): ?callable
163
    {
164
        self::$formatters ??= self::initPlaceholderFormatters();
165
 
166
        return self::$formatters[$name] ?? null;
167
    }
168
 
169
    private function display()
170
    {
171
        if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) {
172
            return;
173
        }
174
 
175
        $this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) {
176
            if ($formatter = self::getPlaceholderFormatterDefinition($matches[1])) {
177
                return $formatter($this);
178
            }
179
 
180
            return $matches[0];
181
        }, $this->format ?? ''));
182
    }
183
 
184
    private function determineBestFormat(): string
185
    {
186
        return match ($this->output->getVerbosity()) {
187
            // OutputInterface::VERBOSITY_QUIET: display is disabled anyway
188
            OutputInterface::VERBOSITY_VERBOSE => $this->output->isDecorated() ? 'verbose' : 'verbose_no_ansi',
189
            OutputInterface::VERBOSITY_VERY_VERBOSE,
190
            OutputInterface::VERBOSITY_DEBUG => $this->output->isDecorated() ? 'very_verbose' : 'very_verbose_no_ansi',
191
            default => $this->output->isDecorated() ? 'normal' : 'normal_no_ansi',
192
        };
193
    }
194
 
195
    /**
196
     * Overwrites a previous message to the output.
197
     */
198
    private function overwrite(string $message)
199
    {
200
        if ($this->output->isDecorated()) {
201
            $this->output->write("\x0D\x1B[2K");
202
            $this->output->write($message);
203
        } else {
204
            $this->output->writeln($message);
205
        }
206
    }
207
 
208
    private function getCurrentTimeInMilliseconds(): float
209
    {
210
        return round(microtime(true) * 1000);
211
    }
212
 
213
    /**
214
     * @return array<string, \Closure>
215
     */
216
    private static function initPlaceholderFormatters(): array
217
    {
218
        return [
219
            'indicator' => function (self $indicator) {
220
                return $indicator->indicatorValues[$indicator->indicatorCurrent % \count($indicator->indicatorValues)];
221
            },
222
            'message' => function (self $indicator) {
223
                return $indicator->message;
224
            },
225
            'elapsed' => function (self $indicator) {
226
                return Helper::formatTime(time() - $indicator->startTime);
227
            },
228
            'memory' => function () {
229
                return Helper::formatMemory(memory_get_usage(true));
230
            },
231
        ];
232
    }
233
}