%PDF- %PDF-
| Direktori : /www/varak.net/nextcloud.varak.net/apps_old/apps/cospend/vendor/adhocore/cli/ |
| Current File : //www/varak.net/nextcloud.varak.net/apps_old/apps/cospend/vendor/adhocore/cli/README.md |
## adhocore/cli
Framework agnostic Command Line Interface utilities and helpers for PHP. Build Console App with ease, fun and love.
[](https://github.com/adhocore/php-cli/releases)
[](https://github.com/adhocore/php-cli/actions/workflows/build.yml)
[](https://scrutinizer-ci.com/g/adhocore/php-cli/?branch=main)
[](https://codecov.io/gh/adhocore/php-cli)
[](https://styleci.io/repos/139012552)
[](LICENSE)
[](https://twitter.com/intent/tweet?text=Framework+agnostic+Command+Line+Interface+utilities+and+helpers+for+PHP&url=https://github.com/adhocore/php-cli&hashtags=php,cli,cliapp,console)
[](https://github.com/sponsors/adhocore)
<!-- [](https://www.paypal.me/ji10/15usd)
[](https://www.paypal.me/ji10/25usd)
[](https://www.paypal.me/ji10/50usd) -->
- Command line application made easy
- Inspired by nodejs [commander](https://github.com/tj/commander.js) (thanks tj)
- Zero dependency.
- For PHP7, PHP8 and for good
[](https://github.com/adhocore/phalcon-ext/tree/master/example/cli)
#### What's included
**Core:** [Argv parser](#argv-parser) · [Cli application](#console-app) · [Shell](#shell)
**IO:** [Colorizer](#color) · [Cursor manipulator](#cursor) · [Progress bar](#progress-bar) · [Stream writer](#writer) · [Stream reader](#reader)
**Other:** [Autocompletion](#autocompletion)
## Installation
```bash
# PHP8.0 and above v1.0.0
composer require adhocore/cli:^v1.0.0
# PHP 7.x
composer require adhocore/cli:^v0.9.0
```
## Usage
### Argv parser
```php
$command = new Ahc\Cli\Input\Command('rmdir', 'Remove dirs');
$command
->version('0.0.1-dev')
// Arguments are separated by space
// Format: `<name>` for required, `[name]` for optional
// `[name:default]` for default value, `[name...]` for variadic (last argument)
->arguments('<dir> [dirs...]')
// `-h --help`, `-V --version`, `-v --verbosity` options are already added by default.
// Format: `<name>` for required, `[name]` for optional
->option('-s --with-subdir', 'Also delete subdirs (`with` means false by default)')
->option('-e,--no-empty', 'Delete empty (`no` means true by default)')
// Specify santitizer/callback as 3rd param, default value as 4th param
->option('-d|--depth [nestlevel]', 'How deep to process subdirs', 'intval', 5)
->parse(['thisfile.php', '-sev', 'dir', 'dir1', 'dir2', '-vv']) // `$_SERVER['argv']`
;
// Print all values:
print_r($command->values());
/*Array
(
[help] =>
[version] => 0.0.1
[verbosity] => 3
[dir] => dir
[dirs] => Array
(
[0] => dir1
[1] => dir2
)
[subdir] => true
[empty] => false
[depth] => 5
)*/
// To get values for options except the default ones (help, version, verbosity)
print_r($command->values(false));
// Pick a value by name
$command->dir; // dir
$command->dirs; // [dir1, dir2]
$command->depth; // 5
```
#### Command help
It can be triggered manually with `$command->showHelp()` or automatic when `-h` or `--help` option is passed to `$command->parse()`.
For above example, the output would be:

#### Command version
It can be triggered manually with `$command->showVersion()` or automatic when `-V` or `--version` option is passed to `$command->parse()`.
For above example, the output would be:
```
0.0.1-dev
```
### Console app
Definitely check [adhocore/phint](https://github.com/adhocore/phint) - a real world console application made using `adhocore/cli`.
Here we simulate a `git` app with limited functionality of `add`, and `checkout`.
You will see how intuitive, fluent and cheese building a console app is!
#### Git app
```php
$app = new Ahc\Cli\Application('git', '0.0.1');
$app
// Register `add` command
->command('add', 'Stage changed files', 'a') // alias a
// Set options and arguments for this command
->arguments('<path> [paths...]')
->option('-f --force', 'Force add ignored file', 'boolval', false)
->option('-N --intent-to-add', 'Add content later but index now', 'boolval', false)
// Handler for this command: param names should match but order can be anything :)
->action(function ($path, $paths, $force, $intentToAdd) {
array_unshift($paths, $path);
echo ($intentToAdd ? 'Intent to add ' : 'Add ')
. implode(', ', $paths)
. ($force ? ' with force' : '');
// If you return integer from here, that will be taken as exit error code
})
// Done setting up this command for now, tap() to retreat back so we can add another command
->tap()
->command('checkout', 'Switch branches', 'co') // alias co
->arguments('<branch>')
->option('-b --new-branch', 'Create a new branch and switch to it', false)
->option('-f --force', 'Checkout even if index differs', 'boolval', false)
->action(function ($branch, $newBranch, $force) {
echo 'Checkout to '
. ($newBranch ? 'new ' . $branch : $branch)
. ($force ? ' with force' : '');
})
;
// Parse only parses input but doesnt invoke action
$app->parse(['git', 'add', 'path1', 'path2', 'path3', '-f']);
// Handle will do both parse and invoke action.
$app->handle(['git', 'add', 'path1', 'path2', 'path3', '-f']);
// Will produce: Add path1, path2, path3 with force
$app->handle(['git', 'co', '-b', 'master-2', '-f']);
// Will produce: Checkout to new master-2 with force
```
### Organized app
Instead of inline commands/actions, we define and add our own commands (having `interact()` and `execute()`) to the app:
```php
class InitCommand extends Ahc\Cli\Input\Command
{
public function __construct()
{
parent::__construct('init', 'Init something');
$this
->argument('<arrg>', 'The Arrg')
->argument('[arg2]', 'The Arg2')
->option('-a --apple', 'The Apple')
->option('-b --ball', 'The ball')
// Usage examples:
->usage(
// append details or explanation of given example with ` ## ` so they will be uniformly aligned when shown
'<bold> init</end> <comment>--apple applet --ball ballon <arggg></end> ## details 1<eol/>' .
// $0 will be interpolated to actual command name
'<bold> $0</end> <comment>-a applet -b ballon <arggg> [arg2]</end> ## details 2<eol/>'
);
}
// This method is auto called before `self::execute()` and receives `Interactor $io` instance
public function interact(Ahc\Cli\IO\Interactor $io) : void
{
// Collect missing opts/args
if (!$this->apple) {
$this->set('apple', $io->prompt('Enter apple'));
}
if (!$this->ball) {
$this->set('ball', $io->prompt('Enter ball'));
}
// ...
}
// When app->handle() locates `init` command it automatically calls `execute()`
// with correct $ball and $apple values
public function execute($ball, $apple)
{
$io = $this->app()->io();
$io->write('Apple ' . $apple, true);
$io->write('Ball ' . $ball, true);
// more codes ...
// If you return integer from here, that will be taken as exit error code
}
}
class OtherCommand extends Ahc\Cli\Input\Command
{
public function __construct()
{
parent::__construct('other', 'Other something');
}
public function execute()
{
$io = $this->app()->io();
$io->write('Other command');
// more codes ...
// If you return integer from here, that will be taken as exit error code
}
}
// Init App with name and version
$app = new Ahc\Cli\Application('App', 'v0.0.1');
// Add commands with optional aliases`
$app->add(new InitCommand, 'i');
$app->add(new OtherCommand, 'o');
// Set logo
$app->logo('Ascii art logo of your app');
$app->handle($_SERVER['argv']); // if argv[1] is `i` or `init` it executes InitCommand
```
#### Grouping commands
Grouped commands are listed together in commands list. Explicit grouping a command is optional.
By default if a command name has a colon `:` then the part before it is taken as a group,
else `*` is taken as a group.
> Example: command name `app:env` has a default group `app`, command name `appenv` has group `*`.
```php
// Add grouped commands:
$app->group('Configuration', function ($app) {
$app->add(new ConfigSetCommand);
$app->add(new ConfigListCommand);
});
// Alternatively, set group one by one in each commands:
$app->add((new ConfigSetCommand)->inGroup('Config'));
$app->add((new ConfigListCommand)->inGroup('Config'));
...
```
#### Exception handler
Set a custom exception handler as callback. The callback receives exception & exit code. The callback may rethrow exception or may exit the program or just log exception and do nothing else.
```php
$app = new Ahc\Cli\Application('App', 'v0.0.1');
$app->add(...);
$app->onException(function (Throwable $e, int $exitCode) {
// send to sentry
// write to logs
// optionally, exit with exit code:
exit($exitCode);
// or optionally rethrow, a rethrown exception is propagated to top layer caller.
throw $e;
})->handle($argv);
```
#### App help
It can be triggered manually with `$app->showHelp()` or automatic when `-h` or `--help` option is passed to `$app->parse()`.
**Note** If you pass something like `['app', cmd', '-h']` to `$app->parse()` it will automatically and instantly show you help of that `cmd` and not the `$app`.
For above example, the output would be:

#### App version
Same version number is passed to all attached Commands. So you can trigger version on any of the commands.
### Shell
Very thin shell wrapper that provides convenience methods around `proc_open()`.
#### Basic usage
```php
$shell = new Ahc\Cli\Helper\Shell($command = 'php -v', $rawInput = null);
// Waits until proc finishes
$shell->execute($async = false); // default false
echo $shell->getOutput(); // PHP version string (often with zend/opcache info)
```
#### Advanced usage
```php
$shell = new Ahc\Cli\Helper\Shell('php /some/long/running/scipt.php');
// With async flag, doesnt wait for proc to finish!
$shell->setOptions($workDir = '/home', $envVars = [])
->execute($async = true)
->isRunning(); // true
// Force stop anytime (please check php.net/proc_close)
$shell->stop(); // also closes pipes
// Force kill anytime (please check php.net/proc_terminate)
$shell->kill();
```
#### Timeout
```php
$shell = new Ahc\Cli\Helper\Shell('php /some/long/running/scipt.php');
// Wait for at most 10.5 seconds for proc to finish!
// If it doesnt complete by then, throws exception
$shell->setOptions($workDir, $envVars, $timeout = 10.5)->execute();
// And if it completes within timeout, you can access the stdout/stderr
echo $shell->getOutput();
echo $shell->getErrorOutput();
```
### Cli Interaction
You can perform user interaction like printing colored output, reading user input programatically and moving the cursors around with provided `Ahc\Cli\IO\Interactor`.
```php
$interactor = new Ahc\Cli\IO\Interactor;
// For mocking io:
$interactor = new Ahc\Cli\IO\Interactor($inputPath, $outputPath);
```
#### Confirm
```php
$confirm = $interactor->confirm('Are you happy?', 'n'); // Default: n (no)
$confirm // is a boolean
? $interactor->greenBold('You are happy :)', true) // Output green bold text
: $interactor->redBold('You are sad :(', true); // Output red bold text
```
#### Single choice
```php
$fruits = ['a' => 'apple', 'b' => 'banana'];
$choice = $interactor->choice('Select a fruit', $fruits, 'b');
$interactor->greenBold("You selected: {$fruits[$choice]}", true);
```
#### Multiple choices
```php
$fruits = ['a' => 'apple', 'b' => 'banana', 'c' => 'cherry'];
$choices = $interactor->choices('Select fruit(s)', $fruits, ['b', 'c']);
$choices = \array_map(function ($c) use ($fruits) { return $fruits[$c]; }, $choices);
$interactor->greenBold('You selected: ' . implode(', ', $choices), true);
```
#### Prompt free input
```php
$any = $interactor->prompt('Anything', rand(1, 100)); // Random default
$interactor->greenBold("Anything is: $any", true);
```
#### Prompt with validation
```php
$nameValidator = function ($value) {
if (\strlen($value) < 5) {
throw new \InvalidArgumentException('Name should be atleast 5 chars');
}
return $value;
};
// No default, Retry 5 more times
$name = $interactor->prompt('Name', null, $nameValidator, 5);
$interactor->greenBold("The name is: $name", true);
```
#### Prompt hidden
> On windows platform, it may change the fontface which can be [fixed](https://superuser.com/a/757591).
```php
$passValidator = function ($pass) {
if (\strlen($pass) < 6) {
throw new \InvalidArgumentException('Password too short');
}
return $pass;
};
$pass = $interactor->promptHidden('Password', $passValidator, 2);
```

## IO Components
The interactor is composed of `Ahc\Cli\Input\Reader` and `Ahc\Cli\Output\Writer` while the `Writer` itself is composed of `Ahc\Cli\Output\Color`. All these components can be used standalone.
### Color
Color looks cool!
```php
$color = new Ahc\Cli\Output\Color;
```
#### Simple usage
```php
echo $color->warn('This is warning');
echo $color->info('This is info');
echo $color->error('This is error');
echo $color->comment('This is comment');
echo $color->ok('This is ok msg');
```
#### Custom style
```php
Ahc\Cli\Output\Color::style('mystyle', [
'bg' => Ahc\Cli\Output\Color::CYAN,
'fg' => Ahc\Cli\Output\Color::WHITE,
'bold' => 1, // You can experiment with 0, 1, 2, 3 ... as well
]);
echo $color->mystyle('My text');
```
### Cursor
Move cursor around, erase line up or down, clear screen.
```php
$cursor = new Ahc\Cli\Output\Cursor;
echo $cursor->up(1)
. $cursor->down(2)
. $cursor->right(3)
. $cursor->left(4)
. $cursor->next(0)
. $cursor->prev(2);
. $cursor->eraseLine()
. $cursor->clear()
. $cursor->clearUp()
. $cursor->clearDown()
. $cursor->moveTo(5, 8); // x, y
```
### Progress Bar
Easily add a progress bar to your output:
```php
$progress = new Ahc\Cli\Output\ProgressBar(100);
for ($i = 0; $i <= 100; $i++) {
$progress->current($i);
// Simulate something happening
usleep(80000);
}
```
You can also manually advance the bar:
```php
$progress = new Ahc\Cli\Output\ProgressBar(100);
// Do something
$progress->advance(); // Adds 1 to the current progress
// Do something
$progress->advance(10); // Adds 10 to the current progress
// Do something
$progress->advance(5, 'Still going.'); // Adds 5, displays a label
```
You can override the progress bar options to customize it to your liking:
```php
$progress = new Ahc\Cli\Output\ProgressBar(100);
$progress->option('pointer', '>>');
$progress->option('loader', '▩');
// You can set the progress fluently
$progress->option('pointer', '>>')->option('loader', '▩');
// You can also use an associative array to set many options in one time
$progress->option([
'pointer' => '>>',
'loader' => '▩'
]);
// Available options
+------------+------------------------------+---------------+
| Option | Description | Default value |
+------------+------------------------------+---------------+
| pointer | The progress bar head symbol | > |
| loader | The loader symbol | = |
| color | The color of progress bar | white |
| labelColor | The text color of the label | white |
+------------+------------------------------+---------------+
```
### Writer
Write anything in style.
```php
$writer = new Ahc\Cli\Output\Writer;
// All writes are forwarded to STDOUT
// But if you specify error, then to STDERR
$writer->errorBold('This is error');
```
#### Output formatting
You can call methods composed of any combinations:
`'<colorName>', 'bold', 'bg', 'fg', 'warn', 'info', 'error', 'ok', 'comment'`
... in any order (eg: `bgRedFgBlaock`, `boldRed`, `greenBold`, `commentBgPurple` and so on ...)
```php
$writer->bold->green->write('It is bold green');
$writer->boldGreen('It is bold green'); // Same as above
$writer->comment('This is grayish comment', true); // True indicates append EOL character.
$writer->bgPurpleBold('This is white on purple background');
```
#### Free style
Many colors with one single call: wrap text with tags `<method>` and `</end>`
For NL/EOL just use `<eol>` or `</eol>` or `<eol/>`.
Great for writing long colorful texts for example command usage info.
```php
$writer->colors('<red>This is red</end><eol><bgGreen>This has bg Green</end>');
```
#### Raw output
```php
$writer->raw('Enter name: ');
```
#### Tables
Just pass array of assoc arrays. The keys of first array will be taken as heading.
Heading is auto inflected to human readable capitalized words (ucwords).
```php
$writer->table([
['a' => 'apple', 'b-c' => 'ball', 'c_d' => 'cat'],
['a' => 'applet', 'b-c' => 'bee', 'c_d' => 'cute'],
]);
```
Gives something like:
```
+--------+------+------+
| A | B C | C D |
+--------+------+------+
| apple | ball | cat |
| applet | bee | cute |
+--------+------+------+
```
> Designing table look and feel
Just pass 2nd param `$styles`:
```php
$writer->table([
['a' => 'apple', 'b-c' => 'ball', 'c_d' => 'cat'],
['a' => 'applet', 'b-c' => 'bee', 'c_d' => 'cute'],
], [
// for => styleName (anything that you would call in $writer instance)
'head' => 'boldGreen', // For the table heading
'odd' => 'bold', // For the odd rows (1st row is odd, then 3, 5 etc)
'even' => 'comment', // For the even rows (2nd row is even, then 4, 6 etc)
]);
// 'head', 'odd', 'even' are all the styles for now
// In future we may support styling a column by its name!
```
#### Reader
Read and pre process user input.
```php
$reader = new Ahc\Cli\Input\Reader;
// No default, callback fn `ucwords()`
$reader->read(null, 'ucwords');
// Default 'abc', callback `trim()`
$reader->read('abc', 'trim');
// Read at most first 5 chars
// (if ENTER is pressed before 5 chars then further read is aborted)
$reader->read('', 'trim', 5);
// Read but dont echo back the input
$reader->readHidden($default, $callback);
// Read from piped stream (or STDIN) if available without waiting
$reader->readPiped();
// Pass in a callback for if STDIN is empty
// The callback recieves $reader instance and MUST return string
$reader->readPiped(function ($reader) {
// Wait to read a line!
return $reader->read();
// Wait to read multi lines (until Ctrl+D pressed)
return $reader->readAll();
});
```
#### Exceptions
Whenever an exception is caught by `Application::handle()`, it will show a beautiful stack trace and exit with non 0 status code.

### Autocompletion
Any console applications that are built on top of **adhocore/cli** can entertain autocomplete of commands and options in zsh shell with oh-my-zsh.
All you have to do is add one line to the end of `~/.oh-my-zsh/custom/plugins/ahccli/ahccli.plugin.zsh`:
> `compdef _ahccli <appname>`
Example: `compdef _ahccli phint` for [phint](https://github.com/adhocore/phint).
That is cumbersome to perform manually, here's a complete command you can copy/paste/run:
#### One time setup
```sh
mkdir -p ~/.oh-my-zsh/custom/plugins/ahccli && cd ~/.oh-my-zsh/custom/plugins/ahccli
[ -f ./ahccli.plugin.zsh ] || curl -sSLo ./ahccli.plugin.zsh https://raw.githubusercontent.com/adhocore/php-cli/master/ahccli.plugin.zsh
chmod 760 ./ahccli.plugin.zsh && cd -
```
##### Load ahccli plugin
> This is also one time setup.
```sh
# Open .zshrc
nano ~/.zshrc
# locate plugins=(... ...) and add ahccli
plugins=(git ... ... ahccli)
# ... then save it (Ctrl + O)
```
#### Registering app
```sh
# replace appname with real name eg: phint
echo compdef _ahccli appname >> ~/.oh-my-zsh/custom/plugins/ahccli/ahccli.plugin.zsh
```
> Of course you can add multiple apps, just change appname in above command
Then either restart the shell or source the plugin like so:
```sh
source ~/.oh-my-zsh/custom/plugins/ahccli/ahccli.plugin.zsh
```
#### Trigger autocomplete
```sh
appname <tab> # autocompletes commands (phint <tab>)
appname subcommand <tab> # autocompletes options for subcommand (phint init <tab>)
```
### Related
- [adhocore/phalcon-ext](https://github.com/adhocore/phalcon-ext) · Phalcon extension using `adhocore/cli`
- [adhocore/phint](https://github.com/adhocore/phint) · PHP project scaffolding app using `adhocore/cli`
- [adhocore/type-hinter](https://github.com/adhocore/php-type-hinter) · Auto PHP7 typehinter tool using `adhocore/cli`
### Contributors
- [adhocore](https://github.com/adhocore)
- [sushilgupta](https://github.com/sushilgupta)
## License
> © 2017-2020, [Jitendra Adhikari](https://github.com/adhocore) | [MIT](./LICENSE)
### Credits
This project is release managed by [please](https://github.com/adhocore/please).