From b73e0f70cab8ebf4fc72d120a0aa12deb9b55723 Mon Sep 17 00:00:00 2001 From: Alex Phillips Date: Mon, 23 Feb 2015 20:56:38 -0500 Subject: [PATCH 01/92] initial commit --- src/Titon/Console/ArgumentLexer.hh | 126 ++++++++++++++++++ src/Titon/Console/Arguments.hh | 99 ++++++++++++++ src/Titon/Console/Command.hh | 12 ++ src/Titon/Console/Command/AbstractCommand.hh | 14 ++ src/Titon/Console/Console.hh | 23 ++++ src/Titon/Console/InputDefinition.php | 17 +++ .../AbstractInputDefinition.hh | 56 ++++++++ src/Titon/Console/InputDefinition/Argument.hh | 12 ++ src/Titon/Console/InputDefinition/Flag.hh | 65 +++++++++ src/Titon/Console/InputDefinition/Option.hh | 38 ++++++ src/Titon/Console/bootstrap.hh | 30 +++++ 11 files changed, 492 insertions(+) create mode 100644 src/Titon/Console/ArgumentLexer.hh create mode 100644 src/Titon/Console/Arguments.hh create mode 100644 src/Titon/Console/Command.hh create mode 100644 src/Titon/Console/Command/AbstractCommand.hh create mode 100644 src/Titon/Console/Console.hh create mode 100644 src/Titon/Console/InputDefinition.php create mode 100644 src/Titon/Console/InputDefinition/AbstractInputDefinition.hh create mode 100644 src/Titon/Console/InputDefinition/Argument.hh create mode 100644 src/Titon/Console/InputDefinition/Flag.hh create mode 100644 src/Titon/Console/InputDefinition/Option.hh create mode 100644 src/Titon/Console/bootstrap.hh diff --git a/src/Titon/Console/ArgumentLexer.hh b/src/Titon/Console/ArgumentLexer.hh new file mode 100644 index 0000000..74bbf26 --- /dev/null +++ b/src/Titon/Console/ArgumentLexer.hh @@ -0,0 +1,126 @@ + $items = array(); + + protected int $position; + + protected int $length = 0; + + protected ?string $current; + + protected bool $first = true; + + public function __construct(array $items = array()) { + $this->items = $items; + $this->length = count($items); + $this->position = 0; + } + + public function current(): mixed { + return $this->items[$this->position]; + } + + public function end(): bool { + return ($this->position + 1) === $this->length; + } + + public function isArgument(string $value): bool { + return $this->isShort($value) || $this->isLong($value); + } + + public function isLong(string $value): bool { + return (0 == strncmp($value, '--', 2)); + } + + public function isShort(string $value): bool { + return !$this->isLong($value) && (0 == strncmp($value, '-', 1)); + } + + public function key(): int { + return $this->position; + } + + public function next(): void { + if ($this->valid()) { + $this->shift(); + } + } + + public function peek(): string { + return $this->processInput($this->items[0]); + } + + public function processInput(string $input): RawInput { + $raw = $input; + $value = $input; + + if ($this->isLong($input)) { + $value = substr($input, 2); + } else { + if ($this->isShort($input)) { + $value = substr($input, 1); + } + } + + return shape( + 'raw' => $raw, + 'value' => $value + ); + } + + public function rewind(): void { + $this->shift(); + + if ($this->first) { + $this->position = 0; + $this->first = false; + } + } + + public function shift(): void { + $key = array_shift($this->items); + + $matches = []; + if (preg_match('#\A([^\s\'"=]+)=(.+?)$#', $key, $matches)) { + $key = $matches[1]; + array_unshift($this->items, $matches[2]); + } else { + $this->position += 1; + } + + $this->current = $this->processInput($key); + + if ($this->isShort($key['raw']) && strlen($key['value']) > 1) { + $exploded = array(); + for ($i = strlen($key['value']); $i > 0; $i--) { + array_push($exploded, $key['value'][$i - 1]); + } + $key['value'] = array_pop($exploded); + $key['raw'] = '-' . $key['value']; + + foreach ($exploded as $piece) { + $this->unshift('-' . $piece); + } + } + } + + public function unshift(string $item): void { + array_unshift($this->items, $item); + $this->length += 1; + } + + public function valid(): bool { + return $this->position < count($this->items); + } +} \ No newline at end of file diff --git a/src/Titon/Console/Arguments.hh b/src/Titon/Console/Arguments.hh new file mode 100644 index 0000000..8543ed6 --- /dev/null +++ b/src/Titon/Console/Arguments.hh @@ -0,0 +1,99 @@ + $args = []) { + if (count($args) === 0) { + $args = array_slice($_SERVER['argv'], 1); + } + + $this->input = new ArgumentLexer($args); + } + + public function parse() { + foreach ($this->input as $val) { + if ($this->parseFlag($val)) { + continue; + } + if ($this->parseOption($val)) { + continue; + } +// if ($this->parseArgument($val)) { +// continue; +// } + } + } + + public function parseFlag($key): bool { + if ($this->flags->contains($key)) { + return false; + } + + if ($this->flags[$key]->isStackable()) { + $this->flags[$key]->increaseValue(); + } else { + $this->flags[$key]->setValue(1); + } + + return true; + } + + public function parseOption(string $key): bool { + if (!$this->options->contains($key))) { + return false; + } + + // Peak ahead to make sure we get a value. + if (!$this->input->end() && !$this->input->isArgument($this->input->peek())) + $this->options[$key]->setValue($this->options[$key]->getDefault()); + + return true; + } + + $values = []; + foreach ($this->input as $value) { + array_push($values, $value); + if (!$this->input->end() && $this->input->isArgument($this->input->peek())) { + break; + } + } + + $value = join($values, ' '); + + if (!$value && $value !== false) { + $value = 1; + } + + $this->options[$key]->setValue($value); + + return true; + } +} \ No newline at end of file diff --git a/src/Titon/Console/Command.hh b/src/Titon/Console/Command.hh new file mode 100644 index 0000000..eb1e34f --- /dev/null +++ b/src/Titon/Console/Command.hh @@ -0,0 +1,12 @@ +commands[$command->getName()] = $command; + + return $this; + } +} \ No newline at end of file diff --git a/src/Titon/Console/InputDefinition.php b/src/Titon/Console/InputDefinition.php new file mode 100644 index 0000000..a55e3d2 --- /dev/null +++ b/src/Titon/Console/InputDefinition.php @@ -0,0 +1,17 @@ +name = $name; + $this->description = $description; + $this->mode = $mode; + } + + public function exists(): bool { + if (is_null($this->value)) { + return false; + } + + return true; + } + + public function getDescription(): string { + return $this->description; + } + + public function getName(): string { + return $this->name; + } + + public function getValue(): mixed { + if (!is_null($this->value)) { + return $this->value; + } + + return $this-> default; + } +} \ No newline at end of file diff --git a/src/Titon/Console/InputDefinition/Argument.hh b/src/Titon/Console/InputDefinition/Argument.hh new file mode 100644 index 0000000..d2447e3 --- /dev/null +++ b/src/Titon/Console/InputDefinition/Argument.hh @@ -0,0 +1,12 @@ +default = 0; + $this->stackable = $stackable; + } + + public function getValue(): int { + if (!is_null($this->value)) { + return (int)$this->value; + } + + return (int)$this->default; + } + + public function increaseValue(): this { + if ($this->stackable) { + if (is_null($this->value)) { + $this->value = 1; + } else { + invariant($this->value instanceof \int, 'Must be an annotation.'); + + $this->value++; + } + } + + return $this; + } + + public function isStackable(): bool { + return $this->stackable; + } + + public function setAlias(string $alias): this { + if (strlen($alias) > strlen($this->name)) { + $this->alias = $this->name; + $this->name = $alias; + } else { + $this->alias = $alias; + } + + return $this; + } + + public function setValue(int $value): this { + $this->value = $value; + + return $this; + } +} \ No newline at end of file diff --git a/src/Titon/Console/InputDefinition/Option.hh b/src/Titon/Console/InputDefinition/Option.hh new file mode 100644 index 0000000..1af388c --- /dev/null +++ b/src/Titon/Console/InputDefinition/Option.hh @@ -0,0 +1,38 @@ +value)) { + return $this->value; + } + + return $default; + } + + public function setAlias(string $alias): this { + if (strlen($alias) > strlen($this->name)) { + $this->alias = $this->name; + $this->name = $alias; + } else { + $this->alias = $alias; + } + + return $this; + } + + public function setDefault(string $default): this { + $this->default = $default; + + return $this; + } +} \ No newline at end of file diff --git a/src/Titon/Console/bootstrap.hh b/src/Titon/Console/bootstrap.hh new file mode 100644 index 0000000..13bc983 --- /dev/null +++ b/src/Titon/Console/bootstrap.hh @@ -0,0 +1,30 @@ + string, + 'value' => string + ); + type CommandMap = Map; + type FlagMap = Map; + type OptionMap = Map; + type ArgumentMap = Map; +} From cf7cfb34d50dc5c680d139ad83cc3b6d1959d043 Mon Sep 17 00:00:00 2001 From: Alex Phillips Date: Mon, 23 Feb 2015 20:58:09 -0500 Subject: [PATCH 02/92] fixed hh extension --- src/Titon/Console/{InputDefinition.php => InputDefinition.hh} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/Titon/Console/{InputDefinition.php => InputDefinition.hh} (100%) diff --git a/src/Titon/Console/InputDefinition.php b/src/Titon/Console/InputDefinition.hh similarity index 100% rename from src/Titon/Console/InputDefinition.php rename to src/Titon/Console/InputDefinition.hh From 14d294faad0f281e457583af5697fb8e62ea350c Mon Sep 17 00:00:00 2001 From: Alex Phillips Date: Tue, 24 Feb 2015 17:42:54 -0500 Subject: [PATCH 03/92] initial commit --- src/Titon/Console/composer.json | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/Titon/Console/composer.json diff --git a/src/Titon/Console/composer.json b/src/Titon/Console/composer.json new file mode 100644 index 0000000..375ddc5 --- /dev/null +++ b/src/Titon/Console/composer.json @@ -0,0 +1,33 @@ +{ + "name": "titon/console", + "type": "library", + "description": "", + "keywords": ["titon", "console", "cli"], + "homepage": "http://titon.io", + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Miles Johnson", + "homepage": "http://milesj.me" + }, + { + "name": "Alex Phillips", + "homepage": "http://wootables.com" + } + ], + "support": { + "irc": "irc://irc.freenode.org/titon", + "source": "https://github.com/titon/console" + }, + "require": { + "hhvm": ">=3.4.0" + }, + "autoload": { + "psr-4": { + "Titon\\Console\\": "src/" + }, + "files": [ + "src/Titon/Console/bootstrap.hh" + ] + } +} From ad8e97e101bda7a794df3d1f514bc80656c7036f Mon Sep 17 00:00:00 2001 From: Alex Phillips Date: Tue, 24 Feb 2015 17:43:42 -0500 Subject: [PATCH 04/92] fleshed out the argument parser --- composer.json | 1 + src/Titon/Console/ArgumentLexer.hh | 38 +++--- src/Titon/Console/Arguments.hh | 108 ++++++++++++++---- .../Exception/MissingValueException.hh | 20 ++++ .../AbstractInputDefinition.hh | 6 +- src/Titon/Console/InputDefinition/Argument.hh | 5 + src/Titon/Console/InputDefinition/Option.hh | 4 + src/Titon/Console/bootstrap.hh | 2 +- tests/Titon/Console/ArgumentParserTest.hh | 30 +++++ 9 files changed, 176 insertions(+), 38 deletions(-) create mode 100644 src/Titon/Console/Exception/MissingValueException.hh create mode 100644 tests/Titon/Console/ArgumentParserTest.hh diff --git a/composer.json b/composer.json index f96161b..abf68fd 100644 --- a/composer.json +++ b/composer.json @@ -40,6 +40,7 @@ "src/Titon/Annotation/bootstrap.hh", "src/Titon/Cache/bootstrap.hh", "src/Titon/Common/bootstrap.hh", + "src/Titon/Console/bootstrap.hh", "src/Titon/Context/bootstrap.hh", "src/Titon/Controller/bootstrap.hh", "src/Titon/Debug/bootstrap.hh", diff --git a/src/Titon/Console/ArgumentLexer.hh b/src/Titon/Console/ArgumentLexer.hh index 74bbf26..69bcb3a 100644 --- a/src/Titon/Console/ArgumentLexer.hh +++ b/src/Titon/Console/ArgumentLexer.hh @@ -7,13 +7,13 @@ namespace Titon\Console; -use Iterator; +use Titon\Common\ArgumentList; -class ArgumentLexer implements Iterator { +class ArgumentLexer implements Iterator { - protected array $items = array(); + public array $items = array(); - protected int $position; + protected int $position = 0; protected int $length = 0; @@ -21,18 +21,17 @@ class ArgumentLexer implements Iterator { protected bool $first = true; - public function __construct(array $items = array()) { + public function __construct(ArgumentList $items) { $this->items = $items; $this->length = count($items); - $this->position = 0; } - public function current(): mixed { - return $this->items[$this->position]; + public function current(): Tv { + return $this->current; } public function end(): bool { - return ($this->position + 1) === $this->length; + return ($this->position + 1) == $this->length; } public function isArgument(string $value): bool { @@ -57,11 +56,15 @@ class ArgumentLexer implements Iterator { } } - public function peek(): string { - return $this->processInput($this->items[0]); + public function peek(): ?Input { + if (!empty($this->items)) { + return $this->processInput($this->items[0]); + } + + return null; } - public function processInput(string $input): RawInput { + public function processInput(string $input): Input { $raw = $input; $value = $input; @@ -81,7 +84,6 @@ class ArgumentLexer implements Iterator { public function rewind(): void { $this->shift(); - if ($this->first) { $this->position = 0; $this->first = false; @@ -96,7 +98,11 @@ class ArgumentLexer implements Iterator { $key = $matches[1]; array_unshift($this->items, $matches[2]); } else { - $this->position += 1; + $this->position++; + } + + if (is_null($key)) { + return; } $this->current = $this->processInput($key); @@ -117,10 +123,10 @@ class ArgumentLexer implements Iterator { public function unshift(string $item): void { array_unshift($this->items, $item); - $this->length += 1; + $this->length++; } public function valid(): bool { - return $this->position < count($this->items); + return ($this->position < $this->length); } } \ No newline at end of file diff --git a/src/Titon/Console/Arguments.hh b/src/Titon/Console/Arguments.hh index 8543ed6..5312365 100644 --- a/src/Titon/Console/Arguments.hh +++ b/src/Titon/Console/Arguments.hh @@ -7,7 +7,11 @@ namespace Titon\Console; -use Iterator; +use Titon\Common\ArgumentList; +use Titon\Console\InputDefinition\Argument; +use Titon\Console\InputDefinition\Flag; +use Titon\Console\InputDefinition\Option; +use Titon\Console\Exception\MissingValueException; class Arguments { @@ -30,14 +34,62 @@ class Arguments { * * @param array $options An array of options for this parser. */ - public function __construct(array $args = []) { - if (count($args) === 0) { + public function __construct(?ArgumentList $args = null) { + if (is_null($args) === 0) { $args = array_slice($_SERVER['argv'], 1); } $this->input = new ArgumentLexer($args); } + public function addArgument(Argument $argument): this { + $this->arguments[$argument->getName()] = $argument; + + return $this; + } + + public function addFlag(Flag $flag): this { + $this->flags[$flag->getName()] = $flag; + + return $this; + } + + public function addOption(Option $option): this { + $this->options[$option->getName()] = $option; + + return $this; + } + + public function getArgument(string $key): ?Argument { + if (!is_null($argument = $this->arguments->get($key))) { + return $argument; + } + + return null; + } + + public function getFlag(string $key): ?Flag { + if (!is_null($flag = $this->flags->get($key))) { + return $flag; + } + + foreach ($this->flags as $flag) { + if ($key === $flag->getName() || $key === $flag->getAlias()) { + return $flag; + } + } + + return null; + } + + public function getOption(string $key): ?Option { + if (!is_null($option = $this->options->get($key))) { + return $option; + } + + return null; + } + public function parse() { foreach ($this->input as $val) { if ($this->parseFlag($val)) { @@ -46,14 +98,27 @@ class Arguments { if ($this->parseOption($val)) { continue; } -// if ($this->parseArgument($val)) { -// continue; -// } + if ($this->parseArgument($val)) { + continue; + } + } + } + + public function parseArgument(Input $key): bool { + foreach ($this->arguments as $argument) { + if (is_null($argument->getValue())) { + $argument->setValue($key['raw']); + + return true; + } } + + return false; } - public function parseFlag($key): bool { - if ($this->flags->contains($key)) { + public function parseFlag(Input $key): bool { + $key = $key['value']; + if (is_null($flag = $this->flags->get($key))) { return false; } @@ -66,24 +131,27 @@ class Arguments { return true; } - public function parseOption(string $key): bool { - if (!$this->options->contains($key))) { + public function parseOption(Input $key): bool { + $key = $key['value']; + if (is_null($option = $this->options->get($key))) { return false; } // Peak ahead to make sure we get a value. - if (!$this->input->end() && !$this->input->isArgument($this->input->peek())) - $this->options[$key]->setValue($this->options[$key]->getDefault()); - - return true; + $nextValue = $this->input->peek(); + if (!$this->input->end() && $this->input->isArgument($nextValue['raw'])) { + throw new MissingValueException("No value is present for option $key"); } - $values = []; + $values = Vector {}; foreach ($this->input as $value) { - array_push($values, $value); - if (!$this->input->end() && $this->input->isArgument($this->input->peek())) { - break; - } + $values[] = $value['raw']; + break; + +// $nextValue = $this->input->peek(); +// if (!$this->input->end() && $this->input->isArgument($nextValue['raw'])) { +// break; +// } } $value = join($values, ' '); @@ -92,7 +160,7 @@ class Arguments { $value = 1; } - $this->options[$key]->setValue($value); + $option->setValue($value); return true; } diff --git a/src/Titon/Console/Exception/MissingValueException.hh b/src/Titon/Console/Exception/MissingValueException.hh new file mode 100644 index 0000000..44eedc2 --- /dev/null +++ b/src/Titon/Console/Exception/MissingValueException.hh @@ -0,0 +1,20 @@ +default; + } + public function getDescription(): string { return $this->description; } @@ -51,6 +55,6 @@ abstract class AbstractInputDefinition implements InputDefinition { return $this->value; } - return $this-> default; + return $this->default; } } \ No newline at end of file diff --git a/src/Titon/Console/InputDefinition/Argument.hh b/src/Titon/Console/InputDefinition/Argument.hh index d2447e3..2d650cb 100644 --- a/src/Titon/Console/InputDefinition/Argument.hh +++ b/src/Titon/Console/InputDefinition/Argument.hh @@ -9,4 +9,9 @@ namespace Titon\Console\InputDefinition; class Argument extends AbstractInputDefinition { + public function setValue(string $value): this { + $this->value = $value; + + return $this; + } } \ No newline at end of file diff --git a/src/Titon/Console/InputDefinition/Option.hh b/src/Titon/Console/InputDefinition/Option.hh index 1af388c..ede9037 100644 --- a/src/Titon/Console/InputDefinition/Option.hh +++ b/src/Titon/Console/InputDefinition/Option.hh @@ -35,4 +35,8 @@ class Option extends AbstractInputDefinition { return $this; } + + public function setValue(string $value): this { + $this->value = $value; + } } \ No newline at end of file diff --git a/src/Titon/Console/bootstrap.hh b/src/Titon/Console/bootstrap.hh index 13bc983..794d986 100644 --- a/src/Titon/Console/bootstrap.hh +++ b/src/Titon/Console/bootstrap.hh @@ -20,7 +20,7 @@ namespace Titon\Console { use Titon\Console\InputDefinition\Argument; type Input = shape( - 'raw' => string, + 'raw' => string, 'value' => string ); type CommandMap = Map; diff --git a/tests/Titon/Console/ArgumentParserTest.hh b/tests/Titon/Console/ArgumentParserTest.hh new file mode 100644 index 0000000..8883522 --- /dev/null +++ b/tests/Titon/Console/ArgumentParserTest.hh @@ -0,0 +1,30 @@ +addFlag(new Flag('f', 'Foo!')); + $args->addOption(new Option('name', 'Your name')); + $args->addArgument(new Argument('bar', 'Bar!')); + + $args->parse(); + + $this->assertEquals(1, $args->getFlag('f')->getValue()); + $this->assertEquals('Alex Phillips', $args->getOption('name')->getValue()); + $this->assertEquals('FooBar', $args->getArgument('bar')->getValue()); + } +} \ No newline at end of file From ced7517016a7c73f7712bc830b44203c9673fec9 Mon Sep 17 00:00:00 2001 From: Alex Phillips Date: Wed, 25 Feb 2015 17:36:36 -0500 Subject: [PATCH 05/92] added argument parsing support for flags, options, and arguments --- src/Titon/Console/ArgumentBag.hh | 26 ++++ src/Titon/Console/ArgumentLexer.hh | 30 +++-- src/Titon/Console/Arguments.hh | 56 ++++----- .../AbstractInputDefinition.hh | 29 ++++- src/Titon/Console/InputDefinition/Flag.hh | 15 +-- src/Titon/Console/InputDefinition/Option.hh | 13 -- tests/Titon/Console/ArgumentParserTest.hh | 113 +++++++++++++++++- 7 files changed, 203 insertions(+), 79 deletions(-) create mode 100644 src/Titon/Console/ArgumentBag.hh diff --git a/src/Titon/Console/ArgumentBag.hh b/src/Titon/Console/ArgumentBag.hh new file mode 100644 index 0000000..c99a1c4 --- /dev/null +++ b/src/Titon/Console/ArgumentBag.hh @@ -0,0 +1,26 @@ + { + + public function get(string $key, ?InputDefinition $default = null): ?InputDefinition { + $arg = null; + if (is_null($arg = parent::get($key))) { + foreach ($this as $val) { + if ($key === $val->getAlias()) { + return $val; + } + } + } + + return $arg; + } +} \ No newline at end of file diff --git a/src/Titon/Console/ArgumentLexer.hh b/src/Titon/Console/ArgumentLexer.hh index 69bcb3a..f70e1f4 100644 --- a/src/Titon/Console/ArgumentLexer.hh +++ b/src/Titon/Console/ArgumentLexer.hh @@ -34,12 +34,27 @@ class ArgumentLexer implements Iterator { return ($this->position + 1) == $this->length; } + private function explode(): void { + if (!$this->isShort($this->current['raw']) || strlen($this->current['value']) <= 1) { + return false; + } + + $exploded = str_split($this->current['value']); + + $this->current['value'] = array_pop($exploded); + $this->current['raw'] = '-' . $this->current['value']; + + foreach ($exploded as $piece) { + $this->unshift('-' . $piece); + } + } + public function isArgument(string $value): bool { return $this->isShort($value) || $this->isLong($value); } public function isLong(string $value): bool { - return (0 == strncmp($value, '--', 2)); + return (0 == strncmp($value, '--', 2)); } public function isShort(string $value): bool { @@ -107,18 +122,7 @@ class ArgumentLexer implements Iterator { $this->current = $this->processInput($key); - if ($this->isShort($key['raw']) && strlen($key['value']) > 1) { - $exploded = array(); - for ($i = strlen($key['value']); $i > 0; $i--) { - array_push($exploded, $key['value'][$i - 1]); - } - $key['value'] = array_pop($exploded); - $key['raw'] = '-' . $key['value']; - - foreach ($exploded as $piece) { - $this->unshift('-' . $piece); - } - } + $this->explode(); } public function unshift(string $item): void { diff --git a/src/Titon/Console/Arguments.hh b/src/Titon/Console/Arguments.hh index 5312365..437bf16 100644 --- a/src/Titon/Console/Arguments.hh +++ b/src/Titon/Console/Arguments.hh @@ -15,47 +15,42 @@ use Titon\Console\Exception\MissingValueException; class Arguments { - protected FlagMap $flags = Map {}; - - protected OptionMap $options = Map {}; - protected ArgumentMap $arguments = Map {}; protected CommandMap $commands = Map {}; + protected ArgumentBag $flags; + protected ArgumentLexer $input; - /** - * Initializes the argument parser. If you wish to change the default behaviour - * you may pass an array of options as the first argument. Valid options are - * `'help'` and `'strict'`, each a boolean. - * - * `'help'` is `true` by default, `'strict'` is false by default. - * - * @param array $options An array of options for this parser. - */ + protected OptionMap $options = Map {}; + public function __construct(?ArgumentList $args = null) { if (is_null($args) === 0) { $args = array_slice($_SERVER['argv'], 1); } + $this->flags = new ArgumentBag(); + $this->options = new ArgumentBag(); + $this->arguments = new ArgumentBag(); + $this->input = new ArgumentLexer($args); } public function addArgument(Argument $argument): this { - $this->arguments[$argument->getName()] = $argument; + $this->arguments->set($argument->getName(), $argument); return $this; } public function addFlag(Flag $flag): this { - $this->flags[$flag->getName()] = $flag; + $this->flags->set($flag->getName(), $flag); return $this; } public function addOption(Option $option): this { - $this->options[$option->getName()] = $option; + $this->options->set($option->getName(), $option); return $this; } @@ -122,10 +117,10 @@ class Arguments { return false; } - if ($this->flags[$key]->isStackable()) { - $this->flags[$key]->increaseValue(); + if ($this->flags->get($key)->isStackable()) { + $this->flags->get($key)->increaseValue(); } else { - $this->flags[$key]->setValue(1); + $this->flags->get($key)->setValue(1); } return true; @@ -143,21 +138,16 @@ class Arguments { throw new MissingValueException("No value is present for option $key"); } - $values = Vector {}; - foreach ($this->input as $value) { - $values[] = $value['raw']; - break; - -// $nextValue = $this->input->peek(); -// if (!$this->input->end() && $this->input->isArgument($nextValue['raw'])) { -// break; -// } - } + $this->input->shift(); + $value = $this->input->current(); - $value = join($values, ' '); - - if (!$value && $value !== false) { - $value = 1; + $matches = []; + if (preg_match('#\A"(.+)"$#', $value['raw'], $matches)) { + $value = $matches[1]; + } else if (preg_match("#\A'(.+)'$#", $value['raw'], $matches)) { + $value = $matches[1]; + } else { + $value = $value['raw']; } $option->setValue($value); diff --git a/src/Titon/Console/InputDefinition/AbstractInputDefinition.hh b/src/Titon/Console/InputDefinition/AbstractInputDefinition.hh index 30f5498..c373dac 100644 --- a/src/Titon/Console/InputDefinition/AbstractInputDefinition.hh +++ b/src/Titon/Console/InputDefinition/AbstractInputDefinition.hh @@ -14,22 +14,28 @@ abstract class AbstractInputDefinition implements InputDefinition { const VALUE_OPTIONAL = 0; const VALUE_REQUIRED = 1; - protected string $name; + protected ?string $alias; + + protected mixed $default; protected string $description; protected int $mode = self::VALUE_OPTIONAL; - protected mixed $value; + protected string $name; - protected mixed $default; + protected mixed $value; - public function __construct(string $name, string $description, int $mode = self::VALUE_OPTIONAL) { + public function __construct(string $name, string $description = '', int $mode = self::VALUE_OPTIONAL) { $this->name = $name; $this->description = $description; $this->mode = $mode; } + public function alias(string $alias): this { + return $this->setAlias($alias); + } + public function exists(): bool { if (is_null($this->value)) { return false; @@ -38,6 +44,10 @@ abstract class AbstractInputDefinition implements InputDefinition { return true; } + public function getAlias(): ?string { + return $this->alias; + } + public function getDefault(): mixed { return $this->default; } @@ -57,4 +67,15 @@ abstract class AbstractInputDefinition implements InputDefinition { return $this->default; } + + public function setAlias(string $alias): this { + if (strlen($alias) > strlen($this->name)) { + $this->alias = $this->name; + $this->name = $alias; + } else { + $this->alias = $alias; + } + + return $this; + } } \ No newline at end of file diff --git a/src/Titon/Console/InputDefinition/Flag.hh b/src/Titon/Console/InputDefinition/Flag.hh index 39c0caa..fbe5512 100644 --- a/src/Titon/Console/InputDefinition/Flag.hh +++ b/src/Titon/Console/InputDefinition/Flag.hh @@ -9,11 +9,9 @@ namespace Titon\Console\InputDefinition; class Flag extends AbstractInputDefinition { - protected ?string $alias; - protected bool $stackable = false; - public function __construct(string $name, string $description, int $mode = self::VALUE_OPTIONAL, bool $stackable = false) { + public function __construct(string $name, string $description = '', int $mode = self::VALUE_OPTIONAL, bool $stackable = false) { parent::__construct($name, $description, $mode); $this->default = 0; @@ -33,7 +31,7 @@ class Flag extends AbstractInputDefinition { if (is_null($this->value)) { $this->value = 1; } else { - invariant($this->value instanceof \int, 'Must be an annotation.'); + invariant(is_int($this->value), 'Must be an integer.'); $this->value++; } @@ -46,13 +44,8 @@ class Flag extends AbstractInputDefinition { return $this->stackable; } - public function setAlias(string $alias): this { - if (strlen($alias) > strlen($this->name)) { - $this->alias = $this->name; - $this->name = $alias; - } else { - $this->alias = $alias; - } + public function setStackable(bool $stackable): this { + $this->stackable = $stackable; return $this; } diff --git a/src/Titon/Console/InputDefinition/Option.hh b/src/Titon/Console/InputDefinition/Option.hh index ede9037..9bf6db4 100644 --- a/src/Titon/Console/InputDefinition/Option.hh +++ b/src/Titon/Console/InputDefinition/Option.hh @@ -9,8 +9,6 @@ namespace Titon\Console\InputDefinition; class Option extends AbstractInputDefinition { - protected ?string $alias; - public function getValue(mixed $default = null): mixed { if (!is_null($this->value)) { return $this->value; @@ -19,17 +17,6 @@ class Option extends AbstractInputDefinition { return $default; } - public function setAlias(string $alias): this { - if (strlen($alias) > strlen($this->name)) { - $this->alias = $this->name; - $this->name = $alias; - } else { - $this->alias = $alias; - } - - return $this; - } - public function setDefault(string $default): this { $this->default = $default; diff --git a/tests/Titon/Console/ArgumentParserTest.hh b/tests/Titon/Console/ArgumentParserTest.hh index 8883522..63a34d1 100644 --- a/tests/Titon/Console/ArgumentParserTest.hh +++ b/tests/Titon/Console/ArgumentParserTest.hh @@ -10,21 +10,124 @@ use Titon\Console\InputDefinition\Argument; class ArgumentParserTest extends TestCase { public function testParseFlags() { + /* + * Check basic flag + */ + $args = new Arguments([ + '--foo', + ]); + $args->addFlag((new Flag('foo'))->alias('f')); + $args->parse(); + + $this->assertEquals(1, $args->getFlag('foo')->getValue()); + $this->assertEquals(1, $args->getFlag('f')->getValue()); + $args = new Arguments([ '-f', - 'FooBar', + ]); + $args->addFlag((new Flag('foo'))->alias('f')); + $args->parse(); + + $this->assertEquals(1, $args->getFlag('foo')->getValue()); + $this->assertEquals(1, $args->getFlag('f')->getValue()); + + /* + * Check stacked, but different, flags + */ + $args = new Arguments([ + '-fb', + ]); + $args->addFlag((new Flag('foo'))->alias('f')); + $args->addFlag((new Flag('bar'))->alias('b')); + $args->parse(); + + $this->assertEquals(1, $args->getFlag('foo')->getValue()); + $this->assertEquals(1, $args->getFlag('bar')->getValue()); + + /* + * Check stacked flag + */ + $args = new Arguments([ + '-vvv', + ]); + $args->addFlag((new Flag('v'))->setStackable(true)); + $args->parse(); + + $this->assertEquals(3, $args->getFlag('v')->getValue()); + } + + public function testParseOptions() { + $args = new Arguments([ '--name', 'Alex Phillips', ]); + $args->addOption((new Option('name'))->alias('n')); + $args->addArgument(new Argument('bar', 'Bar!')); + $args->parse(); + + $this->assertEquals('Alex Phillips', $args->getOption('name')->getValue()); + $this->assertEquals('Alex Phillips', $args->getOption('n')->getValue()); - $args->addFlag(new Flag('f', 'Foo!')); - $args->addOption(new Option('name', 'Your name')); + $args = new Arguments([ + '--name', + 'Alex Phillips', + ]); + $args->addOption((new Option('name'))->alias('n')); $args->addArgument(new Argument('bar', 'Bar!')); + $args->parse(); + + $this->assertEquals('Alex Phillips', $args->getOption('name')->getValue()); + $this->assertEquals('Alex Phillips', $args->getOption('n')->getValue()); + $args = new Arguments([ + '-n', + 'Alex Phillips', + ]); + $args->addOption((new Option('name'))->alias('n')); + $args->addArgument(new Argument('bar', 'Bar!')); + $args->parse(); + + $this->assertEquals('Alex Phillips', $args->getOption('name')->getValue()); + $this->assertEquals('Alex Phillips', $args->getOption('n')->getValue()); + + $args = new Arguments([ + '--name="Alex Phillips"', + ]); + $args->addOption((new Option('name'))->alias('n')); + $args->addArgument(new Argument('bar', 'Bar!')); $args->parse(); - $this->assertEquals(1, $args->getFlag('f')->getValue()); $this->assertEquals('Alex Phillips', $args->getOption('name')->getValue()); - $this->assertEquals('FooBar', $args->getArgument('bar')->getValue()); + $this->assertEquals('Alex Phillips', $args->getOption('n')->getValue()); + } + + public function testParseArguments() { + $args = new Arguments([ + 'Alex Phillips', + ]); + $args->addArgument(new Argument('name')); + $args->parse(); + + $this->assertEquals('Alex Phillips', $args->getArgument('name')->getValue()); + } + + public function testMixedArguments() { + $args = new Arguments([ + 'Alex Phillips', + '-fb', + '--baz="woot"', + ]); + $args->addFlag((new Flag('bar'))->alias('b')); + $args->addOption(new Option('baz')); + $args->addFlag((new Flag('foo'))->alias('f')); + $args->addArgument(new Argument('name')); + $args->parse(); + + $this->assertEquals('Alex Phillips', $args->getArgument('name')->getValue()); + $this->assertEquals(1, $args->getFlag('foo')->getValue()); + $this->assertEquals(1, $args->getFlag('f')->getValue()); + $this->assertEquals(1, $args->getFlag('bar')->getValue()); + $this->assertEquals(1, $args->getFlag('b')->getValue()); + $this->assertEquals('woot', $args->getOption('baz')->getValue()); } } \ No newline at end of file From c9245522c6fc4f32cece0e6f6b3b9e2294e0660e Mon Sep 17 00:00:00 2001 From: Alex Phillips Date: Thu, 26 Feb 2015 18:38:40 -0500 Subject: [PATCH 06/92] fleshed out basic abstract command, added help screen functionality, --- src/Titon/Console/Arguments.hh | 25 ++- src/Titon/Console/Command.hh | 3 + src/Titon/Console/Command/AbstractCommand.hh | 94 ++++++++++ src/Titon/Console/HelpScreen.hh | 164 ++++++++++++++++++ .../AbstractInputDefinition.hh | 8 + src/Titon/Console/InputDefinition/Argument.hh | 4 + src/Titon/Console/bootstrap.hh | 3 - tests/Titon/Console/CommandTest.hh | 39 +++++ 8 files changed, 332 insertions(+), 8 deletions(-) create mode 100644 src/Titon/Console/HelpScreen.hh create mode 100644 tests/Titon/Console/CommandTest.hh diff --git a/src/Titon/Console/Arguments.hh b/src/Titon/Console/Arguments.hh index 437bf16..b2fd0fa 100644 --- a/src/Titon/Console/Arguments.hh +++ b/src/Titon/Console/Arguments.hh @@ -15,7 +15,7 @@ use Titon\Console\Exception\MissingValueException; class Arguments { - protected ArgumentMap $arguments = Map {}; + protected ArgumentBag $arguments; protected CommandMap $commands = Map {}; @@ -23,18 +23,19 @@ class Arguments { protected ArgumentLexer $input; - protected OptionMap $options = Map {}; + protected Vector $invalid = Vector {}; + + protected ArgumentBag $options; public function __construct(?ArgumentList $args = null) { - if (is_null($args) === 0) { + if (is_null($args)) { $args = array_slice($_SERVER['argv'], 1); } + $this->input = new ArgumentLexer($args); $this->flags = new ArgumentBag(); $this->options = new ArgumentBag(); $this->arguments = new ArgumentBag(); - - $this->input = new ArgumentLexer($args); } public function addArgument(Argument $argument): this { @@ -63,6 +64,10 @@ class Arguments { return null; } + public function getArguments(): ArgumentBag { + return $this->arguments; + } + public function getFlag(string $key): ?Flag { if (!is_null($flag = $this->flags->get($key))) { return $flag; @@ -77,6 +82,10 @@ class Arguments { return null; } + public function getFlags(): ArgumentBag { + return $this->flags; + } + public function getOption(string $key): ?Option { if (!is_null($option = $this->options->get($key))) { return $option; @@ -85,6 +94,10 @@ class Arguments { return null; } + public function getOptions(): ArgumentBag { + return $this->options; + } + public function parse() { foreach ($this->input as $val) { if ($this->parseFlag($val)) { @@ -96,6 +109,8 @@ class Arguments { if ($this->parseArgument($val)) { continue; } + + $this->invalid[] = $val; } } diff --git a/src/Titon/Console/Command.hh b/src/Titon/Console/Command.hh index eb1e34f..3ba3d3a 100644 --- a/src/Titon/Console/Command.hh +++ b/src/Titon/Console/Command.hh @@ -9,4 +9,7 @@ namespace Titon\Console; interface Command { + public function configure(): void; + + public function run(): void; } \ No newline at end of file diff --git a/src/Titon/Console/Command/AbstractCommand.hh b/src/Titon/Console/Command/AbstractCommand.hh index 8c8aecb..1ea7d06 100644 --- a/src/Titon/Console/Command/AbstractCommand.hh +++ b/src/Titon/Console/Command/AbstractCommand.hh @@ -7,8 +7,102 @@ namespace Titon\Console\Command; +use Titon\Console\ArgumentBag; use Titon\Console\Command; +use Titon\Console\Arguments; +use Titon\Console\InputDefinition\Argument; +use Titon\Console\InputDefinition\Flag; +use Titon\Console\InputDefinition\Option; abstract class AbstractCommand implements Command { + protected ArgumentBag $arguments; + + protected ?Arguments $input; + + protected ?string $description; + + protected ArgumentBag $flags; + + protected string $name; + + protected ArgumentBag $options; + + public function __construct() { + $this->arguments = new ArgumentBag(); + $this->flags = new ArgumentBag(); + $this->options = new ArgumentBag(); + } + + public function setInput(Arguments $input): this { + $this->input = $input; + + return $this; + } + + public function addArgument(Argument $argument): this { + $this->arguments->set($argument->getName(), $argument); + + return $this; + } + + public function addFlag(Flag $flag): this { + $this->flags->set($flag->getName(), $flag); + + return $this; + } + + public function addOption(Option $option): this { + $this->options->set($option->getName(), $option); + + return $this; + } + + protected function getArgument(string $key): ?string { + if ($argument = $this->input->getArgument($key)) { + return $argument->getValue(); + } + + return null; + } + + protected function getArguments(): ArgumentBag { + return $this->arguments; + } + + protected function getFlag(string $key): ?int { + if ($flag = $this->input->getFlag($key)) { + return $flag->getValue(); + } + + return null; + } + + protected function getFlags(): ArgumentBag { + return $this->flags; + } + + protected function getOption(string $key): ?string { + if ($option = $this->input->getOption($key)) { + return $option->getValue(); + } + + return null; + } + + protected function getOptions(): ArgumentBag { + return $this->options; + } + + public function registerInput(): this { + foreach ($this->arguments as $name => $argument) { + $this->input->addArgument($argument); + } + foreach ($this->flags as $name => $flag) { + $this->input->addFlag($flag); + } + foreach ($this->options as $name => $option) { + $this->input->addOption($option); + } + } } \ No newline at end of file diff --git a/src/Titon/Console/HelpScreen.hh b/src/Titon/Console/HelpScreen.hh new file mode 100644 index 0000000..2e3744d --- /dev/null +++ b/src/Titon/Console/HelpScreen.hh @@ -0,0 +1,164 @@ +name = $name; + $this->description = $description; + + if (is_null($arguments)) { + $this->arguments = new ArgumentBag(); + $this->flags = new ArgumentBag(); + $this->options = new ArgumentBag(); + } else { + $this->arguments = $arguments->getArguments(); + $this->flags = $arguments->getFlags(); + $this->options = $arguments->getOptions(); + } + } + + public function render(): string { + $retval = Vector {}; + + $retval[] = $this->renderUsage(); + + if (!$this->arguments->all()->isEmpty()) { + if ($output = $this->renderSection($this->arguments)) { + $retval[] = "Arguments\n$output"; + } + } + if (!$this->flags->all()->isEmpty()) { + if ($output = $this->renderSection($this->flags)) { + $retval[] = "Flags\n$output"; + } + } + if (!$this->options->all()->isEmpty()) { + if ($output = $this->renderSection($this->options)) { + $retval[] = "Options\n$output"; + } + } + + return join($retval, "\n\n"); + } + + protected function renderSection(ArgumentBag $arguments): string { + $entries = Map {}; + foreach ($arguments as $argument) { + $name = $argument->getFormattedName($argument->getName()); + if ($argument->getAlias()) { + $name .= " ({$argument->getFormattedName($argument->getAlias())})"; + } + $entries[$name] = $argument->getDescription(); + } + + $maxLength = max(array_map('strlen', $entries->keys())); + $descriptionLength = 80 - 4 - $maxLength; + + $output = Vector {}; + foreach ($entries as $name => $description) { + $formatted = ' ' . str_pad($name, $maxLength); + $description = explode('{{BREAK}}', wordwrap($description, $descriptionLength, "{{BREAK}}")); + $formatted .= ' ' . array_shift($description); + + $pad = str_repeat(' ', $maxLength + 4); + while ($desc = array_shift($description)) { + $formatted .= "\n${pad}${desc}"; + } + + $formatted = "$formatted"; + + array_push($output, $formatted); + } + + return join($output, "\n"); + } + + protected function renderUsage(): string { + $usage = Vector {}; + if (!is_null($this->command)) { + $usage[] = $this->command->getName(); + + foreach ($this->command->getArguments() as $argument) { + $arg = $argument->getName(); + if ($argument->getMode() === AbstractInputDefinition::VALUE_OPTIONAL) { + $usage[] = "[$arg]"; + } + } + foreach ($this->command->getArguments() as $argument) { + $arg = $argument->getName(); + if ($argument->getAlias()) { + $arg = "$arg|{$argument->getAlias()}"; + } + if ($argument->getMode() === AbstractInputDefinition::VALUE_OPTIONAL) { + $usage[] = "[$arg]"; + } + } + foreach ($this->command->getArguments() as $argument) { + $arg = $argument->getName(); + if ($argument->getAlias()) { + $arg = "$arg|{$argument->getAlias()}"; + } + + $arg = "$arg=\"...\""; + if ($argument->getMode() === AbstractInputDefinition::VALUE_OPTIONAL) { + $usage[] = "[$arg]"; + } + } + } else { + $usage[] = "command"; + if (!$this->flags->all()->isEmpty()) { + $usage[] = "[flags]"; + } + if (!$this->options->all()->isEmpty()) { + $usage[] = "[options]"; + } + } + + return join(" ", $usage); + } + + public function setCommand(Command $command): this { + $this->command = $command; + + return $this; + } + + public function setArguments(Arguments $arguments): this { + $this->arguments = $arguments; + + return $this; + } + + public function setFlags(ArgumentBag $flags): this { + $this->flags = $flags; + + return $this; + } + + public function setOptions(ArgumentBag $options): this { + $this->options = $options; + + return $this; + } +} \ No newline at end of file diff --git a/src/Titon/Console/InputDefinition/AbstractInputDefinition.hh b/src/Titon/Console/InputDefinition/AbstractInputDefinition.hh index c373dac..a488695 100644 --- a/src/Titon/Console/InputDefinition/AbstractInputDefinition.hh +++ b/src/Titon/Console/InputDefinition/AbstractInputDefinition.hh @@ -56,6 +56,14 @@ abstract class AbstractInputDefinition implements InputDefinition { return $this->description; } + public function getFormattedName(string $name): string { + if (strlen($name) === 1) { + return "-$name"; + } + + return "--$name"; + } + public function getName(): string { return $this->name; } diff --git a/src/Titon/Console/InputDefinition/Argument.hh b/src/Titon/Console/InputDefinition/Argument.hh index 2d650cb..bba1314 100644 --- a/src/Titon/Console/InputDefinition/Argument.hh +++ b/src/Titon/Console/InputDefinition/Argument.hh @@ -9,6 +9,10 @@ namespace Titon\Console\InputDefinition; class Argument extends AbstractInputDefinition { + public function getFormattedName(string $name): string { + return $name; + } + public function setValue(string $value): this { $this->value = $value; diff --git a/src/Titon/Console/bootstrap.hh b/src/Titon/Console/bootstrap.hh index 794d986..b72d4e2 100644 --- a/src/Titon/Console/bootstrap.hh +++ b/src/Titon/Console/bootstrap.hh @@ -24,7 +24,4 @@ namespace Titon\Console { 'value' => string ); type CommandMap = Map; - type FlagMap = Map; - type OptionMap = Map; - type ArgumentMap = Map; } diff --git a/tests/Titon/Console/CommandTest.hh b/tests/Titon/Console/CommandTest.hh new file mode 100644 index 0000000..957149c --- /dev/null +++ b/tests/Titon/Console/CommandTest.hh @@ -0,0 +1,39 @@ +addFlag(new Flag('help', 'Show this help screen')); + + $command = new TestCommand(); + $command->setInput($args); + $command->configure(); + $command->registerInput(); + + $args->parse(); + + $this->assertEquals(2, count($args->getFlags())); + $this->assertEquals(1, $args->getFlag('v')->getValue()); + } +} + +class TestCommand extends \Titon\Console\Command\AbstractCommand { + + public function configure() { + $this->addFlag((new Flag('verbose', 'Set command verbosity'))->alias('v')); + } + + public function run() { + + } +} \ No newline at end of file From 7b8ff199844cc9f8dea4b78f535aee5043f4e1fc Mon Sep 17 00:00:00 2001 From: Alex Phillips Date: Thu, 26 Feb 2015 19:12:12 -0500 Subject: [PATCH 07/92] renamed classes to make more sense (all arguments 'Arguments' is now 'Input') --- src/Titon/Console/Command/AbstractCommand.hh | 6 ++--- src/Titon/Console/HelpScreen.hh | 22 ++++++++++------- src/Titon/Console/{Arguments.hh => Input.hh} | 14 +++++------ .../{ArgumentLexer.hh => InputLexer.hh} | 8 +++---- src/Titon/Console/bootstrap.hh | 6 +---- tests/Titon/Console/CommandTest.hh | 6 ++--- .../{ArgumentParserTest.hh => ParserTest.hh} | 24 +++++++++---------- 7 files changed, 43 insertions(+), 43 deletions(-) rename src/Titon/Console/{Arguments.hh => Input.hh} (92%) rename src/Titon/Console/{ArgumentLexer.hh => InputLexer.hh} (93%) rename tests/Titon/Console/{ArgumentParserTest.hh => ParserTest.hh} (91%) diff --git a/src/Titon/Console/Command/AbstractCommand.hh b/src/Titon/Console/Command/AbstractCommand.hh index 1ea7d06..37baf26 100644 --- a/src/Titon/Console/Command/AbstractCommand.hh +++ b/src/Titon/Console/Command/AbstractCommand.hh @@ -9,7 +9,7 @@ namespace Titon\Console\Command; use Titon\Console\ArgumentBag; use Titon\Console\Command; -use Titon\Console\Arguments; +use Titon\Console\Input; use Titon\Console\InputDefinition\Argument; use Titon\Console\InputDefinition\Flag; use Titon\Console\InputDefinition\Option; @@ -18,7 +18,7 @@ abstract class AbstractCommand implements Command { protected ArgumentBag $arguments; - protected ?Arguments $input; + protected ?Input $input; protected ?string $description; @@ -34,7 +34,7 @@ abstract class AbstractCommand implements Command { $this->options = new ArgumentBag(); } - public function setInput(Arguments $input): this { + public function setInput(Input $input): this { $this->input = $input; return $this; diff --git a/src/Titon/Console/HelpScreen.hh b/src/Titon/Console/HelpScreen.hh index 2e3744d..56b156d 100644 --- a/src/Titon/Console/HelpScreen.hh +++ b/src/Titon/Console/HelpScreen.hh @@ -23,18 +23,16 @@ class HelpScreen { protected ?ArgumentBag $options; - public function __construct(string $name, string $description = '', ?Arguments $arguments = null) { + public function __construct(string $name, string $description = '', ?Input $input = null) { $this->name = $name; $this->description = $description; - if (is_null($arguments)) { + if (is_null($input)) { $this->arguments = new ArgumentBag(); $this->flags = new ArgumentBag(); $this->options = new ArgumentBag(); } else { - $this->arguments = $arguments->getArguments(); - $this->flags = $arguments->getFlags(); - $this->options = $arguments->getOptions(); + $this->setInput($input); } } @@ -138,14 +136,14 @@ class HelpScreen { return join(" ", $usage); } - public function setCommand(Command $command): this { - $this->command = $command; + public function setArguments(Arguments $arguments): this { + $this->arguments = $arguments; return $this; } - public function setArguments(Arguments $arguments): this { - $this->arguments = $arguments; + public function setCommand(Command $command): this { + $this->command = $command; return $this; } @@ -156,6 +154,12 @@ class HelpScreen { return $this; } + public function setInput(Input $input): this { + $this->arguments = $arguments->getArguments(); + $this->flags = $arguments->getFlags(); + $this->options = $arguments->getOptions(); + } + public function setOptions(ArgumentBag $options): this { $this->options = $options; diff --git a/src/Titon/Console/Arguments.hh b/src/Titon/Console/Input.hh similarity index 92% rename from src/Titon/Console/Arguments.hh rename to src/Titon/Console/Input.hh index b2fd0fa..7371d66 100644 --- a/src/Titon/Console/Arguments.hh +++ b/src/Titon/Console/Input.hh @@ -13,7 +13,7 @@ use Titon\Console\InputDefinition\Flag; use Titon\Console\InputDefinition\Option; use Titon\Console\Exception\MissingValueException; -class Arguments { +class Input { protected ArgumentBag $arguments; @@ -21,9 +21,9 @@ class Arguments { protected ArgumentBag $flags; - protected ArgumentLexer $input; + protected InputLexer $input; - protected Vector $invalid = Vector {}; + protected Vector $invalid = Vector {}; protected ArgumentBag $options; @@ -32,7 +32,7 @@ class Arguments { $args = array_slice($_SERVER['argv'], 1); } - $this->input = new ArgumentLexer($args); + $this->input = new InputLexer($args); $this->flags = new ArgumentBag(); $this->options = new ArgumentBag(); $this->arguments = new ArgumentBag(); @@ -114,7 +114,7 @@ class Arguments { } } - public function parseArgument(Input $key): bool { + public function parseArgument(RawInput $key): bool { foreach ($this->arguments as $argument) { if (is_null($argument->getValue())) { $argument->setValue($key['raw']); @@ -126,7 +126,7 @@ class Arguments { return false; } - public function parseFlag(Input $key): bool { + public function parseFlag(RawInput $key): bool { $key = $key['value']; if (is_null($flag = $this->flags->get($key))) { return false; @@ -141,7 +141,7 @@ class Arguments { return true; } - public function parseOption(Input $key): bool { + public function parseOption(RawInput $key): bool { $key = $key['value']; if (is_null($option = $this->options->get($key))) { return false; diff --git a/src/Titon/Console/ArgumentLexer.hh b/src/Titon/Console/InputLexer.hh similarity index 93% rename from src/Titon/Console/ArgumentLexer.hh rename to src/Titon/Console/InputLexer.hh index f70e1f4..3b4a3bc 100644 --- a/src/Titon/Console/ArgumentLexer.hh +++ b/src/Titon/Console/InputLexer.hh @@ -9,7 +9,7 @@ namespace Titon\Console; use Titon\Common\ArgumentList; -class ArgumentLexer implements Iterator { +class InputLexer implements Iterator { public array $items = array(); @@ -26,7 +26,7 @@ class ArgumentLexer implements Iterator { $this->length = count($items); } - public function current(): Tv { + public function current(): RawInput { return $this->current; } @@ -71,7 +71,7 @@ class ArgumentLexer implements Iterator { } } - public function peek(): ?Input { + public function peek(): ?RawInput { if (!empty($this->items)) { return $this->processInput($this->items[0]); } @@ -79,7 +79,7 @@ class ArgumentLexer implements Iterator { return null; } - public function processInput(string $input): Input { + public function processInput(string $input): RawInput { $raw = $input; $value = $input; diff --git a/src/Titon/Console/bootstrap.hh b/src/Titon/Console/bootstrap.hh index b72d4e2..abc7c8f 100644 --- a/src/Titon/Console/bootstrap.hh +++ b/src/Titon/Console/bootstrap.hh @@ -14,12 +14,8 @@ */ namespace Titon\Console { - use Titon\Console\Command; - use Titon\Console\InputDefinition\Flag; - use Titon\Console\InputDefinition\Option; - use Titon\Console\InputDefinition\Argument; - type Input = shape( + type RawInput = shape( 'raw' => string, 'value' => string ); diff --git a/tests/Titon/Console/CommandTest.hh b/tests/Titon/Console/CommandTest.hh index 957149c..0b8ed29 100644 --- a/tests/Titon/Console/CommandTest.hh +++ b/tests/Titon/Console/CommandTest.hh @@ -1,8 +1,8 @@ addFlag(new Flag('help', 'Show this help screen')); diff --git a/tests/Titon/Console/ArgumentParserTest.hh b/tests/Titon/Console/ParserTest.hh similarity index 91% rename from tests/Titon/Console/ArgumentParserTest.hh rename to tests/Titon/Console/ParserTest.hh index 63a34d1..6c041a9 100644 --- a/tests/Titon/Console/ArgumentParserTest.hh +++ b/tests/Titon/Console/ParserTest.hh @@ -1,8 +1,8 @@ addFlag((new Flag('foo'))->alias('f')); @@ -22,7 +22,7 @@ class ArgumentParserTest extends TestCase { $this->assertEquals(1, $args->getFlag('foo')->getValue()); $this->assertEquals(1, $args->getFlag('f')->getValue()); - $args = new Arguments([ + $args = new Input([ '-f', ]); $args->addFlag((new Flag('foo'))->alias('f')); @@ -34,7 +34,7 @@ class ArgumentParserTest extends TestCase { /* * Check stacked, but different, flags */ - $args = new Arguments([ + $args = new Input([ '-fb', ]); $args->addFlag((new Flag('foo'))->alias('f')); @@ -47,7 +47,7 @@ class ArgumentParserTest extends TestCase { /* * Check stacked flag */ - $args = new Arguments([ + $args = new Input([ '-vvv', ]); $args->addFlag((new Flag('v'))->setStackable(true)); @@ -57,7 +57,7 @@ class ArgumentParserTest extends TestCase { } public function testParseOptions() { - $args = new Arguments([ + $args = new Input([ '--name', 'Alex Phillips', ]); @@ -68,7 +68,7 @@ class ArgumentParserTest extends TestCase { $this->assertEquals('Alex Phillips', $args->getOption('name')->getValue()); $this->assertEquals('Alex Phillips', $args->getOption('n')->getValue()); - $args = new Arguments([ + $args = new Input([ '--name', 'Alex Phillips', ]); @@ -79,7 +79,7 @@ class ArgumentParserTest extends TestCase { $this->assertEquals('Alex Phillips', $args->getOption('name')->getValue()); $this->assertEquals('Alex Phillips', $args->getOption('n')->getValue()); - $args = new Arguments([ + $args = new Input([ '-n', 'Alex Phillips', ]); @@ -90,7 +90,7 @@ class ArgumentParserTest extends TestCase { $this->assertEquals('Alex Phillips', $args->getOption('name')->getValue()); $this->assertEquals('Alex Phillips', $args->getOption('n')->getValue()); - $args = new Arguments([ + $args = new Input([ '--name="Alex Phillips"', ]); $args->addOption((new Option('name'))->alias('n')); @@ -102,7 +102,7 @@ class ArgumentParserTest extends TestCase { } public function testParseArguments() { - $args = new Arguments([ + $args = new Input([ 'Alex Phillips', ]); $args->addArgument(new Argument('name')); @@ -112,7 +112,7 @@ class ArgumentParserTest extends TestCase { } public function testMixedArguments() { - $args = new Arguments([ + $args = new Input([ 'Alex Phillips', '-fb', '--baz="woot"', From 617bd5857ed1496bb3d3507208114ec3635b5c28 Mon Sep 17 00:00:00 2001 From: Alex Phillips Date: Thu, 26 Feb 2015 21:19:52 -0500 Subject: [PATCH 08/92] fixed most type checker issues --- src/Titon/Console/Command.hh | 14 +++++ src/Titon/Console/Command/AbstractCommand.hh | 39 +++++++++----- src/Titon/Console/HelpScreen.hh | 53 ++++++++++++------- src/Titon/Console/Input.hh | 41 ++++++++------ .../Console/{ArgumentBag.hh => InputBag.hh} | 4 +- src/Titon/Console/InputDefinition/Option.hh | 2 + src/Titon/Console/InputLexer.hh | 22 +++++--- 7 files changed, 114 insertions(+), 61 deletions(-) rename src/Titon/Console/{ArgumentBag.hh => InputBag.hh} (75%) diff --git a/src/Titon/Console/Command.hh b/src/Titon/Console/Command.hh index 3ba3d3a..70dbf34 100644 --- a/src/Titon/Console/Command.hh +++ b/src/Titon/Console/Command.hh @@ -7,9 +7,23 @@ namespace Titon\Console; +use Titon\Console\InputDefinition\Argument; +use Titon\Console\InputDefinition\Flag; +use Titon\Console\InputDefinition\Option; + interface Command { public function configure(): void; + public function getArguments(): InputBag; + + public function getDescription(): string; + + public function getFlags(): InputBag; + + public function getName(): string; + + public function getOptions(): InputBag