Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit d90a218

Browse filesBrowse files
committed
[Console] Expose the original input arguments and options
PoC
1 parent 68a5704 commit d90a218
Copy full SHA for d90a218

File tree

6 files changed

+624
-0
lines changed
Filter options

6 files changed

+624
-0
lines changed

‎src/Symfony/Component/Console/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/CHANGELOG.md
+8Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
CHANGELOG
22
=========
33

4+
7.2
5+
---
6+
7+
* Add `InputInterface::getRawArguments()`
8+
* Add `InputInterface::getRawOptions()`
9+
* Add `Input::unparse()`
10+
11+
412
7.1
513
---
614

‎src/Symfony/Component/Console/Input/Input.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/Input/Input.php
+91Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Symfony\Component\Console\Exception\InvalidArgumentException;
1515
use Symfony\Component\Console\Exception\RuntimeException;
16+
use function array_keys;
1617

1718
/**
1819
* Input is the base class for all concrete Input classes.
@@ -85,6 +86,35 @@ public function getArguments(): array
8586
return array_merge($this->definition->getArgumentDefaults(), $this->arguments);
8687
}
8788

89+
/**
90+
* Returns all the given arguments NOT merged with the default values.
91+
*
92+
* @param bool $strip Whether to return the raw parameters (false) or the values after the command name (true)
93+
*
94+
* @return array<string|bool|int|float|null|array<string|bool|int|float|null>>
95+
*/
96+
public function getRawArguments(bool $strip = false): array
97+
{
98+
if (!$strip) {
99+
return $this->arguments;
100+
}
101+
102+
$arguments = [];
103+
$keep = false;
104+
foreach ($this->arguments as $argument) {
105+
if (!$keep && $argument === $this->getFirstArgument()) {
106+
$keep = true;
107+
108+
continue;
109+
}
110+
if ($keep) {
111+
$arguments[] = $argument;
112+
}
113+
}
114+
115+
return $arguments;
116+
}
117+
88118
public function getArgument(string $name): mixed
89119
{
90120
if (!$this->definition->hasArgument($name)) {
@@ -113,6 +143,16 @@ public function getOptions(): array
113143
return array_merge($this->definition->getOptionDefaults(), $this->options);
114144
}
115145

146+
/**
147+
* Returns all the given options NOT merged with the default values.
148+
*
149+
* @return array<string|bool|int|float|null|array<string|bool|int|float|null>>
150+
*/
151+
public function getRawOptions(): array
152+
{
153+
return $this->options;
154+
}
155+
116156
public function getOption(string $name): mixed
117157
{
118158
if ($this->definition->hasNegation($name)) {
@@ -171,4 +211,55 @@ public function getStream()
171211
{
172212
return $this->stream;
173213
}
214+
215+
/**
216+
* Returns a stringified representation of the options passed to the command.
217+
*
218+
* InputArguments MUST be escaped as well as the InputOption values passed to the command.
219+
*
220+
* @param string[] $optionNames Name of the options returned. If empty, all options are returned and non-passed or non-existent are ignored.
221+
*
222+
* @return list<string>
223+
*/
224+
public function unparse(array $optionNames = []): array
225+
{
226+
$rawOptions = $this->getRawOptions();
227+
228+
$filteredRawOptions = count($optionNames) === 0
229+
? $rawOptions
230+
: array_intersect_key($rawOptions, array_fill_keys($optionNames, ''),
231+
);
232+
233+
return array_map(
234+
fn (string $optionName) => $this->unparseOption(
235+
$this->definition->getOption($optionName),
236+
$optionName,
237+
$filteredRawOptions[$optionName],
238+
),
239+
array_keys($filteredRawOptions),
240+
);
241+
}
242+
243+
/**
244+
* @param string|bool|int|float|null|array<string|bool|int|float|null> $value
245+
*/
246+
private function unparseOption(
247+
InputOption $option,
248+
string $name,
249+
array|bool|float|int|string|null $value,
250+
): string {
251+
return match(true) {
252+
$option->isNegatable() => sprintf('--%s%s', $value ? '' : 'no-', $name),
253+
!$option->acceptValue() => sprintf('--%s', $name),
254+
$option->isArray() => implode('', array_map(fn($item) => $this->unparseOptionWithValue($name, $item), $value,)),
255+
default => $this->unparseOptionWithValue($name, $value),
256+
};
257+
}
258+
259+
private function unparseOptionWithValue(
260+
string $name,
261+
bool|float|int|string|null $value,
262+
): string {
263+
return sprintf('--%s=%s', $name, $this->escapeToken($value));
264+
}
174265
}

‎src/Symfony/Component/Console/Input/InputInterface.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/Input/InputInterface.php
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
* InputInterface is the interface implemented by all input classes.
1919
*
2020
* @author Fabien Potencier <fabien@symfony.com>
21+
*
22+
* @method getRawArguments(bool $strip = false): array<string|bool|int|float|null|array<string|bool|int|float|null>> Returns all the given arguments NOT merged with the default values.
23+
* @method getRawOptions(): array<string|bool|int|float|null|array<string|bool|int|float|null>> Returns all the given options NOT merged with the default values.
2124
*/
2225
interface InputInterface
2326
{

‎src/Symfony/Component/Console/Tests/Input/ArgvInputTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/Tests/Input/ArgvInputTest.php
+250Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -594,4 +594,254 @@ public static function provideGetRawTokensTrueTests(): iterable
594594
yield [['app/console', '--no-ansi', 'foo:bar', 'foo:bar'], ['foo:bar']];
595595
yield [['app/console', '--no-ansi', 'foo:bar', '--', 'argument'], ['--', 'argument']];
596596
}
597+
598+
/**
599+
* @dataProvider unparseProvider
600+
*/
601+
public function testUnparse(
602+
?InputDefinition $inputDefinition,
603+
ArgvInput $input,
604+
?array $parsedOptions,
605+
array $expected,
606+
)
607+
{
608+
if (null !== $inputDefinition) {
609+
$input->bind($inputDefinition);
610+
}
611+
612+
$actual = null === $parsedOptions ? $input->unparse() : $input->unparse($parsedOptions);
613+
614+
self::assertEquals($expected, $actual);
615+
}
616+
617+
public static function unparseProvider(): iterable
618+
{
619+
yield 'empty input and empty definition' => [
620+
new InputDefinition(),
621+
new ArgvInput([]),
622+
[],
623+
[],
624+
];
625+
626+
yield 'empty input and definition with default values: ignore default values' => [
627+
new InputDefinition([
628+
new InputArgument(
629+
'argWithDefaultValue',
630+
InputArgument::OPTIONAL,
631+
'Argument with a default value',
632+
'arg1DefaultValue',
633+
),
634+
new InputOption(
635+
'optWithDefaultValue',
636+
null,
637+
InputOption::VALUE_REQUIRED,
638+
'Option with a default value',
639+
'opt1DefaultValue',
640+
),
641+
]),
642+
new ArgvInput([]),
643+
[],
644+
[],
645+
];
646+
647+
$completeInputDefinition = new InputDefinition([
648+
new InputArgument(
649+
'requiredArgWithoutDefaultValue',
650+
InputArgument::REQUIRED,
651+
'Argument without a default value',
652+
),
653+
new InputArgument(
654+
'optionalArgWithDefaultValue',
655+
InputArgument::OPTIONAL,
656+
'Argument with a default value',
657+
'argDefaultValue',
658+
),
659+
new InputOption(
660+
'optWithoutDefaultValue',
661+
null,
662+
InputOption::VALUE_REQUIRED,
663+
'Option without a default value',
664+
),
665+
new InputOption(
666+
'optWithDefaultValue',
667+
null,
668+
InputOption::VALUE_REQUIRED,
669+
'Option with a default value',
670+
'optDefaultValue',
671+
),
672+
]);
673+
674+
yield 'arguments & options: returns all passed options but ignore default values' => [
675+
$completeInputDefinition,
676+
new ArgvInput(['argValue', '--optWithoutDefaultValue=optValue']),
677+
[],
678+
['--optWithoutDefaultValue=optValue'],
679+
];
680+
681+
yield 'arguments & options; explicitly pass the default values: the default values are returned' => [
682+
$completeInputDefinition,
683+
new ArgvInput(['argValue', 'argDefaultValue', '--optWithoutDefaultValue=optValue', '--optWithDefaultValue=optDefaultValue']),
684+
[],
685+
[
686+
'--optWithoutDefaultValue=optValue',
687+
'--optWithDefaultValue=optDefaultValue',
688+
],
689+
];
690+
691+
yield 'arguments & options; parsing an argument name instead of an option name: that option is ignored' => [
692+
$completeInputDefinition,
693+
new ArgvInput(['argValue']),
694+
['requiredArgWithoutDefaultValue'],
695+
[],
696+
];
697+
698+
yield 'arguments & options; non passed option: it is ignored' => [
699+
$completeInputDefinition,
700+
new ArgvInput(['argValue']),
701+
['optWithDefaultValue'],
702+
[],
703+
];
704+
705+
$createSingleOptionScenario = static fn (
706+
InputOption $option,
707+
array $input,
708+
array $expected
709+
) => [
710+
new InputDefinition([$option]),
711+
new ArgvInput(['appName', ...$input]),
712+
[],
713+
$expected,
714+
];
715+
716+
yield 'option without value' => $createSingleOptionScenario(
717+
new InputOption(
718+
'opt',
719+
null,
720+
InputOption::VALUE_NONE,
721+
),
722+
['--opt'],
723+
['--opt'],
724+
);
725+
726+
yield 'option without value by shortcut' => $createSingleOptionScenario(
727+
new InputOption(
728+
'opt',
729+
'o',
730+
InputOption::VALUE_NONE,
731+
),
732+
['-o'],
733+
['--opt'],
734+
);
735+
736+
yield 'option with value required' => $createSingleOptionScenario(
737+
new InputOption(
738+
'opt',
739+
null,
740+
InputOption::VALUE_REQUIRED,
741+
),
742+
['--opt=foo'],
743+
['--opt=foo'],
744+
);
745+
746+
yield 'option with non string value (bool)' => $createSingleOptionScenario(
747+
new InputOption(
748+
'opt',
749+
null,
750+
InputOption::VALUE_REQUIRED,
751+
),
752+
['--opt=1'],
753+
['--opt=1'],
754+
);
755+
756+
yield 'option with non string value (int)' => $createSingleOptionScenario(
757+
new InputOption(
758+
'opt',
759+
null,
760+
InputOption::VALUE_REQUIRED,
761+
),
762+
['--opt=20'],
763+
['--opt=20'],
764+
);
765+
766+
yield 'option with non string value (float)' => $createSingleOptionScenario(
767+
new InputOption(
768+
'opt',
769+
null,
770+
InputOption::VALUE_REQUIRED,
771+
),
772+
['--opt=5.3'],
773+
['--opt=\'5.3\''],
774+
);
775+
776+
yield 'option with non string value (array of strings)' => $createSingleOptionScenario(
777+
new InputOption(
778+
'opt',
779+
null,
780+
InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
781+
),
782+
['--opt=v1', '--opt=v2', '--opt=v3'],
783+
['--opt=v1--opt=v2--opt=v3'],
784+
);
785+
786+
yield 'negatable option (positive)' => $createSingleOptionScenario(
787+
new InputOption(
788+
'opt',
789+
null,
790+
InputOption::VALUE_NEGATABLE,
791+
),
792+
['--opt'],
793+
['--opt'],
794+
);
795+
796+
yield 'negatable option (negative)' => $createSingleOptionScenario(
797+
new InputOption(
798+
'opt',
799+
null,
800+
InputOption::VALUE_NEGATABLE,
801+
),
802+
['--no-opt'],
803+
['--no-opt'],
804+
);
805+
806+
$createEscapeOptionTokenScenario = static fn (
807+
string $optionValue,
808+
?string $expected
809+
) => [
810+
new InputDefinition([
811+
new InputOption(
812+
'opt',
813+
null,
814+
InputOption::VALUE_REQUIRED,
815+
),
816+
]),
817+
new ArgvInput(['appName', '--opt='.$optionValue]),
818+
[],
819+
['--opt='.$expected],
820+
];
821+
822+
yield 'escape token; string token' => $createEscapeOptionTokenScenario(
823+
'foo',
824+
'foo',
825+
);
826+
827+
yield 'escape token; escaped string token' => $createEscapeOptionTokenScenario(
828+
'"foo"',
829+
escapeshellarg('"foo"'),
830+
);
831+
832+
yield 'escape token; escaped string token with both types of quotes' => $createEscapeOptionTokenScenario(
833+
'"o_id in(\'20\')"',
834+
escapeshellarg('"o_id in(\'20\')"'),
835+
);
836+
837+
yield 'escape token; string token with spaces' => $createEscapeOptionTokenScenario(
838+
'a b c d',
839+
escapeshellarg('a b c d'),
840+
);
841+
842+
yield 'escape token; string token with line return' => $createEscapeOptionTokenScenario(
843+
"A\nB'C",
844+
escapeshellarg("A\nB'C"),
845+
);
846+
}
597847
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.