Progress::total()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
namespace League\CLImate\TerminalObject\Dynamic;
4
5
use League\CLImate\Exceptions\UnexpectedValueException;
6
7
class Progress extends DynamicTerminalObject
8
{
9
    /**
10
     * The total number of items involved
11
     *
12
     * @var integer $total
13
     */
14
    protected $total = 0;
15
16
    /**
17
     * The current item that the progress bar represents
18
     *
19
     * @var integer $current
20
     */
21
    protected $current = 0;
22
23
    /**
24
     * The current percentage displayed
25
     *
26
     * @var string $current_percentage
27
     */
28
    protected $current_percentage = '';
29
30
    /**
31
     * The string length of the bar when at 100%
32
     *
33
     * @var integer $bar_str_len
34
     */
35
    protected $bar_str_len;
36
37
    /**
38
     * Flag indicating whether we are writing the bar for the first time
39
     *
40
     * @var boolean $first_line
41
     */
42
    protected $first_line = true;
43
44
    /**
45
     * Current status bar label
46
     *
47
     * @var string $label
48
     */
49
    protected $label;
50
51
    /**
52
     * Force a redraw every time
53
     *
54
     * @var boolean $force_redraw
55
     */
56
    protected $force_redraw = false;
57
58
    /**
59
     * If this progress bar ever displayed a label.
60
     *
61
     * @var boolean $has_label_line
62
     */
63
    protected $has_label_line = false;
64
65
    /**
66
     * If they pass in a total, set the total
67
     *
68 52
     * @param integer $total
69
     */
70 52
    public function __construct($total = null)
71 24
    {
72 24
        if ($total !== null) {
73 52
            $this->total($total);
74
        }
75
    }
76
77
    /**
78
     * Set the total property
79
     *
80
     * @param  integer $total
81
     *
82 48
     * @return Progress
83
     */
84 48
    public function total($total)
85
    {
86 48
        $this->total = $total;
87
88
        return $this;
89
    }
90
91
    /**
92
     * Determines the current percentage we are at and re-writes the progress bar
93
     *
94
     * @param integer $current
95
     * @param mixed   $label
96 52
     *
97
     * @return void
98 52
     * @throws UnexpectedValueException
99
     */
100 4
    public function current($current, $label = null)
101
    {
102
        if ($this->total == 0) {
103 48
            // Avoid dividing by 0
104 4
            throw new UnexpectedValueException('The progress total must be greater than zero.');
105
        }
106
107 44
        if ($current > $this->total) {
108
            throw new UnexpectedValueException('The current is greater than the total.');
109 44
        }
110 44
111 44
        $this->drawProgressBar($current, $label);
112
113
        $this->current = $current;
114
        $this->label   = $label;
115
    }
116
117
    /**
118
     * Increments the current position we are at and re-writes the progress bar
119 12
     *
120
     * @param integer $increment The number of items to increment by
121 12
     * @param string $label
122 12
     */
123
    public function advance($increment = 1, $label = null)
124
    {
125
        $this->current($this->current + $increment, $label);
126
    }
127
128
    /**
129
     * Force the progress bar to redraw every time regardless of whether it has changed or not
130 8
     *
131
     * @param boolean $force
132 8
     * @return Progress
133
     */
134 8
    public function forceRedraw($force = true)
135
    {
136
        $this->force_redraw = !!$force;
137
138
        return $this;
139
    }
140
141
142
    /**
143 4
     * Update a progress bar using an iterable.
144
     *
145 4
     * @param iterable $items Array or any other iterable object
146 4
     * @param callable $callback A handler to run on each item
147 4
     */
148
    public function each($items, callable $callback = null)
149 4
    {
150 4
        if ($items instanceof \Traversable) {
151 4
            $items = iterator_to_array($items);
152 4
        }
153 4
154
        $total = count($items);
155
        if (!$total) {
156
            return;
157
        }
158
        $this->total($total);
159
160
        foreach ($items as $key => $item) {
161 44
            if ($callback) {
162
                $label = $callback($item, $key);
163 44
            } else {
164
                $label = null;
165 44
            }
166 44
167 44
            $this->advance(1, $label);
168 44
        }
169
    }
170 44
171 44
172
    /**
173
     * Draw the progress bar, if necessary
174
     *
175
     * @param string $current
176
     * @param string $label
177
     */
178
    protected function drawProgressBar($current, $label)
179
    {
180
        $percentage = $this->percentageFormatted($current / $this->total);
181 44
182
        if ($this->shouldRedraw($percentage, $label)) {
183 44
            $progress_bar = $this->getProgressBar($current, $label);
184
            $this->output->write($this->parser->apply($progress_bar));
185
        }
186 44
187 44
        $this->current_percentage = $percentage;
188 44
    }
189
190
    /**
191 44
     * Build the progress bar str and return it
192
     *
193 44
     * @param integer $current
194 44
     * @param string $label
195 44
     *
196 44
     * @return string
197
     */
198
    protected function getProgressBar($current, $label)
199 44
    {
200 12
        if ($this->first_line) {
201 12
            // Drop down a line, we are about to
202
            // re-write this line for the progress bar
203 44
            $this->output->write('');
204
            $this->first_line = false;
205
        }
206
207
        // Move the cursor up and clear it to the end
208
        $line_count = $this->has_label_line ? 2 : 1;
209
210
        $progress_bar  = $this->util->cursor->up($line_count);
211
        $progress_bar .= $this->util->cursor->startOfCurrentLine();
212
        $progress_bar .= $this->util->cursor->deleteCurrentLine();
213
        $progress_bar .= $this->getProgressBarStr($current, $label);
214
215 44
        // If this line has a label then set that this progress bar has a label line
216
        if (strlen($label) > 0) {
217 44
            $this->has_label_line = true;
218 44
        }
219
220 44
        return $progress_bar;
221 44
    }
222
223 44
    /**
224 12
     * Get the progress bar string, basically:
225
     * =============>             50% label
226
     *
227 44
     * @param integer $current
228 4
     * @param string $label
229 4
     *
230
     * @return string
231 44
     */
232
    protected function getProgressBarStr($current, $label)
233
    {
234
        $percentage = $current / $this->total;
235
        $bar_length = round($this->getBarStrLen() * $percentage);
236
237
        $bar        = $this->getBar($bar_length);
238
        $number     = $this->percentageFormatted($percentage);
239
240
        if ($label) {
241 44
            $label = $this->labelFormatted($label);
242
        // If this line doesn't have a label, but we've had one before,
243 44
        // then ensure the label line is cleared
244 44
        } elseif ($this->has_label_line) {
245
            $label = $this->labelFormatted('');
246 44
        }
247
248
        return trim("{$bar} {$number}{$label}");
249
    }
250
251
    /**
252
     * Get the string for the actual bar based on the current length
253
     *
254 44
     * @param integer $length
255
     *
256 44
     * @return string
257
     */
258 44
    protected function getBar($length)
259 44
    {
260
        $bar     = str_repeat('=', $length);
261 44
        $padding = str_repeat(' ', $this->getBarStrLen() - $length);
262
263
        return "{$bar}>{$padding}";
264
    }
265
266
    /**
267
     * Get the length of the bar string based on the width of the terminal window
268
     *
269
     * @return integer
270 44
     */
271
    protected function getBarStrLen()
272 44
    {
273
        if (!$this->bar_str_len) {
274
            // Subtract 10 because of the '> 100%' plus some padding, max 100
275
            $this->bar_str_len = min($this->util->width() - 10, 100);
276
        }
277
278
        return $this->bar_str_len;
279
    }
280
281 12
    /**
282
     * Format the percentage so it looks pretty
283 12
     *
284
     * @param integer $percentage
285
     * @return float
286
     */
287
    protected function percentageFormatted($percentage)
288
    {
289
        return round($percentage * 100) . '%';
290
    }
291
292
    /**
293
     * Format the label so it is positioned correctly
294 44
     *
295
     * @param string $label
296 44
     * @return string
297
     */
298
    protected function labelFormatted($label)
299
    {
300
        return "\n" . $this->util->cursor->startOfCurrentLine() . $this->util->cursor->deleteCurrentLine() . $label;
301
    }
302
303
    /**
304
     * Determine whether the progress bar has changed and we need to redrew
305
     *
306
     * @param string $percentage
307
     * @param string $label
308
     *
309
     * @return boolean
310
     */
311
    protected function shouldRedraw($percentage, $label)
312
    {
313
        return ($this->force_redraw || $percentage != $this->current_percentage || $label != $this->label);
314
    }
315
}
316