support for positional args
authorMichael Wallner <mike@php.net>
Wed, 2 Sep 2015 14:46:36 +0000 (16:46 +0200)
committerMichael Wallner <mike@php.net>
Wed, 2 Sep 2015 15:08:11 +0000 (17:08 +0200)
src/pharext/Cli/Args.php
src/pharext/Cli/Args/Help.php

index 07fa892fd68196fe0bccf9ea29aa8261230dbf0a..2c148bf2ba7ef6dcc8bd06e4f7063b82534530b0 100644 (file)
@@ -11,54 +11,54 @@ class Args implements \ArrayAccess
         * Optional option
         */
        const OPTIONAL = 0x000;
-       
+
        /**
         * Required Option
         */
        const REQUIRED = 0x001;
-       
+
        /**
         * Only one value, even when used multiple times
         */
        const SINGLE = 0x000;
-       
+
        /**
         * Aggregate an array, when used multiple times
         */
        const MULTI = 0x010;
-       
+
        /**
         * Option takes no argument
         */
        const NOARG = 0x000;
-       
+
        /**
         * Option requires an argument
         */
        const REQARG = 0x100;
-       
+
        /**
         * Option takes an optional argument
         */
        const OPTARG = 0x200;
-       
+
        /**
         * Option halts processing
         */
        const HALT = 0x10000000;
-       
+
        /**
         * Original option spec
         * @var array
         */
        private $orig = [];
-       
+
        /**
         * Compiled spec
         * @var array
         */
        private $spec = [];
-       
+
        /**
         * Parsed args
         * @var array
@@ -73,9 +73,9 @@ class Args implements \ArrayAccess
                if (is_array($spec) || $spec instanceof Traversable) {
                        $this->compile($spec);
                }
-               
+
        }
-       
+
        /**
         * Compile the original spec
         * @param array|Traversable $spec
@@ -83,15 +83,20 @@ class Args implements \ArrayAccess
         */
        public function compile($spec) {
                foreach ($spec as $arg) {
-                       if (isset($arg[0])) { 
+                       if (isset($arg[0]) && is_numeric($arg[0])) {
+                               $arg[3] &= ~0xf00;
+                               $this->spec["--".$arg[0]] = $arg;
+                       } elseif (isset($arg[0])) {
                                $this->spec["-".$arg[0]] = $arg;
+                               $this->spec["--".$arg[1]] = $arg;
+                       } else {
+                               $this->spec["--".$arg[1]] = $arg;
                        }
-                       $this->spec["--".$arg[1]] = $arg;
                        $this->orig[] = $arg;
                }
                return $this;
        }
-       
+
        /**
         * Get original spec
         * @return array
@@ -99,7 +104,7 @@ class Args implements \ArrayAccess
        public function getSpec() {
                return $this->orig;
        }
-       
+
        /**
         * Get compiled spec
         * @return array
@@ -107,40 +112,52 @@ class Args implements \ArrayAccess
        public function getCompiledSpec() {
                return $this->spec;
        }
-       
+
        /**
         * Parse command line arguments according to the compiled spec.
-        * 
+        *
         * The Generator yields any parsing errors.
         * Parsing will stop when all arguments are processed or the first option
         * flagged CliArgs::HALT was encountered.
-        * 
+        *
         * @param int $argc
         * @param array $argv
         * @return Generator
         */
        public function parse($argc, array $argv) {
-               for ($i = 0; $i < $argc; ++$i) {
+               for ($f = false, $p = 0, $i = 0; $i < $argc; ++$i) {
                        $o = $argv[$i];
-                       
-                       if ($o{0} === '-' && strlen($o) > 2 && $o{1} !== '-') {
-                               // multiple short opts, .e.g -vps
+
+                       if ($o{0} === "-" && strlen($o) > 2 && $o{1} !== "-") {
+                               // multiple short opts, e.g. -vps
                                $argc += strlen($o) - 2;
                                array_splice($argv, $i, 1, array_map(function($s) {
                                        return "-$s";
                                }, str_split(substr($o, 1))));
                                $o = $argv[$i];
-                       } elseif ($o{0} === '-' && strlen($o) > 2 && $o{1} === '-' && 0 < ($eq = strpos($o, "="))) {
+                       } elseif ($o{0} === "-" && strlen($o) > 2 && $o{1} === "-" && 0 < ($eq = strpos($o, "="))) {
+                               // long opt with argument, e.g. --foo=bar
                                $argc++;
                                array_splice($argv, $i, 1, [
                                        substr($o, 0, $eq++),
                                        substr($o, $eq)
                                ]);
                                $o = $argv[$i];
+                       } elseif ($o === "--") {
+                               // only positional args following
+                               $f = true;
+                               continue;
                        }
 
-                       if (!isset($this->spec[$o])) {
-                               yield sprintf("Unknown option %s", $o);
+                       if ($f || !isset($this->spec[$o])) {
+                               if ($o{0} !== "-" && isset($this->spec["--$p"])) {
+                                       $this[$p] = $o;
+                                       if (!$this->optIsMulti($p)) {
+                                               ++$p;
+                                       }
+                               } else {
+                                       yield sprintf("Unknown option %s", $o);
+                               }
                        } elseif (!$this->optAcceptsArg($o)) {
                                $this[$o] = true;
                        } elseif ($i+1 < $argc && !isset($this->spec[$argv[$i+1]])) {
@@ -151,18 +168,18 @@ class Args implements \ArrayAccess
                                // OPTARG
                                $this[$o] = $this->optDefaultArg($o);
                        }
-                       
+
                        if ($this->optHalts($o)) {
                                return;
                        }
                }
        }
-       
+
        /**
         * Validate that all required options were given.
-        * 
+        *
         * The Generator yields any validation errors.
-        * 
+        *
         * @return Generator
         */
        public function validate() {
@@ -170,12 +187,21 @@ class Args implements \ArrayAccess
                        return $spec[3] & self::REQUIRED;
                });
                foreach ($required as $req) {
-                       if (!strlen($this[$req[0]])) {
+                       if ($req[3] & self::MULTI) {
+                               if (is_array($this[$req[0]])) {
+                                       continue;
+                               }
+                       } elseif (strlen($this[$req[0]])) {
+                               continue;
+                       }
+                       if (is_numeric($req[0])) {
+                               yield sprintf("Argument <%s> is required", $req[1]);
+                       } else {
                                yield sprintf("Option --%s is required", $req[1]);
                        }
                }
        }
-       
+
 
        public function toArray() {
                $args = [];
@@ -198,7 +224,7 @@ class Args implements \ArrayAccess
                }
                return null;
        }
-       
+
        /**
         * Retrieve the help message of an option
         * @param string $o
@@ -224,7 +250,7 @@ class Args implements \ArrayAccess
                }
                return null;
        }
-       
+
        /**
         * Check whether an option is flagged for halting argument processing
         * @param string $o
@@ -233,7 +259,7 @@ class Args implements \ArrayAccess
        private function optHalts($o) {
                return $this->optFlags($o) & self::HALT;
        }
-       
+
        /**
         * Check whether an option needs an argument
         * @param string $o
@@ -242,7 +268,7 @@ class Args implements \ArrayAccess
        private function optRequiresArg($o) {
                return $this->optFlags($o) & self::REQARG;
        }
-       
+
        /**
         * Check wether an option accepts any argument
         * @param string $o
@@ -251,7 +277,7 @@ class Args implements \ArrayAccess
        private function optAcceptsArg($o) {
                return $this->optFlags($o) & 0xf00;
        }
-       
+
        /**
         * Check whether an option can be used more than once
         * @param string $o
@@ -260,7 +286,7 @@ class Args implements \ArrayAccess
        private function optIsMulti($o) {
                return $this->optFlags($o) & self::MULTI;
        }
-       
+
        /**
         * Retreive the long name of an option
         * @param string $o
@@ -268,9 +294,9 @@ class Args implements \ArrayAccess
         */
        private function optLongName($o) {
                $o = $this->opt($o);
-               return $this->spec[$o][1];
+               return is_numeric($this->spec[$o][0]) ? $this->spec[$o][0] : $this->spec[$o][1];
        }
-       
+
        /**
         * Retreive the short name of an option
         * @param string $o
@@ -278,15 +304,18 @@ class Args implements \ArrayAccess
         */
        private function optShortName($o) {
                $o = $this->opt($o);
-               return $this->spec[$o][0];
+               return is_numeric($this->spec[$o][0]) ? null : $this->spec[$o][0];
        }
-       
+
        /**
         * Retreive the canonical name (--long-name) of an option
         * @param string $o
         * @return string
         */
        private function opt($o) {
+               if (is_numeric($o)) {
+                       return "--$o";
+               }
                if ($o{0} !== '-') {
                        if (strlen($o) > 1) {
                                $o = "-$o";
@@ -295,7 +324,7 @@ class Args implements \ArrayAccess
                }
                return $o;
        }
-       
+
        /**@+
         * Implements ArrayAccess and virtual properties
         */
index 19e8cd409910d5433d8e5283020632ee0a317241..50883b1773616778f63b17b43b884fb323ee6b1d 100644 (file)
@@ -17,7 +17,7 @@ class Help
                $usage = "Usage:\n\n  \$ ";
                $usage .= $this->prog;
 
-               list($flags, $required, $optional) = $this->listSpec();
+               list($flags, $required, $optional, $positional) = $this->listSpec();
                if ($flags) {
                        $usage .= $this->dumpFlags($flags);
                }
@@ -27,9 +27,12 @@ class Help
                if ($optional) {
                        $usage .= $this->dumpOptional($optional);
                }
+               if ($positional) {
+                       $usage .= $this->dumpPositional($positional);
+               }
+
+               $help = $this->dumpHelp($positional);
 
-               $help = $this->dumpHelp();
-               
                return $usage . "\n\n" . $help . "\n";
        }
 
@@ -37,8 +40,11 @@ class Help
                $flags = [];
                $required = [];
                $optional = [];
+               $positional = [];
                foreach ($this->args->getSpec() as $spec) {
-                       if ($spec[3] & Args::REQARG) {
+                       if (is_numeric($spec[0])) {
+                               $positional[] = $spec;
+                       } elseif ($spec[3] & Args::REQARG) {
                                if ($spec[3] & Args::REQUIRED) {
                                        $required[] = $spec;
                                } else {
@@ -49,7 +55,8 @@ class Help
                        }
                }
 
-               return [$flags, $required, $optional] + compact("flags", "required", "optional");
+               return [$flags, $required, $optional, $positional]
+                       + compact("flags", "required", "optional", "positional");
        }
 
        function dumpFlags(array $flags) {
@@ -68,6 +75,23 @@ class Help
                return sprintf(" [-%s <arg>]", implode("|-", array_column($optional, 0)));
        }
 
+       function dumpPositional(array $positional) {
+               $dump = " [--]";
+               foreach ($positional as $pos) {
+                       if ($pos[3] & Args::MULTI) {
+                               $multi = " ...";
+                       } else {
+                               $multi = "";
+                       }
+                       if ($pos[3] & Args::REQUIRED) {
+                               $dump .= sprintf(" <%s%s>", $pos[1], $multi);
+                       } else {
+                               $dump .= sprintf(" [%s%s]", $pos[1], $multi);
+                       }
+               }
+               return $dump;
+       }
+
        function calcMaxLen() {
                $spc = $this->args->getSpec();
                $max = max(array_map("strlen", array_column($spc, 1)));
@@ -77,13 +101,18 @@ class Help
 
        function dumpHelp() {
                $max = $this->calcMaxLen();
+               $parg = "";
                $dump = "";
                foreach ($this->args->getSpec() as $spec) {
                        $dump .= "    ";
-                       if (isset($spec[0])) {
+                       if (is_numeric($spec[0])) {
+                               $dump .= sprintf("--   %s ", $spec[1]);
+                       } elseif (isset($spec[0])) {
                                $dump .= sprintf("-%s|", $spec[0]);
                        }
-                       $dump .= sprintf("--%s ", $spec[1]);
+                       if (!is_numeric($spec[0])) {
+                               $dump .= sprintf("--%s ", $spec[1]);
+                       }
                        if ($spec[3] & Args::REQARG) {
                                $dump .= "<arg>  ";
                        } elseif ($spec[3] & Args::OPTARG) {
@@ -98,6 +127,9 @@ class Help
                        if ($spec[3] & Args::REQUIRED) {
                                $dump .= " (REQUIRED)";
                        }
+                       if ($spec[3] & Args::MULTI) {
+                               $dump .= " (MULTIPLE)";
+                       }
                        if (isset($spec[4])) {
                                $dump .= sprintf(" [%s]", $spec[4]);
                        }