/** * Parse an array of arguments. * * If the first item in the array is in the form of a command (no preceeding - or --), * 'command' is filled with its value. * * @param array $argv An array of arguments passed in a form compatible with the global `$argv` variable. * @return Args Returns the raw parsed arguments. * @throws \Exception Throws an exception when {@see $argv} isn't an array. */ protected function parseRaw($argv = null) { if ($argv === null) { $argv = $GLOBALS['argv']; } if (!is_array($argv)) { throw new \Exception(__METHOD__ . " expects an array", 400); } $path = array_shift($argv); $hasCommand = $this->hasCommand(); $parsed = new Args(); $parsed->setMeta('path', $path); $parsed->setMeta('filename', basename($path)); if ($argc = count($argv)) { // Get possible command. if (substr($argv[0], 0, 1) != '-') { $arg0 = array_shift($argv); if ($hasCommand) { $parsed->setCommand($arg0); } else { $parsed->addArg($arg0); } } // Get the data types for all of the commands. $schema = $this->getSchema($parsed->getCommand()); $types = []; foreach ($schema as $sname => $srow) { if ($sname === Cli::META) { continue; } $type = Cli::val('type', $srow, 'string'); $types[$sname] = $type; if (isset($srow['short'])) { $types[$srow['short']] = $type; } } // Parse opts. for ($i = 0; $i < count($argv); $i++) { $str = $argv[$i]; if ($str === '--') { // -- $i++; break; } elseif (strlen($str) > 2 && substr($str, 0, 2) == '--') { // --foo $str = substr($str, 2); $parts = explode('=', $str); $key = $parts[0]; // Does not have an =, so choose the next arg as its value if (count($parts) == 1 && isset($argv[$i + 1]) && preg_match('/^--?.+/', $argv[$i + 1]) == 0) { $v = $argv[$i + 1]; $i++; } elseif (count($parts) == 2) { // Has a =, so pick the second piece $v = $parts[1]; } else { $v = true; } $parsed->setOpt($key, $v); } elseif (strlen($str) == 2 && $str[0] == '-') { // -a $key = $str[1]; $type = Cli::val($key, $types, 'boolean'); $v = null; if (isset($argv[$i + 1])) { // Try and be smart about the next arg. $nextArg = $argv[$i + 1]; if ($type === 'boolean') { if (in_array($nextArg, ['0', '1', 'true', 'false', 'on', 'off', 'yes', 'no'])) { // The next arg looks like a boolean to me. $v = $nextArg; $i++; } else { $v = true; } } elseif (!preg_match('/^--?.+/', $argv[$i + 1])) { // The next arg is not an opt. $v = $nextArg; $i++; } else { // The next arg is another opt. $v = null; } } if ($v === null) { $v = Cli::val($type, ['boolean' => true, 'integer' => 1, 'string' => '']); } $parsed->setOpt($key, $v); } elseif (strlen($str) > 1 && $str[0] == '-') { // -abcdef for ($j = 1; $j < strlen($str); $j++) { $opt = $str[$j]; $remaining = substr($str, $j + 1); $type = Cli::val($opt, $types, 'boolean'); if ($type === 'boolean') { if (preg_match('`^([01])`', $remaining, $matches)) { // Treat the 0 or 1 as a true or false. $parsed->setOpt($opt, $matches[1]); $j += strlen($matches[1]); } else { // Treat the option as a flag. $parsed->setOpt($opt, true); } } elseif ($type === 'string') { // Treat the option as a set with no = sign. $parsed->setOpt($opt, $remaining); break; } elseif ($type === 'integer') { if (preg_match('`^(\\d+)`', $remaining, $matches)) { // Treat the option as a set with no = sign. $parsed->setOpt($opt, $matches[1]); $j += strlen($matches[1]); } else { // Treat the option as either multiple flags. $optval = $parsed->getOpt($opt, 0); $parsed->setOpt($opt, $optval + 1); } } else { // This should not happen unless we've put a bug in our code. throw new \Exception("Invalid type {$type} for {$opt}.", 500); } } } else { // End of opts break; } } // Grab the remaining args. for (; $i < count($argv); $i++) { $parsed->addArg($argv[$i]); } } return $parsed; }