Skip to content

Navigation Menu

Sign in
Appearance settings

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

[Console] Support binary / negatable options #39642

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 5, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
[WIP] Implements #24314: Support binary / negatable options, e.g. --f…
…oo and --no-foo.
  • Loading branch information
greg-1-anderson authored and jderusse committed Dec 27, 2020
commit 8d455dbd0c4310af1a3b0b70bfd084b58ac82524
3 changes: 1 addition & 2 deletions 3 src/Symfony/Component/Console/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -1033,8 +1033,7 @@ protected function getDefaultInputDefinition()
new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'),
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'),
new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this application version'),
new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'),
new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this flag removed?

new InputOption('--ansi', '', InputOption::VALUE_BINARY, 'Force ANSI output', null),
new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'),
]);
}
Expand Down
4 changes: 4 additions & 0 deletions 4 src/Symfony/Component/Console/Descriptor/JsonDescriptor.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ private function getInputOptionData(InputOption $option): array
'accept_value' => $option->acceptValue(),
'is_value_required' => $option->isValueRequired(),
'is_multiple' => $option->isArray(),
'is_negatable' => $option->isNegatable(),
'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $option->getDescription()),
'default' => \INF === $option->getDefault() ? 'INF' : $option->getDefault(),
];
Expand All @@ -133,6 +134,9 @@ private function getInputDefinitionData(InputDefinition $definition): array

$inputOptions = [];
foreach ($definition->getOptions() as $name => $option) {
if ($option->isHidden()) {
continue;
}
$inputOptions[$name] = $this->getInputOptionData($option);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ protected function describeInputArgument(InputArgument $argument, array $options
*/
protected function describeInputOption(InputOption $option, array $options = [])
{
$name = '--'.$option->getName();
$negatable = $option->isNegatable() ? '[no-]' : '';
$name = '--'.$negatable.$option->getName();
if ($option->getShortcut()) {
$name .= '|-'.str_replace('|', '|-', $option->getShortcut()).'';
}
Expand All @@ -79,6 +80,7 @@ protected function describeInputOption(InputOption $option, array $options = [])
.'* Accept value: '.($option->acceptValue() ? 'yes' : 'no')."\n"
.'* Is value required: '.($option->isValueRequired() ? 'yes' : 'no')."\n"
.'* Is multiple: '.($option->isArray() ? 'yes' : 'no')."\n"
.'* Is negatable: '.($option->isNegatable() ? 'yes' : 'no')."\n"
.'* Default: `'.str_replace("\n", '', var_export($option->getDefault(), true)).'`'
);
}
Expand All @@ -105,6 +107,9 @@ protected function describeInputDefinition(InputDefinition $definition, array $o

$this->write('### Options');
foreach ($definition->getOptions() as $option) {
if ($option->isHidden()) {
continue;
}
$this->write("\n\n");
if (null !== $describeInputOption = $this->describeInputOption($option)) {
$this->write($describeInputOption);
Expand Down
12 changes: 9 additions & 3 deletions 12 src/Symfony/Component/Console/Descriptor/TextDescriptor.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,12 @@ protected function describeInputArgument(InputArgument $argument, array $options
*/
protected function describeInputOption(InputOption $option, array $options = [])
{
$default = '';
if ($option->acceptValue() && null !== $option->getDefault() && (!\is_array($option->getDefault()) || \count($option->getDefault()))) {
$default = sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($option->getDefault()));
} else {
$default = '';
} elseif ($option->isNegatable() && (null !== $option->getDefault())) {
$negative_default = $option->getDefault() ? '' : 'no-';
$default = sprintf('<comment> [default: --%s%s]</comment>', $negative_default, $option->getName());
}

$value = '';
Expand All @@ -72,9 +74,10 @@ protected function describeInputOption(InputOption $option, array $options = [])
}

$totalWidth = isset($options['total_width']) ? $options['total_width'] : $this->calculateTotalWidthForOptions([$option]);
$negatable = $option->isNegatable() ? '[no-]' : '';
$synopsis = sprintf('%s%s',
$option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : ' ',
sprintf('--%s%s', $option->getName(), $value)
sprintf('--%s%s%s', $negatable, $option->getName(), $value)
);

$spacingWidth = $totalWidth - Helper::strlen($synopsis);
Expand Down Expand Up @@ -117,6 +120,9 @@ protected function describeInputDefinition(InputDefinition $definition, array $o

$this->writeText('<comment>Options:</comment>', $options);
foreach ($definition->getOptions() as $option) {
if ($option->isHidden()) {
continue;
}
if (\strlen($option->getShortcut()) > 1) {
$laterOptions[] = $option;
continue;
Expand Down
5 changes: 4 additions & 1 deletion 5 src/Symfony/Component/Console/Descriptor/XmlDescriptor.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ public function getInputDefinitionDocument(InputDefinition $definition): \DOMDoc

$definitionXML->appendChild($optionsXML = $dom->createElement('options'));
foreach ($definition->getOptions() as $option) {
$this->appendDocument($optionsXML, $this->getInputOptionDocument($option));
if (!$option->isHidden()) {
$this->appendDocument($optionsXML, $this->getInputOptionDocument($option));
}
}

return $dom;
Expand Down Expand Up @@ -210,6 +212,7 @@ private function getInputOptionDocument(InputOption $option): \DOMDocument
$objectXML->setAttribute('accept_value', $option->acceptValue() ? 1 : 0);
$objectXML->setAttribute('is_value_required', $option->isValueRequired() ? 1 : 0);
$objectXML->setAttribute('is_multiple', $option->isArray() ? 1 : 0);
$objectXML->setAttribute('is_negatable', $option->isNegatable() ? 1 : 0);
$objectXML->appendChild($descriptionXML = $dom->createElement('description'));
$descriptionXML->appendChild($dom->createTextNode($option->getDescription()));

Expand Down
17 changes: 3 additions & 14 deletions 17 src/Symfony/Component/Console/Input/ArgvInput.php
Original file line number Diff line number Diff line change
Expand Up @@ -208,11 +208,7 @@ private function addShortOption(string $shortcut, $value)
*/
private function addLongOption(string $name, $value)
{
if (!$this->definition->hasOption($name)) {
throw new RuntimeException(sprintf('The "--%s" option does not exist.', $name));
}

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

if (null !== $value && !$option->acceptValue()) {
throw new RuntimeException(sprintf('The "--%s" option does not accept a value.', $name));
Expand All @@ -229,15 +225,8 @@ private function addLongOption(string $name, $value)
}
}

if (null === $value) {
if ($option->isValueRequired()) {
throw new RuntimeException(sprintf('The "--%s" option requires a value.', $name));
}

if (!$option->isArray() && !$option->isValueOptional()) {
$value = true;
}
}
$name = $option->effectiveName();
$value = $option->checkValue($value);

if ($option->isArray()) {
$this->options[$name][] = $value;
Expand Down
18 changes: 1 addition & 17 deletions 18 src/Symfony/Component/Console/Input/ArrayInput.php
Original file line number Diff line number Diff line change
Expand Up @@ -164,23 +164,7 @@ private function addShortOption(string $shortcut, $value)
*/
private function addLongOption(string $name, $value)
{
if (!$this->definition->hasOption($name)) {
throw new InvalidOptionException(sprintf('The "--%s" option does not exist.', $name));
}

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

if (null === $value) {
if ($option->isValueRequired()) {
throw new InvalidOptionException(sprintf('The "--%s" option requires a value.', $name));
}

if (!$option->isValueOptional()) {
$value = true;
}
}

$this->options[$name] = $value;
$this->setOption($name, $value);
}

/**
Expand Down
30 changes: 20 additions & 10 deletions 30 src/Symfony/Component/Console/Input/Input.php
Original file line number Diff line number Diff line change
Expand Up @@ -146,23 +146,17 @@ public function getOptions()
*/
public function getOption(string $name)
{
if (!$this->definition->hasOption($name)) {
throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name));
}

return \array_key_exists($name, $this->options) ? $this->options[$name] : $this->definition->getOption($name)->getDefault();
$option = $this->getOptionDefinition($name);
return \array_key_exists($name, $this->options) ? $this->options[$name] : $option->getDefault();
}

/**
* {@inheritdoc}
*/
public function setOption(string $name, $value)
{
if (!$this->definition->hasOption($name)) {
throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name));
}

$this->options[$name] = $value;
$option = $this->getOptionDefinition($name);
$this->options[$option->effectiveName()] = $option->checkValue($value);
}

/**
Expand Down Expand Up @@ -198,4 +192,20 @@ public function getStream()
{
return $this->stream;
}

/**
* Look up the option definition for the given option name.
*
* @param string $name
*
* @return InputOption
*/
protected function getOptionDefinition($name)
{
if (!$this->definition->hasOption($name)) {
throw new RuntimeException(sprintf('The "--%s" option does not exist.', $name));
}

return $this->definition->getOption($name);
}
}
18 changes: 17 additions & 1 deletion 18 src/Symfony/Component/Console/Input/InputDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,19 @@ public function addOptions(array $options = [])
* @throws LogicException When option given already exist
*/
public function addOption(InputOption $option)
{
$this->doAddOption($option);

if ($option->isNegatable()) {
$negatedOption = new NegatedInputOption($option);
$this->doAddOption($negatedOption);
}
}

/**
* @throws LogicException When option given already exist
*/
private function doAddOption(InputOption $option)
{
if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) {
throw new LogicException(sprintf('An option named "%s" already exists.', $option->getName()));
Expand Down Expand Up @@ -316,7 +329,7 @@ public function getOptionDefaults()
{
$values = [];
foreach ($this->options as $option) {
$values[$option->getName()] = $option->getDefault();
$values[$option->effectiveName()] = $option->getDefault();
}

return $values;
Expand Down Expand Up @@ -351,6 +364,9 @@ public function getSynopsis(bool $short = false)
$elements[] = '[options]';
} elseif (!$short) {
foreach ($this->getOptions() as $option) {
if ($option->isHidden()) {
continue;
}
$value = '';
if ($option->acceptValue()) {
$value = sprintf(
Expand Down
71 changes: 68 additions & 3 deletions 71 src/Symfony/Component/Console/Input/InputOption.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\InvalidOptionException;
use Symfony\Component\Console\Exception\LogicException;

/**
Expand All @@ -25,6 +26,9 @@ class InputOption
public const VALUE_REQUIRED = 2;
public const VALUE_OPTIONAL = 4;
public const VALUE_IS_ARRAY = 8;
public const VALUE_NEGATABLE = 16;
public const VALUE_HIDDEN = 32;
public const VALUE_BINARY = (self::VALUE_NONE | self::VALUE_NEGATABLE);

private $name;
private $shortcut;
Expand Down Expand Up @@ -70,7 +74,7 @@ public function __construct(string $name, $shortcut = null, int $mode = null, st

if (null === $mode) {
$mode = self::VALUE_NONE;
} elseif ($mode > 15 || $mode < 1) {
} elseif ($mode >= (self::VALUE_HIDDEN << 1) || $mode < 1) {
throw new InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode));
}

Expand Down Expand Up @@ -106,6 +110,11 @@ public function getName()
return $this->name;
}

public function effectiveName()
{
return $this->getName();
}

/**
* Returns true if the option accepts a value.
*
Expand Down Expand Up @@ -146,6 +155,39 @@ public function isArray()
return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode);
}

/**
* Returns true if the option is negatable (option --foo can be forced
* to 'false' via the --no-foo option).
*
* @return bool true if mode is self::VALUE_NEGATABLE, false otherwise
*/
public function isNegatable()
jderusse marked this conversation as resolved.
Show resolved Hide resolved
{
return self::VALUE_NEGATABLE === (self::VALUE_NEGATABLE & $this->mode);
}

/**
* Returns true if the option should not be shown in help (e.g. a negated
* option).
*
* @return bool true if mode is self::VALUE_HIDDEN, false otherwise
*/
public function isHidden()
{
return self::VALUE_HIDDEN === (self::VALUE_HIDDEN & $this->mode);
}

/**
* Returns true if the option is binary (can be --foo or --no-foo, and
* nothing else).
*
* @return bool true if negatable and does not have a value.
*/
public function isBinary()
{
return $this->isNegatable() && !$this->acceptValue();
}

/**
* Sets the default value.
*
Expand All @@ -155,7 +197,7 @@ public function isArray()
*/
public function setDefault($default = null)
{
if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) {
if (self::VALUE_NONE === ((self::VALUE_NONE | self::VALUE_NEGATABLE) & $this->mode) && null !== $default) {
throw new LogicException('Cannot set a default value when using InputOption::VALUE_NONE mode.');
}

Expand All @@ -167,7 +209,7 @@ public function setDefault($default = null)
}
}

$this->default = $this->acceptValue() ? $default : false;
$this->default = ($this->acceptValue() || $this->isNegatable()) ? $default : false;
}

/**
Expand All @@ -190,6 +232,27 @@ public function getDescription()
return $this->description;
}

/**
* Checks the validity of a value, and alters it as necessary
*
* @param mixed $value
*
* @return @mixed
*/
public function checkValue($value)
{
if (null === $value) {
if ($this->isValueRequired()) {
throw new InvalidOptionException(sprintf('The "--%s" option requires a value.', $this->getName()));
}

if (!$this->isValueOptional()) {
return true;
}
}
return $value;
}

/**
* Checks whether the given option equals this one.
*
Expand All @@ -200,6 +263,8 @@ public function equals(self $option)
return $option->getName() === $this->getName()
&& $option->getShortcut() === $this->getShortcut()
&& $option->getDefault() === $this->getDefault()
&& $option->isHidden() === $this->isHidden()
&& $option->isNegatable() === $this->isNegatable()
&& $option->isArray() === $this->isArray()
&& $option->isValueRequired() === $this->isValueRequired()
&& $option->isValueOptional() === $this->isValueOptional()
Expand Down
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.