Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .horde.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ license:
uri: http://www.horde.org/licenses/bsd
dependencies:
required:
php: ^7.4 || ^8
php: ^8
composer:
horde/cli: ^3
horde/exception: ^3
Expand Down
2 changes: 1 addition & 1 deletion lib/Horde/Argv/Option.php
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ public function checkChoice($opt, $value)
public $callbackArgs;
public $help;
public $metavar;
public $container;
public $container;

/**
* Constructor.
Expand Down
9 changes: 9 additions & 0 deletions src/ArgvParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php
namespace Horde\Argv;

/**
* Generic interface of command line arguments
*/
interface ArgvParser
{
}
50 changes: 50 additions & 0 deletions src/ArgvWrapper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace Horde\Argv;

use InvalidArgumentException;
use IteratorAggregate;
use RuntimeException;
use Traversable;
use Countable;
/**
* Wrap a copy of Argv into a simple, typed object for DI
*/
class ArgvWrapper implements IteratorAggregate, Countable
{
private readonly array $argv;

public function __construct(array $argv)
{
$argvCopy = [];
// TODO: Check if the array is actually argv-like
foreach ($argv as $pos => $argument) {
if (!is_string($argument)) {
throw new InvalidArgumentException('All members of argv must be strings.');
}

$argvCopy[] = $argument;
}
$this->argv = $argvCopy;
}

public function getIterator(): Traversable
{
return new \ArrayIterator($this->argv);
}

public function count(): int
{
return count($this->argv);
}

public static function fromGlobal()
{
if (empty($GLOBALS['argv']))
{
// Argv always contains at least the binary's name so this indicates a severe error
throw new RuntimeException("Argv Global is not available or in invalid state");
}
return new ArgvWrapper($GLOBALS['argv']);
}
}
13 changes: 13 additions & 0 deletions src/HelpFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,19 @@ abstract class HelpFormatter
const NO_DEFAULT_VALUE = 'none';

public $parser = null;
public $_color;
public $indent_increment;
public $max_help_position;
public $help_position;
public $width;
public $level;
public $current_indent;
public $help_width;
public $default_tag;
public $option_strings;
public $_short_opt_fmt;
public $_long_opt_fmt;
public $short_first;

public function __construct(
$indent_increment, $max_help_position, $width = null,
Expand Down
11 changes: 11 additions & 0 deletions src/ImmutableParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php
namespace Horde\Argv;

/**
* An immutable parser can only configured once through its constructor
* and provides no interface for later modification.
*/
class ImmutableParser implements ArgvParser
{

}
74 changes: 51 additions & 23 deletions src/Option.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
* @package Argv
*/
namespace Horde\Argv;
use Iterator;
use InvalidArgumentException;
use RuntimeException;


/**
* Defines the Option class and some standard value-checking functions.
Expand Down Expand Up @@ -131,8 +135,8 @@ public function checkChoice($opt, $value)
$choices[] = (string)$choice;
}
$choices = "'" . implode("', '", $choices) . "'";
throw new Horde_Argv_OptionValueException(sprintf(
Horde_Argv_Translation::t("option %s: invalid choice: '%s' (choose from %s)"),
throw new OptionValueException(sprintf(
Translation::t("option %s: invalid choice: '%s' (choose from %s)"),
$opt, $value, $choices));
}
}
Expand All @@ -155,6 +159,7 @@ public function checkChoice($opt, $value)
'help',
'metavar',
);


/**
* The set of actions allowed by option parsers. Explicitly listed here so
Expand Down Expand Up @@ -267,14 +272,29 @@ public function checkChoice($opt, $value)

public $shortOpts = array();
public $longOpts = array();
// These should probably be made readonly once we are sure we *really* usually set them through a constructor
public $action;
public $type;
public $dest;
public $default;
public $nargs;
public $const;
public $choices;
public $callback;
public $callbackArgs;
public $help;
// TODO: Where is this even used?
public $metavar;
// Used in OptionContainer->addOption
public $container;

/**
* Constructor.
*/
public function __construct()
{
// TODO: Refactor this to use optional constructor properties

// 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();
Expand Down Expand Up @@ -323,17 +343,17 @@ protected function _setOptStrings($opts)
$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);
throw new 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(
throw new 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(
throw new OptionException(sprintf(
"invalid long option string '%s': " .
"must start with --, followed by non-dash", $opt), $this);
}
Expand All @@ -360,7 +380,7 @@ protected function _setAttrs($attrs)
if ($attrs) {
$attrs = array_keys($attrs);
sort($attrs);
throw new Horde_Argv_OptionException(sprintf(
throw new OptionException(sprintf(
'invalid keyword arguments: %s', implode(', ', $attrs)), $this);
}
}
Expand All @@ -373,7 +393,7 @@ 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);
throw new OptionException(sprintf("invalid action: '%s'", $this->action), $this);
}
}

Expand All @@ -395,11 +415,11 @@ public function _checkType()
}

if (!in_array($this->type, $this->TYPES)) {
throw new Horde_Argv_OptionException(sprintf("invalid option type: '%s'", $this->type), $this);
throw new OptionException(sprintf("invalid option type: '%s'", $this->type), $this);
}

if (!in_array($this->action, $this->TYPED_ACTIONS)) {
throw new Horde_Argv_OptionException(sprintf(
throw new OptionException(sprintf(
"must not supply a type for action '%s'", $this->action), $this);
}
}
Expand All @@ -409,15 +429,15 @@ public function _checkChoice()
{
if ($this->type == 'choice') {
if (is_null($this->choices)) {
throw new Horde_Argv_OptionException(
throw new 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(
throw new 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(
throw new OptionException(sprintf(
"must not supply choices for type '%s'", $this->type), $this);
}
}
Expand All @@ -443,7 +463,7 @@ public function _checkDest()
public function _checkConst()
{
if (!in_array($this->action, $this->CONST_ACTIONS) && !is_null($this->const)) {
throw new Horde_Argv_OptionException(sprintf(
throw new OptionException(sprintf(
"'const' must not be supplied for action '%s'", $this->action),
$this);
}
Expand All @@ -456,38 +476,46 @@ public function _checkNargs()
$this->nargs = 1;
}
} elseif (!is_null($this->nargs)) {
throw new Horde_Argv_OptionException(sprintf(
throw new OptionException(sprintf(
"'nargs' must not be supplied for action '%s'", $this->action),
$this);
}
}

public function _checkCallback()
{
// if action is callback, callback must exist and be valid. If not, callback must be null or exist and be valid
if ($this->action == 'callback') {
// Callback must be a callable or an array with object as first item and method name as second item OR a string in format CLASS#method
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);
$callback_name = '';
if (is_array($this->callback)) {
if (is_object($this->callback[0])) {
$callback_name = get_class($this->callback[0]) . '#' . $this->callback[1];
} else {
$callback_name = implode('#', $this->callback);
}
} else {
throw new OptionException(sprintf(
"callback not callable: '%s'", $callback_name), $this);
}
}
if (!is_null($this->callbackArgs) && !is_array($this->callbackArgs)) {
throw new Horde_Argv_OptionException(sprintf(
throw new 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) :
is_object($this->callback[0]) ? get_class($this->callback[0]) . '#' . $this->callback[1] : implode('#', $this->callback) :
$this->callback;
throw new Horde_Argv_OptionException(sprintf(
throw new OptionException(sprintf(
"callback supplied ('%s') for non-callback option",
$callback_name), $this);
}
if (!is_null($this->callbackArgs)) {
throw new Horde_Argv_OptionException(
throw new OptionException(
'callbackArgs supplied for non-callback option', $this);
}
}
Expand Down
1 change: 1 addition & 0 deletions src/OptionException.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
*/
class OptionException extends Exception
{
public string $optionId;
public function __construct($msg, $option = null)
{
$this->optionId = (string)$option;
Expand Down
3 changes: 2 additions & 1 deletion src/OptionGroup.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@
class OptionGroup extends OptionContainer
{
protected $_title;
public readonly Parser $parser;

public function __construct($parser, $title, $description = null)
public function __construct(Parser $parser, $title, $description = null)
{
$this->parser = $parser;
parent::__construct($parser->optionClass, $parser->conflictHandler, $description);
Expand Down
19 changes: 14 additions & 5 deletions src/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,21 @@
* @copyright 2010-2017 Horde LLC
* @license http://www.horde.org/licenses/bsd BSD
*/
class Parser extends OptionContainer
class Parser extends OptionContainer implements ArgvParser
{
public $standardOptionList = array();

public $standardOptionList = [];
protected $_usage;
public $optionGroups = array();
public $prog;
public $epilog;
public $optionGroups = [];
public $allowInterspersedArgs;
public $ignoreUnknownArgs;
public $rargs;
public $largs;
public $values;
public $formatter;
public $version;
public $allowUnknownArgs;

public function __construct($args = array())
{
Expand Down Expand Up @@ -469,7 +478,7 @@ protected function _processLongOpt(&$rargs, &$values)
throw $e;
}
}

$value = null;
if ($option->takesValue()) {
$nargs = $option->nargs;
if (count($rargs) < $nargs) {
Expand Down
11 changes: 11 additions & 0 deletions src/ParserArgument.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php
namespace Horde\Argv\Parser;

/**
* Generic interface of command line arguments
*/
interface ParserArgument
{
public function getNames();

}
17 changes: 17 additions & 0 deletions src/ParserBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php
namespace Horde\Argv\Parser;

/**
* The ParserBuilder implements the fluent builder pattern.
*
* It is technically a usable, mutable parser
* Eject to an ImmutableParser to seal it against modification
*
*/
class ParserBuilder implements Parser
{
public function withArgument(ParserArgument $argument)
{

}
}
Loading
Loading