Files
server/usr/share/psa-pear/pear/php/Horde/Argv/Option.php
2026-01-07 20:52:11 +01:00

594 lines
19 KiB
PHP

<?php
/**
* Copyright 2010-2017 Horde LLC (http://www.horde.org/)
*
* This package is ported from Python's Optik (http://optik.sourceforge.net/).
*
* See the enclosed file LICENSE for license information (BSD). If you
* did not receive this file, see http://www.horde.org/licenses/bsd.
*
* @author Chuck Hagenbuch <chuck@horde.org>
* @author Mike Naberezny <mike@maintainable.com>
* @license http://www.horde.org/licenses/bsd BSD
* @category Horde
* @package 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 Argv
* @author Chuck Hagenbuch <chuck@horde.org>
* @author Mike Naberezny <mike@maintainable.com>
* @copyright 2010-2017 Horde LLC
* @license http://www.horde.org/licenses/bsd BSD
*/
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'
? Horde_Argv_Translation::t("option %s: invalid integer value: '%s'")
: Horde_Argv_Translation::t("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(Horde_Argv_Translation::t("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(
Horde_Argv_Translation::t("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:
*
* <code>
* mixed function(Horde_Argv_Option $option, string $opt, string $value)
* </code>
*
* where
* - $option is the Horde_Argv_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 PHP 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',
);
/**
* 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;
/**
* Constructor.
*/
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 hasDefault()
{
return $this->default !== self::$NO_DEFAULT;
}
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;
}
}