Skip to content

Commit a0bbf53

Browse files
committed
feature #39642 [Console] Support binary / negatable options (greg-1-anderson, jderusse)
This PR was merged into the 5.3-dev branch. Discussion ---------- [Console] Support binary / negatable options | Q | A | ------------- | --- | Branch? | 5.x | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | Fix #24314 | License | MIT | Doc PR | TODO This PR take over #26292 to introduce a new option mode `NEGATABLE` that ease creation of option and their negation (ie. `--foo`, `--no-foo`) Negated options applies only to flag options (`VALUE_NONE`) ``` ./cli.php --foo var_dump(getOption('foo')); // true ./cli.php --no-foo var_dump(getOption('foo')); // false ``` Commits ------- 1a9a3c3 Implement negatable option 8d455db [WIP] Implements #24314: Support binary / negatable options, e.g. --foo and --no-foo.
2 parents 47e4b8e + 1a9a3c3 commit a0bbf53

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+360
-176
lines changed

src/Symfony/Component/Console/Application.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1033,8 +1033,7 @@ protected function getDefaultInputDefinition()
10331033
new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'),
10341034
new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'),
10351035
new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this application version'),
1036-
new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'),
1037-
new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'),
1036+
new InputOption('--ansi', '', InputOption::VALUE_NEGATABLE, 'Force (or disable --no-ansi) ANSI output', null),
10381037
new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'),
10391038
]);
10401039
}

src/Symfony/Component/Console/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ CHANGELOG
55
-----
66

77
* Added `GithubActionReporter` to render annotations in a Github Action
8+
* Added `InputOption::VALUE_NEGATABLE` flag to handle `--foo`/`--no-foo` options.
89

910
5.2.0
1011
-----

src/Symfony/Component/Console/Descriptor/JsonDescriptor.php

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ protected function describeInputArgument(InputArgument $argument, array $options
4040
protected function describeInputOption(InputOption $option, array $options = [])
4141
{
4242
$this->writeData($this->getInputOptionData($option), $options);
43+
if ($option->isNegatable()) {
44+
$this->writeData($this->getInputOptionData($option, true), $options);
45+
}
4346
}
4447

4548
/**
@@ -111,9 +114,17 @@ private function getInputArgumentData(InputArgument $argument): array
111114
];
112115
}
113116

114-
private function getInputOptionData(InputOption $option): array
117+
private function getInputOptionData(InputOption $option, bool $negated = false): array
115118
{
116-
return [
119+
return $negated ? [
120+
'name' => '--no-'.$option->getName(),
121+
'shortcut' => '',
122+
'accept_value' => false,
123+
'is_value_required' => false,
124+
'is_multiple' => false,
125+
'description' => 'Negate the "--'.$option->getName().'" option',
126+
'default' => false,
127+
] : [
117128
'name' => '--'.$option->getName(),
118129
'shortcut' => $option->getShortcut() ? '-'.str_replace('|', '|-', $option->getShortcut()) : '',
119130
'accept_value' => $option->acceptValue(),
@@ -134,6 +145,9 @@ private function getInputDefinitionData(InputDefinition $definition): array
134145
$inputOptions = [];
135146
foreach ($definition->getOptions() as $name => $option) {
136147
$inputOptions[$name] = $this->getInputOptionData($option);
148+
if ($option->isNegatable()) {
149+
$inputOptions['no-'.$name] = $this->getInputOptionData($option, true);
150+
}
137151
}
138152

139153
return ['arguments' => $inputArguments, 'options' => $inputOptions];

src/Symfony/Component/Console/Descriptor/MarkdownDescriptor.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ protected function describeInputArgument(InputArgument $argument, array $options
6969
protected function describeInputOption(InputOption $option, array $options = [])
7070
{
7171
$name = '--'.$option->getName();
72+
if ($option->isNegatable()) {
73+
$name .= '|--no-'.$option->getName();
74+
}
7275
if ($option->getShortcut()) {
7376
$name .= '|-'.str_replace('|', '|-', $option->getShortcut()).'';
7477
}
@@ -79,6 +82,7 @@ protected function describeInputOption(InputOption $option, array $options = [])
7982
.'* Accept value: '.($option->acceptValue() ? 'yes' : 'no')."\n"
8083
.'* Is value required: '.($option->isValueRequired() ? 'yes' : 'no')."\n"
8184
.'* Is multiple: '.($option->isArray() ? 'yes' : 'no')."\n"
85+
.'* Is negatable: '.($option->isNegatable() ? 'yes' : 'no')."\n"
8286
.'* Default: `'.str_replace("\n", '', var_export($option->getDefault(), true)).'`'
8387
);
8488
}

src/Symfony/Component/Console/Descriptor/TextDescriptor.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ protected function describeInputOption(InputOption $option, array $options = [])
7474
$totalWidth = $options['total_width'] ?? $this->calculateTotalWidthForOptions([$option]);
7575
$synopsis = sprintf('%s%s',
7676
$option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : ' ',
77-
sprintf('--%s%s', $option->getName(), $value)
77+
sprintf($option->isNegatable() ? '--%1$s|--no-%1$s' : '--%1$s%2$s', $option->getName(), $value)
7878
);
7979

8080
$spacingWidth = $totalWidth - Helper::strlen($synopsis);
@@ -325,8 +325,9 @@ private function calculateTotalWidthForOptions(array $options): int
325325
foreach ($options as $option) {
326326
// "-" + shortcut + ", --" + name
327327
$nameLength = 1 + max(Helper::strlen($option->getShortcut()), 1) + 4 + Helper::strlen($option->getName());
328-
329-
if ($option->acceptValue()) {
328+
if ($option->isNegatable()) {
329+
$nameLength += 6 + Helper::strlen($option->getName()); // |--no- + name
330+
} elseif ($option->acceptValue()) {
330331
$valueLength = 1 + Helper::strlen($option->getName()); // = + value
331332
$valueLength += $option->isValueOptional() ? 2 : 0; // [ + ]
332333

src/Symfony/Component/Console/Descriptor/XmlDescriptor.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,17 @@ private function getInputOptionDocument(InputOption $option): \DOMDocument
225225
}
226226
}
227227

228+
if ($option->isNegatable()) {
229+
$dom->appendChild($objectXML = $dom->createElement('option'));
230+
$objectXML->setAttribute('name', '--no-'.$option->getName());
231+
$objectXML->setAttribute('shortcut', '');
232+
$objectXML->setAttribute('accept_value', 0);
233+
$objectXML->setAttribute('is_value_required', 0);
234+
$objectXML->setAttribute('is_multiple', 0);
235+
$objectXML->appendChild($descriptionXML = $dom->createElement('description'));
236+
$descriptionXML->appendChild($dom->createTextNode('Negate the "--'.$option->getName().'" option'));
237+
}
238+
228239
return $dom;
229240
}
230241
}

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,17 @@ private function addShortOption(string $shortcut, $value)
209209
private function addLongOption(string $name, $value)
210210
{
211211
if (!$this->definition->hasOption($name)) {
212-
throw new RuntimeException(sprintf('The "--%s" option does not exist.', $name));
212+
if (!$this->definition->hasNegation($name)) {
213+
throw new RuntimeException(sprintf('The "--%s" option does not exist.', $name));
214+
}
215+
216+
$optionName = $this->definition->negationToName($name);
217+
if (null !== $value) {
218+
throw new RuntimeException(sprintf('The "--%s" option does not accept a value.', $name));
219+
}
220+
$this->options[$optionName] = false;
221+
222+
return;
213223
}
214224

215225
$option = $this->definition->getOption($name);

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,14 @@ private function addShortOption(string $shortcut, $value)
165165
private function addLongOption(string $name, $value)
166166
{
167167
if (!$this->definition->hasOption($name)) {
168-
throw new InvalidOptionException(sprintf('The "--%s" option does not exist.', $name));
168+
if (!$this->definition->hasNegation($name)) {
169+
throw new InvalidOptionException(sprintf('The "--%s" option does not exist.', $name));
170+
}
171+
172+
$optionName = $this->definition->negationToName($name);
173+
$this->options[$optionName] = false;
174+
175+
return;
169176
}
170177

171178
$option = $this->definition->getOption($name);

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

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class InputDefinition
3333
private $hasAnArrayArgument = false;
3434
private $hasOptional;
3535
private $options;
36+
private $negations;
3637
private $shortcuts;
3738

3839
/**
@@ -208,6 +209,7 @@ public function setOptions(array $options = [])
208209
{
209210
$this->options = [];
210211
$this->shortcuts = [];
212+
$this->negations = [];
211213
$this->addOptions($options);
212214
}
213215

@@ -246,6 +248,14 @@ public function addOption(InputOption $option)
246248
$this->shortcuts[$shortcut] = $option->getName();
247249
}
248250
}
251+
252+
if ($option->isNegatable()) {
253+
$negatedName = 'no-'.$option->getName();
254+
if (isset($this->options[$negatedName])) {
255+
throw new LogicException(sprintf('An option named "%s" already exists.', $negatedName));
256+
}
257+
$this->negations[$negatedName] = $option->getName();
258+
}
249259
}
250260

251261
/**
@@ -297,6 +307,14 @@ public function hasShortcut(string $name)
297307
return isset($this->shortcuts[$name]);
298308
}
299309

310+
/**
311+
* Returns true if an InputOption object exists by negated name.
312+
*/
313+
public function hasNegation(string $name): bool
314+
{
315+
return isset($this->negations[$name]);
316+
}
317+
300318
/**
301319
* Gets an InputOption by shortcut.
302320
*
@@ -338,6 +356,22 @@ public function shortcutToName(string $shortcut): string
338356
return $this->shortcuts[$shortcut];
339357
}
340358

359+
/**
360+
* Returns the InputOption name given a negation.
361+
*
362+
* @throws InvalidArgumentException When option given does not exist
363+
*
364+
* @internal
365+
*/
366+
public function negationToName(string $negation): string
367+
{
368+
if (!isset($this->negations[$negation])) {
369+
throw new InvalidArgumentException(sprintf('The "--%s" option does not exist.', $negation));
370+
}
371+
372+
return $this->negations[$negation];
373+
}
374+
341375
/**
342376
* Gets the synopsis.
343377
*
@@ -362,7 +396,8 @@ public function getSynopsis(bool $short = false)
362396
}
363397

364398
$shortcut = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : '';
365-
$elements[] = sprintf('[%s--%s%s]', $shortcut, $option->getName(), $value);
399+
$negation = $option->isNegatable() ? sprintf('|--no-%s', $option->getName()) : '';
400+
$elements[] = sprintf('[%s--%s%s%s]', $shortcut, $option->getName(), $value, $negation);
366401
}
367402
}
368403

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class InputOption
2525
public const VALUE_REQUIRED = 2;
2626
public const VALUE_OPTIONAL = 4;
2727
public const VALUE_IS_ARRAY = 8;
28+
public const VALUE_NEGATABLE = 16;
2829

2930
private $name;
3031
private $shortcut;
@@ -70,7 +71,7 @@ public function __construct(string $name, $shortcut = null, int $mode = null, st
7071

7172
if (null === $mode) {
7273
$mode = self::VALUE_NONE;
73-
} elseif ($mode > 15 || $mode < 1) {
74+
} elseif ($mode >= (self::VALUE_NEGATABLE << 1) || $mode < 1) {
7475
throw new InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode));
7576
}
7677

@@ -82,6 +83,9 @@ public function __construct(string $name, $shortcut = null, int $mode = null, st
8283
if ($this->isArray() && !$this->acceptValue()) {
8384
throw new InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.');
8485
}
86+
if ($this->isNegatable() && $this->acceptValue()) {
87+
throw new InvalidArgumentException('Impossible to have an option mode VALUE_NEGATABLE if the option also accepts a value.');
88+
}
8589

8690
$this->setDefault($default);
8791
}
@@ -146,6 +150,11 @@ public function isArray()
146150
return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode);
147151
}
148152

153+
public function isNegatable(): bool
154+
{
155+
return self::VALUE_NEGATABLE === (self::VALUE_NEGATABLE & $this->mode);
156+
}
157+
149158
/**
150159
* Sets the default value.
151160
*
@@ -158,6 +167,9 @@ public function setDefault($default = null)
158167
if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) {
159168
throw new LogicException('Cannot set a default value when using InputOption::VALUE_NONE mode.');
160169
}
170+
if (self::VALUE_NEGATABLE === (self::VALUE_NEGATABLE & $this->mode) && null !== $default) {
171+
throw new LogicException('Cannot set a default value when using InputOption::VALUE_NEGATABLE mode.');
172+
}
161173

162174
if ($this->isArray()) {
163175
if (null === $default) {
@@ -200,6 +212,7 @@ public function equals(self $option)
200212
return $option->getName() === $this->getName()
201213
&& $option->getShortcut() === $this->getShortcut()
202214
&& $option->getDefault() === $this->getDefault()
215+
&& $option->isNegatable() === $this->isNegatable()
203216
&& $option->isArray() === $this->isArray()
204217
&& $option->isValueRequired() === $this->isValueRequired()
205218
&& $option->isValueOptional() === $this->isValueOptional()

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy