From 1cebfec066cd669ddb82464973d1e65cdc111150 Mon Sep 17 00:00:00 2001 From: Steve Clay Date: Tue, 28 Feb 2012 15:56:47 -0500 Subject: [PATCH] Cli upgrade --- min/lib/MrClay/Cli.php | 104 +++++++++++++++++++++++++------- min/lib/MrClay/Cli/Arg.php | 76 ++++++++++++++--------- min_extras/cli/rewrite-uris.php | 12 +++- 3 files changed, 138 insertions(+), 54 deletions(-) diff --git a/min/lib/MrClay/Cli.php b/min/lib/MrClay/Cli.php index 4c25af7..ac20158 100644 --- a/min/lib/MrClay/Cli.php +++ b/min/lib/MrClay/Cli.php @@ -3,7 +3,17 @@ namespace MrClay; /** - * Front controller for a command line app, handling and validating arguments + * Forms a front controller for a console app, handling and validating arguments (options) + * + * Instantiate, add arguments, then call validate(). Afterwards, the user's valid arguments + * and their values will be available in $cli->values. + * + * You may also specify that some arguments be used to provide input/output. By communicating + * solely through the file pointers provided by openInput()/openOutput(), you can make your + * app more flexible to end users. + * + * @author Steve Clay + * @license http://www.opensource.org/licenses/mit-license.php MIT License */ class Cli { @@ -35,13 +45,24 @@ class Cli { */ public $debug = array(); + /** + * @var bool The user wants help info + */ + public $isHelpRequest = false; + /** * @var array of Cli\Arg */ protected $_args = array(); + /** + * @var resource + */ protected $_stdin = null; + /** + * @var resource + */ protected $_stdout = null; /** @@ -52,6 +73,10 @@ class Cli { if ($exitIfNoStdin && ! defined('STDIN')) { exit('This script is for command-line use only.'); } + if (isset($GLOBALS['argv'][1]) + && ($GLOBALS['argv'][1] === '-?' || $GLOBALS['argv'][1] === '--help')) { + $this->isHelpRequest = true; + } } /** @@ -73,23 +98,33 @@ class Cli { } /** - * @param Cli\Arg|string $letter + * @param string $letter * @param bool $required + * @param Cli\Arg|null $arg * @return Cli\Arg + * @throws \InvalidArgumentException */ - public function addArgument($letter, $required) + public function addArgument($letter, $required, Cli\Arg $arg = null) { - if (! $letter instanceof Cli\Arg) { - if (is_string($letter)) { - $letter = new Cli\Arg($letter, $required); - } else { - throw new \InvalidArgumentException('Must be letter or MrClay\\Cli\\Arg instance'); - } + if (! preg_match('/^[a-zA-Z]$/', $letter)) { + throw new \InvalidArgumentException('$letter must be in [a-zA-z]'); } - $this->_args[$letter->getLetter()] = $letter; - return $letter; + if (! $arg) { + $arg = new Cli\Arg($required); + } + $this->_args[$letter] = $arg; + return $arg; } - + + /** + * @param string $letter + * @return Cli\Arg|null + */ + public function getArgument($letter) + { + return isset($this->_args[$letter]) ? $this->_args[$letter] : null; + } + /* * Read and validate options * @@ -102,17 +137,13 @@ class Cli { $this->values = array(); $this->_stdin = null; - if (isset($GLOBALS['argv'][1]) - && ($GLOBALS['argv'][1] === '-?' - || $GLOBALS['argv'][1] === '--help' - )) { + if ($this->isHelpRequest) { return false; } $lettersUsed = ''; - foreach ($this->_args as $arg) { + foreach ($this->_args as $letter => $arg) { /* @var Cli\Arg $arg */ - $letter = $arg->getLetter(); $options .= $letter; $lettersUsed .= $letter; @@ -127,9 +158,8 @@ class Cli { $this->debug['getopt_options'] = $options; $this->debug['getopt_return'] = $o; - foreach ($this->_args as $arg) { + foreach ($this->_args as $letter => $arg) { /* @var Cli\Arg $arg */ - $letter = $arg->getLetter(); $this->values[$letter] = false; if (isset($o[$letter])) { if (is_bool($o[$letter])) { @@ -209,7 +239,7 @@ class Cli { } } } else { - if ($arg->isRequired) { + if ($arg->isRequired()) { $this->addError($letter, "Missing"); } } @@ -250,13 +280,43 @@ class Cli { if (empty($this->errors)) { return ''; } - $r = "Problems with your options:\n"; + $r = "Some arguments did not pass validation:\n"; foreach ($this->errors as $letter => $arr) { $r .= " $letter : " . implode(', ', $arr) . "\n"; } $r .= "\n"; return $r; } + + /** + * @return string + */ + public function getArgumentsListing() + { + $r = "\n"; + foreach ($this->_args as $letter => $arg) { + /* @var Cli\Arg $arg */ + $desc = $arg->getDescription(); + $flag = " -$letter "; + if ($arg->mayHaveValue) { + $flag .= "[VAL]"; + } elseif ($arg->mustHaveValue) { + $flag .= "VAL"; + } + if ($arg->assertFile) { + $flag = str_replace('VAL', 'FILE', $flag); + } elseif ($arg->assertDir) { + $flag = str_replace('VAL', 'DIR', $flag); + } + if ($arg->isRequired()) { + $desc = "(required) $desc"; + } + $flag = str_pad($flag, 12, " ", STR_PAD_RIGHT); + $desc = wordwrap($desc, 70); + $r .= $flag . str_replace("\n", "\n ", $desc) . "\n\n"; + } + return $r; + } /** * Get resource of open input stream. May be STDIN or a file pointer diff --git a/min/lib/MrClay/Cli/Arg.php b/min/lib/MrClay/Cli/Arg.php index 00570b3..81146a7 100644 --- a/min/lib/MrClay/Cli/Arg.php +++ b/min/lib/MrClay/Cli/Arg.php @@ -6,15 +6,13 @@ namespace MrClay\Cli; * An argument for a CLI app. This specifies the argument, what values it expects and * how it's treated during validation. * - * By default, the argument will be assumed to be a letter flag with no value following. + * By default, the argument will be assumed to be an optional letter flag with no value following. * * If the argument may receive a value, call mayHaveValue(). If there's whitespace after the * flag, the value will be returned as true instead of the string. * - * If the argument is required, or mustHaveValue() is called, the value will be required and - * whitespace is permitted between the flag and its value. - * - * If the argument AND string BOTH must be present for validation, call assertRequired(). + * If the argument MUST be accompanied by a value, call mustHaveValue(). In this case, whitespace + * is permitted between the flag and its value. * * Use assertFile() or assertDir() to indicate that the argument must return a string value * specifying a file or directory. During validation, the value will be resolved to a @@ -31,7 +29,6 @@ namespace MrClay\Cli; * @method \MrClay\Cli\Arg assertReadable() Assert that the specified file/dir must be readable * @method \MrClay\Cli\Arg assertWritable() Assert that the specified file/dir must be writable * - * @property-read bool isRequired * @property-read bool mayHaveValue * @property-read bool mustHaveValue * @property-read bool assertFile @@ -40,6 +37,9 @@ namespace MrClay\Cli; * @property-read bool assertWritable * @property-read bool useAsInfile * @property-read bool useAsOutfile + * + * @author Steve Clay + * @license http://www.opensource.org/licenses/mit-license.php MIT License */ class Arg { /** @@ -59,28 +59,26 @@ class Arg { ); } - /** - * @var string - */ - protected $letter; - /** * @var array */ protected $spec = array(); + /** + * @var bool + */ protected $required = false; /** - * @param string $letter + * @var string + */ + protected $description = ''; + + /** * @param bool $isRequired */ - public function __construct($letter, $isRequired = false) + public function __construct($isRequired = false) { - if (! preg_match('/^[a-zA-Z]$/', $letter)) { - throw new \InvalidArgumentException('$letter must be in [a-zA-z]'); - } - $this->letter = $letter; $this->spec = $this->getDefaultSpec(); $this->required = (bool) $isRequired; if ($isRequired) { @@ -90,7 +88,7 @@ class Arg { /** * Assert that the argument's value points to a writable file. When - * CliArgs::openOutput() is called, a write pointer to this file will + * Cli::openOutput() is called, a write pointer to this file will * be provided. * @return Arg */ @@ -102,7 +100,7 @@ class Arg { /** * Assert that the argument's value points to a readable file. When - * CliArgs::openInput() is called, a read pointer to this file will + * Cli::openInput() is called, a read pointer to this file will * be provided. * @return Arg */ @@ -112,14 +110,6 @@ class Arg { return $this->assertFile()->assertReadable(); } - /** - * @return mixed - */ - public function getLetter() - { - return $this->letter; - } - /** * @return array */ @@ -129,6 +119,34 @@ class Arg { } /** + * @param string $desc + * @return Arg + */ + public function setDescription($desc) + { + $this->description = $desc; + return $this; + } + + /** + * @return string + */ + public function getDescription() + { + return $this->description; + } + + /** + * @return bool + */ + public function isRequired() + { + return $this->required; + } + + /** + * Note: magic methods declared in class PHPDOC + * * @param string $name * @param array $args * @return Arg @@ -148,6 +166,8 @@ class Arg { } /** + * Note: magic properties declared in class PHPDOC + * * @param string $name * @return bool|null */ @@ -155,8 +175,6 @@ class Arg { { if (array_key_exists($name, $this->spec)) { return $this->spec[$name]; - } elseif ($name === 'isRequired') { - return $this->required; } return null; } diff --git a/min_extras/cli/rewrite-uris.php b/min_extras/cli/rewrite-uris.php index eb79ca2..78a3b61 100755 --- a/min_extras/cli/rewrite-uris.php +++ b/min_extras/cli/rewrite-uris.php @@ -17,12 +17,18 @@ spl_autoload_register(function ($class) use ($pathToLib) { }); $cli = new MrClay\Cli; -$cli->addRequiredArg('d')->assertDir(); -$cli->addOptionalArg('o')->useAsOutfile(); -$cli->addOptionalArg('v'); + +$cli->addRequiredArg('d')->assertDir()->setDescription('Path of your webserver\'s DOCUMENT_ROOT. Relative paths will be rewritten relative to this path.'); + +$cli->addOptionalArg('o')->useAsOutfile()->setDescription('Outfile. If given, output will be placed in this file.'); + +$cli->addOptionalArg('v')->setDescription('Verbose: show rewriting algorithm. This is ignored if you don\'t use an outfile.'); if (! $cli->validate()) { echo "USAGE: ./rewrite-uris.php -d DOC_ROOT [-o OUTFILE [-v]] file ...\n"; + if ($cli->isHelpRequest) { + echo $cli->getArgumentsListing(); + } echo "EXAMPLE: ./rewrite-uris.php -v -d../.. ../../min_unit_tests/_test_files/css/paths_rewrite.css ../../min_unit_tests/_test_files/css/comments.css \n"; exit(0);