From: Chuck Hagenbuch Date: Sat, 15 Nov 2008 14:53:18 +0000 (-0500) Subject: move Horde_Argv to git X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=ab1af834a64efd8ba49dd53ecbf1e515e5869839;p=horde.git move Horde_Argv to git --- diff --git a/framework/Argv/CVS/Entries b/framework/Argv/CVS/Entries new file mode 100644 index 000000000..95639e30b --- /dev/null +++ b/framework/Argv/CVS/Entries @@ -0,0 +1,3 @@ +/package.xml/1.1/Fri Feb 1 01:41:30 2008// +D/lib//// +D/test//// diff --git a/framework/Argv/CVS/Repository b/framework/Argv/CVS/Repository new file mode 100644 index 000000000..77c3d32e9 --- /dev/null +++ b/framework/Argv/CVS/Repository @@ -0,0 +1 @@ +framework/Argv diff --git a/framework/Argv/CVS/Root b/framework/Argv/CVS/Root new file mode 100644 index 000000000..5d636129a --- /dev/null +++ b/framework/Argv/CVS/Root @@ -0,0 +1 @@ +chuck@cvs.horde.org:/repository diff --git a/framework/Argv/CVS/Template b/framework/Argv/CVS/Template new file mode 100644 index 000000000..3971591f9 --- /dev/null +++ b/framework/Argv/CVS/Template @@ -0,0 +1,8 @@ + +Bug: +Submitted by: +Merge after: +CVS: ---------------------------------------------------------------------- +CVS: Bug: Fill this in if a listed bug is affected by the change. +CVS: Submitted by: Fill this in if someone else sent in the change. +CVS: Merge after: N [day[s]|week[s]|month[s]] (days assumed by default) diff --git a/framework/Argv/lib/CVS/Entries b/framework/Argv/lib/CVS/Entries new file mode 100644 index 000000000..ae2779e30 --- /dev/null +++ b/framework/Argv/lib/CVS/Entries @@ -0,0 +1 @@ +D/Horde//// diff --git a/framework/Argv/lib/CVS/Repository b/framework/Argv/lib/CVS/Repository new file mode 100644 index 000000000..bc9ed537d --- /dev/null +++ b/framework/Argv/lib/CVS/Repository @@ -0,0 +1 @@ +framework/Argv/lib diff --git a/framework/Argv/lib/CVS/Root b/framework/Argv/lib/CVS/Root new file mode 100644 index 000000000..5d636129a --- /dev/null +++ b/framework/Argv/lib/CVS/Root @@ -0,0 +1 @@ +chuck@cvs.horde.org:/repository diff --git a/framework/Argv/lib/CVS/Template b/framework/Argv/lib/CVS/Template new file mode 100644 index 000000000..3971591f9 --- /dev/null +++ b/framework/Argv/lib/CVS/Template @@ -0,0 +1,8 @@ + +Bug: +Submitted by: +Merge after: +CVS: ---------------------------------------------------------------------- +CVS: Bug: Fill this in if a listed bug is affected by the change. +CVS: Submitted by: Fill this in if someone else sent in the change. +CVS: Merge after: N [day[s]|week[s]|month[s]] (days assumed by default) diff --git a/framework/Argv/lib/Horde/Argv/AmbiguousOptionException.php b/framework/Argv/lib/Horde/Argv/AmbiguousOptionException.php new file mode 100644 index 000000000..ccfbd6419 --- /dev/null +++ b/framework/Argv/lib/Horde/Argv/AmbiguousOptionException.php @@ -0,0 +1,24 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + */ + +/** + * Raised if an ambiguous option is seen on the command line. + * + * @category Horde + * @package Horde_Argv + */ +class Horde_Argv_AmbiguousOptionException extends Horde_Argv_BadOptionException +{ + public function __construct($opt_str, $possibilities) + { + // Have to skip the BadOptionException constructor or the string gets double-prefixed. + Horde_Argv_OptionException::__construct(sprintf(_("ambiguous option: %s (%s?)"), $opt_str, implode(', ', $possibilities))); + } + +} diff --git a/framework/Argv/lib/Horde/Argv/BadOptionException.php b/framework/Argv/lib/Horde/Argv/BadOptionException.php new file mode 100644 index 000000000..56783f4a7 --- /dev/null +++ b/framework/Argv/lib/Horde/Argv/BadOptionException.php @@ -0,0 +1,23 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + */ + +/** + * Raised if an invalid option is seen on the command line. + * + * @category Horde + * @package Horde_Argv + */ +class Horde_Argv_BadOptionException extends Horde_Argv_OptionException +{ + public function __construct($opt_str) + { + parent::__construct(sprintf(_("no such option: %s"), $opt_str)); + } + +} diff --git a/framework/Argv/lib/Horde/Argv/CVS/Entries b/framework/Argv/lib/Horde/Argv/CVS/Entries new file mode 100644 index 000000000..ef3afe48a --- /dev/null +++ b/framework/Argv/lib/Horde/Argv/CVS/Entries @@ -0,0 +1,15 @@ +/AmbiguousOptionException.php/1.2/Wed Jun 18 19:00:45 2008// +/BadOptionException.php/1.2/Wed Jun 18 19:00:45 2008// +/Exception.php/1.2/Wed Jun 18 19:00:45 2008// +/HelpFormatter.php/1.2/Wed Jun 18 19:00:45 2008// +/IndentedHelpFormatter.php/1.3/Wed Jun 18 19:00:45 2008// +/Option.php/1.3/Wed Jun 18 19:00:45 2008// +/OptionConflictException.php/1.2/Wed Jun 18 19:00:45 2008// +/OptionContainer.php/1.2/Wed Jun 18 19:00:45 2008// +/OptionException.php/1.2/Wed Jun 18 19:00:45 2008// +/OptionGroup.php/1.2/Wed Jun 18 19:00:45 2008// +/OptionValueException.php/1.2/Wed Jun 18 19:00:45 2008// +/Parser.php/1.3/Wed Jun 18 19:00:45 2008// +/TitledHelpFormatter.php/1.2/Wed Jun 18 19:00:45 2008// +/Values.php/1.2/Wed Jun 18 19:00:45 2008// +D diff --git a/framework/Argv/lib/Horde/Argv/CVS/Repository b/framework/Argv/lib/Horde/Argv/CVS/Repository new file mode 100644 index 000000000..1867cc4d4 --- /dev/null +++ b/framework/Argv/lib/Horde/Argv/CVS/Repository @@ -0,0 +1 @@ +framework/Argv/lib/Horde/Argv diff --git a/framework/Argv/lib/Horde/Argv/CVS/Root b/framework/Argv/lib/Horde/Argv/CVS/Root new file mode 100644 index 000000000..5d636129a --- /dev/null +++ b/framework/Argv/lib/Horde/Argv/CVS/Root @@ -0,0 +1 @@ +chuck@cvs.horde.org:/repository diff --git a/framework/Argv/lib/Horde/Argv/CVS/Template b/framework/Argv/lib/Horde/Argv/CVS/Template new file mode 100644 index 000000000..3971591f9 --- /dev/null +++ b/framework/Argv/lib/Horde/Argv/CVS/Template @@ -0,0 +1,8 @@ + +Bug: +Submitted by: +Merge after: +CVS: ---------------------------------------------------------------------- +CVS: Bug: Fill this in if a listed bug is affected by the change. +CVS: Submitted by: Fill this in if someone else sent in the change. +CVS: Merge after: N [day[s]|week[s]|month[s]] (days assumed by default) diff --git a/framework/Argv/lib/Horde/Argv/Exception.php b/framework/Argv/lib/Horde/Argv/Exception.php new file mode 100644 index 000000000..55222f3ae --- /dev/null +++ b/framework/Argv/lib/Horde/Argv/Exception.php @@ -0,0 +1,19 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + */ + +/** + * @category Horde + * @package Horde_Argv + */ +class Horde_Argv_Exception extends Exception +{} diff --git a/framework/Argv/lib/Horde/Argv/HelpFormatter.php b/framework/Argv/lib/Horde/Argv/HelpFormatter.php new file mode 100644 index 000000000..a01d74a4b --- /dev/null +++ b/framework/Argv/lib/Horde/Argv/HelpFormatter.php @@ -0,0 +1,263 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + */ + +/** + * Abstract base class for formatting option help. Horde_Argv_Parser + * instances should use one of the HelpFormatter subclasses for + * formatting help; by default IndentedHelpFormatter is used. + * + Instance attributes: + parser : Horde_Argv_Parser + the controlling Horde_Argv_Parser instance + indent_increment : int + the number of columns to indent per nesting level + max_help_position : int + the maximum starting column for option help text + help_position : int + the calculated starting column for option help text; + initially the same as the maximum + width : int + total number of columns for output (pass None to constructor for + this value to be taken from the $COLUMNS environment variable) + level : int + current indentation level + current_indent : int + current indentation level (in columns) + help_width : int + number of columns available for option help text (calculated) + default_tag : str + text to replace with each option's default value, "%default" + by default. Set to false value to disable default value expansion. + option_strings : { Option : str } + maps Option instances to the snippet of help text explaining + the syntax of that option, e.g. "-h, --help" or + "-fFILE, --file=FILE" + _short_opt_fmt : str + format string controlling how short options with values are + printed in help text. Must be either "%s%s" ("-fFILE") or + "%s %s" ("-f FILE"), because those are the two syntaxes that + Optik supports. + _long_opt_fmt : str + similar but for long options; must be either "%s %s" ("--file FILE") + or "%s=%s" ("--file=FILE"). + * + * @category Horde + * @package Horde_Argv + */ +abstract class Horde_Argv_HelpFormatter +{ + const NO_DEFAULT_VALUE = 'none'; + + public $parser = null; + + public function __construct($indent_increment, $max_help_position, $width = null, $short_first = false) + { + $this->indent_increment = $indent_increment; + $this->help_position = $this->max_help_position = $max_help_position; + if (is_null($width)) { + if (!empty($_ENV['COLUMNS'])) { + $width = $_ENV['COLUMNS']; + } else { + $width = 80; + } + $width -= 2; + } + $this->width = $width; + $this->current_indent = 0; + $this->level = 0; + $this->help_width = null; // computed later + $this->short_first = $short_first; + $this->default_tag = "%default"; + $this->option_strings = array(); + $this->_short_opt_fmt = "%s %s"; + $this->_long_opt_fmt = "%s=%s"; + } + + public function setParser($parser) + { + $this->parser = $parser; + } + + public function setShortOptDelimiter($delim) + { + if (!in_array($delim, array('', ' '))) { + throw new InvalidArgumentException('invalid metavar delimiter for short options: ' . $delim); + } + $this->_short_opt_fmt = "%s$delim%s"; + } + + public function setLongOptDelimiter($delim) + { + if (!in_array($delim, array('=', ' '))) { + throw new InvalidArgumentException('invalid metavar delimiter for long options: ' . $delim); + } + $this->_long_opt_fmt = "%s$delim%s"; + } + + public function indent() + { + $this->current_indent += $this->indent_increment; + $this->level += 1; + } + + public function dedent() + { + $this->current_indent -= $this->indent_increment; + assert($this->current_indent >= 0); // Indent decreased below 0 + $this->level -= 1; + } + + public abstract function formatUsage($usage); + + public abstract function formatHeading($heading); + + /** + * Format a paragraph of free-form text for inclusion in the + * help output at the current indentation level. + */ + protected function _formatText($text) + { + $text_width = $this->width - $this->current_indent; + $indent = str_repeat(' ', $this->current_indent); + return wordwrap($indent . $text, $text_width, "\n" . $indent); + } + + public function formatDescription($description) + { + if ($description) { + return $this->_formatText($description) . "\n"; + } else { + return ''; + } + } + + public function formatEpilog($epilog) + { + if ($epilog) { + return "\n" . $this->_formatText($epilog) . "\n"; + } else { + return ''; + } + } + + public function expandDefault($option) + { + if (is_null($this->parser) || !$this->default_tag) { + return $option->help; + } + + $default_value = isset($this->parser->defaults[$option->dest]) ? $this->parser->defaults[$option->dest] : null; + if ($default_value == Horde_Argv_Option::$NO_DEFAULT || !$default_value) { + $default_value = self::NO_DEFAULT_VALUE; + } + + return str_replace($this->default_tag, (string)$default_value, $option->help); + } + + /** + * The help for each option consists of two parts: + * * the opt strings and metavars + * eg. ("-x", or "-fFILENAME, --file=FILENAME") + * * the user-supplied help string + * eg. ("turn on expert mode", "read data from FILENAME") + * + * If possible, we write both of these on the same line: + * -x turn on expert mode + * + * But if the opt string list is too long, we put the help + * string on a second line, indented to the same column it would + * start in if it fit on the first line. + * -fFILENAME, --file=FILENAME + * read data from FILENAME + */ + public function formatOption($option) + { + $result = array(); + $opts = isset($this->option_strings[(string)$option]) ? $this->option_strings[(string)$option] : null; + $opt_width = $this->help_position - $this->current_indent - 2; + if (strlen($opts) > $opt_width) { + $opts = sprintf('%' . $this->current_indent . "s%s\n", "", $opts); + $indent_first = $this->help_position; + } else { + // start help on same line as opts + $opts = sprintf("%" . $this->current_indent . "s%-" . $opt_width . "s ", "", $opts); + $indent_first = 0; + } + $result[] = $opts; + if ($option->help) { + $help_text = $this->expandDefault($option); + $help_lines = explode("\n", wordwrap($help_text, $this->help_width)); + $result[] = sprintf("%" . $indent_first . "s%s\n", '', $help_lines[0]); + for ($i = 1, $i_max = count($help_lines); $i < $i_max; $i++) { + $result[] = sprintf("%" . $this->help_position . "s%s\n", "", $help_lines[$i]); + } + } elseif (substr($opts, -1) != "\n") { + $result[] = "\n"; + } + return implode('', $result); + } + + public function storeOptionStrings($parser) + { + $this->indent(); + $max_len = 0; + foreach ($parser->optionList as $opt) { + $strings = $this->formatOptionStrings($opt); + $this->option_strings[(string)$opt] = $strings; + $max_len = max($max_len, strlen($strings) + $this->current_indent); + } + $this->indent(); + foreach ($parser->optionGroups as $group) { + foreach ($group->optionList as $opt) { + $strings = $this->formatOptionStrings($opt); + $this->option_strings[(string)$opt] = $strings; + $max_len = max($max_len, strlen($strings) + $this->current_indent); + } + } + $this->dedent(); + $this->dedent(); + $this->help_position = min($max_len + 2, $this->max_help_position); + $this->help_width = $this->width - $this->help_position; + } + + /** + * Return a comma-separated list of option strings & metavariables. + */ + public function formatOptionStrings($option) + { + if ($option->takesValue()) { + $metavar = $option->metavar ? $option->metavar : strtoupper($option->dest); + $short_opts = array(); + foreach ($option->shortOpts as $sopt) { + $short_opts[] = sprintf($this->_short_opt_fmt, $sopt, $metavar); + } + $long_opts = array(); + foreach ($option->longOpts as $lopt) { + $long_opts[] = sprintf($this->_long_opt_fmt, $lopt, $metavar); + } + } else { + $short_opts = $option->shortOpts; + $long_opts = $option->longOpts; + } + + if ($this->short_first) { + $opts = array_merge($short_opts, $long_opts); + } else { + $opts = array_merge($long_opts, $short_opts); + } + + return implode(', ', $opts); + } + +} diff --git a/framework/Argv/lib/Horde/Argv/IndentedHelpFormatter.php b/framework/Argv/lib/Horde/Argv/IndentedHelpFormatter.php new file mode 100644 index 000000000..42f09b502 --- /dev/null +++ b/framework/Argv/lib/Horde/Argv/IndentedHelpFormatter.php @@ -0,0 +1,37 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + */ + +/** + * Format help with indented section bodies. + * + * @category Horde + * @package Horde_Argv + */ +class Horde_Argv_IndentedHelpFormatter extends Horde_Argv_HelpFormatter +{ + public function __construct( + $indent_increment = 2, + $max_help_position = 24, + $width = null, + $short_first = true) + { + parent::__construct($indent_increment, $max_help_position, $width, $short_first); + } + + public function formatUsage($usage) + { + return sprintf(_("Usage:") . " %s\n", $usage); + } + + public function formatHeading($heading) + { + return sprintf('%' . $this->current_indent . "s%s:\n", '', $heading); + } + +} diff --git a/framework/Argv/lib/Horde/Argv/Option.php b/framework/Argv/lib/Horde/Argv/Option.php new file mode 100644 index 000000000..db8d5ec75 --- /dev/null +++ b/framework/Argv/lib/Horde/Argv/Option.php @@ -0,0 +1,555 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + */ + +/** + * Defines the Option class and some standard value-checking functions. + * + * Instance attributes: + * shortOpts : [string] + * longOpts : [string] + * + * action : string + * type : string + * dest : string + * default : any + * nargs : int + * const : any + * choices : [string] + * callback : function + * callbackArgs : (any*) + * help : string + * metavar : string + * + * @category Horde + * @package Horde_Argv + */ +class Horde_Argv_Option +{ + const SUPPRESS_HELP = "SUPPRESS HELP"; + const SUPPRESS_USAGE = "SUPPRESS USAGE"; + + /** + * Not supplying a default is different from a default of None, + * so we need an explicit "not supplied" value. + */ + public static $NO_DEFAULT = array("NO", "DEFAULT"); + + public static function parseNumber($value) + { + if (!strlen($value)) { + return false; + } + + // Values to check against or compute with. + $first = substr($value, 0, 1); + $prefix = substr($value, 0, 2); + $suffix = substr($value, 2); + + // Hex + if ($prefix == '0x' || $prefix == '0X') { + if (strspn($suffix, '0123456789abcdefABCDEF') != strlen($suffix)) { + return false; + } + return hexdec($value); + } + + // Binary + if ($prefix == '0b' || $prefix == '0B') { + if (strspn($suffix, '01') != strlen($suffix)) { + return false; + } + return bindec($value); + } + + // Octal + if ($first == '0') { + $suffix = substr($value, 1); + if (strspn($suffix, '01234567') != strlen($suffix)) { + return false; + } + return octdec($suffix); + } + + // Base 10 + if (!is_numeric($value)) { + return false; + } + return intval($value); + } + + public function checkBuiltin($opt, $value) + { + switch ($this->type) { + case 'int': + case 'long': + $number = self::parseNumber($value); + if ($number === false) { + $message = $this->type == 'int' + ? _("option %s: invalid integer value: '%s'") + : _("option %s: invalid long integer value: '%s'"); + throw new Horde_Argv_OptionValueException( + sprintf($message, $opt, $value)); + } + return $number; + + case 'float': + if (!is_numeric($value)) { + throw new Horde_Argv_OptionValueException( + sprintf(_("option %s: invalid floating-point value: '%s'"), + $opt, $value)); + } + return floatval($value); + } + } + + public function checkChoice($opt, $value) + { + if (in_array($value, $this->choices)) { + return $value; + } else { + $choices = array(); + foreach ($this->choices as $choice) { + $choices[] = (string)$choice; + } + $choices = "'" . implode("', '", $choices) . "'"; + throw new Horde_Argv_OptionValueException(sprintf( + _("option %s: invalid choice: '%s' (choose from %s)"), + $opt, $value, $choices)); + } + } + + + # The list of instance attributes that may be set through + # keyword args to the constructor. + public $ATTRS = array('action', + 'type', + 'dest', + 'default', + 'nargs', + 'const', + 'choices', + 'callback', + 'callbackArgs', + 'help', + 'metavar', + ); + + # The set of actions allowed by option parsers. Explicitly listed + # here so the constructor can validate its arguments. + public $ACTIONS = array("store", + "store_const", + "store_true", + "store_false", + "append", + "append_const", + "count", + "callback", + "help", + "version", + ); + + /** + * The set of actions that involve storing a value somewhere; + * also listed just for constructor argument validation. (If + * the action is one of these, there must be a destination.) + */ + public $STORE_ACTIONS = array("store", + "store_const", + "store_true", + "store_false", + "append", + "append_const", + "count", + ); + + # The set of actions for which it makes sense to supply a value + # type, ie. which may consume an argument from the command line. + public $TYPED_ACTIONS = array("store", + "append", + "callback", + ); + + /** + * The set of actions which *require* a value type, ie. that + * always consume an argument from the command line. + */ + public $ALWAYS_TYPED_ACTIONS = array("store", + "append", + ); + + # The set of actions which take a 'const' attribute. + public $CONST_ACTIONS = array("store_const", + "append_const", + ); + + # The set of known types for option parsers. Again, listed here for + # constructor argument validation. + public $TYPES = array("string", "int", "long", "float", "complex", "choice"); + + # Dictionary of argument checking functions, which convert and + # validate option arguments according to the option type. + # + # Signature of checking functions is: + # check(option : Option, opt : string, value : string) -> any + # where + # option is the Option instance calling the checker + # opt is the actual option seen on the command-line + # (eg. "-a", "--file") + # value is the option argument seen on the command-line + # + # The return value should be in the appropriate Python type + # for option.type -- eg. an integer if option.type == "int". + # + # If no checker is defined for a type, arguments will be + # unchecked and remain strings. + public $TYPE_CHECKER = array("int" => 'checkBuiltin', + "long" => 'checkBuiltin', + "float" => 'checkBuiltin', + "complex"=> 'checkBuiltin', + "choice" => 'checkChoice', + ); + + # CHECK_METHODS is a list of unbound method objects; they are called + # by the constructor, in order, after all attributes are + # initialized. The list is created and filled in later, after all + # the methods are actually defined. (I just put it here because I + # like to define and document all class attributes in the same + # place.) Subclasses that add another _check_*() method should + # define their own CHECK_METHODS list that adds their check method + # to those from this class. + public $CHECK_METHODS = array('_checkAction', + '_checkType', + '_checkChoice', + '_checkDest', + '_checkConst', + '_checkNargs', + '_checkCallback', + ); + + // -- Constructor/initialization methods ---------------------------- + + public $shortOpts = array(); + public $longOpts = array(); + public $dest; + public $default; + + public function __construct() + { + // The last argument to this function is an $attrs hash, if it + // is present and an array. All other arguments are $opts. + $opts = func_get_args(); + $num = func_num_args(); + if ($num == 0 || $num == 1 || !is_array($opts[$num - 1])) { + $attrs = array(); + } else { + $attrs = array_pop($opts); + } + + // Set shortOpts, longOpts attrs from 'opts' tuple. + // Have to be set now, in case no option strings are supplied. + $this->shortOpts = array(); + $this->longOpts = array(); + $opts = $this->_checkOptStrings($opts); + $this->_setOptStrings($opts); + + // Set all other attrs (action, type, etc.) from 'attrs' dict + $this->_setAttrs($attrs); + + // Check all the attributes we just set. There are lots of + // complicated interdependencies, but luckily they can be farmed + // out to the _check*() methods listed in CHECK_METHODS -- which + // could be handy for subclasses! The one thing these all share + // is that they raise OptionError if they discover a problem. + foreach ($this->CHECK_METHODS as $checker) { + call_user_func(array($this, $checker)); + } + } + + protected function _checkOptStrings($opts) + { + // Filter out None because early versions of Optik had exactly + // one short option and one long option, either of which + // could be None. + $opts = array_filter($opts); + if (!$opts) { + throw new InvalidArgumentException('at least one option string must be supplied'); + } + return $opts; + } + + protected function _setOptStrings($opts) + { + foreach ($opts as &$opt) { + $opt = (string)$opt; + + if (strlen($opt) < 2) { + throw new Horde_Argv_OptionException(sprintf("invalid option string '%s': must be at least two characters long", $opt), $this); + } elseif (strlen($opt) == 2) { + if (!($opt[0] == "-" && $opt[1] != "-")) { + throw new Horde_Argv_OptionException(sprintf( + "invalid short option string '%s': " . + "must be of the form -x, (x any non-dash char)", $opt), $this); + } + $this->shortOpts[] = $opt; + } else { + if (!(substr($opt, 0, 2) == '--' && $opt[2] != '-')) { + throw new Horde_Argv_OptionException(sprintf( + "invalid long option string '%s': " . + "must start with --, followed by non-dash", $opt), $this); + } + $this->longOpts[] = $opt; + } + } + } + + protected function _setAttrs($attrs) + { + foreach ($this->ATTRS as $attr) { + if (array_key_exists($attr, $attrs)) { + $this->$attr = $attrs[$attr]; + unset($attrs[$attr]); + } else { + if ($attr == 'default') { + $this->$attr = self::$NO_DEFAULT; + } else { + $this->$attr = null; + } + } + } + + if ($attrs) { + $attrs = array_keys($attrs); + sort($attrs); + throw new Horde_Argv_OptionException(sprintf( + "invalid keyword arguments: %s", implode(", ", $attrs)), $this); + } + } + + + // -- Constructor validation methods -------------------------------- + + public function _checkAction() + { + if (is_null($this->action)) { + $this->action = "store"; + } elseif (!in_array($this->action, $this->ACTIONS)) { + throw new Horde_Argv_OptionException(sprintf("invalid action: '%s'", $this->action), $this); + } + } + + public function _checkType() + { + if (is_null($this->type)) { + if (in_array($this->action, $this->ALWAYS_TYPED_ACTIONS)) { + if (!is_null($this->choices)) { + // The "choices" attribute implies "choice" type. + $this->type = "choice"; + } else { + // No type given? "string" is the most sensible default. + $this->type = "string"; + } + } + } else { + if ($this->type == "str") { + $this->type = "string"; + } + + if (!in_array($this->type, $this->TYPES)) { + throw new Horde_Argv_OptionException(sprintf("invalid option type: '%s'", $this->type), $this); + } + + if (!in_array($this->action, $this->TYPED_ACTIONS)) { + throw new Horde_Argv_OptionException(sprintf( + "must not supply a type for action '%s'", $this->action), $this); + } + } + } + + public function _checkChoice() + { + if ($this->type == "choice") { + if (is_null($this->choices)) { + throw new Horde_Argv_OptionException( + "must supply a list of choices for type 'choice'", $this); + } elseif (!(is_array($this->choices) || $this->choices instanceof Iterator)) { + throw new Horde_Argv_OptionException(sprintf( + "choices must be a list of strings ('%s' supplied)", + gettype($this->choices)), $this); + } + } elseif (!is_null($this->choices)) { + throw new Horde_Argv_OptionException(sprintf( + "must not supply choices for type '%s'", $this->type), $this); + } + } + + public function _checkDest() + { + // No destination given, and we need one for this action. The + // $this->type check is for callbacks that take a value. + $takes_value = (in_array($this->action, $this->STORE_ACTIONS) || + !is_null($this->type)); + if (is_null($this->dest) && $takes_value) { + // Glean a destination from the first long option string, + // or from the first short option string if no long options. + if ($this->longOpts) { + // eg. "--foo-bar" -> "foo_bar" + $this->dest = str_replace('-', '_', substr($this->longOpts[0], 2)); + } else { + $this->dest = $this->shortOpts[0][1]; + } + } + } + + public function _checkConst() + { + if (!in_array($this->action, $this->CONST_ACTIONS) && !is_null($this->const)) { + throw new Horde_Argv_OptionException(sprintf( + "'const' must not be supplied for action '%s'", $this->action), + $this); + } + } + + public function _checkNargs() + { + if (in_array($this->action, $this->TYPED_ACTIONS)) { + if (is_null($this->nargs)) { + $this->nargs = 1; + } + } elseif (!is_null($this->nargs)) { + throw new Horde_Argv_OptionException(sprintf( + "'nargs' must not be supplied for action '%s'", $this->action), + $this); + } + } + + public function _checkCallback() + { + if ($this->action == "callback") { + if (!is_callable($this->callback)) { + $callback_name = is_array($this->callback) ? + is_object($this->callback[0]) ? get_class($this->callback[0] . '#' . $this->callback[1]) : implode('#', $this->callback) : + $this->callback; + throw new Horde_Argv_OptionException(sprintf( + "callback not callable: '%s'", $callback_name), $this); + } + if (!is_null($this->callbackArgs) && !is_array($this->callbackArgs)) { + throw new Horde_Argv_OptionException(sprintf( + "callbackArgs, if supplied, must be an array: not '%s'", + $this->callbackArgs), $this); + } + } else { + if (!is_null($this->callback)) { + $callback_name = is_array($this->callback) ? + is_object($this->callback[0]) ? get_class($this->callback[0] . '#' . $this->callback[1]) : implode('#', $this->callback) : + $this->callback; + throw new Horde_Argv_OptionException(sprintf( + "callback supplied ('%s') for non-callback option", + $callback_name), $this); + } + if (!is_null($this->callbackArgs)) { + throw new Horde_Argv_OptionException( + "callbackArgs supplied for non-callback option", $this); + } + } + } + + + // -- Miscellaneous methods ----------------------------------------- + + public function __toString() + { + return implode('/', array_merge($this->shortOpts, $this->longOpts)); + } + + public function takesValue() + { + return !is_null($this->type); + } + + public function getOptString() + { + if ($this->longOpts) + return $this->longOpts[0]; + else + return $this->shortOpts[0]; + } + + + // -- Processing methods -------------------------------------------- + + public function checkValue($opt, $value) + { + if (!isset($this->TYPE_CHECKER[$this->type])) { + return $value; + } + $checker = $this->TYPE_CHECKER[$this->type]; + return call_user_func(array($this, $checker), $opt, $value); + } + + public function convertValue($opt, $value) + { + if (!is_null($value)) { + if ($this->nargs == 1) { + return $this->checkValue($opt, $value); + } else { + $return = array(); + foreach ($value as $v) { + $return[] = $this->checkValue($opt, $v); + } + return $return; + } + } + } + + public function process($opt, $value, $values, $parser) + { + // First, convert the value(s) to the right type. Howl if any + // value(s) are bogus. + $value = $this->convertValue($opt, $value); + + // And then take whatever action is expected of us. + // This is a separate method to make life easier for + // subclasses to add new actions. + return $this->takeAction( + $this->action, $this->dest, $opt, $value, $values, $parser); + } + + public function takeAction($action, $dest, $opt, $value, $values, $parser) + { + if ($action == 'store') + $values->$dest = $value; + elseif ($action == 'store_const') + $values->$dest = $this->const; + elseif ($action == 'store_true') + $values->$dest = true; + elseif ($action == 'store_false') + $values->$dest = false; + elseif ($action == 'append') { + $values->{$dest}[] = $value; + } elseif ($action == 'append_const') { + $values->{$dest}[] = $this->const; + } elseif ($action == 'count') { + $values->ensureValue($dest, 0); + $values->$dest++; + } elseif ($action == 'callback') { + call_user_func($this->callback, $this, $opt, $value, $parser, $this->callbackArgs); + } elseif ($action == 'help') { + $parser->printHelp(); + $parser->parserExit(); + } elseif ($action == 'version') { + $parser->printVersion(); + $parser->parserExit(); + } else { + throw new RuntimeException('unknown action ' . $this->action); + } + + return 1; + } + +} diff --git a/framework/Argv/lib/Horde/Argv/OptionConflictException.php b/framework/Argv/lib/Horde/Argv/OptionConflictException.php new file mode 100644 index 000000000..8238f3105 --- /dev/null +++ b/framework/Argv/lib/Horde/Argv/OptionConflictException.php @@ -0,0 +1,17 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + */ + +/** + * Raised if conflicting options are added to a Horde_Argv_Parser. + * + * @category Horde + * @package Horde_Argv + */ +class Horde_Argv_OptionConflictException extends Horde_Argv_OptionException +{} diff --git a/framework/Argv/lib/Horde/Argv/OptionContainer.php b/framework/Argv/lib/Horde/Argv/OptionContainer.php new file mode 100644 index 000000000..7873a21b4 --- /dev/null +++ b/framework/Argv/lib/Horde/Argv/OptionContainer.php @@ -0,0 +1,264 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + */ + +/** + * Abstract base class. + * + * Class attributes: + * standardOptionList : [Option] + * list of standard options that will be accepted by all instances + * of this parser class (intended to be overridden by subclasses). + * + * Instance attributes: + * optionList : [Option] + * the list of Option objects contained by this OptionContainer + * shortOpt : { string : Option } + * dictionary mapping short option strings, eg. "-f" or "-X", + * to the Option instances that implement them. If an Option + * has multiple short option strings, it will appears in this + * dictionary multiple times. [1] + * longOpt : { string : Option } + * dictionary mapping long option strings, eg. "--file" or + * "--exclude", to the Option instances that implement them. + * Again, a given Option can occur multiple times in this + * dictionary. [1] + * defaults : { string : any } + * dictionary mapping option destination names to default + * values for each destination [1] + * + * [1] These mappings are common to (shared by) all components of the + * controlling Horde_Argv_Parser, where they are initially created. + * + * @category Horde + * @package Horde_Argv + */ +class Horde_Argv_OptionContainer +{ + public $description = ''; + public $optionList = array(); + public $optionClass = 'Horde_Argv_Option'; + public $defaults = array(); + public $shortOpt = array(); + public $longOpt = array(); + public $conflictHandler; + + /** + * Initialize the option list and related data structures. + * This method must be provided by subclasses, and it must + * initialize at least the following instance attributes: + * optionList, shortOpt, longOpt, defaults. + */ + public function __construct($optionClass, $conflictHandler, $description) + { + $this->_createOptionList(); + + $this->optionClass = $optionClass; + $this->setConflictHandler($conflictHandler); + $this->setDescription($description); + } + + /** + * For use by Horde_Argv_Parser constructor -- create the master + * option mappings used by this Horde_Argv_Parser and all + * OptionGroups that it owns. + */ + protected function _createOptionMappings() + { + $this->shortOpt = array(); // single letter -> Option instance + $this->longOpt = array(); // long option -> Option instance + $this->defaults = array(); // maps option dest -> default value + } + + /** + * For use by OptionGroup constructor -- use shared option + * mappings from the Horde_Argv_Parser that owns this OptionGroup. + */ + protected function _shareOptionMappings($parser) + { + $this->shortOpt =& $parser->shortOpt; + $this->longOpt =& $parser->longOpt; + $this->defaults = $parser->defaults; + } + + public function setConflictHandler($handler) + { + if (!in_array($handler, array('error', 'resolve'))) { + throw new InvalidArgumentException('invalid conflictHandler ' . var_export($handler, true)); + } + $this->conflictHandler = $handler; + } + + public function setDescription($description) + { + $this->description = $description; + } + + public function getDescription() + { + return $this->description; + } + + // -- Option-adding methods ----------------------------------------- + + protected function _checkConflict($option) + { + $conflictOpts = array(); + foreach ($option->shortOpts as $opt) { + if (isset($this->shortOpt[$opt])) { + $conflictOpts[$opt] = $this->shortOpt[$opt]; + } + } + foreach ($option->longOpts as $opt) { + if (isset($this->longOpt[$opt])) { + $conflictOpts[$opt] = $this->longOpt[$opt]; + } + } + + if ($conflictOpts) { + $handler = $this->conflictHandler; + if ($handler == 'error') { + throw new Horde_Argv_OptionConflictException(sprintf( + 'conflicting option string(s): %s', + implode(', ', array_keys($conflictOpts))), $option); + } elseif ($handler == 'resolve') { + foreach ($conflictOpts as $opt => $c_option) { + if (strncmp($opt, '--', 2) === 0) { + $key = array_search($opt, $c_option->longOpts); + if ($key !== false) { + unset($c_option->longOpts[$key]); + } + unset($this->longOpt[$opt]); + } else { + $key = array_search($opt, $c_option->shortOpts); + if ($key !== false) { + unset($c_option->shortOpts[$key]); + } + unset($this->shortOpt[$opt]); + } + + if (! ($c_option->shortOpts || $c_option->longOpts)) { + $key = array_search($c_option, $c_option->container->optionList); + unset($c_option->container->optionList[$key]); + } + } + } + } + } + + public function addOption() + { + $opts = func_get_args(); + + if (count($opts) && is_string($opts[0])) { + $optionFactory = new ReflectionClass($this->optionClass); + $option = $optionFactory->newInstanceArgs($opts); + } elseif (count($opts) == 1) { + $option = $opts[0]; + if (!$option instanceof Horde_Argv_Option) + throw new InvalidArgumentException('not an Option instance: ' . var_export($option, true)); + } else { + throw new InvalidArgumentException('invalid arguments'); + } + + $this->_checkConflict($option); + + $this->optionList[] = $option; + $option->container = $this; + foreach ($option->shortOpts as $opt) { + $this->shortOpt[$opt] = $option; + } + foreach ($option->longOpts as $opt) { + $this->longOpt[$opt] = $option; + } + + if (!is_null($option->dest)) { + // option has a dest, we need a default + if ($option->default !== Horde_Argv_Option::$NO_DEFAULT) { + $this->defaults[$option->dest] = $option->default; + } elseif (!isset($this->defaults[$option->dest])) { + $this->defaults[$option->dest] = null; + } + } + + return $option; + } + + public function addOptions($optionList) + { + foreach ($optionList as $option) { + $this->addOption($option); + } + } + + // -- Option query/removal methods ---------------------------------- + + public function getOption($opt_str) + { + if (isset($this->shortOpt[$opt_str])) { + return $this->shortOpt[$opt_str]; + } elseif (isset($this->longOpt[$opt_str])) { + return $this->longOpt[$opt_str]; + } else { + return null; + } + } + + public function hasOption($opt_str) + { + return isset($this->shortOpt[$opt_str]) + || isset($this->longOpt[$opt_str]); + } + + public function removeOption($opt_str) + { + $option = $this->getOption($opt_str); + if (is_null($option)) + throw new InvalidArgumentException("no such option '$opt_str'"); + + foreach ($option->shortOpts as $opt) { + unset($this->shortOpt[$opt]); + } + foreach ($option->longOpts as $opt) { + unset($this->longOpt[$opt]); + } + $key = array_search($option, $option->container->optionList); + unset($option->container->optionList[$key]); + } + + + // -- Help-formatting methods --------------------------------------- + + public function formatOptionHelp($formatter = null) + { + if (!$this->optionList) + return ''; + $result = array(); + foreach ($this->optionList as $option) { + if ($option->help != Horde_Argv_Option::SUPPRESS_HELP) + $result[] = $formatter->formatOption($option); + } + return implode('', $result); + } + + public function formatDescription($formatter = null) + { + return $formatter->formatDescription($this->getDescription()); + } + + public function formatHelp($formatter = null) + { + $result = array(); + if ($this->description) + $result[] = $this->formatDescription($formatter); + if ($this->optionList) + $result[] = $this->formatOptionHelp($formatter); + return implode("\n", $result); + } + +} diff --git a/framework/Argv/lib/Horde/Argv/OptionException.php b/framework/Argv/lib/Horde/Argv/OptionException.php new file mode 100644 index 000000000..4d1516c91 --- /dev/null +++ b/framework/Argv/lib/Horde/Argv/OptionException.php @@ -0,0 +1,29 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + */ + +/** + * Raised if an Option instance is created with invalid or + * inconsistent arguments. + * + * @category Horde + * @package Horde_Argv + */ +class Horde_Argv_OptionException extends Horde_Argv_Exception +{ + public function __construct($msg, $option = null) + { + $this->optionId = (string)$option; + if ($this->optionId) { + parent::__construct(sprintf(_("option %s: %s"), $this->optionId, $msg)); + } else { + parent::__construct($msg); + } + } + +} diff --git a/framework/Argv/lib/Horde/Argv/OptionGroup.php b/framework/Argv/lib/Horde/Argv/OptionGroup.php new file mode 100644 index 000000000..c47550613 --- /dev/null +++ b/framework/Argv/lib/Horde/Argv/OptionGroup.php @@ -0,0 +1,55 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + */ + +/** + * @category Horde + * @package Horde_Argv + */ +class Horde_Argv_OptionGroup extends Horde_Argv_OptionContainer +{ + protected $_title; + + public function __construct($parser, $title, $description = null) + { + $this->parser = $parser; + parent::__construct($parser->optionClass, $parser->conflictHandler, $description); + $this->_title = $title; + } + + protected function _createOptionList() + { + $this->optionList = array(); + $this->_shareOptionMappings($this->parser); + } + + public function setTitle($title) + { + $this->_title = $title; + } + + public function __destruct() + { + unset($this->optionList); + } + + // -- Help-formatting methods --------------------------------------- + + public function formatHelp($formatter = null) + { + if (is_null($formatter)) + return ''; + + $result = $formatter->formatHeading($this->_title); + $formatter->indent(); + $result .= parent::formatHelp($formatter); + $formatter->dedent(); + return $result; + } + +} diff --git a/framework/Argv/lib/Horde/Argv/OptionValueException.php b/framework/Argv/lib/Horde/Argv/OptionValueException.php new file mode 100644 index 000000000..788044201 --- /dev/null +++ b/framework/Argv/lib/Horde/Argv/OptionValueException.php @@ -0,0 +1,17 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + */ + +/** + * Raised if an invalid option value is encountered on the command + * + * @category Horde + * @package Horde_Argv + */ +class Horde_Argv_OptionValueException extends Horde_Argv_OptionException +{} diff --git a/framework/Argv/lib/Horde/Argv/Parser.php b/framework/Argv/lib/Horde/Argv/Parser.php new file mode 100644 index 000000000..ef07569eb --- /dev/null +++ b/framework/Argv/lib/Horde/Argv/Parser.php @@ -0,0 +1,674 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + */ + +/** + * Class attributes: + * standardOptionList : [Option] + * list of standard options that will be accepted by all instances + * of this parser class (intended to be overridden by subclasses). + * + * Instance attributes: + * usage : string + * a usage string for your program. Before it is displayed + * to the user, "%prog" will be expanded to the name of + * your program ($this->prog or os.path.basename(sys.argv[0])). + * prog : string + * the name of the current program (to override + * os.path.basename(sys.argv[0])). + * epilog : string + * paragraph of help text to print after option help + * + * optionGroups : [OptionGroup] + * list of option groups in this parser (option groups are + * irrelevant for parsing the command-line, but very useful + * for generating help) + * + * allow_interspersed_args : bool = true + * if true, positional arguments may be interspersed with options. + * Assuming -a and -b each take a single argument, the command-line + * -ablah foo bar -bboo baz + * will be interpreted the same as + * -ablah -bboo -- foo bar baz + * If this flag were false, that command line would be interpreted as + * -ablah -- foo bar -bboo baz + * -- ie. we stop processing options as soon as we see the first + * non-option argument. (This is the tradition followed by + * Python's getopt module, Perl's Getopt::Std, and other argument- + * parsing libraries, but it is generally annoying to users.) + * + * rargs : [string] + * the argument list currently being parsed. Only set when + * parseArgs() is active, and continually trimmed down as + * we consume arguments. Mainly there for the benefit of + * callback options. + * largs : [string] + * the list of leftover arguments that we have skipped while + * parsing options. If allow_interspersed_args is false, this + * list is always empty. + * values : Values + * the set of option values currently being accumulated. Only + * set when parseArgs() is active. Also mainly for callbacks. + * + * @category Horde + * @package Horde_Argv + */ +class Horde_Argv_Parser extends Horde_Argv_OptionContainer +{ + public $standardOptionList = array(); + + protected $_usage; + public $optionGroups = array(); + + public function __construct($args = array()) + { + $args = array_merge(array( + 'usage' => null, + 'optionList' => null, + 'optionClass' => 'Horde_Argv_Option', + 'version' => null, + 'conflictHandler' => "error", + 'description' => null, + 'formatter' => null, + 'addHelpOption' => true, + 'prog' => null, + 'epilog' => null), + $args); + + parent::__construct($args['optionClass'], $args['conflictHandler'], $args['description']); + $this->setUsage($args['usage']); + $this->prog = $args['prog']; + $this->version = $args['version']; + $this->allow_interspersed_args = true; + if (is_null($args['formatter'])) + $args['formatter'] = new Horde_Argv_IndentedHelpFormatter(); + $this->formatter = $args['formatter']; + $this->formatter->setParser($this); + $this->epilog = $args['epilog']; + + // Populate the option list; initial sources are the + // standardOptionList class attribute, the 'optionList' + // argument, and (if applicable) the _addVersionOption() and + // _addHelpOption() methods. + $this->_populateOptionList($args['optionList'], + $args['addHelpOption']); + + $this->_initParsingState(); + } + + /** + * Declare that you are done with this Horde_Argv_Parser. This cleans up + * reference cycles so the Horde_Argv_Parser (and all objects referenced by + * it) can be garbage-collected promptly. After calling destroy(), the + * Horde_Argv_Parser is unusable. + */ + public function __destruct() + { + foreach ($this->optionGroups as &$group) { + unset($group); + } + + unset($this->optionList); + unset($this->optionGroups); + unset($this->formatter); + } + + // -- Private methods ----------------------------------------------- + // (used by our or OptionContainer's constructor) + + protected function _createOptionList() + { + $this->optionList = array(); + $this->optionGroups = array(); + $this->_createOptionMappings(); + } + + protected function _addHelpOption() + { + $this->addOption('-h', '--help', array('action' => 'help', + 'help' => _("show this help message and exit"))); + } + + protected function _addVersionOption() + { + $this->addOption('--version', array('action' => 'version', + 'help' => _("show program's version number and exit"))); + } + + protected function _populateOptionList($optionList, $add_help = true) + { + if ($this->standardOptionList) + $this->addOptions($this->standardOptionList); + if ($optionList) + $this->addOptions($optionList); + if ($this->version) + $this->_addVersionOption(); + if ($add_help) + $this->_addHelpOption(); + } + + protected function _initParsingState() + { + // These are set in parseArgs() for the convenience of callbacks. + $this->rargs = null; + $this->largs = null; + $this->values = null; + } + + // -- Simple modifier methods --------------------------------------- + + public function setUsage($usage) + { + if (is_null($usage)) + $this->_usage = '%prog ' . _("[options]"); + elseif ($usage == Horde_Argv_Option::SUPPRESS_USAGE) + $this->_usage = null; + else + $this->_usage = $usage; + } + + public function enableInterspersedArgs() + { + $this->allow_interspersed_args = true; + } + + public function disableInterspersedArgs() + { + $this->allow_interspersed_args = false; + } + + public function setDefault($dest, $value) + { + $this->defaults[$dest] = $value; + } + + public function setDefaults($defaults) + { + $this->defaults = array_merge($this->defaults, $defaults); + } + + protected function _getAllOptions() + { + $options = $this->optionList; + foreach ($this->optionGroups as $group) { + $options = array_merge($options, $group->optionList); + } + return $options; + } + + public function getDefaultValues() + { + $defaults = $this->defaults; + foreach ($this->_getAllOptions() as $option) { + $default = isset($defaults[$option->dest]) ? $defaults[$option->dest] : null; + if (is_string($default)) { + $opt_str = $option->getOptString(); + $defaults[$option->dest] = $option->checkValue($opt_str, $default); + } + } + + return new Horde_Argv_Values($defaults); + } + + + // -- OptionGroup methods ------------------------------------------- + + public function addOptionGroup() + { + // XXX lots of overlap with OptionContainer::addOption() + $args = func_get_args(); + + if (count($args) && is_string($args[0])) { + $groupFactory = new ReflectionClass('Horde_Argv_OptionGroup'); + array_unshift($args, $this); + $group = $groupFactory->newInstanceArgs($args); + } elseif (count($args) == 1) { + $group = $args[0]; + if (!$group instanceof Horde_Argv_OptionGroup) + throw new InvalidArgumentException("not an OptionGroup instance: " . var_export($group, true)); + if ($group->parser !== $this) + throw new InvalidArgumentException("invalid OptionGroup (wrong parser)"); + } else { + throw new InvalidArgumentException('invalid arguments'); + } + + $this->optionGroups[] = $group; + return $group; + } + + public function getOptionGroup($opt_str) + { + if (isset($this->shortOpt[$opt_str])) { + $option = $this->shortOpt[$opt_str]; + } elseif (isset($this->longOpt[$opt_str])) { + $option = $this->longOpt[$opt_str]; + } else { + return null; + } + + if ($option->container !== $this) { + return $option->container; + } + + return null; + } + + // -- Option-parsing methods ---------------------------------------- + + protected function _getArgs($args = null) + { + if (is_null($args)) { + $args = $_SERVER['argv']; + array_shift($args); + return $args; + } else { + return $args; + } + } + + /** + * Parse the command-line options found in 'args' (default: + * sys.argv[1:]). Any errors result in a call to 'parserError()', which + * by default prints the usage message to stderr and calls + * exit() with an error message. On success returns a pair + * (values, args) where 'values' is an Values instance (with all + * your option values) and 'args' is the list of arguments left + * over after parsing options. + */ + public function parseArgs($args = null, $values = null) + { + $rargs = $this->_getArgs($args); + $largs = array(); + if (is_null($values)) + $values = $this->getDefaultValues(); + + // Store the halves of the argument list as attributes for the + // convenience of callbacks: + // rargs + // the rest of the command-line (the "r" stands for + // "remaining" or "right-hand") + // largs + // the leftover arguments -- ie. what's left after removing + // options and their arguments (the "l" stands for "leftover" + // or "left-hand") + $this->rargs =& $rargs; + $this->largs =& $largs; + $this->values = $values; + + try { + $this->_processArgs($largs, $rargs, $values); + } catch (Horde_Argv_BadOptionException $e) { + $this->parserError($e->getMessage()); + } catch (Horde_Argv_OptionValueException $e) { + $this->parserError($e->getMessage()); + } + + $args = array_merge($largs, $rargs); + return $this->checkValues($values, $args); + } + + /** + * Check that the supplied option values and leftover arguments are + * valid. Returns the option values and leftover arguments + * (possibly adjusted, possibly completely new -- whatever you + * like). Default implementation just returns the passed-in + * values; subclasses may override as desired. + */ + public function checkValues($values, $args) + { + return array($values, $args); + } + + /** + * _process_args(largs : [string], + * rargs : [string], + * values : Values) + * + * Process command-line arguments and populate 'values', consuming + * options and arguments from 'rargs'. If 'allow_interspersed_args' is + * false, stop at the first non-option argument. If true, accumulate any + * interspersed non-option arguments in 'largs'. + */ + protected function _processArgs(&$largs, &$rargs, &$values) + { + while ($rargs) { + $arg = $rargs[0]; + // We handle bare "--" explicitly, and bare "-" is handled by the + // standard arg handler since the short arg case ensures that the + // len of the opt string is greater than 1. + if ($arg == '--') { + array_shift($rargs); + return; + } elseif (substr($arg, 0, 2) == '--') { + // process a single long option (possibly with value(s)) + $this->_processLongOpt($rargs, $values); + } elseif (substr($arg, 0, 1) == '-' && strlen($arg) > 1) { + // process a cluster of short options (possibly with + // value(s) for the last one only) + $this->_processShortOpts($rargs, $values); + } elseif ($this->allow_interspersed_args) { + $largs[] = $arg; + array_shift($rargs); + } else { + // stop now, leave this arg in rargs + return; + } + } + + // Say this is the original argument list: + // [arg0, arg1, ..., arg(i-1), arg(i), arg(i+1), ..., arg(N-1)] + // ^ + // (we are about to process arg(i)). + // + // Then rargs is [arg(i), ..., arg(N-1)] and largs is a *subset* of + // [arg0, ..., arg(i-1)] (any options and their arguments will have + // been removed from largs). + // + // The while loop will usually consume 1 or more arguments per pass. + // If it consumes 1 (eg. arg is an option that takes no arguments), + // then after _process_arg() is done the situation is: + // + // largs = subset of [arg0, ..., arg(i)] + // rargs = [arg(i+1), ..., arg(N-1)] + // + // If allow_interspersed_args is false, largs will always be + // *empty* -- still a subset of [arg0, ..., arg(i-1)], but + // not a very interesting subset! + } + + /** + * opt : string) -> string + * + * Determine which long option string 'opt' matches, ie. which one + * it is an unambiguous abbrevation for. Raises BadOptionError if + * 'opt' doesn't unambiguously match any long option string. + */ + protected function _matchLongOpt($opt) + { + return self::matchAbbrev($opt, $this->longOpt); + } + + /** + * (s : string, wordmap : {string : Option}) -> string + * + * Return the string key in 'wordmap' for which 's' is an unambiguous + * abbreviation. If 's' is found to be ambiguous or doesn't match any of + * 'words', raise BadOptionError. + */ + public static function matchAbbrev($s, $wordmap) + { + // Is there an exact match? + if (array_key_exists($s, $wordmap)) { + return $s; + } + + // Isolate all words with s as a prefix. + $possibilities = array(); + foreach (array_keys($wordmap) as $word) { + if (strncmp($word, $s, strlen($s)) === 0) { + $possibilities[] = $word; + } + } + + // No exact match, so there had better be just one possibility. + if (count($possibilities) == 1) { + return $possibilities[0]; + } elseif (!$possibilities) { + throw new Horde_Argv_BadOptionException($s); + } else { + // More than one possible completion: ambiguous prefix. + sort($possibilities); + throw new Horde_Argv_AmbiguousOptionException($s, $possibilities); + } + } + + protected function _processLongOpt(&$rargs, &$values) + { + $arg = array_shift($rargs); + + // Value explicitly attached to arg? Pretend it's the next + // argument. + if (strpos($arg, '=') !== false) { + list($opt, $next_arg) = explode('=', $arg, 2); + array_unshift($rargs, $next_arg); + $had_explicit_value = true; + } else { + $opt = $arg; + $had_explicit_value = false; + } + + $opt = $this->_matchLongOpt($opt); + $option = $this->longOpt[$opt]; + if ($option->takesValue()) { + $nargs = $option->nargs; + if (count($rargs) < $nargs) { + if ($nargs == 1) + $this->parserError(sprintf(_("%s option requires an argument"), $opt)); + else + $this->parserError(sprintf(_("%s option requires %d arguments"), + $opt, $nargs)); + } elseif ($nargs == 1) { + $value = array_shift($rargs); + } else { + $value = array_splice($rargs, 0, $nargs); + } + + } elseif ($had_explicit_value) { + $this->parserError(sprintf(_("%s option does not take a value"), $opt)); + + } else { + $value = null; + } + + $option->process($opt, $value, $values, $this); + } + + protected function _processShortOpts(&$rargs, &$values) + { + $arg = array_shift($rargs); + $stop = false; + $i = 1; + for ($c = 1, $c_max = strlen($arg); $c < $c_max; $c++) { + $ch = $arg[$c]; + $opt = '-' . $ch; + $option = isset($this->shortOpt[$opt]) ? $this->shortOpt[$opt] : null; + $i++; // we have consumed a character + + if (!$option) + throw new Horde_Argv_BadOptionException($opt); + + if ($option->takesValue()) { + // Any characters left in arg? Pretend they're the + // next arg, and stop consuming characters of arg. + if ($i < strlen($arg)) { + array_unshift($rargs, substr($arg, $i)); + $stop = true; + } + + $nargs = $option->nargs; + if (count($rargs) < $nargs) { + if ($nargs == 1) + $this->parserError(sprintf(_("%s option requires an argument"), $opt)); + else + $this->parserError(sprintf(_("%s option requires %d arguments"), $opt, $nargs)); + + } elseif ($nargs == 1) { + $value = array_shift($rargs); + } else { + $value = array_splice($rargs, 0, $nargs); + } + + } else { + // option doesn't take a value + $value = null; + } + + $option->process($opt, $value, $values, $this); + + if ($stop) + break; + } + } + + // -- Feedback methods ---------------------------------------------- + + public function getProgName() + { + if (is_null($this->prog)) + return basename($_SERVER['argv'][0]); + else + return $this->prog; + } + + public function expandProgName($s) + { + return str_replace("%prog", $this->getProgName(), $s); + } + + public function getDescription() + { + return $this->expandProgName($this->description); + } + + public function parserExit($status = 0, $msg = null) + { + if ($msg) + fwrite(STDERR, $msg); + exit($status); + } + + /** + * Print a usage message incorporating $msg to stderr and exit. + * If you override this in a subclass, it should not return -- it + * should either exit or raise an exception. + * + * @param string $msg + */ + public function parserError($msg) + { + $this->printUsage(STDERR); + $this->parserExit(2, sprintf("%s: error: %s\n", $this->getProgName(), $msg)); + } + + public function getUsage($formatter = null) + { + if (is_null($formatter)) + $formatter = $this->formatter; + if ($this->_usage) + return $formatter->formatUsage($this->expandProgName($this->_usage)); + else + return ''; + } + + /** + * (file : file = stdout) + * + * Print the usage message for the current program ($this->_usage) to + * 'file' (default stdout). Any occurence of the string "%prog" in + * $this->_usage is replaced with the name of the current program + * (basename of sys.argv[0]). Does nothing if $this->_usage is empty + * or not defined. + */ + public function printUsage($file = null) + { + if (!$this->_usage) + return; + + if (is_null($file)) + echo $this->getUsage(); + else + fwrite($file, $this->getUsage()); + } + + public function getVersion() + { + if ($this->version) + return $this->expandProgName($this->version); + else + return ''; + } + + /** + * file : file = stdout + * + * Print the version message for this program ($this->version) to + * 'file' (default stdout). As with printUsage(), any occurence + * of "%prog" in $this->version is replaced by the current program's + * name. Does nothing if $this->version is empty or undefined. + */ + public function printVersion($file = null) + { + if (!$this->version) + return; + + if (is_null($file)) + echo $this->getVersion() . "\n"; + else + fwrite($file, $this->getVersion() . "\n"); + } + + public function formatOptionHelp($formatter = null) + { + if (is_null($formatter)) + $formatter = $this->formatter; + $formatter->storeOptionStrings($this); + $result = array(); + $result[] = $formatter->formatHeading(_("Options")); + $formatter->indent(); + if ($this->optionList) { + $result[] = parent::formatOptionHelp($formatter); + $result[] = "\n"; + } + foreach ($this->optionGroups as $group) { + $result[] = $group->formatHelp($formatter); + $result[] = "\n"; + } + $formatter->dedent(); + // Drop the last "\n", or the header if no options or option groups: + array_pop($result); + return implode('', $result); + } + + public function formatEpilog($formatter) + { + return $formatter->formatEpilog($this->epilog); + } + + public function formatHelp($formatter = null) + { + if (is_null($formatter)) + $formatter = $this->formatter; + $result = array(); + if ($this->_usage) + $result[] = $this->getUsage($formatter) . "\n"; + if ($this->description) + $result[] = $this->formatDescription($formatter) . "\n"; + $result[] = $this->formatOptionHelp($formatter); + $result[] = $this->formatEpilog($formatter); + return implode('', $result); + } + + /** + * file : file = stdout + * + * Print an extended help message, listing all options and any + * help text provided with them, to 'file' (default stdout). + */ + public function printHelp($file = null) + { + if (is_null($file)) + echo $this->formatHelp(); + else + fwrite($file, $this->formatHelp()); + } + +} diff --git a/framework/Argv/lib/Horde/Argv/TitledHelpFormatter.php b/framework/Argv/lib/Horde/Argv/TitledHelpFormatter.php new file mode 100644 index 000000000..6fcf72ae2 --- /dev/null +++ b/framework/Argv/lib/Horde/Argv/TitledHelpFormatter.php @@ -0,0 +1,38 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + */ + +/** + * Format help with underlined section headers. + * + * @category Horde + * @package Horde_Argv + */ +class Horde_Argv_TitledHelpFormatter extends Horde_Argv_HelpFormatter +{ + public function __construct( + $indent_increment = 0, + $max_help_position = 24, + $width = null, + $short_first = false) + { + parent::__construct($indent_increment, $max_help_position, $width, $short_first); + } + + public function formatUsage($usage) + { + return sprintf("%s %s\n", $this->formatHeading(_("Usage")), $usage); + } + + public function formatHeading($heading) + { + $prefix = array('=', '-'); + return sprintf("%s\n%s\n", $heading, str_repeat($prefix[$this->level], strlen($heading))); + } + +} diff --git a/framework/Argv/lib/Horde/Argv/Values.php b/framework/Argv/lib/Horde/Argv/Values.php new file mode 100644 index 000000000..2b944ebb0 --- /dev/null +++ b/framework/Argv/lib/Horde/Argv/Values.php @@ -0,0 +1,72 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + */ + +/** + * Result hash for Horde_Argv_Parser + * + * @category Horde + * @package Horde_Argv + */ +class Horde_Argv_Values implements IteratorAggregate, ArrayAccess, Countable +{ + public function __construct($defaults = array()) + { + foreach ($defaults as $attr => $val) { + $this->$attr = $val; + } + } + + public function __toString() + { + $str = array(); + foreach ($this as $attr => $val) { + $str[] = $attr . ': ' . (string)$val; + } + return implode(', ', $str); + } + + public function offsetExists($attr) + { + return !is_null($this->$attr); + } + + public function offsetGet($attr) + { + return $this->$attr; + } + + public function offsetSet($attr, $val) + { + $this->$attr = $val; + } + + public function offsetUnset($attr) + { + unset($this->$attr); + } + + public function getIterator() + { + return new ArrayIterator(get_object_vars($this)); + } + + public function count() + { + return count(get_object_vars($this)); + } + + public function ensureValue($attr, $value) + { + if (is_null($this->$attr)) { + $this->$attr = $value; + } + return $this->$attr; + } + +} diff --git a/framework/Argv/lib/Horde/CVS/Entries b/framework/Argv/lib/Horde/CVS/Entries new file mode 100644 index 000000000..7f0438144 --- /dev/null +++ b/framework/Argv/lib/Horde/CVS/Entries @@ -0,0 +1 @@ +D/Argv//// diff --git a/framework/Argv/lib/Horde/CVS/Repository b/framework/Argv/lib/Horde/CVS/Repository new file mode 100644 index 000000000..42d49e7cb --- /dev/null +++ b/framework/Argv/lib/Horde/CVS/Repository @@ -0,0 +1 @@ +framework/Argv/lib/Horde diff --git a/framework/Argv/lib/Horde/CVS/Root b/framework/Argv/lib/Horde/CVS/Root new file mode 100644 index 000000000..5d636129a --- /dev/null +++ b/framework/Argv/lib/Horde/CVS/Root @@ -0,0 +1 @@ +chuck@cvs.horde.org:/repository diff --git a/framework/Argv/lib/Horde/CVS/Template b/framework/Argv/lib/Horde/CVS/Template new file mode 100644 index 000000000..3971591f9 --- /dev/null +++ b/framework/Argv/lib/Horde/CVS/Template @@ -0,0 +1,8 @@ + +Bug: +Submitted by: +Merge after: +CVS: ---------------------------------------------------------------------- +CVS: Bug: Fill this in if a listed bug is affected by the change. +CVS: Submitted by: Fill this in if someone else sent in the change. +CVS: Merge after: N [day[s]|week[s]|month[s]] (days assumed by default) diff --git a/framework/Argv/package.xml b/framework/Argv/package.xml new file mode 100644 index 000000000..3d5712c40 --- /dev/null +++ b/framework/Argv/package.xml @@ -0,0 +1,90 @@ + + + Argv + pear.horde.org + Horde command-line argument parsing package + This package provides classes for parsing command line + arguments with various actions, providing help, grouping options, and + more. + + + Chuck Hagenbuch + chuck + chuck@horde.org + yes + + + Mike Naberezny + mnaberez + mike@maintainable.com + yes + + 2008-01-31 + + 0.1.0 + 0.1.0 + + + beta + beta + + BSD + + * Initial release, ported from Optik (http://optik.sourceforge.net/) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5.1.0 + + + 1.5.0 + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/Argv/test/CVS/Entries b/framework/Argv/test/CVS/Entries new file mode 100644 index 000000000..ae2779e30 --- /dev/null +++ b/framework/Argv/test/CVS/Entries @@ -0,0 +1 @@ +D/Horde//// diff --git a/framework/Argv/test/CVS/Repository b/framework/Argv/test/CVS/Repository new file mode 100644 index 000000000..721c09791 --- /dev/null +++ b/framework/Argv/test/CVS/Repository @@ -0,0 +1 @@ +framework/Argv/test diff --git a/framework/Argv/test/CVS/Root b/framework/Argv/test/CVS/Root new file mode 100644 index 000000000..5d636129a --- /dev/null +++ b/framework/Argv/test/CVS/Root @@ -0,0 +1 @@ +chuck@cvs.horde.org:/repository diff --git a/framework/Argv/test/CVS/Template b/framework/Argv/test/CVS/Template new file mode 100644 index 000000000..3971591f9 --- /dev/null +++ b/framework/Argv/test/CVS/Template @@ -0,0 +1,8 @@ + +Bug: +Submitted by: +Merge after: +CVS: ---------------------------------------------------------------------- +CVS: Bug: Fill this in if a listed bug is affected by the change. +CVS: Submitted by: Fill this in if someone else sent in the change. +CVS: Merge after: N [day[s]|week[s]|month[s]] (days assumed by default) diff --git a/framework/Argv/test/Horde/Argv/AllTests.php b/framework/Argv/test/Horde/Argv/AllTests.php new file mode 100644 index 000000000..1cf19c0fb --- /dev/null +++ b/framework/Argv/test/Horde/Argv/AllTests.php @@ -0,0 +1,71 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + * @subpackage UnitTests + */ + +if (!defined('PHPUnit_MAIN_METHOD')) { + define('PHPUnit_MAIN_METHOD', 'Horde_Argv_AllTests::main'); +} + +require_once 'PHPUnit/Framework/TestSuite.php'; +require_once 'PHPUnit/TextUI/TestRunner.php'; + +/** + * @package Horde_Argv + * @subpackage UnitTests + */ +class Horde_Argv_AllTests { + + public static function main() + { + PHPUnit_TextUI_TestRunner::run(self::suite()); + } + + public static function suite() + { + // Catch strict standards + error_reporting(E_ALL | E_STRICT); + + // Set up autoload + set_include_path(dirname(dirname(dirname(dirname(__FILE__)))) . DIRECTORY_SEPARATOR . 'lib' . PATH_SEPARATOR . get_include_path()); + if (!spl_autoload_functions()) { + spl_autoload_register(create_function('$class', '$filename = str_replace(array(\'::\', \'_\'), \'/\', $class); include "$filename.php";')); + } + + // Test base classes and helper objects + require_once dirname(__FILE__) . '/TestBase.php'; + require_once dirname(__FILE__) . '/ConflictTestBase.php'; + require_once dirname(__FILE__) . '/InterceptedException.php'; + require_once dirname(__FILE__) . '/InterceptingParser.php'; + + $suite = new PHPUnit_Framework_TestSuite('Horde Framework - Horde_Argv'); + + $basedir = dirname(__FILE__); + $baseregexp = preg_quote($basedir . DIRECTORY_SEPARATOR, '/'); + + foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($basedir)) as $file) { + if ($file->isFile() && preg_match('/Test.php$/', $file->getFilename())) { + $pathname = $file->getPathname(); + require $pathname; + + $class = str_replace(DIRECTORY_SEPARATOR, '_', + preg_replace("/^$baseregexp(.*)\.php/", '\\1', $pathname)); + $suite->addTestSuite('Horde_Argv_' . $class); + } + } + + return $suite; + } + +} + +if (PHPUnit_MAIN_METHOD == 'Horde_Argv_AllTests::main') { + Horde_Argv_AllTests::main(); +} diff --git a/framework/Argv/test/Horde/Argv/BoolTest.php b/framework/Argv/test/Horde/Argv/BoolTest.php new file mode 100644 index 000000000..43f5c602b --- /dev/null +++ b/framework/Argv/test/Horde/Argv/BoolTest.php @@ -0,0 +1,56 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + * @subpackage UnitTests + */ + +class Horde_Argv_BoolTest extends Horde_Argv_TestBase +{ + public function setUp() + { + $options = array( + $this->makeOption('-v', '--verbose', + array('action' => 'store_true', 'dest' => 'verbose', 'default' => '')), + $this->makeOption('-q', '--quiet', + array('action' => 'store_false', 'dest' => 'verbose')) + ); + + $this->parser = new Horde_Argv_Parser(array('optionList' => $options)); + } + + public function testBoolDefault() + { + $this->assertParseOk(array(), + array('verbose' => ''), + array()); + } + + public function testBoolFalse() + { + list($options, $args) = $this->assertParseOk(array('-q'), + array('verbose' => false), + array()); + + $this->assertSame(false, $options->verbose); + } + + public function testBoolTrue() + { + list($options, $args) = $this->assertParseOk(array('-v'), + array('verbose' => true), + array()); + $this->assertSame(true, $options->verbose); + } + + public function testBoolFlickerOnAndOff() + { + $this->assertParseOk(array('-qvq', '-q', '-v'), + array('verbose' => true), + array()); + } + +} diff --git a/framework/Argv/test/Horde/Argv/CVS/Entries b/framework/Argv/test/Horde/Argv/CVS/Entries new file mode 100644 index 000000000..81f39ef37 --- /dev/null +++ b/framework/Argv/test/Horde/Argv/CVS/Entries @@ -0,0 +1,36 @@ +/AllTests.php/1.3/Wed Jun 18 19:00:45 2008// +/BoolTest.php/1.2/Wed Jun 18 19:00:45 2008// +/CallbackCheckAbbrevTest.php/1.2/Wed Jun 18 19:00:46 2008// +/CallbackExtraArgsTest.php/1.2/Wed Jun 18 19:00:46 2008// +/CallbackManyArgsTest.php/1.2/Wed Jun 18 19:00:46 2008// +/CallbackMeddleArgsTest.php/1.2/Wed Jun 18 19:00:46 2008// +/CallbackTest.php/1.2/Wed Jun 18 19:00:46 2008// +/CallbackVarArgsTest.php/1.2/Wed Jun 18 19:00:46 2008// +/ChoiceTest.php/1.2/Wed Jun 18 19:00:46 2008// +/ConflictOverrideTest.php/1.2/Wed Jun 18 19:00:46 2008// +/ConflictResolveTest.php/1.2/Wed Jun 18 19:00:46 2008// +/ConflictTest.php/1.2/Wed Jun 18 19:00:46 2008// +/ConflictTestBase.php/1.2/Wed Jun 18 19:00:46 2008// +/ConflictingDefaultsTest.php/1.2/Wed Jun 18 19:00:46 2008// +/CountTest.php/1.2/Wed Jun 18 19:00:46 2008// +/DefaultValuesTest.php/1.2/Wed Jun 18 19:00:46 2008// +/ExpandDefaultsTest.php/1.2/Wed Jun 18 19:00:46 2008// +/ExtendAddActionsTest.php/1.2/Wed Jun 18 19:00:46 2008// +/ExtendAddTypesTest.php/1.2/Wed Jun 18 19:00:46 2008// +/HelpTest.php/1.2/Wed Jun 18 19:00:46 2008// +/InterceptedException.php/1.2/Wed Jun 18 19:00:46 2008// +/InterceptingParser.php/1.2/Wed Jun 18 19:00:46 2008// +/MatchAbbrevTest.php/1.2/Wed Jun 18 19:00:46 2008// +/MultipleArgsAppendTest.php/1.2/Wed Jun 18 19:00:46 2008// +/MultipleArgsTest.php/1.2/Wed Jun 18 19:00:46 2008// +/OptionChecksTest.php/1.2/Wed Jun 18 19:00:46 2008// +/OptionGroupTest.php/1.2/Wed Jun 18 19:00:46 2008// +/OptionValuesTest.php/1.2/Wed Jun 18 19:00:46 2008// +/ParseNumTest.php/1.2/Wed Jun 18 19:00:46 2008// +/ParserTest.php/1.2/Wed Jun 18 19:00:46 2008// +/ProgNameTest.php/1.2/Wed Jun 18 19:00:46 2008// +/StandardTest.php/1.2/Wed Jun 18 19:00:46 2008// +/TestBase.php/1.2/Wed Jun 18 19:00:46 2008// +/TypeAliasesTest.php/1.2/Wed Jun 18 19:00:46 2008// +/VersionTest.php/1.2/Wed Jun 18 19:00:46 2008// +D diff --git a/framework/Argv/test/Horde/Argv/CVS/Repository b/framework/Argv/test/Horde/Argv/CVS/Repository new file mode 100644 index 000000000..1c7ec4a2b --- /dev/null +++ b/framework/Argv/test/Horde/Argv/CVS/Repository @@ -0,0 +1 @@ +framework/Argv/test/Horde/Argv diff --git a/framework/Argv/test/Horde/Argv/CVS/Root b/framework/Argv/test/Horde/Argv/CVS/Root new file mode 100644 index 000000000..5d636129a --- /dev/null +++ b/framework/Argv/test/Horde/Argv/CVS/Root @@ -0,0 +1 @@ +chuck@cvs.horde.org:/repository diff --git a/framework/Argv/test/Horde/Argv/CVS/Template b/framework/Argv/test/Horde/Argv/CVS/Template new file mode 100644 index 000000000..3971591f9 --- /dev/null +++ b/framework/Argv/test/Horde/Argv/CVS/Template @@ -0,0 +1,8 @@ + +Bug: +Submitted by: +Merge after: +CVS: ---------------------------------------------------------------------- +CVS: Bug: Fill this in if a listed bug is affected by the change. +CVS: Submitted by: Fill this in if someone else sent in the change. +CVS: Merge after: N [day[s]|week[s]|month[s]] (days assumed by default) diff --git a/framework/Argv/test/Horde/Argv/CallbackCheckAbbrevTest.php b/framework/Argv/test/Horde/Argv/CallbackCheckAbbrevTest.php new file mode 100644 index 000000000..d8b75d0dd --- /dev/null +++ b/framework/Argv/test/Horde/Argv/CallbackCheckAbbrevTest.php @@ -0,0 +1,29 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + * @subpackage UnitTests + */ + +class Horde_Argv_CallbackCheckAbbrevTest extends Horde_Argv_TestBase +{ + public function setUp() + { + $this->parser = new Horde_Argv_Parser(); + $this->parser->addOption('--foo-bar', array('action' => 'callback', + 'callback' => array($this, 'checkAbbrev'))); + } + + public function checkAbbrev($option, $opt, $value, $parser) + { + $this->assertEquals($opt, '--foo-bar'); + } + + public function testAbbrevCallbackExpansion() + { + $this->assertParseOk(array('--foo'), array(), array()); + } +} diff --git a/framework/Argv/test/Horde/Argv/CallbackExtraArgsTest.php b/framework/Argv/test/Horde/Argv/CallbackExtraArgsTest.php new file mode 100644 index 000000000..264d39744 --- /dev/null +++ b/framework/Argv/test/Horde/Argv/CallbackExtraArgsTest.php @@ -0,0 +1,52 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + * @subpackage UnitTests + */ + +class Horde_Argv_CallbackExtraArgsTest extends Horde_Argv_TestBase +{ + public function setUp() + { + $options = array( + $this->makeOption('-p', '--point', array('action' => 'callback', + 'callback' => array($this, 'processTuple'), + 'callbackArgs' => array(3, 'int'), 'type' => 'string', + 'dest' => 'points', 'default' => array())), + ); + $this->parser = new Horde_Argv_Parser(array('optionList' => $options)); + } + + public function processTuple($option, $opt, $value, $parser, $args) + { + list($len, $type) = $args; + + $this->assertEquals(3, $len); + $this->assertEquals('int', $type); + + if ($opt == '-p') { + $this->assertEquals('1,2,3', $value); + } else if ($option == '--point') { + $this->assertEquals('4,5,6', $value); + } + + $values = explode(',', $value); + foreach ($values as &$value) { + settype($value, $type); + } + + $parser->values->{$option->dest}[] = $values; + } + + public function testCallbackExtraArgs() + { + $this->assertParseOk(array("-p1,2,3", "--point", "4,5,6"), + array('points' => array(array(1,2,3), array(4,5,6))), + array()); + } + +} diff --git a/framework/Argv/test/Horde/Argv/CallbackManyArgsTest.php b/framework/Argv/test/Horde/Argv/CallbackManyArgsTest.php new file mode 100644 index 000000000..f98fca920 --- /dev/null +++ b/framework/Argv/test/Horde/Argv/CallbackManyArgsTest.php @@ -0,0 +1,46 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + * @subpackage UnitTests + */ + +class Horde_Argv_CallbackManyArgsTest extends Horde_Argv_TestBase +{ + public function setUp() + { + $options = array( + $this->makeOption('-a', '--apple', array('action' => 'callback', 'nargs' => 2, + 'callback' => array($this, 'processMany'), 'type' => 'string')), + $this->makeOption('-b', '--bob', array('action' => 'callback', 'nargs' => 3, + 'callback' => array($this, 'processMany'), 'type' => 'int')) + ); + + $this->parser = new Horde_Argv_Parser(array('optionList' => $options)); + } + + public function processMany($option, $opt, $value, $parser_) + { + if ($opt == '-a') { + $this->assertEquals(array('foo', 'bar'), $value); + } else if ($opt == '--apple') { + $this->assertEquals(array('ding', 'dong'), $value); + } else if ($opt == '-b') { + $this->assertEquals(array(1, 2, 3), $value); + } else if ($option == '--bob') { + $this->assertEquals(array(-666, 42, 0), $value); + } + } + + public function testManyArgs() + { + $this->assertParseOk(array("-a", "foo", "bar", "--apple", "ding", "dong", + "-b", "1", "2", "3", "--bob", "-666", "42", + "0"), + array('apple' => null, 'bob' => null), + array()); + } +} diff --git a/framework/Argv/test/Horde/Argv/CallbackMeddleArgsTest.php b/framework/Argv/test/Horde/Argv/CallbackMeddleArgsTest.php new file mode 100644 index 000000000..79800905e --- /dev/null +++ b/framework/Argv/test/Horde/Argv/CallbackMeddleArgsTest.php @@ -0,0 +1,54 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + * @subpackage UnitTests + */ + +class Horde_Argv_CallbackMeddleArgsTest extends Horde_Argv_TestBase +{ + public function setUp() + { + $options = array(); + for ($i = -1; $i > -6; $i--) { + $options[] = $this->makeOption((string)$i, array('action' => 'callback', + 'callback' => array($this, 'process_n'), + 'dest' => 'things')); + } + $this->parser = new Horde_Argv_Parser(array('optionList' => $options)); + } + + /** + * Callback that meddles in rargs, largs + */ + public function process_n($option, $opt, $value, $parser) + { + // option is -3, -5, etc. + $nargs = (int)substr($opt, 1); + $rargs =& $parser->rargs; + if (count($rargs) < $nargs) { + $this->fail(sprintf("Expected %d arguments for %s option.", $nargs, $opt)); + } + + $parser->values->{$option->dest}[] = array_splice($rargs, 0, $nargs); + $parser->largs[] = $nargs; + } + + public function testCallbackMeddleArgs() + { + $this->assertParseOK(array("-1", "foo", "-3", "bar", "baz", "qux"), + array('things' => array(array('foo'), array('bar', 'baz', 'qux'))), + array(1, 3)); + } + + public function testCallbackMeddleArgsSeparator() + { + $this->assertParseOK(array("-2", "foo", "--"), + array('things' => array(array('foo', '--'))), + array(2)); + } + +} diff --git a/framework/Argv/test/Horde/Argv/CallbackTest.php b/framework/Argv/test/Horde/Argv/CallbackTest.php new file mode 100644 index 000000000..ab1dc8c82 --- /dev/null +++ b/framework/Argv/test/Horde/Argv/CallbackTest.php @@ -0,0 +1,75 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + * @subpackage UnitTests + */ + +class Horde_Argv_CallbackTest extends Horde_Argv_TestBase +{ + public function setUp() + { + $options = array( + new Horde_Argv_Option('-x', null, + array('action' => 'callback', 'callback' => array($this, 'processOpt'))), + new Horde_Argv_Option('-f', '--file', + array('action' => 'callback', + 'callback' => array($this, 'processOpt'), + 'type' => 'string', + 'dest' => 'filename')), + ); + + $this->parser = new Horde_Argv_Parser(array('optionList' => $options)); + } + + public function processOpt($option, $opt, $value, $parser_) + { + if ($opt == '-x') { + $this->assertEquals(array('-x'), $option->shortOpts); + $this->assertEquals(array(), $option->longOpts); + $this->assertType(get_class($this->parser), $parser_); + $this->assertNull($value); + $this->assertEquals(array('filename' => null), iterator_to_array($parser_->values)); + + $parser_->values->x = 42; + } else if ($opt == '--file') { + $this->assertEquals(array('-f'), $option->shortOpts); + $this->assertEquals(array('--file'), $option->longOpts); + $this->assertType(gettype($this->parser), $parser_); + $this->assertEquals('foo', $value); + $this->assertEquals(array('filename' => null, 'x' => 42), iterator_to_array($parser_->values)); + + $parser_->values->{$option->dest} = $value; + } else { + $this->fail(sprintf('Unknown option %r in processOpt.', $opt)); + } + } + + public function testCallback() + { + $this->assertParseOk(array('-x', '--file=foo'), + array('filename' => 'foo', 'x' => 42), + array()); + } + + public function testCallbackHelp() + { + // This test was prompted by SF bug #960515 -- the point is + // not to inspect the help text, just to make sure that + // formatHelp() doesn't crash. + $parser = new Horde_Argv_Parser(array('usage' => Horde_Argv_Option::SUPPRESS_USAGE)); + $parser->removeOption('-h'); + $parser->addOption('-t', '--test', + array('action' => 'callback', 'callback' => array($this, 'returnNull'), + 'type' => 'string', 'help' => 'foo')); + + $expectedHelp = "Options:\n -t TEST, --test=TEST foo\n"; + $this->assertHelp($parser, $expectedHelp); + } + + public function returnNull() + {} +} diff --git a/framework/Argv/test/Horde/Argv/CallbackVarArgsTest.php b/framework/Argv/test/Horde/Argv/CallbackVarArgsTest.php new file mode 100644 index 000000000..174ecef71 --- /dev/null +++ b/framework/Argv/test/Horde/Argv/CallbackVarArgsTest.php @@ -0,0 +1,76 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + * @subpackage UnitTests + */ + +class Horde_Argv_CallbackVarArgsTest extends Horde_Argv_TestBase +{ + public function setUp() + { + $options = array( + $this->makeOption('-a', array('type' => 'int', 'nargs' => 2, 'dest' => 'a')), + $this->makeOption('-b', array('action' => 'store_true', 'dest' => 'b')), + $this->makeOption('-c', '--callback', array('action' => 'callback', 'callback' => array($this, 'variableArgs'), 'dest' => 'c')), + ); + $this->parser = new Horde_Argv_InterceptingParser(array('usage' => Horde_Argv_Option::SUPPRESS_USAGE, + 'optionList' => $options)); + } + + public function variableArgs($option, $opt, $value, $parser) + { + $this->assertNull($value); + $done = 0; + $value = array(); + $rargs =& $parser->rargs; + while ($rargs) { + $arg = $rargs[0]; + if ((substr($arg, 0, 2) == '--' && strlen($arg) > 2) || + (substr($arg, 0, 1) == '-' && strlen($arg) > 1 && substr($arg, 1, 1) != '-')) { + break; + } else { + $value[] = $arg; + array_shift($rargs); + } + } + $parser->values->{$option->dest} = $value; + } + + public function testVariableArgs() + { + $this->assertParseOK(array('-a3', '-5', '--callback', 'foo', 'bar'), + array('a' => array(3, -5), 'b' => null, 'c' => array('foo', 'bar')), + array()); + } + + public function testConsumeSeparatorStopAtOption() + { + $this->assertParseOK(array('-c', '37', '--', 'xxx', '-b', 'hello'), + array('a' => null, 'b' => true, 'c' => array('37', '--', 'xxx')), + array('hello')); + } + + public function testPositionalArgAndVariableArgs() + { + $this->assertParseOK(array('hello', '-c', 'foo', '-', 'bar'), + array('a' => null, 'b' => null, 'c' => array('foo', '-', 'bar')), + array('hello')); + } + + public function testStopAtOption() + { + $this->assertParseOK(array('-c', 'foo', '-b'), + array('a' => null, 'b' => true, 'c' => array('foo')), + array()); + } + + public function testStopAtInvalidOption() + { + $this->assertParseFail(array('-c', '3', '-5', '-a'), 'no such option: -5'); + } + +} diff --git a/framework/Argv/test/Horde/Argv/ChoiceTest.php b/framework/Argv/test/Horde/Argv/ChoiceTest.php new file mode 100644 index 000000000..925b3d8fe --- /dev/null +++ b/framework/Argv/test/Horde/Argv/ChoiceTest.php @@ -0,0 +1,41 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + * @subpackage UnitTests + */ + +class Horde_Argv_ChoiceTest extends Horde_Argv_TestBase +{ + public function setUp() + { + $this->parser = new Horde_Argv_InterceptingParser(array('usage' => Horde_Argv_Option::SUPPRESS_USAGE)); + $this->parser->addOption('-c', array('action' => 'store', 'type' => 'choice', + 'dest' => 'choice', 'choices' => array('one', 'two', 'three'))); + } + + public function testValidChoice() + { + $this->assertParseOk(array('-c', 'one', 'xyz'), + array('choice' => 'one'), + array('xyz')); + } + + public function testInvalidChoice() + { + $this->assertParseFail(array('-c', 'four', 'abc'), + "option -c: invalid choice: 'four' " . + "(choose from 'one', 'two', 'three')"); + } + + public function testAddChoiceOption() + { + $this->parser->addOption('-d', '--default', array('choices' => array('four', 'five', 'six'))); + $opt = $this->parser->getOption('-d'); + $this->assertEquals('choice', $opt->type); + $this->assertEquals('store', $opt->action); + } +} diff --git a/framework/Argv/test/Horde/Argv/ConflictOverrideTest.php b/framework/Argv/test/Horde/Argv/ConflictOverrideTest.php new file mode 100644 index 000000000..51943bfcc --- /dev/null +++ b/framework/Argv/test/Horde/Argv/ConflictOverrideTest.php @@ -0,0 +1,47 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + * @subpackage UnitTests + */ + +class Horde_Argv_ConflictOverrideTest extends Horde_Argv_TestBase +{ + public function setUp() + { + $this->parser = new Horde_Argv_InterceptingParser(array('usage' => Horde_Argv_Option::SUPPRESS_USAGE)); + $this->parser->setConflictHandler('resolve'); + $this->parser->addOption('-n', '--dry-run', + array('action' => 'store_true', 'dest' => 'dry_run', + 'help' => "don't do anything")); + $this->parser->addOption('--dry-run', '-n', + array('action' => 'store_const', 'const' => 42, 'dest' => 'dry_run', + 'help' => 'dry run mode')); + } + + public function testConflictOverrideOpts() + { + $opt = $this->parser->getOption('--dry-run'); + + $this->assertEquals(array('-n'), $opt->shortOpts); + $this->assertEquals(array('--dry-run'), $opt->longOpts); + } + + public function testConflictOverrideHelp() + { + $output = "Options:\n" + . " -h, --help show this help message and exit\n" + . " -n, --dry-run dry run mode\n"; + $this->assertOutput(array('-h'), $output); + } + + public function testConflictOverrideArgs() + { + $this->assertParseOk(array('-n'), + array('dry_run' => 42), + array()); + } +} diff --git a/framework/Argv/test/Horde/Argv/ConflictResolveTest.php b/framework/Argv/test/Horde/Argv/ConflictResolveTest.php new file mode 100644 index 000000000..5eb281a18 --- /dev/null +++ b/framework/Argv/test/Horde/Argv/ConflictResolveTest.php @@ -0,0 +1,68 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + * @subpackage UnitTests + */ + +class Horde_Argv_ConflictResolveTest extends Horde_Argv_ConflictTestBase +{ + public function setUp() + { + parent::setUp(); + $this->parser->setConflictHandler('resolve'); + $this->parser->addOption('-v', '--version', array('action' => 'callback', + 'callback' => array($this, 'showVersion'), + 'help' => 'show version')); + } + + public function testConflictResolve() + { + $vOpt = $this->parser->getOption('-v'); + $verboseOpt = $this->parser->getOption('--verbose'); + $versionOpt = $this->parser->getOption('--version'); + + $this->assertSame($vOpt, $versionOpt); + $this->assertNotSame($vOpt, $verboseOpt); + + $this->assertEquals(array('--version'), $vOpt->longOpts); + $this->assertEquals(array('-v'), $versionOpt->shortOpts); + $this->assertEquals(array('--version'), $versionOpt->longOpts); + $this->assertEquals(array(), $verboseOpt->shortOpts); + $this->assertEquals(array('--verbose'), $verboseOpt->longOpts); + } + + public function testConflictResolveHelp() + { + $output = "Options:\n" + . " --verbose increment verbosity\n" + . " -h, --help show this help message and exit\n" + . " -v, --version show version\n"; + + $this->assertOutput(array('-h'), $output); + } + + public function testConflictResolveShortOpt() + { + $this->assertParseOk(array('-v'), + array('verbose' => null, 'showVersion' => 1), + array()); + } + + public function testConflictResolveLongOpt() + { + $this->assertParseOk(array('--verbose'), + array('verbose' => 1), + array()); + } + + public function testConflictResolveLongOpts() + { + $this->assertParseOk(array('--verbose', '--version'), + array('verbose' => 1, 'showVersion' => 1), + array()); + } +} diff --git a/framework/Argv/test/Horde/Argv/ConflictTest.php b/framework/Argv/test/Horde/Argv/ConflictTest.php new file mode 100644 index 000000000..6263839be --- /dev/null +++ b/framework/Argv/test/Horde/Argv/ConflictTest.php @@ -0,0 +1,44 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + * @subpackage UnitTests + */ + +class Horde_Argv_ConflictTest extends Horde_Argv_ConflictTestBase +{ + public function assertConflictError($func) + { + try { + call_user_func($func, '-v', '--version', array( + 'action' => 'callback', + 'callback' => array($this, 'showVersion'), + 'help' => 'show version')); + $this->fail(); + } catch (Horde_Argv_OptionConflictException $e) { + $this->assertEquals("option -v/--version: conflicting option string(s): -v", + $e->getMessage()); + $this->assertEquals('-v/--version', $e->optionId); + } + } + + public function testConflictError() + { + $this->assertConflictError(array($this->parser, 'addOption')); + } + + public function testConflictErrorGroup() + { + $group = new Horde_Argv_OptionGroup($this->parser, 'Group 1'); + $this->assertConflictError(array($group, 'addOption')); + } + + public function testNoSuchConflictHandler() + { + $this->assertRaises(array($this->parser, 'setConflictHandler'), array('foo'), 'InvalidArgumentException', "invalid conflictHandler 'foo'"); + } + +} diff --git a/framework/Argv/test/Horde/Argv/ConflictTestBase.php b/framework/Argv/test/Horde/Argv/ConflictTestBase.php new file mode 100644 index 000000000..769e66b9e --- /dev/null +++ b/framework/Argv/test/Horde/Argv/ConflictTestBase.php @@ -0,0 +1,31 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + * @subpackage UnitTests + */ + +class Horde_Argv_ConflictTestBase extends Horde_Argv_TestBase +{ + public function setUp() + { + $options = array(new Horde_Argv_Option('-v', '--verbose', array( + 'action' => 'count', + 'dest' => 'verbose', + 'help' => 'increment verbosity')) + ); + + $this->parser = new Horde_Argv_InterceptingParser( + array('usage' => Horde_Argv_Option::SUPPRESS_USAGE, 'optionList' => $options) + ); + } + + public function showVersion($option, $opt, $value, $parser) + { + $this->parser->values->showVersion = 1; + } + +} diff --git a/framework/Argv/test/Horde/Argv/ConflictingDefaultsTest.php b/framework/Argv/test/Horde/Argv/ConflictingDefaultsTest.php new file mode 100644 index 000000000..12361546a --- /dev/null +++ b/framework/Argv/test/Horde/Argv/ConflictingDefaultsTest.php @@ -0,0 +1,40 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + * @subpackage UnitTests + */ + +/** + * Conflicting default values: the last one should win. + */ +class Horde_Argv_ConflictingDefaultsTest extends Horde_Argv_TestBase +{ + public function setUp() + { + $options = array( + $this->makeOption('-v', array('action' => 'store_true', 'dest' => 'verbose', 'default' => 1)) + ); + + $this->parser = new Horde_Argv_Parser(array('optionList' => $options)); + } + + public function testConflictDefault() + { + $this->parser->addOption('-q', array('action' => 'store_false', 'dest' => 'verbose', + 'default' => 0)); + + $this->assertParseOk(array(), array('verbose' => 0), array()); + } + + public function testConflictDefaultNone() + { + $this->parser->addOption('-q', array('action' => 'store_false', 'dest' => 'verbose', + 'default' => null)); + + $this->assertParseOk(array(), array('verbose' => null), array()); + } +} diff --git a/framework/Argv/test/Horde/Argv/CountTest.php b/framework/Argv/test/Horde/Argv/CountTest.php new file mode 100644 index 000000000..293adaa18 --- /dev/null +++ b/framework/Argv/test/Horde/Argv/CountTest.php @@ -0,0 +1,97 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + * @subpackage UnitTests + */ + +class Horde_Argv_CountTest extends Horde_Argv_TestBase +{ + public function setUp() + { + $this->parser = new Horde_Argv_InterceptingParser(array('usage' => Horde_Argv_Option::SUPPRESS_USAGE)); + $this->vOpt = $this->makeOption('-v', array('action' => 'count', 'dest' => 'verbose')); + $this->parser->addOption($this->vOpt); + $this->parser->addOption('--verbose', array('type' => 'int', 'dest' => 'verbose')); + $this->parser->addOption('-q', '--quiet', + array('action' => 'store_const', 'dest' => 'verbose', 'const' => 0)); + } + + public function testEmpty() + { + $this->assertParseOk(array(), array('verbose' => null), array()); + } + + public function testCountOne() + { + $this->assertParseOk(array('-v'), array('verbose' => 1), array()); + } + + public function testCountThree() + { + $this->assertParseOk(array('-vvv'), array('verbose' => 3), array()); + } + + public function testCountThreeApart() + { + $this->assertParseOk(array('-v', '-v', '-v'), array('verbose' => 3), array()); + } + + public function testCountOverrideAmount() + { + $this->assertParseOk(array('-vvv', '--verbose=2'), array('verbose' => 2), array()); + } + + public function testCountOverrideQuiet() + { + $this->assertParseOk(array('-vvv', '--verbose=2', '-q'), array('verbose' => 0), array()); + } + + public function testCountOverriding() + { + $this->assertParseOk(array('-vvv', '--verbose=2', '-q', '-v'), + array('verbose' => 1), array()); + } + + public function testCountInterspersedArgs() + { + $this->assertParseOk(array('--quiet', '3', '-v'), + array('verbose' => 1), + array('3')); + } + + public function testCountNoInterspersedArgs() + { + $this->parser->disableInterspersedArgs(); + $this->assertParseOk(array('--quiet', '3', '-v'), + array('verbose' => 0), + array('3', '-v')); + } + + public function testCountNoSuchOption() + { + $this->assertParseFail(array('-q3', '-v'), 'no such option: -3'); + } + + public function testCountOptionNoValue() + { + $this->assertParseFail(array('--quiet=3', 'v'), + '--quiet option does not take a value'); + } + + public function testCountWithDefault() + { + $this->parser->setDefault('verbose', 0); + $this->assertParseOk(array(), array('verbose' => 0), array()); + } + + public function testCountOverridingDefault() + { + $this->parser->setDefault('verbose', 0); + $this->assertParseOk(array('-vvv', '--verbose=2', '-q', '-v'), + array('verbose' => 1), array()); + } +} diff --git a/framework/Argv/test/Horde/Argv/DefaultValuesTest.php b/framework/Argv/test/Horde/Argv/DefaultValuesTest.php new file mode 100644 index 000000000..3ea1c4e20 --- /dev/null +++ b/framework/Argv/test/Horde/Argv/DefaultValuesTest.php @@ -0,0 +1,99 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + * @subpackage UnitTests + */ + +class Horde_Argv_DefaultValuesTest extends Horde_Argv_TestBase +{ + public function setUp() + { + $this->parser = new Horde_Argv_Parser(); + $this->parser->addOption('-v', '--verbose', array('default' => true)); + $this->parser->addOption('-q', '--quiet', array('dest' => 'verbose')); + $this->parser->addOption('-n', array('type' => 'int', 'default' => 37)); + $this->parser->addOption('-m', array('type' => 'int')); + $this->parser->addOption('-s', array('default' => 'foo')); + $this->parser->addOption('-t'); + $this->parser->addOption('-u', array('default' => null)); + + $this->expected = array('verbose' => true, + 'n' => 37, + 'm' => null, + 's' => 'foo', + 't' => null, + 'u' => null); + } + + public function testBasicDefault() + { + $this->assertEquals($this->expected, iterator_to_array($this->parser->getDefaultValues())); + } + + public function testMixedDefaultsPost() + { + $this->parser->setDefaults(array('n' => 42, 'm' => -100)); + $this->expected = array_merge($this->expected, array('n' => 42, 'm' => -100)); + $this->assertEquals($this->expected, iterator_to_array($this->parser->getDefaultValues())); + } + + public function testMixedDefaultsPre() + { + $this->parser->setDefaults(array('x' => 'barf', 'y' => 'blah')); + $this->parser->addOption('-x', array('default' => 'frob')); + $this->parser->addOption('-y'); + + $this->expected = array_merge($this->expected, array('x' => 'frob', 'y' => 'blah')); + $this->assertEquals($this->expected, iterator_to_array($this->parser->getDefaultValues())); + + $this->parser->removeOption('-y'); + $this->parser->addOption('-y', array('default' => null)); + $this->expected = array_merge($this->expected, array('y' => null)); + $this->assertEquals($this->expected, iterator_to_array($this->parser->getDefaultValues())); + } + + public function testProcessDefault() + { + $this->parser->optionClass = 'Horde_Argv_DurationOption'; + $this->parser->addOption('-d', array('type' => 'duration', 'default' => 300)); + $this->parser->addOption('-e', array('type' => 'duration', 'default' => '6m')); + $this->parser->setDefaults(array('n' => '42')); + + $this->expected = array_merge($this->expected, array('d' => 300, 'e' => 360, 'n' => '42')); + $this->assertEquals($this->expected, iterator_to_array($this->parser->getDefaultValues())); + } +} + +class Horde_Argv_DurationOption extends Horde_Argv_Option +{ + public $TYPES = array('string', 'int', 'long', 'float', 'complex', 'choice', 'duration'); + + public $TYPE_CHECKER = array('int' => 'checkBuiltin', + 'long' => 'checkBuiltin', + 'float' => 'checkBuiltin', + 'complex'=> 'checkBuiltin', + 'choice' => 'checkChoice', + 'duration' => 'checkDuration', + ); + + public function checkDuration($opt, $value) + { + // Custom type for testing processing of default values. + $time_units = array('s' => 1, 'm' => 60, 'h' => 60 * 60, 'd' => 60 * 60 * 24); + + $last = substr($value, -1); + if (is_numeric($last)) { + return (int)$value; + } elseif (isset($time_units[$last])) { + return (int)substr($value, 0, -1) * $time_units[$last]; + } else { + throw new Horde_Argv_OptionValueException(sprintf( + 'option %s: invalid duration: %s', $opt, $value)); + } + } + +} diff --git a/framework/Argv/test/Horde/Argv/ExpandDefaultsTest.php b/framework/Argv/test/Horde/Argv/ExpandDefaultsTest.php new file mode 100644 index 000000000..ba8e96ea1 --- /dev/null +++ b/framework/Argv/test/Horde/Argv/ExpandDefaultsTest.php @@ -0,0 +1,104 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + * @subpackage UnitTests + */ + +class Horde_Argv_ExpandDefaultsTest extends Horde_Argv_TestBase +{ + public function setUp() + { + $this->parser = new Horde_Argv_Parser(array('prog' => 'test')); + $this->help_prefix = 'Usage: test [options] + +Options: + -h, --help show this help message and exit'; + + $this->file_help = "read from FILE [default: %default]"; + $this->expected_help_file = $this->help_prefix . "\n" . + " -f FILE, --file=FILE read from FILE [default: foo.txt]\n"; + $this->expected_help_none = $this->help_prefix . "\n" . + " -f FILE, --file=FILE read from FILE [default: none]\n"; + } + + public function testOptionDefault() + { + $this->parser->addOption("-f", "--file", array('default' => 'foo.txt', 'help' => $this->file_help)); + $this->assertHelp($this->parser, $this->expected_help_file); + } + + public function testParserDefault1() + { + $this->parser->addOption("-f", "--file", + array('help' => $this->file_help)); + $this->parser->setDefault('file', "foo.txt"); + $this->assertHelp($this->parser, $this->expected_help_file); + } + + public function testParserDefault2() + { + $this->parser->addOption("-f", "--file", + array('help' => $this->file_help)); + $this->parser->setDefaults(array('file' => 'foo.txt')); + $this->assertHelp($this->parser, $this->expected_help_file); + } + + public function testNoDefault() + { + $this->parser->addOption("-f", "--file", + array('help' => $this->file_help)); + $this->assertHelp($this->parser, $this->expected_help_none); + } + + public function testDefaultNone1() + { + $this->parser->addOption("-f", "--file", + array('default' => null, + 'help' => $this->file_help)); + $this->assertHelp($this->parser, $this->expected_help_none); + } + + public function testDefaultNone2() + { + $this->parser->addOption("-f", "--file", + array('help' => $this->file_help)); + $this->parser->setDefaults(array('file' => null)); + $this->assertHelp($this->parser, $this->expected_help_none); + } + + public function testFloatDefault() + { + $this->parser->addOption( + "-p", "--prob", + array('help' => "blow up with probability PROB [default: %default]")); + $this->parser->setDefaults(array('prob' => 0.43)); + $expected_help = $this->help_prefix . "\n" . + " -p PROB, --prob=PROB blow up with probability PROB [default: 0.43]\n"; + $this->assertHelp($this->parser, $expected_help); + } + + public function testAltExpand() + { + $this->parser->addOption("-f", "--file", + array('default' => "foo.txt", + 'help' => "read from FILE [default: *DEFAULT*]")); + $this->parser->formatter->default_tag = "*DEFAULT*"; + $this->assertHelp($this->parser, $this->expected_help_file); + } + + public function testNoExpand() + { + $this->parser->addOption("-f", "--file", + array('default' => "foo.txt", + 'help' => "read from %default file")); + $this->parser->formatter->default_tag = null; + $expected_help = $this->help_prefix . "\n" . + " -f FILE, --file=FILE read from %default file\n"; + $this->assertHelp($this->parser, $expected_help); + } + +} diff --git a/framework/Argv/test/Horde/Argv/ExtendAddActionsTest.php b/framework/Argv/test/Horde/Argv/ExtendAddActionsTest.php new file mode 100644 index 000000000..59623e11b --- /dev/null +++ b/framework/Argv/test/Horde/Argv/ExtendAddActionsTest.php @@ -0,0 +1,77 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + * @subpackage UnitTests + */ + +class Horde_Argv_ExtendAddActionsTest extends Horde_Argv_TestBase +{ + public function setUp() + { + $options = array(new Horde_Argv_ExtendAddActionsTest_MyOption("-a", "--apple", array( + 'action' => "extend", 'type' => "string", 'dest' => "apple"))); + $this->parser = new Horde_Argv_Parser(array('optionList' => $options)); + } + + public function testExtendAddAction() + { + $this->assertParseOK(array("-afoo,bar", "--apple=blah"), + array('apple' => array("foo", "bar", "blah")), + array()); + } + + public function testExtendAddActionNormal() + { + $this->assertParseOK(array("-a", "foo", "-abar", "--apple=x,y"), + array('apple' => array("foo", "bar", "x", "y")), + array()); + } + +} + +class Horde_Argv_ExtendAddActionsTest_MyOption extends Horde_Argv_Option +{ + public $ACTIONS = array("store", + "store_const", + "store_true", + "store_false", + "append", + "append_const", + "count", + "callback", + "help", + "version", + "extend", + ); + + public $STORE_ACTIONS = array("store", + "store_const", + "store_true", + "store_false", + "append", + "append_const", + "count", + "extend", + ); + + public $TYPED_ACTIONS = array("store", + "append", + "callback", + "extend", + ); + + public function takeAction($action, $dest, $opt, $value, $values, $parser) + { + if ($action == "extend") { + $lvalue = explode(',', $value); + $values->$dest = array_merge($values->ensureValue($dest, array()), $lvalue); + } else { + parent::takeAction($action, $dest, $opt, $parser, $value, $values); + } + } + +} diff --git a/framework/Argv/test/Horde/Argv/ExtendAddTypesTest.php b/framework/Argv/test/Horde/Argv/ExtendAddTypesTest.php new file mode 100644 index 000000000..ef3a71e5c --- /dev/null +++ b/framework/Argv/test/Horde/Argv/ExtendAddTypesTest.php @@ -0,0 +1,80 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + * @subpackage UnitTests + */ + +class Horde_Argv_ExtendAddTypesTest extends Horde_Argv_TestBase +{ + public function setUp() + { + $this->parser = new Horde_Argv_InterceptingParser(array('usage' => Horde_Argv_Option::SUPPRESS_USAGE, + 'optionClass' => 'Horde_Argv_ExtendAddTypesTest_MyOption')); + $this->parser->addOption("-a", null, array('type' => "string", 'dest' => "a")); + $this->parser->addOption("-f", "--file", array('type' => "file", 'dest' => "file")); + + /* @todo make more system independent */ + $this->testPath = tempnam('/tmp', 'horde_argv'); + } + + public function tearDown() + { + if (!is_link($this->testPath) && is_dir($this->testPath)) { + rmdir($this->testPath); + } elseif (is_file($this->testPath)) { + unlink($this->testPath); + } + } + + public function testFiletypeOk() + { + touch($this->testPath); + $this->assertParseOK(array("--file", $this->testPath, "-afoo"), + array('file' => $this->testPath, 'a' => 'foo'), + array()); + } + + public function testFiletypeNoexist() + { + unlink($this->testPath); + $this->assertParseFail(array("--file", $this->testPath, "-afoo"), + sprintf("%s: file does not exist", $this->testPath)); + } + + public function testFiletypeNotfile() + { + unlink($this->testPath); + mkdir($this->testPath); + $this->assertParseFail(array("--file", $this->testPath, "-afoo"), + sprintf("%s: not a regular file", $this->testPath)); + } + +} + +class Horde_Argv_ExtendAddTypesTest_MyOption extends Horde_Argv_Option +{ + public $TYPES = array('string', 'int', 'long', 'float', 'complex', 'choice', 'file'); + + public $TYPE_CHECKER = array("int" => 'checkBuiltin', + "long" => 'checkBuiltin', + "float" => 'checkBuiltin', + "complex"=> 'checkBuiltin', + "choice" => 'checkChoice', + 'file' => 'checkFile', + ); + + public function checkFile($opt, $value) + { + if (!file_exists($value)) { + throw new Horde_Argv_OptionValueException(sprintf("%s: file does not exist", $value)); + } elseif (!is_file($value)) { + throw new Horde_Argv_OptionValueException(sprintf("%s: not a regular file", $value)); + } + return $value; + } + +} diff --git a/framework/Argv/test/Horde/Argv/HelpTest.php b/framework/Argv/test/Horde/Argv/HelpTest.php new file mode 100644 index 000000000..ef3378258 --- /dev/null +++ b/framework/Argv/test/Horde/Argv/HelpTest.php @@ -0,0 +1,202 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + * @subpackage UnitTests + */ + +class Horde_Argv_HelpTest extends Horde_Argv_TestBase +{ + + public static $expected_help_basic = 'Usage: bar.php [options] + +Options: + -a APPLE throw APPLEs at basket + -b NUM, --boo=NUM shout "boo!" NUM times (in order to frighten away all the + evil spirits that cause trouble and mayhem) + --foo=FOO store FOO in the foo list for later fooing + -h, --help show this help message and exit +'; + + public static $expected_help_long_opts_first = 'Usage: bar.php [options] + +Options: + -a APPLE throw APPLEs at basket + --boo=NUM, -b NUM shout "boo!" NUM times (in order to frighten away all the + evil spirits that cause trouble and mayhem) + --foo=FOO store FOO in the foo list for later fooing + --help, -h show this help message and exit +'; + + public static $expected_help_title_formatter = 'Usage +===== + bar.php [options] + +Options +======= +-a APPLE throw APPLEs at basket +--boo=NUM, -b NUM shout "boo!" NUM times (in order to frighten away all the + evil spirits that cause trouble and mayhem) +--foo=FOO store FOO in the foo list for later fooing +--help, -h show this help message and exit +'; + + public static $expected_help_short_lines = 'Usage: bar.php [options] + +Options: + -a APPLE throw APPLEs at basket + -b NUM, --boo=NUM shout "boo!" NUM times (in order to + frighten away all the evil spirits + that cause trouble and mayhem) + --foo=FOO store FOO in the foo list for later + fooing + -h, --help show this help message and exit +'; + + public function setUp() + { + $this->parser = $this->makeParser(80); + $this->origColumns = isset($_ENV['COLUMNS']) ? $_ENV['COLUMNS'] : null; + } + + public function tearDown() + { + if (is_null($this->origColumns)) { + unset($_ENV['COLUMNS']); + } else { + $_ENV['COLUMNS'] = $this->origColumns; + } + } + + public function makeParser($columns) + { + $options = array( + $this->makeOption("-a", array('type' => "string", 'dest' => 'a', + 'metavar' => "APPLE", 'help' => "throw APPLEs at basket")), + + $this->makeOption("-b", "--boo", array('type' => "int", 'dest' => 'boo', + 'metavar' => "NUM", + 'help' => "shout \"boo!\" NUM times (in order to frighten away " . + "all the evil spirits that cause trouble and mayhem)")), + + $this->makeOption("--foo", array('action' => 'append', 'type' => 'string', 'dest' => 'foo', + 'help' => "store FOO in the foo list for later fooing")), + ); + + $_ENV['COLUMNS'] = $columns; + + return new Horde_Argv_InterceptingParser(array('optionList' => $options)); + } + + public function assertHelpEquals($expectedOutput) + { + // @todo + // if type(expected_output) is types.UnicodeType: + // encoding = self.parser._get_encoding(sys.stdout) + // expected_output = expected_output.encode(encoding, "replace") + + $origArgv = $_SERVER['argv']; + $_SERVER['argv'][0] = 'foo/bar.php'; + $this->assertOutput(array('-h'), $expectedOutput); + + $_SERVER['argv'] = $origArgv; + } + + public function testHelp() + { + $this->assertHelpEquals(self::$expected_help_basic); + } + + public function tesHelpOldUsage() + { + $this->parser->setUsage("Usage: %prog [options]"); + $this->assertHelpEquals(self::$expected_help_basic); + } + + public function testHelpLongOptsFirst() + { + $this->parser->formatter->short_first = false; + $this->assertHelpEquals(self::$expected_help_long_opts_first); + } + + public function testHelpTitleFormatter() + { + $this->parser->formatter = new Horde_Argv_TitledHelpFormatter(); + $this->assertHelpEquals(self::$expected_help_title_formatter); + } + + public function testWrapColumns() + { + // Ensure that wrapping respects $COLUMNS environment variable. + // Need to reconstruct the parser, since that's the only time + // we look at $COLUMNS. + $this->parser = $this->makeParser(60); + $this->assertHelpEquals(self::$expected_help_short_lines); + } + + public function testHelpDescriptionGroups() + { + $this->parser->setDescription( + "This is the program description for %prog. %prog has " . + "an option group as well as single options."); + + $group = new Horde_Argv_OptionGroup( + $this->parser, "Dangerous Options", + "Caution: use of these options is at your own risk. " . + "It is believed that some of them bite."); + $group->addOption("-g", array('action' => "store_true", 'help' => "Group option.")); + $this->parser->addOptionGroup($group); + + $expect = 'Usage: bar.php [options] + +This is the program description for bar.php. bar.php has an option group as +well as single options. + +Options: + -a APPLE throw APPLEs at basket + -b NUM, --boo=NUM shout "boo!" NUM times (in order to frighten away all the + evil spirits that cause trouble and mayhem) + --foo=FOO store FOO in the foo list for later fooing + -h, --help show this help message and exit + + Dangerous Options: + Caution: use of these options is at your own risk. It is believed + that some of them bite. + + -g Group option. +'; + + $this->assertHelpEquals($expect); + + $this->parser->epilog = "Please report bugs to /dev/null."; + $this->assertHelpEquals($expect . "\nPlease report bugs to /dev/null.\n"); + } + + /* @todo + def test_help_unicode(self): + self.parser = Horde_Argv_InterceptingParser(usage=Horde_Argv_Option::SUPPRESS_USAGE) + self.parser.addOption("-a", action="store_true", help=u"ol\u00E9!") + expect = u"""\ +Options: + -h, --help show this help message and exit + -a ol\u00E9! +""" + self.assertHelpEquals(expect) + + def test_help_unicode_description(self): + self.parser = Horde_Argv_InterceptingParser(usage=Horde_Argv_Option::SUPPRESS_USAGE, + description=u"ol\u00E9!") + expect = u"""\ +ol\u00E9! + +Options: + -h, --help show this help message and exit +""" + self.assertHelpEquals(expect) + + */ + +} diff --git a/framework/Argv/test/Horde/Argv/InterceptedException.php b/framework/Argv/test/Horde/Argv/InterceptedException.php new file mode 100644 index 000000000..4f4464680 --- /dev/null +++ b/framework/Argv/test/Horde/Argv/InterceptedException.php @@ -0,0 +1,29 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + * @subpackage UnitTests + */ + +class Horde_Argv_InterceptedException extends Exception +{ + public function __construct($error_message = null, $exit_status = null, $exit_message = null) + { + $this->error_message = $error_message; + $this->exit_status = $exit_status; + $this->exit_message = $exit_message; + } + + public function __toString() + { + if ($this->error_message) + return $this->error_message; + if ($this->exit_message) + return $this->exit_message; + return "intercepted error"; + } + +} diff --git a/framework/Argv/test/Horde/Argv/InterceptingParser.php b/framework/Argv/test/Horde/Argv/InterceptingParser.php new file mode 100644 index 000000000..2878d19fa --- /dev/null +++ b/framework/Argv/test/Horde/Argv/InterceptingParser.php @@ -0,0 +1,23 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + * @subpackage UnitTests + */ + +class Horde_Argv_InterceptingParser extends Horde_Argv_Parser +{ + public function parserExit($status = 0, $msg = null) + { + throw new Horde_Argv_InterceptedException(null, $status, $msg); + } + + public function parserError($msg) + { + throw new Horde_Argv_InterceptedException($msg); + } + +} diff --git a/framework/Argv/test/Horde/Argv/MatchAbbrevTest.php b/framework/Argv/test/Horde/Argv/MatchAbbrevTest.php new file mode 100644 index 000000000..e5312366a --- /dev/null +++ b/framework/Argv/test/Horde/Argv/MatchAbbrevTest.php @@ -0,0 +1,36 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + * @subpackage UnitTests + */ + +class Horde_Argv_MatchAbbrevTest extends Horde_Argv_TestBase +{ + public function testMatchAbbrev() + { + $this->assertEquals(Horde_Argv_Parser::matchAbbrev("--f", + array("--foz" => null, + "--foo" => null, + "--fie" => null, + "--f" => null)), + '--f'); + } + + public function testMatchAbbrevError() + { + $s = '--f'; + $wordmap = array("--foz" => null, "--foo" => null, "--fie" => null); + + try { + Horde_Argv_Parser::matchAbbrev($s, $wordmap); + $this->fail(); + } catch (Horde_Argv_BadOptionException $e) { + $this->assertEquals("ambiguous option: --f (--fie, --foo, --foz?)", + $e->getMessage()); + } + } +} diff --git a/framework/Argv/test/Horde/Argv/MultipleArgsAppendTest.php b/framework/Argv/test/Horde/Argv/MultipleArgsAppendTest.php new file mode 100644 index 000000000..2ac1a31d6 --- /dev/null +++ b/framework/Argv/test/Horde/Argv/MultipleArgsAppendTest.php @@ -0,0 +1,51 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + * @subpackage UnitTests + */ + +class Horde_Argv_MultipleArgsAppendTest extends Horde_Argv_TestBase +{ + public function setUp() + { + $this->parser = new Horde_Argv_InterceptingParser(array('usage' => Horde_Argv_Option::SUPPRESS_USAGE)); + $this->parser->addOption("-p", "--point", array( + 'action' => "store", 'nargs' => 3, 'type' => 'float', 'dest' => 'point')); + $this->parser->addOption("-f", "--foo", array( + 'action' => "append", 'nargs' => 2, 'type' => "int", 'dest' => "foo")); + $this->parser->addOption("-z", "--zero", array( + 'action' => "append_const", 'dest' => "foo", 'const' => array(0, 0))); + } + + public function testNargsAppend() + { + $this->assertParseOK(array("-f", "4", "-3", "blah", "--foo", "1", "666"), + array('point' => null, 'foo' => array(array(4, -3), array(1, 666))), + array('blah')); + } + + public function testNargsAppendRequiredValues() + { + $this->assertParseFail(array("-f4,3"), + "-f option requires 2 arguments"); + } + + public function testNargsAppendSimple() + { + $this->assertParseOK(array("--foo=3", "4"), + array('point' => null, 'foo' => array(array(3, 4))), + array()); + } + + public function testNargsAppendConst() + { + $this->assertParseOK(array("--zero", "--foo", "3", "4", "-z"), + array('point' => null, 'foo' => array(array(0, 0), array(3, 4), array(0, 0))), + array()); + } + +} diff --git a/framework/Argv/test/Horde/Argv/MultipleArgsTest.php b/framework/Argv/test/Horde/Argv/MultipleArgsTest.php new file mode 100644 index 000000000..1b7a083c1 --- /dev/null +++ b/framework/Argv/test/Horde/Argv/MultipleArgsTest.php @@ -0,0 +1,46 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + * @subpackage UnitTests + */ + +class Horde_Argv_MultipleArgsTest extends Horde_Argv_TestBase +{ + public function setUp() + { + $this->parser = new Horde_Argv_InterceptingParser(array('usage' => Horde_Argv_Option::SUPPRESS_USAGE)); + $this->parser->addOption("-p", "--point", + array('action' => "store", 'nargs' => 3, 'type' => "float", 'dest' => "point")); + } + + public function testNargsWithPositionalArgs() + { + $this->assertParseOK(array("foo", "-p", "1", "2.5", "-4.3", "xyz"), + array('point' => array(1.0, 2.5, -4.3)), + array('foo', 'xyz')); + } + + public function testNargsLongOpt() + { + $this->assertParseOK(array("--point", "-1", "2.5", "-0", "xyz"), + array('point' => array(-1.0, 2.5, -0.0)), + array("xyz")); + } + + public function testNargsInvalidFloatValue() + { + $this->assertParseFail(array("-p", "1.0", "2x", "3.5"), + "option -p: invalid floating-point value: '2x'"); + } + + public function testNargsRequiredValues() + { + $this->assertParseFail(array("--point", "1.0", "3.5"), + "--point option requires 3 arguments"); + } + +} diff --git a/framework/Argv/test/Horde/Argv/OptionChecksTest.php b/framework/Argv/test/Horde/Argv/OptionChecksTest.php new file mode 100644 index 000000000..f2e6b17b1 --- /dev/null +++ b/framework/Argv/test/Horde/Argv/OptionChecksTest.php @@ -0,0 +1,165 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + * @subpackage UnitTests + */ + +class Horde_Argv_OptionChecksTest extends Horde_Argv_TestBase +{ + public function setUp() + { + $this->parser = new Horde_Argv_Parser(array('usage' => Horde_Argv_Option::SUPPRESS_USAGE)); + } + + public function assertOptionError($expected_message, $args) + { + $this->assertRaises(array($this, 'makeOption'), $args, 'Horde_Argv_OptionException', $expected_message); + } + + public function testOptStringEmpty() + { + try { + new Horde_Argv_Option(); + } catch (Exception $e) { + $this->assertType('InvalidArgumentException', $e); + $this->assertEquals("at least one option string must be supplied", $e->getMessage()); + return true; + } + + $this->fail("InvalidArgumentException for no option strings not thrown"); + } + + public function testOptStringTooShort() + { + $this->assertOptionError( + "invalid option string 'b': must be at least two characters long", + array("b")); + } + + public function testOptStringShortInvalid() + { + $this->assertOptionError( + "invalid short option string '--': must be " . + "of the form -x, (x any non-dash char)", + array("--")); + } + + public function testOptStringLongInvalid() + { + $this->assertOptionError( + "invalid long option string '---': " . + "must start with --, followed by non-dash", + array("---")); + } + + public function testAttrInvalid() + { + $this->assertOptionError( + "option -b: invalid keyword arguments: bar, foo", + array("-b", array('foo' => null, 'bar' => null))); + } + + public function testActionInvalid() + { + $this->assertOptionError( + "option -b: invalid action: 'foo'", + array("-b", array('action' => 'foo'))); + } + + public function testTypeInvalid() + { + $this->assertOptionError( + "option -b: invalid option type: 'foo'", + array("-b", array('type' => 'foo'))); + $this->assertOptionError( + "option -b: invalid option type: 'Array'", + array("-b", array('type' => array()))); + } + + public function testNoTypeForAction() + { + $this->assertOptionError( + "option -b: must not supply a type for action 'count'", + array("-b", array('action' => 'count', 'type' => 'int'))); + } + + public function testNoChoicesList() + { + $this->assertOptionError( + "option -b/--bad: must supply a list of " . + "choices for type 'choice'", + array("-b", "--bad", array('type' => "choice"))); + } + + public function testBadChoicesList() + { + $typename = gettype(''); + $this->assertOptionError( + sprintf("option -b/--bad: choices must be a list of " . + "strings ('%s' supplied)", $typename), + array("-b", "--bad", array('type' => 'choice', 'choices' => 'bad choices'))); + } + + public function testNoChoicesForType() + { + $this->assertOptionError( + "option -b: must not supply choices for type 'int'", + array("-b", array('type' => 'int', 'choices' => "bad"))); + } + + public function testNoConstForAction() + { + $this->assertOptionError( + "option -b: 'const' must not be supplied for action 'store'", + array("-b", array('action' => 'store', 'const' => 1))); + } + + public function testNoNargsForAction() + { + $this->assertOptionError( + "option -b: 'nargs' must not be supplied for action 'count'", + array("-b", array('action' => 'count', 'nargs' => 2))); + } + + public function testCallbackNotCallable() + { + $this->assertOptionError( + "option -b: callback not callable: 'foo'", + array("-b", array('action' => 'callback', 'callback' => 'foo'))); + } + + public function dummy() + { + } + + public function testCallbackArgsNoArray() + { + $this->assertOptionError( + "option -b: callbackArgs, if supplied, " . + "must be an array: not 'foo'", + array("-b", array('action' => 'callback', + 'callback' => array($this, 'dummy'), + 'callbackArgs' => 'foo'))); + } + + public function testNoCallbackForAction() + { + $this->assertOptionError( + "option -b: callback supplied ('foo') for non-callback option", + array("-b", array('action' => 'store', + 'callback' => 'foo'))); + } + + public function testNoCallbackArgsForAction() + { + $this->assertOptionError( + "option -b: callbackArgs supplied for non-callback option", + array("-b", array('action' => 'store', + 'callbackArgs' => 'foo'))); + } + +} diff --git a/framework/Argv/test/Horde/Argv/OptionGroupTest.php b/framework/Argv/test/Horde/Argv/OptionGroupTest.php new file mode 100644 index 000000000..447321c04 --- /dev/null +++ b/framework/Argv/test/Horde/Argv/OptionGroupTest.php @@ -0,0 +1,56 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + * @subpackage UnitTests + */ + +class Horde_Argv_OptionGroupTest extends Horde_Argv_TestBase +{ + public function setUp() + { + $this->parser = new Horde_Argv_Parser(array('usage' => Horde_Argv_Option::SUPPRESS_USAGE)); + } + + public function testOptionGroupCreateInstance() + { + $group = new Horde_Argv_OptionGroup($this->parser, "Spam"); + $this->parser->addOptionGroup($group); + $group->addOption("--spam", array('action' => "store_true", + 'help' => "spam spam spam spam")); + $this->assertParseOK(array("--spam"), array('spam' => true), array()); + } + + public function testAddGroupNoGroup() + { + $this->assertTypeError(array($this->parser, 'addOptionGroup'), + "not an OptionGroup instance: NULL", array(null)); + } + + public function testAddGroupInvalidArguments() + { + $this->assertTypeError(array($this->parser, 'addOptionGroup'), + "invalid arguments", null); + } + + public function testAddGroupWrongParser() + { + $group = new Horde_Argv_OptionGroup($this->parser, "Spam"); + $group->parser = new Horde_Argv_Parser(); + $this->assertRaises(array($this->parser, 'addOptionGroup'), array($group), + 'InvalidArgumentException', "invalid OptionGroup (wrong parser)"); + } + + public function testGroupManipulate() + { + $group = $this->parser->addOptionGroup("Group 2", + array('description' => "Some more options")); + $group->setTitle("Bacon"); + $group->addOption("--bacon", array('type' => "int")); + $this->assertSame($group, $this->parser->getOptionGroup("--bacon")); + } + +} diff --git a/framework/Argv/test/Horde/Argv/OptionValuesTest.php b/framework/Argv/test/Horde/Argv/OptionValuesTest.php new file mode 100644 index 000000000..caa844667 --- /dev/null +++ b/framework/Argv/test/Horde/Argv/OptionValuesTest.php @@ -0,0 +1,27 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + * @subpackage UnitTests + */ + +class Horde_Argv_OptionValuesTest extends Horde_Argv_TestBase +{ + public function testBasics() + { + $values = new Horde_Argv_Values(); + $this->assertEquals(iterator_to_array($values), array()); + $this->assertNotEquals($values, array('foo' => 'bar')); + $this->assertNotEquals($values, ''); + + $dict = array('foo' => 'bar', 'baz' => 42); + $values = new Horde_Argv_Values($dict); + $this->assertEquals($dict, iterator_to_array($values)); + $this->assertNotEquals($values, array('foo' => 'bar')); + $this->assertNotEquals($values, array()); + $this->assertNotEquals($values, ""); + } +} diff --git a/framework/Argv/test/Horde/Argv/ParseNumTest.php b/framework/Argv/test/Horde/Argv/ParseNumTest.php new file mode 100644 index 000000000..3a7c024fb --- /dev/null +++ b/framework/Argv/test/Horde/Argv/ParseNumTest.php @@ -0,0 +1,59 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + * @subpackage UnitTests + */ + +class Horde_Argv_ParseNumTest extends Horde_Argv_TestBase +{ + public function setUp() + { + $this->parser = new Horde_Argv_InterceptingParser(); + $this->parser->addOption('-n', array('type' => 'int')); + $this->parser->addOption('-l', array('type' => 'long')); + } + + public function testParseNumFail() + { + $this->assertFalse(Horde_Argv_Option::parseNumber('')); + $this->assertFalse(Horde_Argv_Option::parseNumber("0xOoops")); + } + + public function testParseNumOk() + { + $this->assertSame(0, + Horde_Argv_Option::parseNumber('0')); + $this->assertSame(16, + Horde_Argv_Option::parseNumber('0x10')); + $this->assertSame(10, + Horde_Argv_Option::parseNumber('0XA')); + $this->assertSame(8, + Horde_Argv_Option::parseNumber('010')); + $this->assertSame(3, + Horde_Argv_Option::parseNumber('0b11')); + $this->assertSame(0, + Horde_Argv_Option::parseNumber('0b')); + } + + public function testNumericOptions() + { + $this->assertParseOk(array("-n", "42", "-l", "0x20"), + array("n" => 42, "l" => 0x20), array()); + + $this->assertParseOk(array("-n", "0b0101", "-l010"), + array("n" => 5, "l" => 8), array()); + + $this->assertParseFail(array("-n008"), + "option -n: invalid integer value: '008'"); + + $this->assertParseFail(array("-l0b0123"), + "option -l: invalid long integer value: '0b0123'"); + + $this->assertParseFail(array("-l", "0x12x"), + "option -l: invalid long integer value: '0x12x'"); + } +} diff --git a/framework/Argv/test/Horde/Argv/ParserTest.php b/framework/Argv/test/Horde/Argv/ParserTest.php new file mode 100644 index 000000000..a52e4ad85 --- /dev/null +++ b/framework/Argv/test/Horde/Argv/ParserTest.php @@ -0,0 +1,111 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + * @subpackage UnitTests + */ + +class Horde_Argv_ParserTest extends Horde_Argv_TestBase +{ + public function setUp() + { + $this->parser = new Horde_Argv_Parser(); + $this->parser->addOption('-v', '--verbose', '-n', '--noisy', + array('action' => 'store_true', 'dest' => 'verbose')); + $this->parser->addOption('-q', '--quiet', '--silent', + array('action' => 'store_false', 'dest' => 'verbose')); + } + + public function testAddOptionNoOption() + { + $this->assertTypeError(array($this->parser, 'addOption'), + "not an Option instance: NULL", array(null)); + } + + public function testAddOptionInvalidArguments() + { + $this->assertTypeError(array($this->parser, 'addOption'), + "invalid arguments", null); + } + + public function testGetOption() + { + $opt1 = $this->parser->getOption("-v"); + $this->assertType('Horde_Argv_Option', $opt1); + $this->assertEquals($opt1->shortOpts, array("-v", "-n")); + $this->assertEquals($opt1->longOpts, array("--verbose", "--noisy")); + $this->assertEquals($opt1->action, "store_true"); + $this->assertEquals($opt1->dest, "verbose"); + } + + public function testGetOptionEquals() + { + $opt1 = $this->parser->getOption("-v"); + $opt2 = $this->parser->getOption("--verbose"); + $opt3 = $this->parser->getOption("-n"); + $opt4 = $this->parser->getOption("--noisy"); + $this->assertEquals($opt1, $opt2); + $this->assertEquals($opt1, $opt3); + $this->assertEquals($opt1, $opt4); + } + + public function testHasOption() + { + $this->assertTrue($this->parser->hasOption("-v")); + $this->assertTrue($this->parser->hasOption("--verbose")); + } + + public function assertRemoved() + { + $this->assertNull($this->parser->getOption("-v")); + $this->assertNull($this->parser->getOption("--verbose")); + $this->assertNull($this->parser->getOption("-n")); + $this->assertNull($this->parser->getOption("--noisy")); + + $this->assertFalse($this->parser->hasOption("-v")); + $this->assertFalse($this->parser->hasOption("--verbose")); + $this->assertFalse($this->parser->hasOption("-n")); + $this->assertFalse($this->parser->hasOption("--noisy")); + + $this->assertTrue($this->parser->hasOption("-q")); + $this->assertTrue($this->parser->hasOption("--silent")); + } + + public function testRemoveShortOpt() + { + $this->parser->removeOption('-n'); + $this->assertRemoved(); + } + + public function testRemoveLongOpt() + { + $this->parser->removeOption('--verbose'); + $this->assertRemoved(); + } + + public function testRemoveNonexistent() + { + $this->assertRaises(array($this->parser, 'removeOption'), array('foo'), 'InvalidArgumentException', "no such option 'foo'"); + } + + /** + def test_refleak(self): + # If a Horde_Argv_Parser is carrying around a reference to a large + # object, various cycles can prevent it from being GC'd in + # a timely fashion. destroy() breaks the cycles to ensure stuff + # can be cleaned up. + big_thing = [42] + refcount = sys.getrefcount(big_thing) + parser = Horde_Argv_Parser() + parser.addOption("-a", "--aaarggh") + parser.big_thing = big_thing + + parser.destroy() + del parser + $this->assertEquals(refcount, sys.getrefcount(big_thing)) + */ + +} diff --git a/framework/Argv/test/Horde/Argv/ProgNameTest.php b/framework/Argv/test/Horde/Argv/ProgNameTest.php new file mode 100644 index 000000000..7e7be3712 --- /dev/null +++ b/framework/Argv/test/Horde/Argv/ProgNameTest.php @@ -0,0 +1,58 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + * @subpackage UnitTests + */ + +class Horde_Argv_ProgNameTest extends Horde_Argv_TestBase +{ + public function assertUsage($parser, $expectedUsage) + { + $this->assertEquals($parser->getUsage(), $expectedUsage); + } + + public function assertVersion($parser, $expectedVersion) + { + $this->assertEquals($parser->getVersion(), $expectedVersion); + } + + public function testDefaultProgName() + { + // Make sure that program name is taken from $_SERVER['argv'][0] by default. + $saveArgv = $_SERVER['argv']; + try { + $_SERVER['argv'][0] = 'foo/bar/baz.php'; + $parser = new Horde_Argv_Parser(array('usage' => "%prog ...", 'version' => "%prog 1.2")); + $expectedUsage = "Usage: baz.php ...\n"; + } catch (Exception $e) { + $_SERVER['argv'] = $saveArgv; + throw($e); + } + + $this->assertUsage($parser, $expectedUsage); + $this->assertVersion($parser, "baz.php 1.2"); + $this->assertHelp($parser, + $expectedUsage . "\n" . + "Options:\n" . + " --version show program's version number and exit\n" . + " -h, --help show this help message and exit\n"); + } + + public function testCustomProgName() + { + $parser = new Horde_Argv_Parser(array('prog' => 'thingy', + 'version' => "%prog 0.1", + 'usage' => "%prog arg arg")); + $parser->removeOption('-h'); + $parser->removeOption('--version'); + $expectedUsage = "Usage: thingy arg arg\n"; + $this->assertUsage($parser, $expectedUsage); + $this->assertVersion($parser, "thingy 0.1"); + $this->assertHelp($parser, $expectedUsage . "\n"); + } + +} diff --git a/framework/Argv/test/Horde/Argv/StandardTest.php b/framework/Argv/test/Horde/Argv/StandardTest.php new file mode 100644 index 000000000..b9de2beca --- /dev/null +++ b/framework/Argv/test/Horde/Argv/StandardTest.php @@ -0,0 +1,203 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + * @subpackage UnitTests + */ + +class Horde_Argv_StandardTest extends Horde_Argv_TestBase +{ + public function setUp() + { + $options = array( + $this->makeOption('-a', array('type' => 'string')), + $this->makeOption('-b', '--boo', array('type' => 'int', 'dest' => 'boo')), + $this->makeOption('--foo', array('action' => 'append')) + ); + + $this->parser = new Horde_Argv_InterceptingParser(array('usage' => Horde_Argv_Option::SUPPRESS_USAGE, + 'optionList' => $options)); + } + + public function testRequiredValue() + { + $this->assertParseFail(array('-a'), + '-a option requires an argument'); + } + + public function testInvalidInteger() + { + $this->assertParseFail(array('-b', '5x'), + "option -b: invalid integer value: '5x'"); + } + + public function testNoSuchOption() + { + $this->assertParseFail(array('--boo13'), + "no such option: --boo13"); + } + + public function testLongInvalidInteger() + { + $this->assertParseFail(array("--boo=x5"), + "option --boo: invalid integer value: 'x5'"); + } + + public function testEmpty() + { + $this->assertParseOk(array(), + array('a' => null, 'boo' => null, 'foo' => null), array()); + } + + public function testShortOptEmptyLongOptAppend() + { + $this->assertParseOk(array("-a", "", "--foo=blah", "--foo="), + array('a' => "", 'boo' => null, 'foo' => array("blah", "")), + array()); + } + + public function testLongOptionAppend() + { + $this->assertParseOk(array("--foo", "bar", "--foo", "", "--foo=x"), + array('a' => null, + 'boo' => null, + 'foo' => array('bar', '', 'x')), + array()); + } + + public function testOptionArgumentJoined() + { + $this->assertParseOk(array("-abc"), + array('a' => "bc", 'boo' => null, 'foo' => null), + array()); + } + + public function testOptionArgumentSplit() + { + $this->assertParseOk(array("-a", "34"), + array('a' => "34", 'boo' => null, 'foo' => null), + array()); + } + + public function testOptionArgumentJoinedInteger() + { + $this->assertParseOk(array("-b34"), + array('a' => null, 'boo' => 34, 'foo' => null), + array()); + } + + public function testOptionArgumentSplitNegativeInteger() + { + $this->assertParseOk(array("-b", "-5"), + array('a' => null, 'boo' => -5, 'foo' => null), + array()); + } + + public function testLongOptionArgumentJoined() + { + $this->assertParseOk(array("--boo=13"), + array('a' => null, 'boo' => 13, 'foo' => null), + array()); + } + + public function testLongOptionArgumentSplit() + { + $this->assertParseOk(array("--boo", "111"), + array('a' => null, 'boo' => 111, 'foo' => null), + array()); + } + + public function testLongOptionShortOption() + { + $this->assertParseOk(array("--foo=bar", "-axyz"), + array('a' => 'xyz', 'boo' => null, 'foo' => array("bar")), + array()); + } + + public function testAbbrevLongOption() + { + $this->assertParseOk(array("--f=bar", "-axyz"), + array('a' => 'xyz', 'boo' => null, 'foo' => array("bar")), + array()); + } + + public function testDefaults() + { + list($options, $args) = $this->parser->parseArgs(array()); + $defaults = $this->parser->getDefaultValues(); + + $this->assertEquals($defaults, $options); + } + + public function testAmbiguousOption() + { + $this->parser->addOption("--foz", array('action' => 'store', + 'type' => 'string', 'dest' => 'foo')); + $this->assertParseFail(array('--f=bar'), + "ambiguous option: --f (--foo, --foz?)"); + } + + public function testShortAndLongOptionSplit() + { + $this->assertParseOk(array("-a", "xyz", "--foo", "bar"), + array('a' => 'xyz', 'boo' => null, 'foo' => array("bar")), + array()); + } + + public function testShortOptionSplitLongOptionAppend() + { + $this->assertParseOk(array("--foo=bar", "-b", "123", "--foo", "baz"), + array('a' => null, 'boo' => 123, 'foo' => array("bar", "baz")), + array()); + } + + public function testShortOptionSplitOnePositionalArg() + { + $this->assertParseOk(array("-a", "foo", "bar"), + array('a' => "foo", 'boo' => null, 'foo' => null), + array("bar")); + } + + public function testShortOptionConsumesSeparator() + { + $this->assertParseOk(array("-a", "--", "foo", "bar"), + array('a' => "--", 'boo' => null, 'foo' => null), + array("foo", "bar")); + + $this->assertParseOk(array("-a", "--", "--foo", "bar"), + array('a' => "--", 'boo' => null, 'foo' => array("bar")), + array()); + } + + public function testShortOptionJoinedAndSeparator() + { + $this->assertParseOk(array("-ab", "--", "--foo", "bar"), + array('a' => "b", 'boo' => null, 'foo' => null), + array("--foo", "bar")); + } + + public function testHyphenBecomesPositionalArg() + { + $this->assertParseOk(array("-ab", "-", "--foo", "bar"), + array('a' => "b", 'boo' => null, 'foo' => array("bar")), + array("-")); + } + + public function testNoAppendVersusAppend() + { + $this->assertParseOk(array("-b3", "-b", "5", "--foo=bar", "--foo", "baz"), + array('a' => null, 'boo' => 5, 'foo' => array("bar", "baz")), + array()); + } + + public function testOptionConsumesOptionLikeString() + { + $this->assertParseOk(array("-a", "-b3"), + array('a' => "-b3", 'boo' => null, 'foo' => null), + array()); + } +} + diff --git a/framework/Argv/test/Horde/Argv/TestBase.php b/framework/Argv/test/Horde/Argv/TestBase.php new file mode 100644 index 000000000..cd021f8f8 --- /dev/null +++ b/framework/Argv/test/Horde/Argv/TestBase.php @@ -0,0 +1,141 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + * @subpackage UnitTests + */ + +class Horde_Argv_TestBase extends PHPUnit_Framework_TestCase +{ + public function makeOption() + { + $args = func_get_args(); + $reflector = new ReflectionClass('Horde_Argv_Option'); + return $reflector->newInstanceArgs($args); + } + + /** + * Assert the options are what we expected when parsing arguments. + * + * Otherwise, fail with a nicely formatted message. + * + * Keyword arguments: + * args -- A list of arguments to parse with Horde_Argv_Parser. + * expected_opts -- The options expected. + * expected_positional_args -- The positional arguments expected. + * + * Returns the options and positional args for further testing. + */ + public function assertParseOK($args, $expected_opts, $expected_positional_args) + { + list($options, $positional_args) = $this->parser->parseArgs($args); + $optdict = iterator_to_array($options); + + $this->assertEquals($expected_opts, $optdict, + 'Expected options don\'t match. Args were ' . print_r($args, true)); + + $this->assertEquals($positional_args, $expected_positional_args, + 'Positional arguments don\'t match. Args were ' . print_r($args, true)); + + return array($options, $positional_args); + } + + /** + * Assert that the expected exception is raised when calling a + * function, and that the right error message is included with + * that exception. + * + * Arguments: + * func -- the function to call + * args -- positional arguments to `func` + * expected_exception -- exception that should be raised + * expected_message -- expected exception message (or pattern + * if a compiled regex object) + * + * Returns the exception raised for further testing. + */ + public function assertRaises($func, $args = array(), + $expected_exception, $expected_message) { + $caught = false; + try { + if (is_array($args)) { + call_user_func_array($func, $args); + } else { + call_user_func($func); + } + } catch (Exception $e) { + if (get_class($e) == $expected_exception) { + $caught = true; + $this->assertEquals($expected_message, $e->getMessage(), 'Expected exception message not matched'); + } + } + + if (!$caught) { + $this->fail("Expected exception $expected_exception not thrown"); + } + } + + // -- Assertions used in more than one class -------------------- + + /** + * Assert the parser fails with the expected message. Caller + * must ensure that $this->parser is an InterceptingParser. + */ + public function assertParseFail($cmdline_args, $expected_output) + { + try { + $this->parser->parseArgs($cmdline_args); + } catch (Horde_Argv_InterceptedException $e) { + $this->assertEquals($expected_output, (string)$e); + return true; + } catch (Exception $e) { + $this->fail("unexpected Exception: " . $e->getMessage()); + } + + $this->fail("expected parse failure"); + } + + /** + * Assert the parser prints the expected output on stdout. + */ + public function assertOutput( + $cmdline_args, + $expected_output, + $expected_status = 0, + $expected_error = null) + { + ob_start(); + try { + $this->parser->parseArgs($cmdline_args); + } catch (Horde_Argv_InterceptedException $e) { + $output = ob_get_clean(); + + $this->assertEquals($expected_output, $output, 'Expected parser output to match'); + $this->assertEquals($expected_status, $e->exit_status); + $this->assertEquals($expected_error, $e->exit_message); + return; + } + + ob_end_clean(); + + $this->fail("expected parser->parserExit()"); + } + + /** + * Assert that TypeError is raised when executing func. + */ + public function assertTypeError($func, $expected_message, $args) + { + $this->assertRaises($func, $args, 'InvalidArgumentException', $expected_message); + } + + public function assertHelp($parser, $expected_help) + { + $actual_help = $parser->formatHelp(); + $this->assertEquals($expected_help, $actual_help); + } + +} diff --git a/framework/Argv/test/Horde/Argv/TypeAliasesTest.php b/framework/Argv/test/Horde/Argv/TypeAliasesTest.php new file mode 100644 index 000000000..e51c16fd1 --- /dev/null +++ b/framework/Argv/test/Horde/Argv/TypeAliasesTest.php @@ -0,0 +1,32 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + * @subpackage UnitTests + */ + +class Horde_Argv_TypeAliasesTest extends Horde_Argv_TestBase +{ + public function setUp() + { + $this->parser = new Horde_Argv_Parser(); + } + + public function testStrAliasesString() + { + $this->parser->addOption("-s", array('type' => "str")); + $this->assertEquals($this->parser->getOption("-s")->type, "string"); + } + + public function testNewTypeObject() + { + $this->parser->addOption("-s", array('type' => 'str')); + $this->assertEquals($this->parser->getOption("-s")->type, "string"); + $this->parser->addOption("-x", array('type' => 'int')); + $this->assertEquals($this->parser->getOption("-x")->type, "int"); + } + +} diff --git a/framework/Argv/test/Horde/Argv/VersionTest.php b/framework/Argv/test/Horde/Argv/VersionTest.php new file mode 100644 index 000000000..2621c650e --- /dev/null +++ b/framework/Argv/test/Horde/Argv/VersionTest.php @@ -0,0 +1,35 @@ + + * @author Mike Naberezny + * @license http://opensource.org/licenses/bsd-license.php BSD + * @category Horde + * @package Horde_Argv + * @subpackage UnitTests + */ + +class Horde_Argv_VersionTest extends Horde_Argv_TestBase +{ + public function testVersion() + { + $this->parser = new Horde_Argv_InterceptingParser(array( + 'usage' => Horde_Argv_Option::SUPPRESS_USAGE, + 'version' => "%prog 0.1")); + $saveArgv = $_SERVER['argv']; + try { + $_SERVER['argv'][0] = dirname(__FILE__) . '/foo/bar'; + $this->assertOutput(array("--version"), "bar 0.1\n"); + } catch (Exception $e) { + $_SERVER['argv'] = $saveArgv; + throw $e; + } + + $_SERVER['argv'] = $saveArgv; + } + + public function testNoVersion() + { + $this->parser = new Horde_Argv_InterceptingParser(array('usage' => Horde_Argv_Option::SUPPRESS_USAGE)); + $this->assertParseFail(array("--version"), "no such option: --version"); + } +} diff --git a/framework/Argv/test/Horde/CVS/Entries b/framework/Argv/test/Horde/CVS/Entries new file mode 100644 index 000000000..7f0438144 --- /dev/null +++ b/framework/Argv/test/Horde/CVS/Entries @@ -0,0 +1 @@ +D/Argv//// diff --git a/framework/Argv/test/Horde/CVS/Repository b/framework/Argv/test/Horde/CVS/Repository new file mode 100644 index 000000000..37c77fcc5 --- /dev/null +++ b/framework/Argv/test/Horde/CVS/Repository @@ -0,0 +1 @@ +framework/Argv/test/Horde diff --git a/framework/Argv/test/Horde/CVS/Root b/framework/Argv/test/Horde/CVS/Root new file mode 100644 index 000000000..5d636129a --- /dev/null +++ b/framework/Argv/test/Horde/CVS/Root @@ -0,0 +1 @@ +chuck@cvs.horde.org:/repository diff --git a/framework/Argv/test/Horde/CVS/Template b/framework/Argv/test/Horde/CVS/Template new file mode 100644 index 000000000..3971591f9 --- /dev/null +++ b/framework/Argv/test/Horde/CVS/Template @@ -0,0 +1,8 @@ + +Bug: +Submitted by: +Merge after: +CVS: ---------------------------------------------------------------------- +CVS: Bug: Fill this in if a listed bug is affected by the change. +CVS: Submitted by: Fill this in if someone else sent in the change. +CVS: Merge after: N [day[s]|week[s]|month[s]] (days assumed by default)