Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
/* vim: set expandtab tabstop=4 softtabstop=4 shiftwidth=4: */
3
 
4
// Copyright (c) 2007 Stefan Walk
5
//
6
// Permission is hereby granted, free of charge, to any person obtaining a copy
7
// of this software and associated documentation files (the "Software"), to
8
// deal in the Software without restriction, including without limitation the
9
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10
// sell copies of the Software, and to permit persons to whom the Software is
11
// furnished to do so, subject to the following conditions:
12
//
13
// The above copyright notice and this permission notice shall be included in
14
// all copies or substantial portions of the Software.
15
//
16
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22
// IN THE SOFTWARE.
23
 
24
 
25
// Authors: Stefan Walk <et@php.net>
26
 
27
/**
28
 * Class to display a progressbar in the console
29
 *
30
 * @package Console_ProgressBar
31
 * @category Console
32
 * @version 0.5.2
33
 * @author Stefan Walk <et@php.net>
34
 * @license MIT License
35
 */
36
 
37
class Console_ProgressBar {
38
 
39
    // properties {{{
40
    /**
41
     * Skeleton for use with sprintf
42
     */
43
    var $_skeleton;
44
    /**
45
     * The bar gets filled with this
46
     */
47
    var $_bar;
48
    /**
49
     * The width of the bar
50
     */
51
    var $_blen;
52
    /**
53
     * The total width of the display
54
     */
55
    var $_tlen;
56
    /**
57
     * The position of the counter when the job is `done'
58
     */
59
    var $_target_num;
60
    /**
61
     * Options, like the precision used to display the numbers
62
     */
63
    var $_options = array();
64
    /**
65
     * Length to erase
66
     */
67
    var $_rlen = 0;
68
    /**
69
     * When the progress started
70
     */
71
    var $_start_time = null;
72
    var $_rate_datapoints = array();
73
    /**
74
     * Time when the bar was last drawn
75
     */
76
    var $_last_update_time = 0.0;
77
    // }}}
78
 
79
    // constructor() {{{
80
    /**
81
     * Constructor, sets format and size
82
     *
83
     * See the reset() method for documentation.
84
     *
85
     * @param string The format string
86
     * @param string The string filling the progress bar
87
     * @param string The string filling empty space in the bar
88
     * @param int    The width of the display
89
     * @param float  The target number for the bar
90
     * @param array  Options for the progress bar
91
     * @see reset
92
     */
93
    function Console_ProgressBar($formatstring, $bar, $prefill, $width,
94
                                  $target_num, $options = array())
95
    {
96
        $this->reset($formatstring, $bar, $prefill, $width, $target_num,
97
                     $options);
98
    }
99
    // }}}
100
 
101
    // {{{ reset($formatstring, $bar, $prefill, $width, $target_num[, $options])
102
    /**
103
     * Re-sets format and size.
104
     *
105
     * <pre>
106
     * The reset method expects 5 to 6 arguments:
107
     * - The first argument is the format string used to display the progress
108
     *   bar. It may (and should) contain placeholders that the class will
109
     *   replace with information like the progress bar itself, the progress in
110
     *   percent, and so on. Current placeholders are:
111
     *     %bar%         The progress bar
112
     *     %current%     The current value
113
     *     %max%         The maximum malue (the "target" value)
114
     *     %fraction%    The same as %current%/%max%
115
     *     %percent%     The status in percent
116
     *     %elapsed%     The elapsed time
117
     *     %estimate%    An estimate of how long the progress will take
118
     *   More placeholders will follow. A format string like:
119
     *   "* stuff.tar %fraction% KB [%bar%] %percent%"
120
     *   will lead to a bar looking like this:
121
     *   "* stuff.tar 391/900 KB [=====>---------]  43.44%"
122
     * - The second argument is the string that is going to fill the progress
123
     *   bar. In the above example, the string "=>" was used. If the string you
124
     *   pass is too short (like "=>" in this example), the leftmost character
125
     *   is used to pad it to the needed size. If the string you pass is too long,
126
     *   excessive characters are stripped from the left.
127
     * - The third argument is the string that fills the "empty" space in the
128
     *   progress bar. In the above example, that would be "-". If the string
129
     *   you pass is too short (like "-" in this example), the rightmost
130
     *   character is used to pad it to the needed size. If the string you pass
131
     *   is too short, excessive characters are stripped from the right.
132
     * - The fourth argument specifies the width of the display. If the options
133
     *   are left untouched, it will tell how many characters the display should
134
     *   use in total. If the "absolute_width" option is set to false, it tells
135
     *   how many characters the actual bar (that replaces the %bar%
136
     *   placeholder) should use.
137
     * - The fifth argument is the target number of the progress bar. For
138
     *   example, if you wanted to display a progress bar for a download of a
139
     *   file that is 115 KB big, you would pass 115 here.
140
     * - The sixth argument optional. If passed, it should contain an array of
141
     *   options. For example, passing array('absolute_width' => false) would
142
     *   set the absolute_width option to false. Current options are:
143
     *
144
     *     option             | def.  |  meaning
145
     *     --------------------------------------------------------------------
146
     *     percent_precision  | 2     |  Number of decimal places to show when
147
     *                        |       |  displaying the percentage.
148
     *     fraction_precision | 0     |  Number of decimal places to show when
149
     *                        |       |  displaying the current or target
150
     *                        |       |  number.
151
     *     percent_pad        | ' '   |  Character to use when padding the
152
     *                        |       |  percentage to a fixed size. Senseful
153
     *                        |       |  values are ' ' and '0', but any are
154
     *                        |       |  possible.
155
     *     fraction_pad       | ' '   |  Character to use when padding max and
156
     *                        |       |  current number to a fixed size.
157
     *                        |       |  Senseful values are ' ' and '0', but
158
     *                        |       |  any are possible.
159
     *     width_absolute     | true  |  If the width passed as an argument
160
     *                        |       |  should mean the total size (true) or
161
     *                        |       |  the width of the bar alone.
162
     *     ansi_terminal      | false |  If this option is true, a better
163
     *                        |       |  (faster) method for erasing the bar is
164
     *                        |       |  used. CAUTION - this is known to cause
165
     *                        |       |  problems with some terminal emulators,
166
     *                        |       |  for example Eterm.
167
     *     ansi_clear         | false |  If the bar should be cleared everytime
168
     *     num_datapoints     | 5     |  How many datapoints to use to create
169
     *                        |       |  the estimated remaining time
170
     *     min_draw_interval  | 0.0   |  If the last call to update() was less
171
     *                        |       |  than this amount of seconds ago,
172
     *                        |       |  don't update.
173
     * </pre>
174
     *
175
     * @param string The format string
176
     * @param string The string filling the progress bar
177
     * @param string The string filling empty space in the bar
178
     * @param int    The width of the display
179
     * @param float  The target number for the bar
180
     * @param array  Options for the progress bar
181
     * @return bool
182
     */
183
    function reset($formatstring, $bar, $prefill, $width, $target_num,
184
                   $options = array())
185
    {
186
        if ($target_num == 0) {
187
            trigger_error("PEAR::Console_ProgressBar: Using a target number equal to 0 is invalid, setting to 1 instead");
188
            $this->_target_num = 1;
189
        } else {
190
            $this->_target_num = $target_num;
191
        }
192
        $default_options = array(
193
            'percent_precision' => 2,
194
            'fraction_precision' => 0,
195
            'percent_pad' => ' ',
196
            'fraction_pad' => ' ',
197
            'width_absolute' => true,
198
            'ansi_terminal' => false,
199
            'ansi_clear' => false,
200
            'num_datapoints' => 5,
201
            'min_draw_interval' => 0.0,
202
        );
203
        $intopts = array();
204
        foreach ($default_options as $key => $value) {
205
            if (!isset($options[$key])) {
206
                $intopts[$key] = $value;
207
            } else {
208
                settype($options[$key], gettype($value));
209
                $intopts[$key] = $options[$key];
210
            }
211
        }
212
        $this->_options = $options = $intopts;
213
        // placeholder
214
        $cur = '%2$\''.$options['fraction_pad']{0}.strlen((int)$target_num).'.'
215
               .$options['fraction_precision'].'f';
216
        $max = $cur; $max{1} = 3;
217
        // pre php-4.3.7 %3.2f meant 3 characters before . and two after
218
        // php-4.3.7 and later it means 3 characters for the whole number
219
        if (version_compare(PHP_VERSION, '4.3.7', 'ge')) {
220
            $padding = 4 + $options['percent_precision'];
221
        } else {
222
            $padding = 3;
223
        }
224
        $perc = '%4$\''.$options['percent_pad']{0}.$padding.'.'
225
                .$options['percent_precision'].'f';
226
 
227
        $transitions = array(
228
            '%%' => '%%',
229
            '%fraction%' => $cur.'/'.$max,
230
            '%current%' => $cur,
231
            '%max%' => $max,
232
            '%percent%' => $perc.'%%',
233
            '%bar%' => '%1$s',
234
            '%elapsed%' => '%5$s',
235
            '%estimate%' => '%6$s',
236
        );
237
 
238
        $this->_skeleton = strtr($formatstring, $transitions);
239
 
240
        $slen = strlen(sprintf($this->_skeleton, '', 0, 0, 0, '00:00:00','00:00:00'));
241
 
242
        if ($options['width_absolute']) {
243
            $blen = $width - $slen;
244
            $tlen = $width;
245
        } else {
246
            $tlen = $width + $slen;
247
            $blen = $width;
248
        }
249
 
250
        $lbar = str_pad($bar, $blen, $bar{0}, STR_PAD_LEFT);
251
        $rbar = str_pad($prefill, $blen, substr($prefill, -1, 1));
252
 
253
        $this->_bar   = substr($lbar,-$blen).substr($rbar,0,$blen);
254
        $this->_blen  = $blen;
255
        $this->_tlen  = $tlen;
256
        $this->_first = true;
257
 
258
 
259
        return true;
260
    }
261
    // }}}
262
 
263
    // {{{ update($current)
264
    /**
265
     * Updates the bar with new progress information
266
     *
267
     * @param int current position of the progress counter
268
     * @return bool
269
     */
270
    function update($current)
271
    {
272
        $time = $this->_fetchTime();
273
        $this->_addDatapoint($current, $time);
274
        if ($this->_first) {
275
            if ($this->_options['ansi_terminal']) {
276
                echo "\x1b[s"; // save cursor position
277
            }
278
            $this->_first = false;
279
            $this->_start_time = $this->_fetchTime();
280
            $this->display($current);
281
            return;
282
        }
283
        if ($time - $this->_last_update_time <
284
            $this->_options['min_draw_interval'] and $current != $this->_target_num) {
285
            return;
286
        }
287
        $this->erase();
288
        $this->display($current);
289
        $this->_last_update_time = $time;
290
    }
291
    // }}}
292
 
293
    // {{{ display($current)
294
    /**
295
     * Prints the bar. Usually, you don't need this method, just use update()
296
     * which handles erasing the previously printed bar also. If you use a
297
     * custom function (for whatever reason) to erase the bar, use this method.
298
     *
299
     * @param int current position of the progress counter
300
     * @return bool
301
     */
302
    function display($current)
303
    {
304
        $percent = $current / $this->_target_num;
305
        $filled = round($percent * $this->_blen);
306
        $visbar = substr($this->_bar, $this->_blen - $filled, $this->_blen);
307
        $elapsed = $this->_formatSeconds(
308
            $this->_fetchTime() - $this->_start_time
309
        );
310
        $estimate = $this->_formatSeconds($this->_generateEstimate());
311
        $this->_rlen = printf($this->_skeleton,
312
            $visbar, $current, $this->_target_num, $percent * 100, $elapsed,
313
            $estimate
314
        );
315
        // fix for php-versions where printf doesn't return anything
316
        if (is_null($this->_rlen)) {
317
            $this->_rlen = $this->_tlen;
318
        // fix for php versions between 4.3.7 and 5.x.y(?)
319
        } elseif ($this->_rlen < $this->_tlen) {
320
            echo str_repeat(' ', $this->_tlen - $this->_rlen);
321
            $this->_rlen = $this->_tlen;
322
        }
323
        return true;
324
    }
325
    // }}}
326
 
327
    // {{{ erase($clear = false)
328
    /**
329
     * Erases a previously printed bar.
330
     *
331
     * @param bool if the bar should be cleared in addition to resetting the
332
     *             cursor position
333
     * @return bool
334
     */
335
    function erase($clear = false)
336
    {
337
        if ($this->_options['ansi_terminal'] and !$clear) {
338
            if ($this->_options['ansi_clear']) {
339
                echo "\x1b[2K\x1b[u"; // restore cursor position
340
            } else {
341
                echo "\x1b[u"; // restore cursor position
342
            }
343
        } elseif (!$clear) {
344
            echo str_repeat(chr(0x08), $this->_rlen);
345
        } else {
346
            echo str_repeat(chr(0x08), $this->_rlen),
347
                 str_repeat(chr(0x20), $this->_rlen),
348
                 str_repeat(chr(0x08), $this->_rlen);
349
        }
350
    }
351
    // }}}
352
 
353
    // {{{ format_seconds()
354
    /**
355
     * Returns a string containing the formatted number of seconds
356
     *
357
     * @param float The number of seconds
358
     * @return string
359
     */
360
    function _formatSeconds($seconds)
361
    {
362
        $hou = floor($seconds/3600);
363
        $min = floor(($seconds - $hou * 3600) / 60);
364
        $sec = $seconds - $hou * 3600 - $min * 60;
365
        if ($hou == 0) {
366
            if (version_compare(PHP_VERSION, '4.3.7', 'ge')) {
367
                $format = '%2$02d:%3$05.2f';
368
            } else {
369
                $format = '%2$02d:%3$02.2f';
370
            }
371
        } elseif ($hou < 100) {
372
            $format = '%02d:%02d:%02d';
373
        } else {
374
            $format = '%05d:%02d';
375
        }
376
        return sprintf($format, $hou, $min, $sec);
377
    }
378
    // }}}
379
 
380
    function _fetchTime() {
381
        if (!function_exists('microtime')) {
382
            return time();
383
        }
384
        if (version_compare(PHP_VERSION, '5.0.0', 'ge')) {
385
            return microtime(true);
386
        }
387
        return array_sum(explode(' ', microtime()));
388
    }
389
 
390
    function _addDatapoint($val, $time) {
391
        if (count($this->_rate_datapoints)
392
            == $this->_options['num_datapoints']) {
393
            array_shift($this->_rate_datapoints);
394
        }
395
        $this->_rate_datapoints[] = array(
396
            'time' => $time,
397
            'value' => $val,
398
        );
399
    }
400
 
401
    function _generateEstimate() {
402
        if (count($this->_rate_datapoints) < 2) {
403
            return 0.0;
404
        }
405
        $first = $this->_rate_datapoints[0];
406
        $last = end($this->_rate_datapoints);
407
        return ($this->_target_num - $last['value'])/($last['value'] - $first['value']) * ($last['time'] - $first['time']);
408
    }
409
}
410