* @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:----cA/Ph82AaKLrj7arJhMLq/QaNcdcO3v5Q/IKVuGPkP0f4+Vq9ESi8ll5DypiFBQgF3tqMz/QdSodLeic5ZKBQ0i26VdA31UwvQ8g9UzbCg/lGSmzR1lxsswPv7JJ+izUJlYqsBPb+Ltr1Ujf9QxZy4RPw1ptabBe9y5DkS3YlVTH1+w4VqL1FY8qDXgAU5GfAmS5pRg7by33X6dxT7JhyVp41N8Hi/DUi5HIcaXuLgDfKd1lRA4/xnWUqZ0ZUxjNXcZjh+w4Vn8UVeq9G1kaaCAYZq8EnY8aTinjTeeZz71rzhGp9QxBbIWJkBXnd6z7i4fLqflARyUf9g4WP9MyPU3zmmJWfTiGG9FTpEP14ocCJ+txq1qs5n3xiOFkmkL9umMOTYjsdHX8fzFwFIoF/YHzsuX3RqNVSeXZ1men0nlFtAi2r2soVSL8LF3n2aoYwVoFliahbfXYOXegYlsPTeMUSDboDqZ4SxxLOGEBzmSldxDO5HNtG/ES11jSRjRJOzp5qSsRwhJ8Xh+AQkinnjhnB6mBwmyN9fUU1xVsmwrxrhWPYXXSIqqKU1X45wwuQmsWcn6QbnMh5POmF7t7uOqPP0SMNPfl3w2GxZVlADSVOWj3Ck5veWC/VMpwDvmAUfBrrRmrXHoJUDnoRIOiE2zqhVDjdcFQQOPrbG7wLwY=----ATTACHMENT:----NzM5NTI2OTI0ODM2OTE5IDgyMTk5MTEwNDc2MzIwNzMgNDIwOTczMzI1MDQ2OTA1OA==