One file console framework to help you write build scripts.
Single file console framework to help you write scripts quickly, v2.0 requires PHP 8.4 or later., (*2)
This package is highly inspired by Symfony Console and Python argparse., (*3)
* PHP Simple Console V2.0
* Installation
* Getting Started
* Run Console by Closure
* Run Console by Custom Class
* The Return Value
* Parameter Parser
* Parameter Definitions
* Show Help
* Override Help Information
* Parameter Configurations
* Get Parameters Value
* Parameters Type
* ARRAY
type
* LEVEL
type
* Parameters Options
* description
* required
* default
* negatable
* Parameters Parsing
* Error Handling
* Wrong Parameters
* Verbosity
* The Built-In Options
* Disable Built-In Options for Console App
* Input/Output
* STDIN/STDOUT/STDERR
* Output Methods
* Input and Asking Questions
* Run Sub-Process
* Hide Command Name
* Custom Output
* Disable the Output
* Override exec()
* Delegating Multiple Tasks
* Contributing and PR is Welcome
, (*4)
Use composer:, (*5)
``` bash composer require asika/simple-console, (*6)
Download single file: [Download Here](https://raw.githubusercontent.com/asika32764/php-simple-console/master/src/Console.php) CLI quick download: ```shell # WGET wget https://raw.githubusercontent.com/asika32764/php-simple-console/master/src/Console.php chmod +x Console.php # CURL curl https://raw.githubusercontent.com/asika32764/php-simple-console/master/src/Console.php -o Console.php chmod +x Console.php
Use closure to create a simple console script., (*7)
#!/bin/sh php <?php // Include single file include_once __DIR__ . '/Console.php'; // Or use composer include_once __DIR__ . '/vendor/autolod.php'; $app = new \Asika\SimpleConsole\Console(); $app->execute( $argv, function () use ($app) { $this->writeln('Hello'); // OR $app->writeln('Hello'); return $app::SUCCESS; // OR `0` as success } );
The closure can receive a Console
object as parameter, so you can use $this
or $app
to access the console object., (*8)
$app->execute( $argv, function (Console $app) { $app->writeln('Hello'); // ... } );
The $argv
can be omit, Console will get it from $_SERVER['argv']
, (*9)
$app->execute( main: function (Console $app) { $app->writeln('Hello'); // ... } );
You can also use class mode, extends Asika\SimpleConsole\Console
class to create a console application., (*10)
$app = new class () extends \Asika\SimpleConsole\Console { protected function doExecute(): int|bool { $this->writeln('Hello'); return static::SUCCESS; } } $app->execute(); // OR $app->execute($argv);
You can return true
or 0
as success, false
or any int larger than 0
as failure. Please refer to
GNU/Linux Exit Codes., (*11)
Simple Console provides constants for success and failure:, (*12)
retrun $app::SUCCESS; // 0 retrun $app::FAILURE; // 255
Simple Console supports arguments and options pre-defined. Below is a parser object to help use define parameters and
parse argv
variables. You can pass the parsed data to any function or entry point., (*13)
use Asika\SimpleConsole\Console; function main(array $options) { // Run your code... } $parser = \Asika\SimpleConsole\Console::createArgvParser(); // Arguments $parser->addParameter('name', type: Console::STRING, description: 'Your name', required: true); $parser->addParameter('age', type: Console::INT, description: 'Your age'); // Name starts with `-` or `--` will be treated as option $parser->addParameter('--height|-h', type: Console::FLOAT, description: 'Your height', required: true); $parser->addParameter('--location|-l', type: Console::STRING, description: 'Live location', required: true); $parser->addParameter('--muted|-m', type: Console::BOOLEAN, description: 'Is muted'); main($parser->parse($argv));
Same as:, (*14)
use Asika\SimpleConsole\ArgvParser; use Asika\SimpleConsole\Console; $params = Console::parseArgv( function (ArgvParser $parser) { // Arguments $parser->addParameter('name', type: Console::STRING, description: 'Your name', required: true); $parser->addParameter('age', type: Console::INT, description: 'Your age'); // Name starts with `-` or `--` will be treated as option $parser->addParameter('--height|-h', type: Console::FLOAT, description: 'Your height', required: true); $parser->addParameter('--location|-l', type: Console::STRING, description: 'Live location', required: true); $parser->addParameter('--muted|-m', type: Console::BOOLEAN, description: 'Is muted'); }, // pass $argv or leave empty ); main($params);
After upgraded to 2.0, the parameter defining is required for Console
app, if a provided argument or option is not
exists
in pre-defined parameters, it will raise an error., (*15)
// me.php $app = new Console(); // Arguments $app->addParameter('name', type: $app::STRING, description: 'Your name', required: true); $app->addParameter('age', type: $app::INT, description: 'Your age'); // Name starts with `-` or `--` will be treated as option $app->addParameter('--height', type: $app::FLOAT, description: 'Your height', required: true); $app->addParameter('--location|-l', type: $app::STRING, description: 'Live location', required: true); $app->addParameter('--muted|-m', type: $app::BOOLEAN, description: 'Is muted'); $app->execute( argv: $argv, main: function () use ($app) { $app->writeln('Hello'); $app->writeln('Name: ' . $app->get('name')); $app->writeln('Age: ' . $app->get('age')); $app->writeln('Height: ' . $app->get('height')); $app->writeln('Location: ' . $app->get('location')); $app->writeln('Muted: ' . $app->get('muted') ? 'Y' : 'N'); return $app::SUCCESS; } );
Also same as:, (*16)
// me.php $app = new class () extends Console { protected function configure(): void { // Arguments $this->addParameter('name', type: $this::STRING, description: 'Your name', required: true); $this->addParameter('age', type: $this::INT, description: 'Your age'); // Name starts with `-` or `--` will be treated as option $this->addParameter('--height', type: $this::FLOAT, description: 'Your height', required: true); $this->addParameter('--location|-l', type: $this::STRING, description: 'Live location', required: true); $this->addParameter('--muted|-m', type: $this::BOOLEAN, description: 'Is muted'); } protected function doExecute(): int|bool { $this->writeln('Hello'); $this->writeln('Name: ' . $this->get('name')); $this->writeln('Age: ' . $this->get('age')); $this->writeln('Height: ' . $this->get('height')); $this->writeln('Location: ' . $this->get('location')); $this->writeln('Muted: ' . ($this->get('muted') ? 'Y' : 'N')); return $this::SUCCESS; } }; $app->execute();
Now if we enter, (*17)
php me.php --name="John Doe " --age=18 --height=1.8 --location=America --muted
It shows:, (*18)
Hello Name: John Doe Age: 25 Height: 1.8 Location: America Muted: Y
Then, if we enter wrong parameters, Simple Console will throw errors:, (*19)
php me.php # [Warning] Required argument "name" is missing. php me.php Simon eighteen # [Warning] Invalid value type for "age". Expected INT. php me.php Simon 18 foo bar # [Warning] Unknown argument "foo". php me.php Simon 18 --nonexists # [Warning] The "-nonexists" option does not exist. php me.php Simon 18 --location # [Warning] Required value for "location" is missing. php me.php Simon 18 --muted=ON # [Warning] Option "muted" does not accept value. php me.php Simon 18 --height one-point-eight # [Warning] Invalid value type for "height". Expected FLOAT.
Simple Console supports to describe arguments/options information which follows docopt standards., (*20)
Add --help
or -h
to Console App:, (*21)
php me.php --help
Will print the help information:, (*22)
Usage: me.php [options] [--] <name> [<age>] Arguments: name Your name age Your age Options: --height=HEIGHT Your height -l, --location=LOCATION Live location -m, --muted Is muted -h, --help Show description of all parameters -v, --verbosity The verbosity level of the output
Add your heading/epilog and command name:, (*23)
// Use constructor $app = new \Asika\SimpleConsole\Console( heading: <<<HEADER [Console] SHOW ME - v1.0 This command can show personal information. HEADER, epilog: <<<EPILOG $ show-me.php John 18 $ show-me.php John 18 --location=Europe --height 1.75 ...more please see https://show-me.example EPILOG, commandName: 'show-me.php' ); // Or set properties $app->heading = <<<HEADER [Console] SHOW ME - v1.0 This command can show personal information. HEADER; $app->commandName = 'show-me.php'; // If not provided, will auto use script file name $app->epilog = <<<EPILOG $ show-me.php John 18 $ show-me.php John 18 --location=Europe --height 1.75 ...more please see https://show-me.example EPILOG; $app->execute();
The result:, (*24)
[Console] SHOW ME - v1.0 This command can show personal information. Usage: show-me.php [options] [--] <name> [<age>] Arguments: name Your name age Your age Options: --height=HEIGHT Your height -l, --location=LOCATION Live location -m, --muted Is muted -h, --help Show description of all parameters -v, --verbosity The verbosity level of the output Help: $ show-me.php John 18 $ show-me.php John 18 --location=Europe --height 1.75 ...more please see https://show-me.example
If your are using class extending, you may override showHelp()
method to add your own help information., (*25)
$app = new class () extends Console { public function showHelp(): void { $this->writeln( <<<HELP My script v1.0 Options: -h, --help Show this help message -q, --quiet Suppress output -l, --location Your location HELP ); }
To define parameters, you can use addParameter()
method., (*26)
The parameter name without -
and --
will be treated as argument., (*27)
// Function style $app->addParameter('name', type: $app::STRING, description: 'Your name', required: true); // Chaining style $app->addParameter('name', type: $app::STRING) ->description('Your name') ->required(true) ->default('John Doe');
The parameter name starts with -
and --
will be treated as options, you can use |
to separate primary name
and shortcuts., (*28)
$app->addParameter('--foo', type: $app::STRING, description: 'Foo description'); $app->addParameter('--muted|-m', type: $app::BOOLEAN, description: 'Muted description');
Arguments' name cannot same as options' name, otherwise it will throw an error., (*29)
To get parameters' value, you can use get()
method, all values will cast to the type which you defined., (*30)
$name = $app->get('name'); // String $height = $app->get('height'); // Int $muted = $app->get('muted'); // Bool
Array access also works:, (*31)
$name = $app['name']; $height = $app['height'];
If a parameter is not provided, it will return FALSE
, and if a parameter provided but has no value, it will
return as NULL
., (*32)
php console.php # `dir` is FALSE php console.php --dir # `dir` is NULL php console.php --dir /path/to/dir # `dir` is `/path/to/dir`
So you can easily detect the parameter existence and give a default value., (*33)
if (($dir = $app['dir']) !== false) { $dir ??= '/path/to/default'; }
You can define the type of parameters, it will auto convert to the type you defined., (*34)
Type | Argument | Option | Description |
---|---|---|---|
STRING | String type | String type | |
INT | Integer type | Integer type | |
FLOAT | Float type | Flot type | Can be int or float, will all converts to float |
NUMERIC | Int or Float | Int or Float | Can be int or float, will all converts to float |
BOOLEAN | (X) | Add --opt as TRUE
|
Use negatable to supports --opt as TRUE and --no-opt as FALSE
|
ARRAY | Array, must be last argument | Array | Can provide multiple as string[]
|
LEVEL | (X) | Int type | Can provide multiple times and convert the times to int |
All parameter values parsed from argv
is default as string
type, and convert to the type you defined., (*35)
ARRAY
typeThe ARRAY
can be use to arguments and options., (*36)
If you set an argument as ARRAY
type, it must be last argument, and you can add more tailing arguments., (*37)
$app->addParameter('name', $app::STRING); $app->addParameter('tag', $app::ARRAY); // Run: console.php foo a b c d e $app->get('tag'); // [a, b, c ,d, e]
Use --
to escape all following options, all will be treated as arguments, it is useful if you are
writing a proxy script., (*38)
php listen.php --timeout 500 --wait 100 -- php flower.php hello --name=sakura --location Japan --muted // The last argument values will be: // ['php', 'flower.php', 'hello', '--name=sakura', '--location', 'Japan', '--muted']
If you set an option as ARRAY
type, it can be used as --tag a --tag b --tag c
., (*39)
$app->addParameter('--tag|-t', $app::ARRAY); $app->get('tag'); // [a, b, c]
LEVEL
typeThe LEVEL
type is a special type, it will convert the times to int. For example, a verbosity level of -vvv
will be
converted to 3
,
and -v
will be converted to 1
. You can use this type to define the verbosity level of your argv parser., (*40)
$parser->addParameter('--verbosity|-v', type: $app::LEVEL, description: 'The verbosity level of the output');
If you are using Console
class, the verbosity is built-in option, you don't need to define it again., (*41)
description
required
optional
.
required
, it means this option requires a value, only --option
without value is not
allowed.boolean
option should not be required.default
null
, false
for boolean type, or []
for array type.null
, false
for boolean type, or []
for array type.negatable
boolean
type.
true
, it will support --xxx|--no-xxx
2 styles to set true|false
.TRUE
and use --no-xxx
to set it as FALSE
, you can do this:
php
$app->addParameter('--muted|-m', $app::BOOLEAN, default: true, negatable: true);
Simple Console follows docopt style to parse parameters., (*42)
--
-
-a
requires value, -abc
will parse as $a = bc
-a
dose not require value, -abc
will parse as $a = true, $b = true, $c = true
=
while shortcuts are not. The --foo=bar
is valid and -f=bar
is invalid, you should use
-f bar
.--
to escape all following options, all will be treated as arguments.Just throw Exception in doExecute()
, Console will auto catch error., (*43)
``` php throw new \RuntimeException('An error occurred');, (*44)
If Console app receive an Throwable or Exception, it will render an `[ERROR]` message: ```bash [Error] An error occurred.
Add -v
to show backtrace if error., (*45)
[Error] An error occurred. [Backtrace]: #0 /path/to/Console.php(145): Asika\SimpleConsole\Console@anonymous->doExecute() #1 /path/to/test.php(36): Asika\SimpleConsole\Console->execute() #2 {main}
If you provide wrong parameters, Console will render a [WARNING]
message with synopsis:, (*46)
[Warning] Invalid value type for "age". Expected INT. test.php [-h|--help] [-v|--verbosity] [--height HEIGHT] [-l|--location LOCATION] [-m|--muted] [--] <name> [<age>]
You can manually raise this [WARNING]
by throwing InvalidParameterException
:, (*47)
if ($app->get('age') < 18) { throw new \Asika\SimpleConsole\InvalidParameterException('Age must greater than 18.'); }
You can set verbosity by option -v
, (*48)
php console.php # verbosity: 0 php console.php -v # verbosity: 1 php console.php -vv # verbosity: 2 php console.php -vvv # verbosity: 3
or ser it manually in PHP:, (*49)
$app->verbosity = 3;
If verbosity
is larger than 0
, it will show backtrace in exception output., (*50)
You can show your own debug information on different verbosity:, (*51)
if ($app->verbosity > 2) { $app->writeln($debugInfo); }
The --help|-h
and --verbosity|-v
options are built-in options if you use Console
app., (*52)
$app = new \Asika\SimpleConsole\Console(); // add parameters... $app->execute(); // You can use built-in `-h` and `-v` options
If you parse argv
by ArgvParser
, you must add it manually. To avoid the required parameters error,
you can set validate
to false
when parsing. Then validate and cast parameters after parsing and help
content display., (*53)
$parser->addParameter( '--help|-h', static::BOOLEAN, 'Show description of all parameters', default: false ); $parser->addParameter( '--verbosity|-v', static::LEVEL, 'The verbosity level of the output', ); // Add other parameters... /** @var \Asika\SimpleConsole\ArgvParser $parser */ $params = $parser->parse($argv, validate: false); if ($params['help'] !== false) { echo \Asika\SimpleConsole\ParameterDescriptor::describe($parser, 'command.php'); exit(0); } // Now we can validate and cast params $params = $parser->validateAndCastParams($params); main($params);
If you don't want to use built-in options for Console App, you can set disableDefaultParameters
to true
:, (*54)
$app = new \Asika\SimpleConsole\Console(); $app->disableDefaultParameters = true; // Add it manually $app->addParameter('--help|-h', $app::BOOLEAN, default: false); // Set verbosity $app->verbosity = (int) env('DEBUG_LEVEL'); $app->execute( main: function (\Asika\SimpleConsole\Console $app) { if ($app->get('help')) { $this->showHelp(); return 0; } // ... } );
Simple Console supports to read from STDIN and write to STDOUT/STDERR. The default stream can be set at constructor., (*55)
new Console( stdout: STDOUT, stderr: STDERR, stdin: STDIN );
If you want to catch the output, you can set stdout
to a file pointer or a stream., (*56)
$fp = fopen('php://memory', 'r+'); $app = new Console(stdout: $fp); $app->execute(); rewind($fp); echo stream_get_contents($fp);
To output messages, you can use these methods:, (*57)
write(string $message, bool $err = false)
: Write to STDOUT or STDERRwriteln(string $message, bool $err = false)
: Write to STDOUT or STDERR with a new linenewLine(int $lines, bool $err = false)
: Write empty new lines to STDOUT or STDERRTo input data, you can use in()
methods:, (*58)
// This will wait user enter text... $app->write('Please enter something: '); $ans = $app->in();
Use ask()
to ask a question, if return empty string, the default value will instead., (*59)
$ans = $app->ask('What is your name: ', [$default]);
Use askConfirm()
to ask a question with Y/n
:, (*60)
$ans = $app->askConfirm('Are you sure you want to do this? [y/N]: '); // Return BOOL // Set default as Yes $ans = $app->askConfirm('Are you sure you want to do this? [Y/n]: ', 'y');
'n', 'no', 'false', 0, '0'
will be FALSE
.'y', 'yes', 'true', 1, '1'
will be TRUE
.To add your boolean mapping, set values to boolMapping
, (*61)
$app->boolMapping[0] = [...]; // Falsy values $app->boolMapping[1] = [...]; // Truly values
Use exec()
to run a sub-process, it will instantly print the output and return the result code of the command., (*62)
$app->exec('ls'); $app->exec('git status'); $app->exec('git commit ...'); $result = $app->exec('git push'); // All output will instantly print to STDOUT if ($result->code !== 0) { // Failure } $result->code; // 0 is success $result->success; // BOOL
Use mustExec()
to make sure a sub-process should run success, otherwise it will throw an exception., (*63)
try { $this->mustExec('...'); } catch (\RuntimeException $e) { // }
Bt default, exec()
and mustExec()
will show the command name before executing, for example., (*64)
>> git show ... >> git commit -am "" ... >> git push ...
if you want to hide it, set the arg: showCmd
to false
., (*65)
$app->exec('cmd...', showCmd: false);
Simple Console use proc_open()
to run sub-process, so you can set your own output stream by callback., (*66)
$log = ''; $app->exec( 'cmd ...', output: function (string $data, bool $err) use ($app, &$log) { $app->write($data, $err); $log .= $data; } );
Use false
to disable the output, you can get full output from result object after sub-process finished., (*67)
Note, the output will only write to result object if output
set to false
. If you set output
as closure or
keep default NULL
, the output will be empty in result object., (*68)
$result = $app->exec('cmd ...', output: false); $result->output; // StdOutput of sub-process $result->errOutput; // StdErr Output of sub-process // Below will not write to the result object $result = $app->exec('cmd ...'); // OR $result = $app->exec('cmd ...', output: function () { ... }); $result->output; // Empty $result->errOutput; // Empty
exec()
By now, running sub-process by prop_open()
is in BETA, if prop_open()
not work for your environment, simply override
exec()
to use PHP system()
instead., (*69)
public function exec(string $cmd, \Closure|null $output = null, bool $showCmd = true): ExecResult { !$showCmd || $this->writeln('>> ' . $cmd); $returnLine = system($cmd, $code); return new \Asika\SimpleConsole\ExecResult($code, $returnLine, $returnLine); }
If your script has multiple tasks, for example, the build script contains configure|make|clear
etc..., (*70)
Here is an example to show how to delegate multiple tasks and pass the necessary params to method interface., (*71)
$app = new class () extends Console { protected function configure(): void { $this->addParameter('task', type: $this::STRING, description: 'Task (configure|build|make|move|clear)', required: true); $this->addParameter('--lib-path', type: $this::STRING); $this->addParameter('--temp-path', type: $this::STRING); $this->addParameter('--nested', type: $this::STRING); $this->addParameter('--all', type: $this::STRING); } protected function doExecute(): int|bool { $params = []; foreach ($this->params as $k => $v) { // Use any camel case convert library $params[Str::toCamelCase($k)] = $v; } return $this->{$this['task']}(...$params); } // `...$args` is required, otherwise the redundant params will make method calling error protected function build(string $libPath, string $tempPath, ...$args): int { $this->writeln("Building: $libPath | $tempPath"); return 0; } protected function clear(string $nested, string $dir, ...$args): int { $this->writeln("Clearing: $nested | $dir"); return 0; } }; $app->execute();
Now run:, (*72)
php make.php build --lib-path foo --temp-path bar # Building foo | bar
I'm apologize that I'm too busy to fix or handle all issues and reports, but pull-request is very welcome and will speed up the fixing process., (*73)