nategood / commando Goto Github PK
View Code? Open in Web Editor NEWAn Elegant CLI Library for PHP
License: MIT License
An Elegant CLI Library for PHP
License: MIT License
Is there a clean way to implement this using the existing code? Or would it need to be a new feature?
Think ssh -v[vvvvvv...] (incrementing levels of verbosity if the option is repeated)
Note that with ssh
, it functions the same with: ssh -v -v -v -v example.com
and ssh -vvvv example.com
I don't recall ever seeing this type of "option repetition" used with anything other than verbosity levels, and I'm not sure if that's even a GNU utility psuedo-standard, or if it's just specific to ssh
and a few other oddball utilities, but it is handy. I'd actually like to use that for a project I'm building, using your Commando library that wraps around ssh
, and would like to provide the same -v[vvv...] functionality if possible. Ideas?
I love your work on this by the way -- great library.
Is it possible to access/use options with multiple words as input?
somecommand.php --countries usa gb fr
I tried it, but $hello_cmd['countries']
returns just the first (in this case usa).
If i've configured argument parsing for x
and y
, in scenarios below what is the best way to retrieve all tokens after the last parsed argument?
script.php -x arg1 -y arg2 everything else that's here
script.php -x arg1 everything else that's here
I can achieve a variable containing everything else that's here
but only if I enclose it in quotes. Any way to do this without quotes?
I know I must be missing something here, but what is the actual process of determining when a predefined flag is passed into the script endpoint? I've resorted to writing my own function to parse the $_SERVER['argc']
array.
Pretty sure the library is supposed to be taking care of this for me already, right?
It's very common for a file processing script to either take the filename OR '-' to get data from stdin.
With this library I cannot do this:
php convert.php input.log
php convert.php - < input.log
Whenever I pass "-" it says
ERROR: Unable to parse option -: Invalid syntax
My definition was like:
$cmd->option()
->require()
->describedAs('Input log file or `-` to read from stdin');
and value reading part was as simple as:
$filename = $cmd[0];
readme.md contains decriptions about getOptions() method, but I cannot find it in the source code. How can I get all registered options from Command's subclass ? ( This method could be very useful for customizing help message )
There is no way to get the private title variable in the Option class.
I just checked on two systems: Ubuntu 10.04.4 LTS and Mac OS X 10.7.4 Lion: tputs
is not installed, but instead uses tput
. I think this might be a change in the ncurses library? Use of tputs
Instead of Issue #86, perhaps it makes more sense to implement a transform
method that handles both validation and value mapping.
I have certain options that accept simple json strings as values, for example. These will require both a transformation from string to php array, and also certain object-specific validations.
To handle this well, I'll need the ability to deliver fine-grained error messages and also the ability to parse the value only once, rather than parsing the value for validation, then parsing again for mapping.
Here's how I see this working:
$cmd->option("t")
->require()
->transform(function($val) {
$val = json_decode($val, true);
if (!$val) {
throw new OptionValidationException("You must provide a valid json-encoded object for option 't'");
}
$errors = [];
if (empty($val['prop1']) || !is_string($val['prop1'])) {
$errors[] = "`prop1` of option 't' must be a string";
}
if (empty($val['prop2']) || !preg_match("/^[a-fA-F0-9-]{32,36}$/", $val['prop2'])) {
$errors[] = "`prop2` of option 't' must be a valid GUID or UUID"
}
if (!empty($errors)) {
$e = new OptionValidationException("");
$e->setOptionValidationErrors($errors);
throw $e;
}
if (empty($val['prop3'])) {
$val['prop3'] = 0;
} elseif (!is_int($val['prop3'])) {
$val['prop3'] = (int)$val['prop3'];
}
// Return the transformed value for the option
return $val;
});
--help command gives me Could not find the specified path.
PHP code:
<?php
require_once 'vendor/autoload.php';
use Commando\Command;
// command config
$cmd = new Command;
$cmd
->setHelp('This is a great command it. It can be used by calling `run <argument>`.')
->option()->referToAs('the first arg')->describeAs('run takes an optional single argument. e.g. run argument0')
->option('a')->description("Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.")
->option('b')->boolean()->describeAs("A boolean option.")
->option('c')->aka('foo')->describeAs("Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt.")->required();
Output:
C:\Users\MYUSER\Desktop\myscript>php index.php --help
Could not find the specified path.
Could not find the specified path.
Could not find the specified path.
Could not find the specified path.
Could not find the specified path.
Could not find the specified path.
Could not find the specified path.
index.php
This is a great command it. It can be used by calling `run <argument>`.
the first arg
run takes an optional single argument. e.g. run argument0
-a <argument>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
velit esse cillum dolore eu fugiat nulla pariatur.
-b
A boolean option.
-c/--foo <argument>
Required. Excepteur sint occaecat cupidatat non proident, sunt in culpa
qui officia deserunt.
--help
Show the help page for this command.
composer.json:
{
"name": "my/my",
"description": "my",
"require": {
"nategood/commando": "^0.2.9"
},
"authors": [
{
"name": "me",
"email": "[email protected]"
}
]
}
I am on Windows
echo $command['n]
successfully returns what's passed as -n
, but $command->getOption('n')->getValue()
returns nothing
/**
around line 589, Command.php, the code should be changed to:
// sort($keys, SORT_NATURAL);
natsort($keys);
"SORT_NATURAL" constant was added in PHP 5.4. While you are trying to support 5.3.
Without this change, PHP5.3 gives errors/warnings.
When printing the '--help' on PHP 5.3, the following notice and warning occures:
PHP Notice: Use of undefined constant SORT_NATURAL - assumed 'SORT_NATURAL' in Command.php on line 589
PHP Warning: sort() expects parameter 2 to be long, string given in Command.php on line 589
I'm just wondering.
I want to declare an option which could be invoked several times, e.g.:
--option value1 --option value2 --option value3
This is similar to what tar
have for files exclusion, .e.g
$ tar --exclude='./folder' --exclude='./upload/folder2' -zcvf /backup/filename.tgz .
I cannot find a way how to do this, or I'm missing something?
I am in the process of writing a CLI for a framework/ORM (see my comments in #20 for more details).
One thing I am wondering about is how the completed CLI is intended to be deployed. This is, perhaps, not really an issue per se but GitHub only allows issues and no true discussions so here goes...heh).
So assuming my CLI is part of a Composer package for framework (or vice verse, I could make the framework a dependency of my CLI package). I don't think it's good practice to run PHP directly from the vendor
folder and the CLI will need the context of the app
being developed (environment, etc).
I notice that most frameworks have the base/core in one repository and then have a skeleton or bootstrap repository from which the actual app is built from. Is that the best way to deploy a CLI using Commando
?
Any thoughts?
add like this method at Commando/Command
Thanks
Should be able to do this:
$cmd->option('t')
->must(function($t) {
return is_int($t);
}, "Option `t` must be an integer")
->must(function($t) {
return $t >= 0 && $t <= 10;
}, "Option `t` must be between 0 and 10 inclusive.");
Hi, nice project!
Can you release a new version with this method?
Thanks.
Great library, though! Just discovered it and am working to figure it out fully!
Would be cool if Commando integrated this: https://github.com/kevinlebrun/colors.php
Hi
Using windows environment
try to use --help, got this errors, just right before filename appeared=
The system cannot find the path specified.
The system cannot find the path specified.
The system cannot find the path specified.
Yes, 3 in a row.
Thanks
Value isn't getting set when passing a argument to a option
use Commando\Command;
$tokens = array('status');
$cmd = new Command($tokens);
$cmd->setHelp('This tool is for managing status from the cli. It takes no arguments just options that take arguments')
->option()->describe('status take a optional single action argument. e.g. status {create|view|update}')
->option('m')->aka('message')->describe('This is message you wish to use with create or update. wrap in double quotes or vim will be used');
var_dump($cmd->getOption('m'));
var_dump($cmd['message']);exit;
08:43:49 baldr:[~/cvs/status-cli/bin] $ php status.php create -m 'this is a test'
object(Commando\Option)#10 (9) {
["name":"Commando\Option":private]=>
string(1) "m"
["aliases":"Commando\Option":private]=>
array(1) {
[0]=>
string(7) "message"
}
["value":"Commando\Option":private]=>
NULL
["description":"Commando\Option":private]=>
string(96) "This is message you wish to use with create or update. wrap in double quotes or vim will be used"
["required":"Commando\Option":private]=>
bool(false)
["boolean":"Commando\Option":private]=>
bool(false)
["type":"Commando\Option":private]=>
int(1)
["rule":"Commando\Option":private]=>
NULL
["map":"Commando\Option":private]=>
NULL
}
NULL
When executing the examples/help.php
example, if the last line ($cmd->printHelp();
) is removed, the help message doesn't work, even if the script is called with the --help
flag.
Needs behaves a bit strangely and should interact with "requires" but doesn't. It's actually acting as a defacto "requires". Even if a "needed" command isn't "required" Commando will still throw an exception if it is specified as needed by an unused argument.
E.g
$cmd
->option("a");
$cmd->option("b")
->needs("c")
$cmd->option("c");
The following command should run:
filename.php --a --b --c
Only this should fail
filename.php --a --b
But this also (incorrectly) fails:
filename.php --a
In the last one the "needs" clause says that "b needs c" even though c isn't a required argument and b wasn't even specified.
The fix in my local branch is to replace the current "needs" block in Command.php with the following:
// See if our options have what they require
foreach ($keyvals as $key => $value) {
$option = $this->getOption($key);
if(!is_null($option->getValue()) || $option->isRequired()){
$needs = $option->hasNeeds($this->options);
if ($needs !== true) {
throw new \InvalidArgumentException(
'Option "'.$option->getName().'" does not have required option(s): '.implode(', ', $needs)
);
}
}
}
But I'd like to know if the current behaviour of "needs" also acting as a defacto "requires" is actually the desired behaviour before I upload it and submit a pull request.
try {
throw new \Exception('Expect to see error caught');
} catch (\Throwable $t) {
var_dump($t->getMessage());
}
But in your library...
$cmd = CMD::getInstance()->doNotTrapErrors();
try {
$cmd->option('r')
->require()
->aka('run')
->describedAs('When set, use this title to address the person');
} catch (\Throwable $t) {
var_dump('caught');
}
Never gets to that statement var_dump('caught');
Instead it just dumps out the error and dies...
PHP Fatal error: Uncaught Exception: Required option r must be specified in /var/www/nano/vendor/nategood/commando/src/Commando/Command.php:466
Stack trace:
#0 /var/www/nano/vendor/nategood/commando/src/Commando/Command.php(164): Commando\Command->parse()
#1 [internal function]: Commando\Command->__destruct()
#2 {main}
thrown in /var/www/nano/vendor/nategood/commando/src/Commando/Command.php on line 466
Fatal error: Uncaught Exception: Required option r must be specified in /var/www/nano/vendor/nategood/commando/src/Commando/Command.php on line 466
Exception: Required option r must be specified in /var/www/nano/vendor/nategood/commando/src/Commando/Command.php on line 466
Call Stack:
0.0237 585528 1. Commando\Command->__destruct() /var/www/nano/vendor/nategood/commando/src/Commando/Command.php:0
0.0237 585528 2. Commando\Command->parse() /var/www/nano/vendor/nategood/commando/src/Commando/Command.php:164
0.0239 589064 3. Commando\Command->error() /var/www/nano/vendor/nategood/commando/src/Commando/Command.php:498
Too add:
public function run(){
if (!$this->parsed) {
$this->parse();
}
}
public function __destruct()
{
if (!$this->parsed) {
$this->parse();
}
}
Created an identical function run(), when called (in sequence) the error is now caught properly. Is the a PHP issue whereby thrown \Exception after destruct? No idea...
try {
$cmd = CMD::getInstance()->doNotTrapErrors();
$cmd->option('r')
->require()
->aka('run')
->describedAs('When set, use this title to address the person')
->run();
} catch (\Throwable $t) {
var_dump('now gets here when run() is used');
}
Fixed in #69
If you have an option/flag with a hyphen in the middle of its name, Commando accepts it but only parses the received argument's name up to the hyphen.
Example:
<?php
$cmd = new Commando\Command();
$cmd->option('basicarg');
$cmd->option('hyphen-arg');
var_dump($cmd['basicarg']);
var_dump($cmd['hyphen-arg']);
Calls:
$ php script.php --basicarg hello
string(5) "hello"
NULL
$ php script.php --hyphen-arg hello
ERROR: Unknown option, hyphen, specified
is there any?
Subj. I copy sample and run it:
php /srv/bitrix/batches/user_generation.php --title=Mister
ERROR: Invalid value, , for option t
The problem in must
method:
->must(function($title) {
var_dump($title); // *always null*
$titles = array('Mister', 'Mr', 'Misses', 'Mrs', 'Miss', 'Ms');
return in_array($title, $titles);
})
PHP 5.6.32 (cli) (built: Oct 25 2017 07:00:59)
When a value passed to default() does not pass a must() closure, the output should be formatted in the standard prettyprint. However, the current behavior is to throw an ugly exception and stacktrace.
Calling exit aborts the entire application making it difficult work around in some situations, such as during unit tests or when the outcome of an invocation should be logged.
An option to throw an exception instead of calling exit would be great!
Relevant line:
commando/src/Commando/Command.php
Line 460 in 7ae153a
How can I create params with dash in it?
I tried this: (new Command)->option('reset-list')->boolean()
then I call my script this:
$ php test.php --reset-list
ERROR: Unknown option, reset, specified
The help shows the param correctly:
$ php test.php --help
test.php
--help
Show the help page for this command.
--reset-list
I created a PHP 5.4-7.1 version of this and then a PHP 7.2 version: https://github.com/tolidano/commandox/releases
Can you make a new version so we have the latest fixes in a stable build?
I was wondering if there was a particular reason why you're type hinting to \Closure instead of the less restrictive \Callable?
for example, if I call $command->option("foo")->default() and then followed by another map() call, the closure passed to map() is not called on the default value. Is this intentional (design?) or this is a bug?
let me know if you need further information
Shouldn't this default to false if not specified, true if option is defined? Seems more intuitive for a bool flag.
When the value assigned to the key is NULL, an exception is thrown: 'Unable to parse option XX Expected an argument'. Instead of returning an exception, I consider the set of values from the 'default' for the option.
So, if it has a default value this is considered, otherwise consider the $val value itself.
Either the needs method isn't working, or my understanding of what it's supposed to do isn't working.
Say you've got a script with two options: -t and -e. If you use -t, you must include -e with a value. According to the documentation, needs('e') on the -t option would require the -e option to be set. However, that doesn't seem to be the case.
For example:
<?php
require_once 'vendor/autoload.php';
$cmd = new Commando\Command();
$cmd->option('e');
$cmd->option('t')
->boolean()
->needs('e');
var_dump($cmd['e']);
var_dump($cmd['t']);
It appears that when it's checking to see if the -e option is set, it actually is just checking to see if there is a -e option defined (src/Commando/Option.php: 305) not that it has been passed in by the user.
I'm willing to fix the issue if this isn't how the method is intended to behave.
I don't see what the minimum version of php this will work on listed.
Probably related to #75, the --help option doesn't short circuit unless you're accessing an option via the array access interface.
Just noticed this on a machine where I hadn't installed mbstring yet. I got this error:
Fatal error: Call to undefined function Commando\mb_strlen() in /path/to/vendor/nategood/commando/src/Commando/Option.php on line 38
So your composer.json
should probably include "ext-mbstring": "*"
as a required runtime dependency.
Given this script:
$cmd = new \Commando\Command();
// Define an option
$cmd->option("t");
// If that option is a certain value, then define another option (this causes autoparsing)
if ($cmd['t'] == 'something') {
// do something, like perhaps define other options
}
// Now define a required option. Because we've already parsed the options, the rules
// for this option won't cause an error when really they should.
$cmd->option("e")
->required()
->must(function($t) {
return preg_match("/^[^0-9]+$/", $t);
});
// Try to echo the required 'e' option. Script should not get to this point, but does,
// and also doesn't throw an error here.
echo $cmd['e'];
Expected Behavior
required
called on option 'e'.must
called on option 'e'.Actual Behavior
Does not show any errors at all, and outputs any value given for option 'e'.
I want to set the default value for a ->file()
option based on the value of another option. This appears to work only if the ->file()
option is the last one defined. Examples:
#!/usr/bin/env php
<?php
require_once 'vendor/autoload.php';
$cmd = new Commando\Command();
$cmd->option()
->require()
->describedAs('Output directory.');
$cmd->option('t')
->aka('type')
->describedAs('Type of thing to process.');
// Hard-coded value for default() works.
$cmd->option('c')
->aka('connfigfile')
->describedAs('The config file to use.')
->file()
->default('configs/foo.yml');
$cmd->option('n')
->aka('number')
->describedAs('The number of things to process.');
Running ./file_test.php -n 10 -t foo /tmp
does not produce an error. But if set the default value of the ->file()
option based on the value of another option:
#!/usr/bin/env php
<?php
require_once 'vendor/autoload.php';
$cmd = new Commando\Command();
$cmd->option()
->require()
->describedAs('Output directory.');
$cmd->option('t')
->aka('type')
->describedAs('Type of thing to process.');
// Dynamically generated value for default() produces error.
$cmd->option('c')
->aka('connfigfile')
->describedAs('The config file to use.')
->file()
->default('configs/' . $cmd['type'] . '.yml');
// Now this option is not recognized.
$cmd->option('n')
->aka('number')
->describedAs('The number of things to process.');
running ./file_test.php -n 10 -t foo /tmp
produces ERROR: Unknown option, n, specified
.
However, if the ->file()
option is the last option defined, its default value can be dynamically generated:
#!/usr/bin/env php
<?php
require_once 'vendor/autoload.php';
$cmd = new Commando\Command();
$cmd->option()
->require()
->describedAs('Output directory.');
$cmd->option('t')
->aka('type')
->describedAs('Type of thing to process.');
$cmd->option('n')
->aka('number')
->describedAs('The number of things to process.');
// Dynamically generated value for default() works.
$cmd->option('c')
->aka('connfigfile')
->describedAs('The config file to use.')
->file()
->default('configs/' . $cmd['type'] . '.yml');
Running ./file_test.php -n 10 -t foo /tmp
does not produce an error.
Is this expected behavior, or am I missing something obvious?
Can you make a new release for the latest fixes?
Let's have
$cmd->flag('a')->boolean();
$cmd->flag('b')->boolean()->conflictsWith('c');
$cmd->flag('c')->boolean();
to get the following result:
PASS: (no args)
, -a
, -b
, -c
, -ab
, -ac
FAIL: -bc
, -abc
(Please use thumb to +1 instead reply unless you are adding additional information.)
I am trying to figure out how to properly write my commands. All the examples are essentially linear main() PHP programs. I am trying to figure out if that is a good practice or whether I should be making a class to define my options and such to make it more OOP.
Just wondering if anyone has opinions or examples I could see?
A lot of commands use this syntax, not sure how standard it is, but I think supporting it would help prevent confusion.
I know you can require single commands, eg:
$command->option('t')->require(true);
$command->option('b')->require(true);
Is it possible to do something like this:
$command->option('t')->requireOr('b');
$command->option('b')->requireOr('t');
$command->option('t')->requireAndOr('b');
$command->option('b')->requireAndOr('t');
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.