Completed
Push — progress-each2 ( 4d9523 )
by Craig
04:53
created

Progress::each()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 5.2

Importance

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