Skip to content

Commit

Permalink
added support more readable error message
Browse files Browse the repository at this point in the history
can still tell Commando to throw Exceptions instead with doNotTrapErrors
  • Loading branch information
nategood committed Aug 20, 2012
1 parent 93ddf92 commit 2479770
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 71 deletions.
6 changes: 6 additions & 0 deletions README.md
Expand Up @@ -142,6 +142,12 @@ Commando has automatic `--help` support built in. Calling your script with this

![help screenshot](http://cl.ly/image/1y3i2m2h220u/Screen%20Shot%202012-08-19%20at%208.54.49%20PM.png)

## Error Messaging

By default, Commando will catch Exceptions that occur during the parsing process. Instead print a formatted, user friendly error message to standard error and finally exits. If you wish to have Commando through Exceptions in these cases, call the `doNotTrapErrors` method on your Command instance.

![error screenshot](http://f.cl.ly/items/150H2d3x0l3O3J0s3i1G/Screen%20Shot%202012-08-19%20at%209.58.21%20PM.png)

## Trainwreck

If you, [like Martin](http://www.amazon.com/gp/product/0132350882), are of the _train_ of thought that the chaining pattern is a "trainwreck", Commando can also be used without chaining. Commando reads nicer and is more concise with the chaining.
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Expand Up @@ -15,7 +15,7 @@
"kevinlebrun/colors.php": "0.2.*"

},
"version": "0.1.0",
"version": "0.1.1",
"autoload": {
"psr-0": {"Commando": "src/"}
}
Expand Down
4 changes: 2 additions & 2 deletions examples/basic
@@ -1,6 +1,6 @@
#! /usr/bin/env php
<?php
// Same as basic.php demoing that the library works fine as an executable as well
// The library works fine as an executable as well
// This example is Unix-ish specific
// Usage
// ./basic test
Expand All @@ -9,4 +9,4 @@ require dirname(__DIR__) . '/vendor/autoload.php';

$cmd = new Commando\Command();

echo "Argument #1: {$cmd[0]}";
echo "Argument #1: " . $cmd[0] . PHP_EOL;
2 changes: 1 addition & 1 deletion examples/basic.php
Expand Up @@ -6,4 +6,4 @@

$cmd = new Commando\Command();

echo "Argument #1: {$cmd[0]}";
echo "Argument #1: " . $cmd[0] . PHP_EOL;
3 changes: 2 additions & 1 deletion examples/help.php
Expand Up @@ -12,7 +12,8 @@
$tokens = array('mycmd');
$cmd = new Command($tokens);
$cmd
->setHelp('This is a great command it. It can be used by calling mycmd <argument>. Optional options are listed below.')
->setHelp('This is a great command it. It can be used by calling mycmd <argument>.')
->option()->describe('mycmd takes an optional single argument. e.g. mycmd 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()->description("A boolean option.")
->option('c')->aka('foo')->description("Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt.");
Expand Down
167 changes: 101 additions & 66 deletions src/Commando/Command.php
Expand Up @@ -20,26 +20,8 @@ class Command implements \ArrayAccess
$tokens = array(),
$help = null,
$parsed = false,
$use_default_help = true;

public function __construct($tokens = null)
{
if (empty($tokens)) {
$tokens = $_SERVER['argv'];
}

$this->setTokens($tokens);
}

/**
* Factory style reads a little nicer
* @param array $tokens defaults to $argv
* @return Commando
*/
public static function define($tokens = null)
{
return new Command($tokens);
}
$use_default_help = true,
$trap_errors = true;

/**
* @var array Valid "option" options, mapped to their aliases
Expand Down Expand Up @@ -74,6 +56,24 @@ public static function define($tokens = null)
'must' => 'must',
);

public function __construct($tokens = null)
{
if (empty($tokens)) {
$tokens = $_SERVER['argv'];
}

$this->setTokens($tokens);
}

/**
* Factory style reads a little nicer
* @param array $tokens defaults to $argv
* @return Commando
*/
public static function define($tokens = null)
{
return new Command($tokens);
}

/**
* This is the meat of Command. Any time we are operating on
Expand Down Expand Up @@ -101,7 +101,6 @@ public function __call($name, $arguments)
throw new \Exception(sprintf('Invalid Option Chain: Attempting to call %s before an "option" declaration', $name));
}

// call method
array_unshift($arguments, $this->current_option);
$option = call_user_func_array(array($this, "_$name"), $arguments);

Expand Down Expand Up @@ -221,63 +220,82 @@ private function parseIfNotParsed()
*/
public function parse()
{
$tokens = $this->tokens;
// the executed filename
$this->name = array_shift($tokens);
try {
$tokens = $this->tokens;
// the executed filename
$this->name = array_shift($tokens);

$keyvals = array();
$count = 0; // standalone argument count
$keyvals = array();
$count = 0; // standalone argument count

while (!empty($tokens)) {
$token = array_shift($tokens);
while (!empty($tokens)) {
$token = array_shift($tokens);

list($name, $type) = $this->_parseOption($token);
list($name, $type) = $this->_parseOption($token);

if ($type === self::OPTION_TYPE_ARGUMENT) {
// its an argument, use an int as the index
$keyvals[$count] = $name;
if ($type === self::OPTION_TYPE_ARGUMENT) {
// its an argument, use an int as the index
$keyvals[$count] = $name;

// We allow for "dynamic" annonymous arguments, so we
// add an option for any annonymous arguments that
// weren't predefined
if (!$this->hasOption($count)) {
$this->options[$count] = new Option($count);
}
// We allow for "dynamic" annonymous arguments, so we
// add an option for any annonymous arguments that
// weren't predefined
if (!$this->hasOption($count)) {
$this->options[$count] = new Option($count);
}

$count++;
} else {
// Short circuit if the help flag was set and we're using default help
if ($this->use_default_help === true && $name === 'help') {
$this->printHelp();
exit;
$count++;
} else {
// Short circuit if the help flag was set and we're using default help
if ($this->use_default_help === true && $name === 'help') {
$this->printHelp();
exit;
}

$option = $this->getOption($name);
if ($option->isBoolean()) {
$keyvals[$name] = true;
} else {
// the next token MUST be an "argument" and not another flag/option
list($val, $type) = $this->_parseOption(array_shift($tokens));
if ($type !== self::OPTION_TYPE_ARGUMENT)
throw new \Exception(sprintf('Unable to parse option %s: Expected an argument', $token));
$keyvals[$name] = $val;
}
}
}

$option = $this->getOption($name);
if ($option->isBoolean()) {
$keyvals[$name] = true;
} else {
// the next token MUST be an "argument" and not another flag/option
list($val, $type) = $this->_parseOption(array_shift($tokens));
if ($type !== self::OPTION_TYPE_ARGUMENT)
throw new \Exception(sprintf('Unable to parse option %s: Expected an argument', $token));
$keyvals[$name] = $val;
// Set values (validates and performs map when applicable)
foreach ($keyvals as $key => $value) {
$this->getOption($key)->setValue($value);
}

// todo protect against duplicates caused by aliases
foreach ($this->options as $option) {
if (is_null($option->getValue()) && $option->isRequired()) {
throw new \Exception(sprintf('Required %s %s must be specified',
$option->getType() & Option::TYPE_NAMED ?
'option' : 'argument', $option->getName()));
}
}
}

// Set values (validates and performs map when applicable)
foreach ($keyvals as $key => $value) {
$this->getOption($key)->setValue($value);
$this->parsed = true;

} catch(\Exception $e) {
$this->error($e);
}
}

// todo protect against duplicates caused by aliases
foreach ($this->options as $option) {
if (is_null($option->getValue()) && $option->isRequired()) {
throw new \Exception(sprintf('Required option, %s, must be specified', $option->getName()));
}
public function error(\Exception $e)
{
if ($this->trap_errors !== true) {
throw $e;
}

$this->parsed = true;
$color = new \Colors\Color();
$error = sprintf('ERROR: %s %s', $e->getMessage(), PHP_EOL);
echo $color($error)->bg('red')->bold()->white();
exit(1);
}

/**
Expand Down Expand Up @@ -361,6 +379,25 @@ public function setHelp($help)
return $this;
}

/**
* @param bool $trap when true, exceptions will be caught by Commando and
* printed cleanly to standard error.
* @return Commando
*/
public function trapErrors($trap = true)
{
$this->trap_errors = $trap;
return $this;
}

/**
* @return Commando
*/
public function doNotTrapErrors()
{
return $this->trapErrors(false);
}

/**
* @return string help docs
*/
Expand Down Expand Up @@ -425,9 +462,7 @@ public function offsetExists($offset)
public function offsetGet($offset)
{
// Support implicit/lazy parsing
if (!$this->isParsed()) {
$this->parse();
}
$this->parseIfNotParsed();
if (!isset($this->options[$offset])) {
return null; // follows normal php convention
}
Expand Down
7 changes: 7 additions & 0 deletions src/Commando/Option.php
Expand Up @@ -139,6 +139,13 @@ public function getName()
return $this->name;
}

/**
* @return int type (see OPTION_TYPE_CONST)
*/
public function getType()
{
return $this->type;
}

/**
* @return mixed value of the option
Expand Down

0 comments on commit 2479770

Please sign in to comment.