Completed
Push — progress-each ( b08ce8...d697f0 )
by Craig
40:41 queued 05:31
created

Progress::getProgressBar()   B

Complexity

Conditions 4
Paths 8

Size

Total Lines 24
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4

Importance

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