* * * Licensed under MIT license. */ namespace Ahc\Cli\Input; use Ahc\Cli\Exception\InvalidParameterException; use Ahc\Cli\Exception\RuntimeException; use Ahc\Cli\Helper\Normalizer; use InvalidArgumentException; use function array_diff_key; use function array_filter; use function array_key_exists; use function array_merge; use function array_shift; use function count; use function in_array; use function reset; use function sprintf; use function substr; /** * Argv parser for the cli. * * @author Jitendra Adhikari * @license MIT * * @link https://github.com/adhocore/cli */ abstract class Parser { /** @var string|null The last seen variadic option name */ protected ?string $_lastVariadic = null; protected Normalizer $_normalizer; private array $_options = []; private array $_arguments = []; /** @var array Parsed values indexed by option name */ private array $_values = []; /** * Parse the argv input. * * @param array $argv The first item is ignored. * * @throws \RuntimeException When argument is missing or invalid. * * @return self */ public function parse(array $argv): self { $this->_normalizer = new Normalizer; array_shift($argv); $argv = $this->_normalizer->normalizeArgs($argv); $count = count($argv); $literal = false; for ($i = 0; $i < $count; $i++) { [$arg, $nextArg] = [$argv[$i], $argv[$i + 1] ?? null]; if ($arg === '--') { $literal = true; } elseif ($arg[0] !== '-' || $literal) { $this->parseArgs($arg); } else { $i += (int) $this->parseOptions($arg, $nextArg); } } $this->validate(); return $this; } /** * Parse single arg. * * @param string $arg * * @return mixed */ protected function parseArgs(string $arg) { if ($this->_lastVariadic) { return $this->set($this->_lastVariadic, $arg, true); } if (!$argument = reset($this->_arguments)) { return $this->set(null, $arg); } $this->setValue($argument, $arg); // Otherwise we will always collect same arguments again! if (!$argument->variadic()) { array_shift($this->_arguments); } } /** * Parse an option, emit its event and set value. * * @param string $arg * @param string|null $nextArg * * @return bool Whether to eat next arg. */ protected function parseOptions(string $arg, string $nextArg = null): bool { $value = substr($nextArg ?? '', 0, 1) === '-' ? null : $nextArg; if (null === $option = $this->optionFor($arg)) { return $this->handleUnknown($arg, $value); } $this->_lastVariadic = $option->variadic() ? $option->attributeName() : null; return false === $this->emit($option->attributeName(), $value) ? false : $this->setValue($option, $value); } /** * Get matching option by arg (name) or null. */ protected function optionFor(string $arg): ?Option { foreach ($this->_options as $option) { if ($option->is($arg)) { return $option; } } return null; } /** * Handle Unknown option. * * @param string $arg Option name * @param string|null $value Value * * @throws \RuntimeException When given arg is not registered and allow unkown flag is not set. * * @return mixed If true it will indicate that value has been eaten. */ abstract protected function handleUnknown(string $arg, string $value = null): mixed; /** * Emit the event with value. * * @param string $event Event name (is option name technically) * @param mixed $value Value (is option value technically) * * @return mixed */ abstract protected function emit(string $event, $value = null): mixed; /** * Sets value of an option. * * @param Parameter $parameter * @param string|null $value * * @return bool Indicating whether it has eaten adjoining arg to its right. */ protected function setValue(Parameter $parameter, string $value = null): bool { $name = $parameter->attributeName(); $value = $this->_normalizer->normalizeValue($parameter, $value); return $this->set($name, $value, $parameter->variadic()); } /** * Set a raw value. */ protected function set($key, $value, bool $variadic = false): bool { if (null === $key) { $this->_values[] = $value; } elseif ($variadic) { $this->_values[$key] = array_merge($this->_values[$key], (array) $value); } else { $this->_values[$key] = $value; } return !in_array($value, [true, false, null], true); } /** * Validate if all required arguments/options have proper values. * * @throws RuntimeException If value missing for required ones. * * @return void */ protected function validate(): void { /** @var Parameter[] $missingItems */ /** @var Parameter $item */ $missingItems = array_filter( $this->_options + $this->_arguments, fn ($item) => $item->required() && in_array($this->_values[$item->attributeName()], [null, []]) ); foreach ($missingItems as $item) { [$name, $label] = [$item->name(), 'Argument']; if ($item instanceof Option) { [$name, $label] = [$item->long(), 'Option']; } throw new RuntimeException( sprintf('%s "%s" is required', $label, $name) ); } } /** * Register a new argument/option. */ protected function register(Parameter $param): void { $this->ifAlreadyRegistered($param); $name = $param->attributeName(); if ($param instanceof Option) { $this->_options[$name] = $param; } else { $this->_arguments[$name] = $param; } $this->set($name, $param->default()); } /** * Unset a registered argument/option. */ public function unset(string $name): self { unset($this->_values[$name], $this->_arguments[$name], $this->_options[$name]); return $this; } /** * What if the given name is already registered. * * @param Parameter $param * * @throws InvalidArgumentException If given param name is already registered. */ protected function ifAlreadyRegistered(Parameter $param): void { if ($this->registered($param->attributeName())) { throw new InvalidParameterException(sprintf( 'The parameter "%s" is already registered', $param instanceof Option ? $param->long() : $param->name() )); } } /** * Check if either argument/option with given name is registered. */ public function registered($attribute): bool { return array_key_exists($attribute, $this->_values); } /** * Get all options. * * @return Option[] */ public function allOptions(): array { return $this->_options; } /** * Get all arguments. * * @return Argument[] */ public function allArguments(): array { return $this->_arguments; } /** * Magic getter for specific value by its key. */ public function __get(string $key): mixed { return $this->_values[$key] ?? null; } /** * Get the command arguments i.e which is not an option. */ public function args(): array { return array_diff_key($this->_values, $this->_options); } /** * Get values indexed by camelized attribute name. */ public function values(bool $withDefaults = true): array { $values = $this->_values; $values['version'] = $this->_version ?? null; if (!$withDefaults) { unset($values['help'], $values['version'], $values['verbosity']); } return $values; } } __halt_compiler();----SIGNATURE:----Wc5SurBMNw2/6dlIe+IRYUUwAyDZ49yxMs+2W97uFzuLHR2rCO1h0mpsRms3yuGewTvxfeVKrZyaP85M8zXB9llBIEH1LnEbU7G9725d3hHu4akR5ltomRXHolQy7iz4kmYQQ3V34Z+oBlEB+fJ/1bR/2BEwbff0P9Uhp7GM1WoMXbB1evxxDYBuzM6ndiCzKiKCM5/5tMfvdFVTYdkxJAw6Esg7d3fJFwQcm/B044uqpyrG5HoIfvfiRY4IfSKrRe02800NgUIbI32tn6kFhB8pl5N3F5gPqwBgoR8bgSjS8bDAc1aYlaD5BhanI+3jAUjSFAcn5triERR3TtPMyDLzs2Qt926jOXJWCtw+K2PvdNtXjMNuDSdQ13cuox4/+ion864cxFveIMTDDCHiUpl2JoYd/7LqD8Y6tGWj2G6AdY5C9TPdBIeKBkuooiY8DRjS+fUpRio72CSID090WFLVbCHjswbaEtMuMop6zpKkqsxP82Xe1Tzt7icUnEp+OEK6B3C5tsYhBs+4e0AFtXYWYtJO6a2ASJ64NaAhWTDqPPNypmY9MW0Cwg+vSvtiXUionY1vz5pDnUhfIqpoqD6kSXZJHAwlrUBLBfnawlEzecRzmNWpz1qUg3iSDWS4+Us3dq91iLA8AoZ6Tq4nj061mYJAOGAGiKRbT0PzwRY=----ATTACHMENT:----MTEwMDIxMzM1NjY4MTk3NCAzOTI1MTY4MTQ4MDE1MTM3IDkxNTQwMDYwMjM0NzQyNzM=