Skip to content

Commit 9e60ddf

Browse files
authored
Merge pull request #82 from adhocore/81-progress-bar
Add progress bar
2 parents 2e46d00 + 48d5821 commit 9e60ddf

File tree

10 files changed

+610
-27
lines changed

10 files changed

+610
-27
lines changed

README.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,65 @@ echo $cursor->up(1)
456456
. $cursor->moveTo(5, 8); // x, y
457457
```
458458

459+
### Progress Bar
460+
461+
Easily add a progress bar to your output:
462+
463+
```php
464+
$progress = new Ahc\Cli\Output\ProgressBar(100);
465+
for ($i = 0; $i <= 100; $i++) {
466+
$progress->current($i);
467+
468+
// Simulate something happening
469+
usleep(80000);
470+
}
471+
```
472+
473+
You can also manually advance the bar:
474+
475+
```php
476+
$progress = new Ahc\Cli\Output\ProgressBar(100);
477+
478+
// Do something
479+
480+
$progress->advance(); // Adds 1 to the current progress
481+
482+
// Do something
483+
484+
$progress->advance(10); // Adds 10 to the current progress
485+
486+
// Do something
487+
488+
$progress->advance(5, 'Still going.'); // Adds 5, displays a label
489+
```
490+
491+
You can override the progress bar options to customize it to your liking:
492+
493+
```php
494+
$progress = new Ahc\Cli\Output\ProgressBar(100);
495+
$progress->option('pointer', '>>');
496+
$progress->option('loader', '▩');
497+
498+
// You can set the progress fluently
499+
$progress->option('pointer', '>>')->option('loader', '▩');
500+
501+
// You can also use an associative array to set many options in one time
502+
$progress->option([
503+
'pointer' => '>>',
504+
'loader' => '▩'
505+
]);
506+
507+
// Available options
508+
+------------+------------------------------+---------------+
509+
| Option | Description | Default value |
510+
+------------+------------------------------+---------------+
511+
| pointer | The progress bar head symbol | > |
512+
| loader | The loader symbol | = |
513+
| color | The color of progress bar | white |
514+
| labelColor | The text color of the label | white |
515+
+------------+------------------------------+---------------+
516+
```
517+
459518
### Writer
460519

461520
Write anything in style.

src/Helper/OutputHelper.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use Ahc\Cli\Input\Parameter;
2020
use Ahc\Cli\Output\Writer;
2121
use Throwable;
22+
2223
use function array_map;
2324
use function array_shift;
2425
use function asort;
@@ -44,6 +45,7 @@
4445
use function trim;
4546
use function uasort;
4647
use function var_export;
48+
4749
use const STR_PAD_LEFT;
4850

4951
/**
@@ -102,7 +104,7 @@ public function printTrace(Throwable $e): void
102104
$this->writer->colors($traceStr);
103105
}
104106

105-
protected function stringifyArgs(array $args): string
107+
public function stringifyArgs(array $args): string
106108
{
107109
$holder = [];
108110

src/Helper/Shell.php

Lines changed: 8 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Ahc\Cli\Helper;
1313

1414
use Ahc\Cli\Exception\RuntimeException;
15+
1516
use function fclose;
1617
use function function_exists;
1718
use function fwrite;
@@ -23,7 +24,6 @@
2324
use function proc_terminate;
2425
use function stream_get_contents;
2526
use function stream_set_blocking;
26-
use const DIRECTORY_SEPARATOR;
2727

2828
/**
2929
* A thin proc_open wrapper to execute shell commands.
@@ -109,7 +109,7 @@ public function __construct(protected string $command, protected ?string $input
109109

110110
protected function prepareDescriptors(?array $stdin = null, ?array $stdout = null, ?array $stderr = null): array
111111
{
112-
$win = $this->isWindows();
112+
$win = Terminal::isWindows();
113113
if (!$stdin) {
114114
$stdin = $win ? self::DEFAULT_STDIN_WIN : self::DEFAULT_STDIN_NIX;
115115
}
@@ -127,16 +127,6 @@ protected function prepareDescriptors(?array $stdin = null, ?array $stdout = nul
127127
];
128128
}
129129

130-
protected function isWindows(): bool
131-
{
132-
// If PHP_OS is defined, use it - More reliable:
133-
if (defined('PHP_OS')) {
134-
return 'WIN' === strtoupper(substr(PHP_OS, 0, 3)); // May be 'WINNT' or 'WIN32' or 'Windows'
135-
}
136-
137-
return '\\' === DIRECTORY_SEPARATOR; // Fallback - Less reliable (Windows 7...)
138-
}
139-
140130
protected function setInput(): void
141131
{
142132
//Make sure the pipe is a stream resource before writing to it to avoid a warning
@@ -263,12 +253,9 @@ public function execute(bool $async = false, ?array $stdin = null, ?array $stdou
263253

264254
private function setOutputStreamNonBlocking(): bool
265255
{
266-
// Make sure the pipe is a stream resource before setting it to non-blocking to avoid a warning
267-
if (!is_resource($this->pipes[self::STDOUT_DESCRIPTOR_KEY])) {
268-
return false;
269-
}
256+
$isRes = is_resource($this->pipes[self::STDOUT_DESCRIPTOR_KEY]);
270257

271-
return stream_set_blocking($this->pipes[self::STDOUT_DESCRIPTOR_KEY], false);
258+
return $isRes ? stream_set_blocking($this->pipes[self::STDOUT_DESCRIPTOR_KEY], false) : false;
272259
}
273260

274261
public function getState(): string
@@ -278,21 +265,16 @@ public function getState(): string
278265

279266
public function getOutput(): string
280267
{
281-
// Make sure the pipe is a stream resource before reading it to avoid a warning
282-
if (!is_resource($this->pipes[self::STDOUT_DESCRIPTOR_KEY])) {
283-
return '';
284-
}
268+
$isRes = is_resource($this->pipes[self::STDOUT_DESCRIPTOR_KEY]);
285269

286-
return stream_get_contents($this->pipes[self::STDOUT_DESCRIPTOR_KEY]);
270+
return $isRes ? stream_get_contents($this->pipes[self::STDOUT_DESCRIPTOR_KEY]) : '';
287271
}
288272

289273
public function getErrorOutput(): string
290274
{
291-
if (!is_resource($this->pipes[self::STDERR_DESCRIPTOR_KEY])) {
292-
return '';
293-
}
275+
$isRes = is_resource($this->pipes[self::STDERR_DESCRIPTOR_KEY]);
294276

295-
return stream_get_contents($this->pipes[self::STDERR_DESCRIPTOR_KEY]);
277+
return $isRes ? stream_get_contents($this->pipes[self::STDERR_DESCRIPTOR_KEY]) : '';
296278
}
297279

298280
public function getExitCode(): ?int

src/Helper/Terminal.php

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the PHP-CLI package.
5+
*
6+
* (c) Jitendra Adhikari <[email protected]>
7+
* <https://github.com/adhocore>
8+
*
9+
* Licensed under MIT license.
10+
*/
11+
12+
namespace Ahc\Cli\Helper;
13+
14+
use function array_map;
15+
use function array_search;
16+
use function exec;
17+
use function implode;
18+
use function is_array;
19+
use function preg_match_all;
20+
21+
/**
22+
* A thin to find some information about the current terminal (width, height, ect...).
23+
*
24+
* @todo provide different adapters for the platforms (linux and windows) for better organization.
25+
*
26+
* @author Dimitri Sitchet Tomkeu <[email protected]>
27+
* @license MIT
28+
*
29+
* @link https://github.com/adhocore/cli
30+
*/
31+
class Terminal
32+
{
33+
public static function isWindows(): bool
34+
{
35+
// If PHP_OS is defined, use it - More reliable:
36+
if (defined('PHP_OS')) {
37+
return str_starts_with(strtoupper(PHP_OS), 'WIN'); // May be 'WINNT' or 'WIN32' or 'Windows'
38+
}
39+
40+
// @codeCoverageIgnoreStart
41+
return '\\' === DIRECTORY_SEPARATOR; // Fallback - Less reliable (Windows 7...)
42+
// @codeCoverageIgnoreEnd
43+
}
44+
45+
/**
46+
* Get the width of the terminal.
47+
*/
48+
public function width(): ?int
49+
{
50+
return $this->getDimension('width');
51+
}
52+
53+
/**
54+
* Get the height of the terminal.
55+
*/
56+
public function height(): ?int
57+
{
58+
return $this->getDimension('height');
59+
}
60+
61+
/**
62+
* Get specified terminal dimension.
63+
*/
64+
protected function getDimension(string $key): ?int
65+
{
66+
if (static::isWindows()) {
67+
// @codeCoverageIgnoreStart
68+
return $this->getDimensions()[array_search($key, ['height', 'width'])] ?? null;
69+
// @codeCoverageIgnoreEnd
70+
}
71+
72+
$type = ['width' => 'cols', 'height' => 'lines'][$key];
73+
$result = exec("tput {$type} 2>/dev/null");
74+
75+
return $result === false ? null : (int) $result;
76+
}
77+
78+
/**
79+
* Get information about the dimensions of the Windows terminal.
80+
*
81+
* @codeCoverageIgnore
82+
*
83+
* @return int[]
84+
*/
85+
protected function getDimensions(): array
86+
{
87+
exec('mode CON', $output);
88+
89+
if (!is_array($output)) {
90+
return [];
91+
}
92+
93+
$output = implode("\n", $output);
94+
95+
preg_match_all('/.*:\s*(\d+)/', $output, $matches);
96+
97+
return array_map('intval', $matches[1] ?? []);
98+
}
99+
}

src/Input/Command.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717
use Ahc\Cli\Helper\InflectsString;
1818
use Ahc\Cli\Helper\OutputHelper;
1919
use Ahc\Cli\IO\Interactor;
20+
use Ahc\Cli\Output\ProgressBar;
2021
use Ahc\Cli\Output\Writer;
2122
use Closure;
23+
2224
use function array_filter;
2325
use function array_keys;
2426
use function end;
@@ -382,4 +384,12 @@ protected function io(): Interactor
382384
{
383385
return $this->_app ? $this->_app->io() : new Interactor;
384386
}
387+
388+
/**
389+
* Get ProgressBar instance.
390+
*/
391+
protected function progress(int $total = null): ProgressBar
392+
{
393+
return new ProgressBar($total, $this->writer());
394+
}
385395
}

0 commit comments

Comments
 (0)