Completed
Push — master ( 1640ec...bc712d )
by Craig
16:00 queued 04:33
created

Progress::each()   A

Complexity

Conditions 3
Paths 4

Duplication

Lines 0
Ratio 0 %

Size

Total Lines 11
Code Lines 6

Code Coverage

Tests 9
CRAP Score 3

Importance

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