Chris@0: Chris@0: * Chris@0: * For the full copyright and license information, please view the LICENSE Chris@0: * file that was distributed with this source code. Chris@0: */ Chris@0: Chris@0: namespace Symfony\Component\Console\Descriptor; Chris@0: Chris@0: use Symfony\Component\Console\Application; Chris@0: use Symfony\Component\Console\Command\Command; Chris@0: use Symfony\Component\Console\Formatter\OutputFormatter; Chris@0: use Symfony\Component\Console\Helper\Helper; Chris@0: use Symfony\Component\Console\Input\InputArgument; Chris@0: use Symfony\Component\Console\Input\InputDefinition; Chris@0: use Symfony\Component\Console\Input\InputOption; Chris@0: Chris@0: /** Chris@0: * Text descriptor. Chris@0: * Chris@0: * @author Jean-François Simon Chris@0: * Chris@0: * @internal Chris@0: */ Chris@0: class TextDescriptor extends Descriptor Chris@0: { Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@17: protected function describeInputArgument(InputArgument $argument, array $options = []) Chris@0: { Chris@17: if (null !== $argument->getDefault() && (!\is_array($argument->getDefault()) || \count($argument->getDefault()))) { Chris@0: $default = sprintf(' [default: %s]', $this->formatDefaultValue($argument->getDefault())); Chris@0: } else { Chris@0: $default = ''; Chris@0: } Chris@0: Chris@0: $totalWidth = isset($options['total_width']) ? $options['total_width'] : Helper::strlen($argument->getName()); Chris@17: $spacingWidth = $totalWidth - \strlen($argument->getName()); Chris@0: Chris@0: $this->writeText(sprintf(' %s %s%s%s', Chris@0: $argument->getName(), Chris@0: str_repeat(' ', $spacingWidth), Chris@0: // + 4 = 2 spaces before , 2 spaces after Chris@0: preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $argument->getDescription()), Chris@0: $default Chris@0: ), $options); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@17: protected function describeInputOption(InputOption $option, array $options = []) Chris@0: { Chris@17: if ($option->acceptValue() && null !== $option->getDefault() && (!\is_array($option->getDefault()) || \count($option->getDefault()))) { Chris@0: $default = sprintf(' [default: %s]', $this->formatDefaultValue($option->getDefault())); Chris@0: } else { Chris@0: $default = ''; Chris@0: } Chris@0: Chris@0: $value = ''; Chris@0: if ($option->acceptValue()) { Chris@0: $value = '='.strtoupper($option->getName()); Chris@0: Chris@0: if ($option->isValueOptional()) { Chris@0: $value = '['.$value.']'; Chris@0: } Chris@0: } Chris@0: Chris@17: $totalWidth = isset($options['total_width']) ? $options['total_width'] : $this->calculateTotalWidthForOptions([$option]); Chris@0: $synopsis = sprintf('%s%s', Chris@0: $option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : ' ', Chris@0: sprintf('--%s%s', $option->getName(), $value) Chris@0: ); Chris@0: Chris@0: $spacingWidth = $totalWidth - Helper::strlen($synopsis); Chris@0: Chris@0: $this->writeText(sprintf(' %s %s%s%s%s', Chris@0: $synopsis, Chris@0: str_repeat(' ', $spacingWidth), Chris@0: // + 4 = 2 spaces before , 2 spaces after Chris@0: preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $option->getDescription()), Chris@0: $default, Chris@0: $option->isArray() ? ' (multiple values allowed)' : '' Chris@0: ), $options); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@17: protected function describeInputDefinition(InputDefinition $definition, array $options = []) Chris@0: { Chris@0: $totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions()); Chris@0: foreach ($definition->getArguments() as $argument) { Chris@0: $totalWidth = max($totalWidth, Helper::strlen($argument->getName())); Chris@0: } Chris@0: Chris@0: if ($definition->getArguments()) { Chris@0: $this->writeText('Arguments:', $options); Chris@0: $this->writeText("\n"); Chris@0: foreach ($definition->getArguments() as $argument) { Chris@17: $this->describeInputArgument($argument, array_merge($options, ['total_width' => $totalWidth])); Chris@0: $this->writeText("\n"); Chris@0: } Chris@0: } Chris@0: Chris@0: if ($definition->getArguments() && $definition->getOptions()) { Chris@0: $this->writeText("\n"); Chris@0: } Chris@0: Chris@0: if ($definition->getOptions()) { Chris@17: $laterOptions = []; Chris@0: Chris@0: $this->writeText('Options:', $options); Chris@0: foreach ($definition->getOptions() as $option) { Chris@17: if (\strlen($option->getShortcut()) > 1) { Chris@0: $laterOptions[] = $option; Chris@0: continue; Chris@0: } Chris@0: $this->writeText("\n"); Chris@17: $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth])); Chris@0: } Chris@0: foreach ($laterOptions as $option) { Chris@0: $this->writeText("\n"); Chris@17: $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth])); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@17: protected function describeCommand(Command $command, array $options = []) Chris@0: { Chris@0: $command->getSynopsis(true); Chris@0: $command->getSynopsis(false); Chris@0: $command->mergeApplicationDefinition(false); Chris@0: Chris@0: $this->writeText('Usage:', $options); Chris@17: foreach (array_merge([$command->getSynopsis(true)], $command->getAliases(), $command->getUsages()) as $usage) { Chris@0: $this->writeText("\n"); Chris@14: $this->writeText(' '.OutputFormatter::escape($usage), $options); Chris@0: } Chris@0: $this->writeText("\n"); Chris@0: Chris@0: $definition = $command->getNativeDefinition(); Chris@0: if ($definition->getOptions() || $definition->getArguments()) { Chris@0: $this->writeText("\n"); Chris@0: $this->describeInputDefinition($definition, $options); Chris@0: $this->writeText("\n"); Chris@0: } Chris@0: Chris@0: if ($help = $command->getProcessedHelp()) { Chris@0: $this->writeText("\n"); Chris@0: $this->writeText('Help:', $options); Chris@0: $this->writeText("\n"); Chris@0: $this->writeText(' '.str_replace("\n", "\n ", $help), $options); Chris@0: $this->writeText("\n"); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@17: protected function describeApplication(Application $application, array $options = []) Chris@0: { Chris@0: $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null; Chris@0: $description = new ApplicationDescription($application, $describedNamespace); Chris@0: Chris@0: if (isset($options['raw_text']) && $options['raw_text']) { Chris@0: $width = $this->getColumnWidth($description->getCommands()); Chris@0: Chris@0: foreach ($description->getCommands() as $command) { Chris@0: $this->writeText(sprintf("%-{$width}s %s", $command->getName(), $command->getDescription()), $options); Chris@0: $this->writeText("\n"); Chris@0: } Chris@0: } else { Chris@0: if ('' != $help = $application->getHelp()) { Chris@0: $this->writeText("$help\n\n", $options); Chris@0: } Chris@0: Chris@0: $this->writeText("Usage:\n", $options); Chris@0: $this->writeText(" command [options] [arguments]\n\n", $options); Chris@0: Chris@0: $this->describeInputDefinition(new InputDefinition($application->getDefinition()->getOptions()), $options); Chris@0: Chris@0: $this->writeText("\n"); Chris@0: $this->writeText("\n"); Chris@0: Chris@14: $commands = $description->getCommands(); Chris@14: $namespaces = $description->getNamespaces(); Chris@14: if ($describedNamespace && $namespaces) { Chris@14: // make sure all alias commands are included when describing a specific namespace Chris@14: $describedNamespaceInfo = reset($namespaces); Chris@14: foreach ($describedNamespaceInfo['commands'] as $name) { Chris@14: $commands[$name] = $description->getCommand($name); Chris@14: } Chris@14: } Chris@14: Chris@14: // calculate max. width based on available commands per namespace Chris@17: $width = $this->getColumnWidth(\call_user_func_array('array_merge', array_map(function ($namespace) use ($commands) { Chris@14: return array_intersect($namespace['commands'], array_keys($commands)); Chris@14: }, $namespaces))); Chris@0: Chris@0: if ($describedNamespace) { Chris@0: $this->writeText(sprintf('Available commands for the "%s" namespace:', $describedNamespace), $options); Chris@0: } else { Chris@0: $this->writeText('Available commands:', $options); Chris@0: } Chris@0: Chris@14: foreach ($namespaces as $namespace) { Chris@14: $namespace['commands'] = array_filter($namespace['commands'], function ($name) use ($commands) { Chris@14: return isset($commands[$name]); Chris@14: }); Chris@0: Chris@14: if (!$namespace['commands']) { Chris@14: continue; Chris@14: } Chris@14: Chris@0: if (!$describedNamespace && ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) { Chris@0: $this->writeText("\n"); Chris@0: $this->writeText(' '.$namespace['id'].'', $options); Chris@0: } Chris@0: Chris@0: foreach ($namespace['commands'] as $name) { Chris@14: $this->writeText("\n"); Chris@14: $spacingWidth = $width - Helper::strlen($name); Chris@14: $command = $commands[$name]; Chris@14: $commandAliases = $name === $command->getName() ? $this->getCommandAliasesText($command) : ''; Chris@14: $this->writeText(sprintf(' %s%s%s', $name, str_repeat(' ', $spacingWidth), $commandAliases.$command->getDescription()), $options); Chris@0: } Chris@0: } Chris@0: Chris@0: $this->writeText("\n"); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@17: private function writeText($content, array $options = []) Chris@0: { Chris@0: $this->write( Chris@0: isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content, Chris@0: isset($options['raw_output']) ? !$options['raw_output'] : true Chris@0: ); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Formats command aliases to show them in the command description. Chris@0: * Chris@0: * @return string Chris@0: */ Chris@14: private function getCommandAliasesText(Command $command) Chris@0: { Chris@0: $text = ''; Chris@0: $aliases = $command->getAliases(); Chris@0: Chris@0: if ($aliases) { Chris@0: $text = '['.implode('|', $aliases).'] '; Chris@0: } Chris@0: Chris@0: return $text; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Formats input option/argument default value. Chris@0: * Chris@0: * @param mixed $default Chris@0: * Chris@0: * @return string Chris@0: */ Chris@0: private function formatDefaultValue($default) Chris@0: { Chris@12: if (INF === $default) { Chris@12: return 'INF'; Chris@12: } Chris@12: Chris@17: if (\is_string($default)) { Chris@0: $default = OutputFormatter::escape($default); Chris@17: } elseif (\is_array($default)) { Chris@0: foreach ($default as $key => $value) { Chris@17: if (\is_string($value)) { Chris@0: $default[$key] = OutputFormatter::escape($value); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: return str_replace('\\\\', '\\', json_encode($default, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); Chris@0: } Chris@0: Chris@0: /** Chris@14: * @param (Command|string)[] $commands Chris@0: * Chris@0: * @return int Chris@0: */ Chris@0: private function getColumnWidth(array $commands) Chris@0: { Chris@17: $widths = []; Chris@0: Chris@0: foreach ($commands as $command) { Chris@14: if ($command instanceof Command) { Chris@14: $widths[] = Helper::strlen($command->getName()); Chris@14: foreach ($command->getAliases() as $alias) { Chris@14: $widths[] = Helper::strlen($alias); Chris@14: } Chris@14: } else { Chris@14: $widths[] = Helper::strlen($command); Chris@0: } Chris@0: } Chris@0: Chris@14: return $widths ? max($widths) + 2 : 0; Chris@0: } Chris@0: Chris@0: /** Chris@0: * @param InputOption[] $options Chris@0: * Chris@0: * @return int Chris@0: */ Chris@14: private function calculateTotalWidthForOptions(array $options) Chris@0: { Chris@0: $totalWidth = 0; Chris@0: foreach ($options as $option) { Chris@0: // "-" + shortcut + ", --" + name Chris@0: $nameLength = 1 + max(Helper::strlen($option->getShortcut()), 1) + 4 + Helper::strlen($option->getName()); Chris@0: Chris@0: if ($option->acceptValue()) { Chris@0: $valueLength = 1 + Helper::strlen($option->getName()); // = + value Chris@0: $valueLength += $option->isValueOptional() ? 2 : 0; // [ + ] Chris@0: Chris@0: $nameLength += $valueLength; Chris@0: } Chris@0: $totalWidth = max($totalWidth, $nameLength); Chris@0: } Chris@0: Chris@0: return $totalWidth; Chris@0: } Chris@0: }