Skip to content

Commit 2e488dd

Browse files
authored
Merge pull request #4 from horde/feat/php8.4-compat
feat/php8.4 compat
2 parents f173ad2 + 0d03091 commit 2e488dd

18 files changed

+268
-44
lines changed

.horde.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ license:
3838
uri: http://www.horde.org/licenses/bsd
3939
dependencies:
4040
required:
41-
php: ^7.4 || ^8
41+
php: ^8
4242
composer:
4343
horde/cli: ^3
4444
horde/exception: ^3

lib/Horde/Argv/Option.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ public function checkChoice($opt, $value)
277277
public $callbackArgs;
278278
public $help;
279279
public $metavar;
280-
public $container;
280+
public $container;
281281

282282
/**
283283
* Constructor.

src/ArgvParser.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
namespace Horde\Argv;
3+
4+
/**
5+
* Generic interface of command line arguments
6+
*/
7+
interface ArgvParser
8+
{
9+
}

src/ArgvWrapper.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
declare(strict_types=1);
3+
namespace Horde\Argv;
4+
5+
use InvalidArgumentException;
6+
use IteratorAggregate;
7+
use RuntimeException;
8+
use Traversable;
9+
use Countable;
10+
/**
11+
* Wrap a copy of Argv into a simple, typed object for DI
12+
*/
13+
class ArgvWrapper implements IteratorAggregate, Countable
14+
{
15+
private readonly array $argv;
16+
17+
public function __construct(array $argv)
18+
{
19+
$argvCopy = [];
20+
// TODO: Check if the array is actually argv-like
21+
foreach ($argv as $pos => $argument) {
22+
if (!is_string($argument)) {
23+
throw new InvalidArgumentException('All members of argv must be strings.');
24+
}
25+
26+
$argvCopy[] = $argument;
27+
}
28+
$this->argv = $argvCopy;
29+
}
30+
31+
public function getIterator(): Traversable
32+
{
33+
return new \ArrayIterator($this->argv);
34+
}
35+
36+
public function count(): int
37+
{
38+
return count($this->argv);
39+
}
40+
41+
public static function fromGlobal()
42+
{
43+
if (empty($GLOBALS['argv']))
44+
{
45+
// Argv always contains at least the binary's name so this indicates a severe error
46+
throw new RuntimeException("Argv Global is not available or in invalid state");
47+
}
48+
return new ArgvWrapper($GLOBALS['argv']);
49+
}
50+
}

src/HelpFormatter.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,19 @@ abstract class HelpFormatter
7676
const NO_DEFAULT_VALUE = 'none';
7777

7878
public $parser = null;
79+
public $_color;
80+
public $indent_increment;
81+
public $max_help_position;
82+
public $help_position;
83+
public $width;
84+
public $level;
85+
public $current_indent;
86+
public $help_width;
87+
public $default_tag;
88+
public $option_strings;
89+
public $_short_opt_fmt;
90+
public $_long_opt_fmt;
91+
public $short_first;
7992

8093
public function __construct(
8194
$indent_increment, $max_help_position, $width = null,

src/ImmutableParser.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
namespace Horde\Argv;
3+
4+
/**
5+
* An immutable parser can only configured once through its constructor
6+
* and provides no interface for later modification.
7+
*/
8+
class ImmutableParser implements ArgvParser
9+
{
10+
11+
}

src/Option.php

Lines changed: 51 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
* @package Argv
1616
*/
1717
namespace Horde\Argv;
18+
use Iterator;
19+
use InvalidArgumentException;
20+
use RuntimeException;
21+
1822

1923
/**
2024
* Defines the Option class and some standard value-checking functions.
@@ -131,8 +135,8 @@ public function checkChoice($opt, $value)
131135
$choices[] = (string)$choice;
132136
}
133137
$choices = "'" . implode("', '", $choices) . "'";
134-
throw new Horde_Argv_OptionValueException(sprintf(
135-
Horde_Argv_Translation::t("option %s: invalid choice: '%s' (choose from %s)"),
138+
throw new OptionValueException(sprintf(
139+
Translation::t("option %s: invalid choice: '%s' (choose from %s)"),
136140
$opt, $value, $choices));
137141
}
138142
}
@@ -155,6 +159,7 @@ public function checkChoice($opt, $value)
155159
'help',
156160
'metavar',
157161
);
162+
158163

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

268273
public $shortOpts = array();
269274
public $longOpts = array();
275+
// These should probably be made readonly once we are sure we *really* usually set them through a constructor
276+
public $action;
277+
public $type;
270278
public $dest;
271279
public $default;
280+
public $nargs;
281+
public $const;
282+
public $choices;
283+
public $callback;
284+
public $callbackArgs;
285+
public $help;
286+
// TODO: Where is this even used?
287+
public $metavar;
288+
// Used in OptionContainer->addOption
289+
public $container;
272290

273291
/**
274292
* Constructor.
275293
*/
276294
public function __construct()
277295
{
296+
// TODO: Refactor this to use optional constructor properties
297+
278298
// The last argument to this function is an $attrs hash, if it
279299
// is present and an array. All other arguments are $opts.
280300
$opts = func_get_args();
@@ -323,17 +343,17 @@ protected function _setOptStrings($opts)
323343
$opt = (string)$opt;
324344

325345
if (strlen($opt) < 2) {
326-
throw new Horde_Argv_OptionException(sprintf("invalid option string '%s': must be at least two characters long", $opt), $this);
346+
throw new OptionException(sprintf("invalid option string '%s': must be at least two characters long", $opt), $this);
327347
} elseif (strlen($opt) == 2) {
328348
if (!($opt[0] == '-' && $opt[1] != '-')) {
329-
throw new Horde_Argv_OptionException(sprintf(
349+
throw new OptionException(sprintf(
330350
"invalid short option string '%s': " .
331351
"must be of the form -x, (x any non-dash char)", $opt), $this);
332352
}
333353
$this->shortOpts[] = $opt;
334354
} else {
335355
if (!(substr($opt, 0, 2) == '--' && $opt[2] != '-')) {
336-
throw new Horde_Argv_OptionException(sprintf(
356+
throw new OptionException(sprintf(
337357
"invalid long option string '%s': " .
338358
"must start with --, followed by non-dash", $opt), $this);
339359
}
@@ -360,7 +380,7 @@ protected function _setAttrs($attrs)
360380
if ($attrs) {
361381
$attrs = array_keys($attrs);
362382
sort($attrs);
363-
throw new Horde_Argv_OptionException(sprintf(
383+
throw new OptionException(sprintf(
364384
'invalid keyword arguments: %s', implode(', ', $attrs)), $this);
365385
}
366386
}
@@ -373,7 +393,7 @@ public function _checkAction()
373393
if (is_null($this->action)) {
374394
$this->action = 'store';
375395
} elseif (!in_array($this->action, $this->ACTIONS)) {
376-
throw new Horde_Argv_OptionException(sprintf("invalid action: '%s'", $this->action), $this);
396+
throw new OptionException(sprintf("invalid action: '%s'", $this->action), $this);
377397
}
378398
}
379399

@@ -395,11 +415,11 @@ public function _checkType()
395415
}
396416

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

401421
if (!in_array($this->action, $this->TYPED_ACTIONS)) {
402-
throw new Horde_Argv_OptionException(sprintf(
422+
throw new OptionException(sprintf(
403423
"must not supply a type for action '%s'", $this->action), $this);
404424
}
405425
}
@@ -409,15 +429,15 @@ public function _checkChoice()
409429
{
410430
if ($this->type == 'choice') {
411431
if (is_null($this->choices)) {
412-
throw new Horde_Argv_OptionException(
432+
throw new OptionException(
413433
"must supply a list of choices for type 'choice'", $this);
414434
} elseif (!(is_array($this->choices) || $this->choices instanceof Iterator)) {
415-
throw new Horde_Argv_OptionException(sprintf(
435+
throw new OptionException(sprintf(
416436
"choices must be a list of strings ('%s' supplied)",
417437
gettype($this->choices)), $this);
418438
}
419439
} elseif (!is_null($this->choices)) {
420-
throw new Horde_Argv_OptionException(sprintf(
440+
throw new OptionException(sprintf(
421441
"must not supply choices for type '%s'", $this->type), $this);
422442
}
423443
}
@@ -443,7 +463,7 @@ public function _checkDest()
443463
public function _checkConst()
444464
{
445465
if (!in_array($this->action, $this->CONST_ACTIONS) && !is_null($this->const)) {
446-
throw new Horde_Argv_OptionException(sprintf(
466+
throw new OptionException(sprintf(
447467
"'const' must not be supplied for action '%s'", $this->action),
448468
$this);
449469
}
@@ -456,38 +476,46 @@ public function _checkNargs()
456476
$this->nargs = 1;
457477
}
458478
} elseif (!is_null($this->nargs)) {
459-
throw new Horde_Argv_OptionException(sprintf(
479+
throw new OptionException(sprintf(
460480
"'nargs' must not be supplied for action '%s'", $this->action),
461481
$this);
462482
}
463483
}
464484

465485
public function _checkCallback()
466486
{
487+
// if action is callback, callback must exist and be valid. If not, callback must be null or exist and be valid
467488
if ($this->action == 'callback') {
489+
// 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
468490
if (!is_callable($this->callback)) {
469-
$callback_name = is_array($this->callback) ?
470-
is_object($this->callback[0]) ? get_class($this->callback[0] . '#' . $this->callback[1]) : implode('#', $this->callback) :
471-
$this->callback;
472-
throw new Horde_Argv_OptionException(sprintf(
473-
"callback not callable: '%s'", $callback_name), $this);
491+
$callback_name = '';
492+
if (is_array($this->callback)) {
493+
if (is_object($this->callback[0])) {
494+
$callback_name = get_class($this->callback[0]) . '#' . $this->callback[1];
495+
} else {
496+
$callback_name = implode('#', $this->callback);
497+
}
498+
} else {
499+
throw new OptionException(sprintf(
500+
"callback not callable: '%s'", $callback_name), $this);
501+
}
474502
}
475503
if (!is_null($this->callbackArgs) && !is_array($this->callbackArgs)) {
476-
throw new Horde_Argv_OptionException(sprintf(
504+
throw new OptionException(sprintf(
477505
"callbackArgs, if supplied, must be an array: not '%s'",
478506
$this->callbackArgs), $this);
479507
}
480508
} else {
481509
if (!is_null($this->callback)) {
482510
$callback_name = is_array($this->callback) ?
483-
is_object($this->callback[0]) ? get_class($this->callback[0] . '#' . $this->callback[1]) : implode('#', $this->callback) :
511+
is_object($this->callback[0]) ? get_class($this->callback[0]) . '#' . $this->callback[1] : implode('#', $this->callback) :
484512
$this->callback;
485-
throw new Horde_Argv_OptionException(sprintf(
513+
throw new OptionException(sprintf(
486514
"callback supplied ('%s') for non-callback option",
487515
$callback_name), $this);
488516
}
489517
if (!is_null($this->callbackArgs)) {
490-
throw new Horde_Argv_OptionException(
518+
throw new OptionException(
491519
'callbackArgs supplied for non-callback option', $this);
492520
}
493521
}

src/OptionException.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
*/
3030
class OptionException extends Exception
3131
{
32+
public string $optionId;
3233
public function __construct($msg, $option = null)
3334
{
3435
$this->optionId = (string)$option;

src/OptionGroup.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@
3030
class OptionGroup extends OptionContainer
3131
{
3232
protected $_title;
33+
public readonly Parser $parser;
3334

34-
public function __construct($parser, $title, $description = null)
35+
public function __construct(Parser $parser, $title, $description = null)
3536
{
3637
$this->parser = $parser;
3738
parent::__construct($parser->optionClass, $parser->conflictHandler, $description);

src/Parser.php

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,21 @@
8282
* @copyright 2010-2017 Horde LLC
8383
* @license http://www.horde.org/licenses/bsd BSD
8484
*/
85-
class Parser extends OptionContainer
85+
class Parser extends OptionContainer implements ArgvParser
8686
{
87-
public $standardOptionList = array();
88-
87+
public $standardOptionList = [];
8988
protected $_usage;
90-
public $optionGroups = array();
89+
public $prog;
90+
public $epilog;
91+
public $optionGroups = [];
92+
public $allowInterspersedArgs;
93+
public $ignoreUnknownArgs;
94+
public $rargs;
95+
public $largs;
96+
public $values;
97+
public $formatter;
98+
public $version;
99+
public $allowUnknownArgs;
91100

92101
public function __construct($args = array())
93102
{
@@ -469,7 +478,7 @@ protected function _processLongOpt(&$rargs, &$values)
469478
throw $e;
470479
}
471480
}
472-
481+
$value = null;
473482
if ($option->takesValue()) {
474483
$nargs = $option->nargs;
475484
if (count($rargs) < $nargs) {

0 commit comments

Comments
 (0)