Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
48 / 48
100.00% covered (success)
100.00%
14 / 14
CRAP
100.00% covered (success)
100.00%
1 / 1
ProgressBar
100.00% covered (success)
100.00%
48 / 48
100.00% covered (success)
100.00%
14 / 14
21
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 setMessage
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setWidth
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setChars
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 setColor
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 setIndent
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 advance
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setProgress
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 finish
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 getCurrent
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getTotal
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isFinished
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 render
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 buildLine
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
6
1<?php
2declare(strict_types=1);
3/**
4 * Pop PHP Framework (https://www.popphp.org/)
5 *
6 * @link       https://github.com/popphp/popphp-framework
7 * @author     Nick Sagona, III <dev@noladev.com>
8 * @copyright  Copyright (c) 2009-2027 NOLA Interactive, LLC.
9 * @license    https://www.popphp.org/license     New BSD License
10 */
11
12/**
13 * @namespace
14 */
15namespace Pop\Console;
16
17/**
18 * Console progress bar class
19 *
20 * @category   Pop
21 * @package    Pop\Console
22 * @author     Nick Sagona, III <dev@noladev.com>
23 * @copyright  Copyright (c) 2009-2027 NOLA Interactive, LLC.
24 * @license    https://www.popphp.org/license     New BSD License
25 * @version    5.0.0
26 */
27class ProgressBar
28{
29
30    /**
31     * Total number of steps
32     * @var int
33     */
34    protected int $total;
35
36    /**
37     * Current step
38     * @var int
39     */
40    protected int $current = 0;
41
42    /**
43     * Width of the bar's fill portion
44     * @var int
45     */
46    protected int $width = 28;
47
48    /**
49     * Character used for the filled portion of the bar
50     * @var string
51     */
52    protected string $barChar = '=';
53
54    /**
55     * Character used at the leading edge of the filled portion
56     * @var string
57     */
58    protected string $progressChar = '>';
59
60    /**
61     * Character used for the unfilled portion of the bar
62     * @var string
63     */
64    protected string $emptyChar = ' ';
65
66    /**
67     * Optional leading message
68     * @var ?string
69     */
70    protected ?string $message = null;
71
72    /**
73     * Indent prefix
74     * @var string
75     */
76    protected string $indent = '';
77
78    /**
79     * Foreground color of the filled portion
80     * @var ?int
81     */
82    protected ?int $fg = null;
83
84    /**
85     * Background color of the filled portion
86     * @var ?int
87     */
88    protected ?int $bg = null;
89
90    /**
91     * Flag for if the progress bar has finished
92     * @var bool
93     */
94    protected bool $finished = false;
95
96    /**
97     * Last rendered line, so redraws are skipped when the visible output hasn't changed
98     * @var ?string
99     */
100    protected ?string $lastLine = null;
101
102    /**
103     * Instantiate the progress bar object
104     *
105     * @param  int     $total
106     * @param  ?string $message
107     * @param  int     $width
108     * @throws Exception
109     */
110    public function __construct(int $total, ?string $message = null, int $width = 28)
111    {
112        if ($total <= 0) {
113            throw new Exception('The total number of steps must be greater than zero.');
114        }
115
116        $this->total   = $total;
117        $this->message = $message;
118        $this->width   = $width;
119    }
120
121    /**
122     * Set the leading message
123     *
124     * @param  ?string $message
125     * @return ProgressBar
126     */
127    public function setMessage(?string $message): ProgressBar
128    {
129        $this->message = $message;
130        return $this;
131    }
132
133    /**
134     * Set the width of the bar's fill portion
135     *
136     * @param  int $width
137     * @return ProgressBar
138     */
139    public function setWidth(int $width): ProgressBar
140    {
141        $this->width = $width;
142        return $this;
143    }
144
145    /**
146     * Set the bar characters
147     *
148     * @param  string $barChar
149     * @param  string $progressChar
150     * @param  string $emptyChar
151     * @return ProgressBar
152     */
153    public function setChars(string $barChar = '=', string $progressChar = '>', string $emptyChar = ' '): ProgressBar
154    {
155        $this->barChar      = $barChar;
156        $this->progressChar = $progressChar;
157        $this->emptyChar    = $emptyChar;
158        return $this;
159    }
160
161    /**
162     * Set the color of the filled portion of the bar
163     *
164     * @param  ?int $fg
165     * @param  ?int $bg
166     * @return ProgressBar
167     */
168    public function setColor(?int $fg, ?int $bg = null): ProgressBar
169    {
170        $this->fg = $fg;
171        $this->bg = $bg;
172        return $this;
173    }
174
175    /**
176     * Set the indent prefix
177     *
178     * @param  string $indent
179     * @return ProgressBar
180     */
181    public function setIndent(string $indent): ProgressBar
182    {
183        $this->indent = $indent;
184        return $this;
185    }
186
187    /**
188     * Advance the progress bar by the given number of steps
189     *
190     * @param  int $step
191     * @return ProgressBar
192     */
193    public function advance(int $step = 1): ProgressBar
194    {
195        return $this->setProgress($this->current + $step);
196    }
197
198    /**
199     * Set the current progress
200     *
201     * @param  int $current
202     * @return ProgressBar
203     */
204    public function setProgress(int $current): ProgressBar
205    {
206        $this->current = max(0, min($this->total, $current));
207        $this->render();
208        return $this;
209    }
210
211    /**
212     * Finish the progress bar
213     *
214     * @return ProgressBar
215     */
216    public function finish(): ProgressBar
217    {
218        $this->current  = $this->total;
219        $this->finished = true;
220        $this->render();
221        echo PHP_EOL;
222        return $this;
223    }
224
225    /**
226     * Get the current progress
227     *
228     * @return int
229     */
230    public function getCurrent(): int
231    {
232        return $this->current;
233    }
234
235    /**
236     * Get the total number of steps
237     *
238     * @return int
239     */
240    public function getTotal(): int
241    {
242        return $this->total;
243    }
244
245    /**
246     * Determine if the progress bar has finished
247     *
248     * @return bool
249     */
250    public function isFinished(): bool
251    {
252        return $this->finished;
253    }
254
255    /**
256     * Render the progress bar to the console
257     *
258     * @return void
259     */
260    protected function render(): void
261    {
262        $line = $this->buildLine();
263        if ($line !== $this->lastLine) {
264            echo "\r" . $line;
265            $this->lastLine = $line;
266        }
267    }
268
269    /**
270     * Build the progress bar line
271     *
272     * @return string
273     */
274    protected function buildLine(): string
275    {
276        $percent = (int)round(($this->current / $this->total) * 100);
277        $filled  = (int)round(($this->current / $this->total) * $this->width);
278        $filled  = max(0, min($this->width, $filled));
279
280        if ($filled >= $this->width) {
281            $bar = str_repeat($this->barChar, $this->width);
282        } else if ($filled <= 0) {
283            $bar = str_repeat($this->emptyChar, $this->width);
284        } else {
285            $bar = str_repeat($this->barChar, $filled - 1) . $this->progressChar .
286                str_repeat($this->emptyChar, $this->width - $filled);
287        }
288
289        if (($this->fg !== null) || ($this->bg !== null)) {
290            $bar = Color::colorize($bar, $this->fg, $this->bg);
291        }
292
293        $message = ($this->message !== null) ? $this->message . ' ' : '';
294
295        return $this->indent . $message . '[' . $bar . ']' .
296            sprintf(' %3d%% (%d/%d)  ', $percent, $this->current, $this->total);
297    }
298
299}