From 496e3a6f34d97e44e44340c1fbd2d3cbc262c05c Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Mon, 17 Mar 2014 23:03:53 +0100 Subject: completely new base for CLI scripts This introduces an abstract base class that command line tools need to inherit from. It provides a simple framework for registering accepted command line options and provides commonly needed things like help output and colored text. Existing CLI scripts still need to be converted. --- inc/cli.php | 590 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 590 insertions(+) create mode 100644 inc/cli.php (limited to 'inc/cli.php') diff --git a/inc/cli.php b/inc/cli.php new file mode 100644 index 000000000..2827ae233 --- /dev/null +++ b/inc/cli.php @@ -0,0 +1,590 @@ + + */ +abstract class DokuCLI { + /** @var string the executed script itself */ + protected $bin; + /** @var array list of non-option arguments */ + protected $args; + /** @var DokuCLI_Options the option parser */ + protected $options; + /** @var DokuCLI_Colors */ + public $colors; + + /** + * constructor + * + * Initialize the arguments, set up helper classes and set up the CLI environment + */ + public function __construct() { + set_exception_handler(array($this, 'fatal')); + + $this->args = $this->readPHPArgv(); + $this->bin = basename(array_shift($this->args)); + + $this->options = new DokuCLI_Options(); + $this->colors = new DokuCLI_Colors(); + } + + /** + * Register options and arguments on the given $options object + * + * @param DokuCLI_Options $options + * @return void + */ + abstract protected function setup(DokuCLI_Options $options); + + /** + * Your main program + * + * Arguments and options have been parsed when this is run + * + * @param DokuCLI_Options $options + * @param array $args + * @return void + */ + abstract protected function main(DokuCLI_Options $options, &$args); + + /** + * Execute the CLI program + * + * Executes the setup() routine, adds default options, initiate the options parsing and argument checking + * and finally executes main() + */ + public function run() { + if('cli' != php_sapi_name()) throw new DokuCLI_Exception('This has to be run from the command line'); + + // setup + $this->setup($this->options); + $this->options->registerOption( + 'no-colors', + 'Do not use any colors in output. Useful when piping output to other tools or files.' + ); + $this->options->registerOption( + 'help', + 'Display this help screen and exit immeadiately.', + 'h' + ); + + // parse + $this->options->parseOptions($this->args); + + // handle defaults + if($this->options->getOpt('no-colors')) { + $this->colors->disable(); + } + if($this->options->getOpt('help')) { + $this->options->help($this->bin); + exit(0); + } + + // check arguments + $this->options->checkArguments($this->args); + + // execute + $this->main($this->options, $this->args); + } + + /** + * Exits the program on a fatal error + * + * @param Exception|string $error either an exception or an error message + */ + public function fatal($error) { + $code = 0; + if(is_a($error, 'Exception')) { + /** @var Exception $error */ + $code = $error->getCode(); + $error = $error->getMessage(); + } + if(!$code) $code = DokuCLI_Exception::E_ANY; + + $this->colors->ptln($error, 'red'); + exit($code); + } + + /** + * Print an error message + * + * @param $string + */ + public function error($string) { + $this->colors->ptln("E: $string", 'red'); + } + + /** + * Print a success message + * + * @param $string + */ + public function success($string) { + $this->colors->ptln("S: $string", 'green'); + } + + /** + * Print an info message + * + * @param $string + */ + public function info($string) { + $this->colors->ptln("I: $string", 'cyan'); + } + + /** + * Safely read the $argv PHP array across different PHP configurations. + * Will take care on register_globals and register_argc_argv ini directives + * + * @throws DokuCLI_Exception + * @return array the $argv PHP array or PEAR error if not registered + */ + private function readPHPArgv() { + global $argv; + if(!is_array($argv)) { + if(!@is_array($_SERVER['argv'])) { + if(!@is_array($GLOBALS['HTTP_SERVER_VARS']['argv'])) { + throw new DokuCLI_Exception( + "Could not read cmd args (register_argc_argv=Off?)", + DOKU_CLI_OPTS_ARG_READ + ); + } + return $GLOBALS['HTTP_SERVER_VARS']['argv']; + } + return $_SERVER['argv']; + } + return $argv; + } +} + +/** + * Class DokuCLI_Colors + * + * Handles color output on (Linux) terminals + * + * @author Andreas Gohr + */ +class DokuCLI_Colors { + /** @var array known color names */ + protected $colors = array( + 'reset' => "\33[0m", + 'black' => "\33[0;30m", + 'darkgray' => "\33[1;30m", + 'blue' => "\33[0;34m", + 'lightblue' => "\33[1;34m", + 'green' => "\33[0;32m", + 'lightgreen' => "\33[1;32m", + 'cyan' => "\33[0;36m", + 'lightcyan' => "\33[1;36m", + 'red' => "\33[0;31m", + 'lightred' => "\33[1;31m", + 'purple' => "\33[0;35m", + 'lightpurple' => "\33[1;35m", + 'brown' => "\33[0;33m", + 'yellow' => "\33[1;33m", + 'lightgray' => "\33[0;37m", + 'white' => "\33[1;37m", + ); + + /** @var bool should colors be used? */ + protected $enabled = true; + + /** + * Constructor + * + * Tries to disable colors for non-terminals + */ + public function __construct() { + if(function_exists('posix_isatty') && !posix_isatty(STDOUT)) { + $this->enabled = false; + return; + } + if(!getenv('TERM')) { + $this->enabled = false; + return; + } + } + + /** + * enable color output + */ + public function enable() { + $this->enabled = true; + } + + /** + * disable color output + */ + public function disable() { + $this->enabled = false; + } + + /** + * Convenience function to print a line in a given color + * + * @param $line + * @param $color + */ + public function ptln($line, $color) { + $this->set($color); + echo rtrim($line) . "\n"; + $this->reset(); + } + + /** + * Set the given color for consecutive output + * + * @param string $color one of the supported color names + * @throws DokuCLI_Exception + */ + public function set($color) { + if(!$this->enabled) return; + if(!isset($this->colors[$color])) throw new DokuCLI_Exception("No such color $color"); + echo $this->colors[$color]; + } + + /** + * reset the terminal color + */ + public function reset() { + $this->set('reset'); + } +} + +/** + * Class DokuCLI_Options + * + * Parses command line options passed to the CLI script. Allows CLI scripts to easily register all accepted options and + * commands and even generates a help text from this setup. + * + * @author Andreas Gohr + */ +class DokuCLI_Options { + /** @var array $setup keeps the list of options to parse */ + protected $setup; + + /** @var array $options store parsed options */ + protected $options; + + protected $command = ''; + + public function __construct() { + $this->setup = array( + '' => array( + 'opts' => array(), + 'args' => array(), + 'help' => '' + ) + ); // default command + + $this->options = array(); + } + + /** + * Sets the help text for the tool itself + * + * @param string $help + */ + public function setHelp($help) { + $this->setup['']['help'] = $help; + } + + /** + * Register the names of arguments for help generation and number checking + * + * This has to be called in the order arguments are expected + * + * @param string $arg argument name (just for help) + * @param string $help help text + * @param bool $required is this a required argument + * @param string $command if theses apply to a sub command only + * @throws DokuCLI_Exception + */ + public function registerArgument($arg, $help, $required = true, $command = '') { + if(!isset($this->setup[$command])) throw new DokuCLI_Exception("Command $command not registered"); + + $this->setup[$command]['args'][] = array( + 'name' => $arg, + 'help' => $help, + 'required' => $required + ); + } + + /** + * This registers a sub command + * + * Sub commands have their own options and use their own function (not main()) + * + * @param string $command + * @param string $help + * @throws DokuCLI_Exception + */ + public function registerCommand($command, $help) { + if(isset($this->setup[$command])) throw new DokuCLI_Exception("Command $command already registered"); + + $this->setup[$command] = array( + 'opts' => array(), + 'args' => array(), + 'help' => $help + ); + + } + + /** + * Register an option for option parsing and help generation + * + * @param string $long multi character option (specified with --) + * @param string $help help text for this option + * @param string|null $short one character option (specified with -) + * @param bool $needsarg does this option require an argument? + * @param string $command what command does this option apply to + * @throws DokuCLI_Exception + */ + public function registerOption($long, $help, $short = null, $needsarg = false, $command = '') { + if(!isset($this->setup[$command])) throw new DokuCLI_Exception("Command $command not registered"); + + $this->setup[$command]['opts'][$long] = array( + 'needsarg' => $needsarg, + 'help' => $help, + 'short' => $short + ); + + if($short) { + if(strlen($short) > 1) throw new DokuCLI_Exception("Short options should be exactly one ASCII character"); + + $this->setup[$command]['short'][$short] = $long; + } + } + + /** + * Checks the actual number of arguments against the required number + * + * Throws an exception if arguments are missing. Called from parseOptions() + * + * @param array $args + * @throws DokuCLI_Exception + */ + public function checkArguments($args) { + $argc = count($args); + + $req = 0; + foreach($this->setup[$this->command]['args'] as $arg) { + if(!$args['required']) break; // last required arguments seen + $req++; + } + + if($req > $argc) throw new DokuCLI_Exception("Not enough arguments", DokuCLI_Exception::E_OPT_ARG_REQUIRED); + } + + /** + * Parses the given arguments for known options and command + * + * The given $args array should NOT contain the executed file as first item anymore! The $args + * array is stripped from any options and possible command. All found otions can be accessed via the + * getOpt() function + * + * Note that command options will overwrite any global options with the same name + * + * @param array $args + * @throws DokuCLI_Exception + */ + public function parseOptions(&$args) { + $non_opts = array(); + + $argc = count($args); + for($i = 0; $i < $argc; $i++) { + $arg = $args[$i]; + + // The special element '--' means explicit end of options. Treat the rest of the arguments as non-options + // and end the loop. + if($arg == '--') { + $non_opts = array_merge($non_opts, array_slice($args, $i + 1)); + break; + } + + // '-' is stdin - a normal argument + if($arg == '-') { + $non_opts = array_merge($non_opts, array_slice($args, $i)); + break; + } + + // first non-option + if($arg{0} != '-') { + $non_opts = array_merge($non_opts, array_slice($args, $i)); + break; + } + + // long option + if(strlen($arg) > 1 && $arg{1} == '-') { + list($opt, $val) = explode('=', substr($arg, 2), 2); + + if(!isset($this->setup[$this->command]['opts'][$opt])) { + throw new DokuCLI_Exception("No such option $arg", DokuCLI_Exception::E_UNKNOWN_OPT); + } + + // argument required? + if($this->setup[$this->command]['opts'][$opt]['needsarg']) { + if(is_null($val) && $i + 1 < $argc && !preg_match('/^--?[\w]/', $args[$i + 1])) { + $val = $args[++$i]; + } + if(is_null($val)) { + throw new DokuCLI_Exception("Option $arg requires an argument", DokuCLI_Exception::E_OPT_ARG_REQUIRED); + } + $this->options[$opt] = $val; + } else { + $this->options[$opt] = true; + } + + continue; + } + + // short option + $opt = substr($arg, 1); + if(!isset($this->setup[$this->command]['short'][$opt])) { + throw new DokuCLI_Exception("No such option $arg", DokuCLI_Exception::E_UNKNOWN_OPT); + } else { + $opt = $this->setup[$this->command]['short'][$opt]; // store it under long name + } + + // argument required? + if($this->setup[$this->command]['opts'][$opt]['needsarg']) { + $val = null; + if($i + 1 < $argc && !preg_match('/^--?[\w]/', $args[$i + 1])) { + $val = $args[++$i]; + } + if(is_null($val)) { + throw new DokuCLI_Exception("Option $arg requires an argument", DokuCLI_Exception::E_OPT_ARG_REQUIRED); + } + $this->options[$opt] = $val; + } else { + $this->options[$opt] = true; + } + } + + // parsing is now done, update args array + $args = $non_opts; + + // if not done yet, check if first argument is a command and reexecute argument parsing if it is + if(!$this->command && $args && isset($this->setup[$args[0]])) { + // it is a command! + $this->command = array_shift($args); + $this->parseOptions($args); // second pass + } + } + + /** + * Get the value of the given option + * + * Please note that all options are accessed by their long option names regardless of how they were + * specified on commandline. + * + * Can only be used after parseOptions() has been run + * + * @param $option + * @return mixed + */ + public function getOpt($option) { + if(isset($this->options[$option])) return $this->options[$option]; + return false; + } + + /** + * Return the found command if any + * + * @return string + */ + public function getCmd() { + return $this->command; + } + + /** + * Builds a help screen from the available options. You may want to call it from -h or on error + * + * @param string $bin name of the script itself + * @return string + */ + public function help($bin) { + $text = ''; + + $hascommands = (count($this->setup) > 1); + foreach($this->setup as $command => $config) { + $hasopts = (bool) $this->setup[$command]['opts']; + $hasargs = (bool) $this->setup[$command]['args']; + + if(!$command) { + $text .= 'USAGE: ' . $bin; + } else { + $text .= "\n$command"; + } + + if($hasopts) $text .= ' '; + foreach($this->setup[$command]['args'] as $arg) { + if($arg['required']) { + $text .= ' <' . $arg['name'] . '>'; + } else { + $text .= ' [<' . $arg['name'] . '>]'; + } + } + $text .= "\n\n"; + + if($this->setup[$command]['help']) { + $text .= ' ' . $this->setup[$command]['help'] . "\n\n"; + } + + if($hasopts) { + $text .= " OPTIONS\n\n"; + + foreach($this->setup[$command]['opts'] as $long => $opt) { + + $name = "--$long"; + if($opt['short']) $name = '-' . $opt['short'] . ' ' . $name; + if($opt['needsarg']) $name .= ' '; + + $text .= sprintf(" %-15s %s\n", $name, $opt['help']); + + } + } + + if($hasargs) { + $text .= " ARGUMENTS\n\n"; + foreach($this->setup[$command]['args'] as $arg) { + $name = '<' . $arg['name'] . '>'; + $text .= sprintf(" %-15s %s\n", $name, $arg['help']); + } + } + + if($command == '' && $hascommands){ + $text .= "\n\nThis tool accepts a command as first parameter as outlined below:\n"; + } + } + + return $text; + } + +} + +/** + * Class DokuCLI_Exception + * + * The code is used as exit code for the CLI tool. This should probably be extended. Many cases just fall back to the + * E_ANY code. + * + * @author Andreas Gohr + */ +class DokuCLI_Exception extends Exception { + const E_ANY = -1; // no error code specified + const E_UNKNOWN_OPT = 1; //Unrecognized option + const E_OPT_ARG_REQUIRED = 2; //Option requires argument + const E_OPT_ARG_DENIED = 3; //Option not allowed argument + const E_OPT_ABIGUOUS = 4; //Option abiguous + const E_ARG_READ = 5; //Could not read argv + + public function __construct($message = "", $code = 0, Exception $previous = null) { + if(!$code) $code = DokuCLI_Exception::E_ANY; + parent::__construct($message, $code, $previous); + } +} -- cgit v1.2.3 From 9fb664942d6f51d48d02b592980455259907d9a5 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Wed, 19 Mar 2014 11:52:22 +0100 Subject: converted git tool to new CLI base --- inc/cli.php | 125 +++++++++++++++++++++++++++++++----------------------------- 1 file changed, 65 insertions(+), 60 deletions(-) (limited to 'inc/cli.php') diff --git a/inc/cli.php b/inc/cli.php index 2827ae233..2cba828f0 100644 --- a/inc/cli.php +++ b/inc/cli.php @@ -10,8 +10,6 @@ abstract class DokuCLI { /** @var string the executed script itself */ protected $bin; - /** @var array list of non-option arguments */ - protected $args; /** @var DokuCLI_Options the option parser */ protected $options; /** @var DokuCLI_Colors */ @@ -25,8 +23,6 @@ abstract class DokuCLI { public function __construct() { set_exception_handler(array($this, 'fatal')); - $this->args = $this->readPHPArgv(); - $this->bin = basename(array_shift($this->args)); $this->options = new DokuCLI_Options(); $this->colors = new DokuCLI_Colors(); @@ -46,10 +42,9 @@ abstract class DokuCLI { * Arguments and options have been parsed when this is run * * @param DokuCLI_Options $options - * @param array $args * @return void */ - abstract protected function main(DokuCLI_Options $options, &$args); + abstract protected function main(DokuCLI_Options $options); /** * Execute the CLI program @@ -73,22 +68,24 @@ abstract class DokuCLI { ); // parse - $this->options->parseOptions($this->args); + $this->options->parseOptions(); // handle defaults if($this->options->getOpt('no-colors')) { $this->colors->disable(); } if($this->options->getOpt('help')) { - $this->options->help($this->bin); + echo $this->options->help($this->bin); exit(0); } // check arguments - $this->options->checkArguments($this->args); + $this->options->checkArguments(); // execute - $this->main($this->options, $this->args); + $this->main($this->options); + + exit(0); } /** @@ -98,7 +95,7 @@ abstract class DokuCLI { */ public function fatal($error) { $code = 0; - if(is_a($error, 'Exception')) { + if(is_object($error) && is_a($error, 'Exception')) { /** @var Exception $error */ $code = $error->getCode(); $error = $error->getMessage(); @@ -136,29 +133,6 @@ abstract class DokuCLI { $this->colors->ptln("I: $string", 'cyan'); } - /** - * Safely read the $argv PHP array across different PHP configurations. - * Will take care on register_globals and register_argc_argv ini directives - * - * @throws DokuCLI_Exception - * @return array the $argv PHP array or PEAR error if not registered - */ - private function readPHPArgv() { - global $argv; - if(!is_array($argv)) { - if(!@is_array($_SERVER['argv'])) { - if(!@is_array($GLOBALS['HTTP_SERVER_VARS']['argv'])) { - throw new DokuCLI_Exception( - "Could not read cmd args (register_argc_argv=Off?)", - DOKU_CLI_OPTS_ARG_READ - ); - } - return $GLOBALS['HTTP_SERVER_VARS']['argv']; - } - return $_SERVER['argv']; - } - return $argv; - } } /** @@ -264,14 +238,21 @@ class DokuCLI_Colors { * @author Andreas Gohr */ class DokuCLI_Options { - /** @var array $setup keeps the list of options to parse */ + /** @var array keeps the list of options to parse */ protected $setup; - /** @var array $options store parsed options */ - protected $options; + /** @var array store parsed options */ + protected $options = array(); + /** @var string current parsed command if any */ protected $command = ''; + /** @var array passed non-option arguments*/ + public $args = array(); + + /** @var string the executed script */ + protected $bin; + public function __construct() { $this->setup = array( '' => array( @@ -281,6 +262,9 @@ class DokuCLI_Options { ) ); // default command + $this->args = $this->readPHPArgv(); + $this->bin = basename(array_shift($this->args)); + $this->options = array(); } @@ -317,7 +301,7 @@ class DokuCLI_Options { /** * This registers a sub command * - * Sub commands have their own options and use their own function (not main()) + * Sub commands have their own options and use their own function (not main()). * * @param string $command * @param string $help @@ -365,15 +349,14 @@ class DokuCLI_Options { * * Throws an exception if arguments are missing. Called from parseOptions() * - * @param array $args * @throws DokuCLI_Exception */ - public function checkArguments($args) { - $argc = count($args); + public function checkArguments() { + $argc = count($this->args); $req = 0; foreach($this->setup[$this->command]['args'] as $arg) { - if(!$args['required']) break; // last required arguments seen + if(!$arg['required']) break; // last required arguments seen $req++; } @@ -389,32 +372,31 @@ class DokuCLI_Options { * * Note that command options will overwrite any global options with the same name * - * @param array $args * @throws DokuCLI_Exception */ - public function parseOptions(&$args) { + public function parseOptions() { $non_opts = array(); - $argc = count($args); + $argc = count($this->args); for($i = 0; $i < $argc; $i++) { - $arg = $args[$i]; + $arg = $this->args[$i]; // The special element '--' means explicit end of options. Treat the rest of the arguments as non-options // and end the loop. if($arg == '--') { - $non_opts = array_merge($non_opts, array_slice($args, $i + 1)); + $non_opts = array_merge($non_opts, array_slice($this->args, $i + 1)); break; } // '-' is stdin - a normal argument if($arg == '-') { - $non_opts = array_merge($non_opts, array_slice($args, $i)); + $non_opts = array_merge($non_opts, array_slice($this->args, $i)); break; } // first non-option if($arg{0} != '-') { - $non_opts = array_merge($non_opts, array_slice($args, $i)); + $non_opts = array_merge($non_opts, array_slice($this->args, $i)); break; } @@ -428,8 +410,8 @@ class DokuCLI_Options { // argument required? if($this->setup[$this->command]['opts'][$opt]['needsarg']) { - if(is_null($val) && $i + 1 < $argc && !preg_match('/^--?[\w]/', $args[$i + 1])) { - $val = $args[++$i]; + if(is_null($val) && $i + 1 < $argc && !preg_match('/^--?[\w]/', $this->args[$i + 1])) { + $val = $this->args[++$i]; } if(is_null($val)) { throw new DokuCLI_Exception("Option $arg requires an argument", DokuCLI_Exception::E_OPT_ARG_REQUIRED); @@ -453,8 +435,8 @@ class DokuCLI_Options { // argument required? if($this->setup[$this->command]['opts'][$opt]['needsarg']) { $val = null; - if($i + 1 < $argc && !preg_match('/^--?[\w]/', $args[$i + 1])) { - $val = $args[++$i]; + if($i + 1 < $argc && !preg_match('/^--?[\w]/', $this->args[$i + 1])) { + $val = $this->args[++$i]; } if(is_null($val)) { throw new DokuCLI_Exception("Option $arg requires an argument", DokuCLI_Exception::E_OPT_ARG_REQUIRED); @@ -466,13 +448,13 @@ class DokuCLI_Options { } // parsing is now done, update args array - $args = $non_opts; + $this->args = $non_opts; // if not done yet, check if first argument is a command and reexecute argument parsing if it is - if(!$this->command && $args && isset($this->setup[$args[0]])) { + if(!$this->command && $this->args && isset($this->setup[$this->args[0]]) ) { // it is a command! - $this->command = array_shift($args); - $this->parseOptions($args); // second pass + $this->command = array_shift($this->args); + $this->parseOptions(); // second pass } } @@ -504,10 +486,9 @@ class DokuCLI_Options { /** * Builds a help screen from the available options. You may want to call it from -h or on error * - * @param string $bin name of the script itself * @return string */ - public function help($bin) { + public function help() { $text = ''; $hascommands = (count($this->setup) > 1); @@ -516,7 +497,7 @@ class DokuCLI_Options { $hasargs = (bool) $this->setup[$command]['args']; if(!$command) { - $text .= 'USAGE: ' . $bin; + $text .= 'USAGE: ' . $this->bin; } else { $text .= "\n$command"; } @@ -565,6 +546,30 @@ class DokuCLI_Options { return $text; } + /** + * Safely read the $argv PHP array across different PHP configurations. + * Will take care on register_globals and register_argc_argv ini directives + * + * @throws DokuCLI_Exception + * @return array the $argv PHP array or PEAR error if not registered + */ + private function readPHPArgv() { + global $argv; + if(!is_array($argv)) { + if(!@is_array($_SERVER['argv'])) { + if(!@is_array($GLOBALS['HTTP_SERVER_VARS']['argv'])) { + throw new DokuCLI_Exception( + "Could not read cmd args (register_argc_argv=Off?)", + DOKU_CLI_OPTS_ARG_READ + ); + } + return $GLOBALS['HTTP_SERVER_VARS']['argv']; + } + return $_SERVER['argv']; + } + return $argv; + } + } /** -- cgit v1.2.3 From ae1ce4a68df467af30fafe2e47a3c11983d7f8be Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Wed, 19 Mar 2014 22:56:29 +0100 Subject: wrap help texts to fit typial terminal width --- inc/cli.php | 67 ++++++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 53 insertions(+), 14 deletions(-) (limited to 'inc/cli.php') diff --git a/inc/cli.php b/inc/cli.php index 2cba828f0..6380c9174 100644 --- a/inc/cli.php +++ b/inc/cli.php @@ -23,7 +23,6 @@ abstract class DokuCLI { public function __construct() { set_exception_handler(array($this, 'fatal')); - $this->options = new DokuCLI_Options(); $this->colors = new DokuCLI_Colors(); } @@ -247,7 +246,7 @@ class DokuCLI_Options { /** @var string current parsed command if any */ protected $command = ''; - /** @var array passed non-option arguments*/ + /** @var array passed non-option arguments */ public $args = array(); /** @var string the executed script */ @@ -451,7 +450,7 @@ class DokuCLI_Options { $this->args = $non_opts; // if not done yet, check if first argument is a command and reexecute argument parsing if it is - if(!$this->command && $this->args && isset($this->setup[$this->args[0]]) ) { + if(!$this->command && $this->args && isset($this->setup[$this->args[0]])) { // it is a command! $this->command = array_shift($this->args); $this->parseOptions(); // second pass @@ -502,7 +501,6 @@ class DokuCLI_Options { $text .= "\n$command"; } - if($hasopts) $text .= ' '; foreach($this->setup[$command]['args'] as $arg) { if($arg['required']) { $text .= ' <' . $arg['name'] . '>'; @@ -510,36 +508,45 @@ class DokuCLI_Options { $text .= ' [<' . $arg['name'] . '>]'; } } - $text .= "\n\n"; + $text .= "\n"; if($this->setup[$command]['help']) { - $text .= ' ' . $this->setup[$command]['help'] . "\n\n"; + $text .= "\n"; + $text .= $this->tableFormat( + array(2, 72), + array('', $this->setup[$command]['help'] . "\n") + ); } if($hasopts) { - $text .= " OPTIONS\n\n"; - + $text .= "\n"; foreach($this->setup[$command]['opts'] as $long => $opt) { $name = "--$long"; if($opt['short']) $name = '-' . $opt['short'] . ' ' . $name; if($opt['needsarg']) $name .= ' '; - $text .= sprintf(" %-15s %s\n", $name, $opt['help']); - + $text .= $this->tableFormat( + array(2, 20, 52), + array('', $name, $opt['help']) + ); } } if($hasargs) { - $text .= " ARGUMENTS\n\n"; + $text .= "\n"; foreach($this->setup[$command]['args'] as $arg) { $name = '<' . $arg['name'] . '>'; - $text .= sprintf(" %-15s %s\n", $name, $arg['help']); + + $text .= $this->tableFormat( + array(2, 20, 52), + array('', $name, $arg['help']) + ); } } - if($command == '' && $hascommands){ - $text .= "\n\nThis tool accepts a command as first parameter as outlined below:\n"; + if($command == '' && $hascommands) { + $text .= "\nThis tool accepts a command as first parameter as outlined below:\n"; } } @@ -570,6 +577,38 @@ class DokuCLI_Options { return $argv; } + /** + * Displays text in multiple word wrapped columns + * + * @param array $widths list of column widths (in characters) + * @param array $texts list of texts for each column + * @return string + */ + private function tableFormat($widths, $texts) { + $wrapped = array(); + $maxlen = 0; + + foreach($widths as $col => $width) { + $wrapped[$col] = explode("\n", wordwrap($texts[$col], $width - 1, true)); // -1 char border + $len = count($wrapped[$col]); + if($len > $maxlen) $maxlen = $len; + + } + + $out = ''; + for($i = 0; $i < $maxlen; $i++) { + foreach($widths as $col => $width) { + if(isset($wrapped[$col][$i])) { + $val = $wrapped[$col][$i]; + } else { + $val = ''; + } + $out .= sprintf('%-' . $width . 's', $val); + } + $out .= "\n"; + } + return $out; + } } /** -- cgit v1.2.3 From 193eb41176fb95f01489814bcc879dc500257c84 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Thu, 20 Mar 2014 19:41:27 +0100 Subject: fixed broken wordwrapping --- inc/cli.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'inc/cli.php') diff --git a/inc/cli.php b/inc/cli.php index 6380c9174..e8c2c9f3e 100644 --- a/inc/cli.php +++ b/inc/cli.php @@ -589,7 +589,7 @@ class DokuCLI_Options { $maxlen = 0; foreach($widths as $col => $width) { - $wrapped[$col] = explode("\n", wordwrap($texts[$col], $width - 1, true)); // -1 char border + $wrapped[$col] = explode("\n", wordwrap($texts[$col], $width - 1, "\n", true)); // -1 char border $len = count($wrapped[$col]); if($len > $maxlen) $maxlen = $len; -- cgit v1.2.3 From 99c6702358b65e9f98d1799d261b4dcc7b88d414 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Thu, 20 Mar 2014 19:43:48 +0100 Subject: removed superflous argument --- inc/cli.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'inc/cli.php') diff --git a/inc/cli.php b/inc/cli.php index e8c2c9f3e..84aad3411 100644 --- a/inc/cli.php +++ b/inc/cli.php @@ -74,7 +74,7 @@ abstract class DokuCLI { $this->colors->disable(); } if($this->options->getOpt('help')) { - echo $this->options->help($this->bin); + echo $this->options->help(); exit(0); } -- cgit v1.2.3 From b0b7909bdd454e9614f4ffe34f384b0da0ce4585 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Thu, 20 Mar 2014 20:55:57 +0100 Subject: converted some more CLI tools, minor CLI class updates --- inc/cli.php | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) (limited to 'inc/cli.php') diff --git a/inc/cli.php b/inc/cli.php index 84aad3411..c51d9b7d2 100644 --- a/inc/cli.php +++ b/inc/cli.php @@ -323,7 +323,7 @@ class DokuCLI_Options { * @param string $long multi character option (specified with --) * @param string $help help text for this option * @param string|null $short one character option (specified with -) - * @param bool $needsarg does this option require an argument? + * @param bool|string $needsarg does this option require an argument? give it a name here * @param string $command what command does this option apply to * @throws DokuCLI_Exception */ @@ -465,12 +465,13 @@ class DokuCLI_Options { * * Can only be used after parseOptions() has been run * - * @param $option + * @param string $option + * @param mixed $default what to return if the option was not set * @return mixed */ - public function getOpt($option) { + public function getOpt($option, $default = false) { if(isset($this->options[$option])) return $this->options[$option]; - return false; + return $default; } /** @@ -501,6 +502,8 @@ class DokuCLI_Options { $text .= "\n$command"; } + if($hasopts) $text .= ' '; + foreach($this->setup[$command]['args'] as $arg) { if($arg['required']) { $text .= ' <' . $arg['name'] . '>'; @@ -519,17 +522,23 @@ class DokuCLI_Options { } if($hasopts) { - $text .= "\n"; + $text .= "\n OPTIONS\n\n"; foreach($this->setup[$command]['opts'] as $long => $opt) { - $name = "--$long"; - if($opt['short']) $name = '-' . $opt['short'] . ' ' . $name; - if($opt['needsarg']) $name .= ' '; + $name = ''; + if($opt['short']) { + $name .= '-' . $opt['short']; + if($opt['needsarg']) $name .= ' <'.$opt['needsarg'].'>'; + $name .= ', '; + } + $name .= "--$long"; + if($opt['needsarg']) $name .= ' <'.$opt['needsarg'].'>'; $text .= $this->tableFormat( array(2, 20, 52), array('', $name, $opt['help']) ); + $text .= "\n"; } } -- cgit v1.2.3 From 272e9decc1b5a964c3303ffe8b5d52e9e08fde68 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Sat, 22 Mar 2014 17:23:35 +0100 Subject: CLI: send messages to STDERR --- inc/cli.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'inc/cli.php') diff --git a/inc/cli.php b/inc/cli.php index c51d9b7d2..f2da6a49d 100644 --- a/inc/cli.php +++ b/inc/cli.php @@ -111,7 +111,7 @@ abstract class DokuCLI { * @param $string */ public function error($string) { - $this->colors->ptln("E: $string", 'red'); + $this->colors->ptln("E: $string", 'red', STDERR); } /** @@ -120,7 +120,7 @@ abstract class DokuCLI { * @param $string */ public function success($string) { - $this->colors->ptln("S: $string", 'green'); + $this->colors->ptln("S: $string", 'green', STDERR); } /** @@ -129,7 +129,7 @@ abstract class DokuCLI { * @param $string */ public function info($string) { - $this->colors->ptln("I: $string", 'cyan'); + $this->colors->ptln("I: $string", 'cyan', STDERR); } } @@ -201,10 +201,11 @@ class DokuCLI_Colors { * * @param $line * @param $color + * @param resource $channel */ - public function ptln($line, $color) { + public function ptln($line, $color, $channel=STDOUT) { $this->set($color); - echo rtrim($line) . "\n"; + fwrite($channel, rtrim($line) . "\n"); $this->reset(); } -- cgit v1.2.3 From 1c36b3d86f90185519cadaad85251922dc771fe1 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Fri, 16 May 2014 09:11:15 +0200 Subject: code reformat --- inc/cli.php | 67 ++++++++++++++++++++++++++++++++----------------------------- 1 file changed, 35 insertions(+), 32 deletions(-) (limited to 'inc/cli.php') diff --git a/inc/cli.php b/inc/cli.php index f2da6a49d..25bfddf7d 100644 --- a/inc/cli.php +++ b/inc/cli.php @@ -57,13 +57,13 @@ abstract class DokuCLI { // setup $this->setup($this->options); $this->options->registerOption( - 'no-colors', - 'Do not use any colors in output. Useful when piping output to other tools or files.' + 'no-colors', + 'Do not use any colors in output. Useful when piping output to other tools or files.' ); $this->options->registerOption( - 'help', - 'Display this help screen and exit immeadiately.', - 'h' + 'help', + 'Display this help screen and exit immeadiately.', + 'h' ); // parse @@ -101,7 +101,7 @@ abstract class DokuCLI { } if(!$code) $code = DokuCLI_Exception::E_ANY; - $this->colors->ptln($error, 'red'); + $this->error($error); exit($code); } @@ -199,13 +199,13 @@ class DokuCLI_Colors { /** * Convenience function to print a line in a given color * - * @param $line - * @param $color + * @param $line + * @param $color * @param resource $channel */ - public function ptln($line, $color, $channel=STDOUT) { + public function ptln($line, $color, $channel = STDOUT) { $this->set($color); - fwrite($channel, rtrim($line) . "\n"); + fwrite($channel, rtrim($line)."\n"); $this->reset(); } @@ -253,6 +253,9 @@ class DokuCLI_Options { /** @var string the executed script */ protected $bin; + /** + * Constructor + */ public function __construct() { $this->setup = array( '' => array( @@ -282,10 +285,10 @@ class DokuCLI_Options { * * This has to be called in the order arguments are expected * - * @param string $arg argument name (just for help) - * @param string $help help text - * @param bool $required is this a required argument - * @param string $command if theses apply to a sub command only + * @param string $arg argument name (just for help) + * @param string $help help text + * @param bool $required is this a required argument + * @param string $command if theses apply to a sub command only * @throws DokuCLI_Exception */ public function registerArgument($arg, $help, $required = true, $command = '') { @@ -321,11 +324,11 @@ class DokuCLI_Options { /** * Register an option for option parsing and help generation * - * @param string $long multi character option (specified with --) - * @param string $help help text for this option - * @param string|null $short one character option (specified with -) + * @param string $long multi character option (specified with --) + * @param string $help help text for this option + * @param string|null $short one character option (specified with -) * @param bool|string $needsarg does this option require an argument? give it a name here - * @param string $command what command does this option apply to + * @param string $command what command does this option apply to * @throws DokuCLI_Exception */ public function registerOption($long, $help, $short = null, $needsarg = false, $command = '') { @@ -467,7 +470,7 @@ class DokuCLI_Options { * Can only be used after parseOptions() has been run * * @param string $option - * @param mixed $default what to return if the option was not set + * @param mixed $default what to return if the option was not set * @return mixed */ public function getOpt($option, $default = false) { @@ -498,7 +501,7 @@ class DokuCLI_Options { $hasargs = (bool) $this->setup[$command]['args']; if(!$command) { - $text .= 'USAGE: ' . $this->bin; + $text .= 'USAGE: '.$this->bin; } else { $text .= "\n$command"; } @@ -507,9 +510,9 @@ class DokuCLI_Options { foreach($this->setup[$command]['args'] as $arg) { if($arg['required']) { - $text .= ' <' . $arg['name'] . '>'; + $text .= ' <'.$arg['name'].'>'; } else { - $text .= ' [<' . $arg['name'] . '>]'; + $text .= ' [<'.$arg['name'].'>]'; } } $text .= "\n"; @@ -517,8 +520,8 @@ class DokuCLI_Options { if($this->setup[$command]['help']) { $text .= "\n"; $text .= $this->tableFormat( - array(2, 72), - array('', $this->setup[$command]['help'] . "\n") + array(2, 72), + array('', $this->setup[$command]['help']."\n") ); } @@ -528,7 +531,7 @@ class DokuCLI_Options { $name = ''; if($opt['short']) { - $name .= '-' . $opt['short']; + $name .= '-'.$opt['short']; if($opt['needsarg']) $name .= ' <'.$opt['needsarg'].'>'; $name .= ', '; } @@ -536,8 +539,8 @@ class DokuCLI_Options { if($opt['needsarg']) $name .= ' <'.$opt['needsarg'].'>'; $text .= $this->tableFormat( - array(2, 20, 52), - array('', $name, $opt['help']) + array(2, 20, 52), + array('', $name, $opt['help']) ); $text .= "\n"; } @@ -546,11 +549,11 @@ class DokuCLI_Options { if($hasargs) { $text .= "\n"; foreach($this->setup[$command]['args'] as $arg) { - $name = '<' . $arg['name'] . '>'; + $name = '<'.$arg['name'].'>'; $text .= $this->tableFormat( - array(2, 20, 52), - array('', $name, $arg['help']) + array(2, 20, 52), + array('', $name, $arg['help']) ); } } @@ -591,7 +594,7 @@ class DokuCLI_Options { * Displays text in multiple word wrapped columns * * @param array $widths list of column widths (in characters) - * @param array $texts list of texts for each column + * @param array $texts list of texts for each column * @return string */ private function tableFormat($widths, $texts) { @@ -613,7 +616,7 @@ class DokuCLI_Options { } else { $val = ''; } - $out .= sprintf('%-' . $width . 's', $val); + $out .= sprintf('%-'.$width.'s', $val); } $out .= "\n"; } -- cgit v1.2.3