2 namespace Drush\Commands\help;
4 use Consolidation\AnnotatedCommand\Help\HelpDocument;
5 use Consolidation\OutputFormatters\FormatterManager;
6 use Consolidation\OutputFormatters\Options\FormatterOptions;
7 use Consolidation\OutputFormatters\StructuredData\RowsOfFields;
8 use Drush\Commands\DrushCommands;
10 use Symfony\Component\Console\Command\Command;
11 use Symfony\Component\Console\Descriptor\JsonDescriptor;
12 use Symfony\Component\Console\Descriptor\XmlDescriptor;
13 use Symfony\Component\Console\Helper\Table;
14 use Symfony\Component\Console\Helper\TableCell;
15 use Symfony\Component\Console\Output\OutputInterface;
17 class ListCommands extends DrushCommands
20 * List available commands.
23 * @option filter Restrict command list to those commands defined in the specified file. Omit value to choose from a list of names.
24 * @option raw Show a simple table of command names and descriptions.
28 * @usage drush list --filter=devel_generate
29 * Show only commands starting with devel-
30 * @usage drush list --format=xml
31 * List all commands in Symfony compatible xml format.
35 public function helpList($options = ['format' => 'listcli', 'raw' => false, 'filter' => self::REQ])
37 $application = Drush::getApplication();
38 $all = $application->all();
39 $namespaced = $this->categorize($all);
41 // Filter out namespaces that the user does not want to see
42 $filter_category = $options['filter'];
43 if (!empty($filter_category)) {
44 if (!array_key_exists($filter_category, $namespaced)) {
45 throw new \Exception(dt("The specified command category !filter does not exist.", ['!filter' => $filter_category]));
47 $namespaced = [$filter_category => $namespaced[$filter_category]];
51 * The listcli,json and raw formats don't yet go through the output formatter system.
52 * because \Consolidation\OutputFormatters\Transformations\DomToArraySimplifier
53 * can't yet handle the DomDocument that produces the Symfony expected XML. For consistency, the XML
54 * output chooses to use the Symfony descriptor as well.
56 if ($options['raw']) {
57 $this->renderListRaw($namespaced);
59 } elseif ($options['format'] == 'listcli') {
60 $preamble = dt('Run `drush help [command]` to view command-specific help. Run `drush topic` to read even more documentation.');
61 $this->renderListCLI($application, $namespaced, $this->output(), $preamble);
62 if (!Drush::bootstrapManager()->hasBootstrapped((DRUSH_BOOTSTRAP_DRUPAL_ROOT))) {
63 $this->io()->note(dt('Drupal root not found. Pass --root or a @siteAlias in order to see Drupal-specific commands.'));
66 } elseif ($options['format'] == 'xml') {
67 $descriptor = new XmlDescriptor($this->output(), []);
68 return $descriptor->describe($this->output, $application, []);
69 } elseif ($options['format'] == 'json') {
70 $descriptor = new JsonDescriptor($this->output(), []);
71 return $descriptor->describe($this->output, $application, []);
73 // No longer used. Works for XML, but gives error for JSON.
74 // $dom = $this->buildDom($namespaced, $application);
82 * @return \DOMDocument
84 public function buildDom($namespaced, $application)
86 $dom = new \DOMDocument('1.0', 'UTF-8');
87 $rootXml = $dom->createElement('symfony');
88 $rootXml->setAttribute('name', $application->getName());
89 if ($application->getVersion() !== 'UNKNOWN') {
90 $rootXml->setAttribute('version', $application->getVersion());
94 // Create two top level elements.
95 $commandsXML = $dom->createElement('commands');
96 $namespacesXML = $dom->createElement('namespaces');
98 foreach ($namespaced as $namespace => $commands) {
99 $namespaceXML = $dom->createElement('namespace');
100 $namespaceXML->setAttribute('id', $namespace);
101 foreach ($commands as $key => $command) {
102 $helpDocument = new HelpDocument($command);
103 $domData = $helpDocument->getDomData();
104 $node = $domData->getElementsByTagName("command")->item(0);
105 $element = $dom->importNode($node, true);
106 $commandsXML->appendChild($element);
108 $ncommandXML = $dom->createElement('command', $key);
109 $namespaceXML->appendChild($ncommandXML);
111 $namespacesXML->appendChild($namespaceXML);
114 // Append top level elements to root element in correct order.
115 $rootXml->appendChild($commandsXML);
116 $rootXml->appendChild($namespacesXML);
117 $dom->appendChild($rootXml);
122 * @param \Symfony\Component\Console\Application $application
123 * @param array $namespaced
124 * @param OutputInterface $output
125 * @param string $preamble
127 public static function renderListCLI($application, $namespaced, $output, $preamble)
129 $output->writeln($application->getHelp());
130 $output->writeln('');
132 ->writeln($preamble);
133 $output->writeln('');
135 $rows[] = ['Available commands:', ''];
136 foreach ($namespaced as $namespace => $list) {
137 $rows[] = ['<comment>' . $namespace . ':</comment>', ''];
138 foreach ($list as $name => $command) {
139 $description = $command->getDescription();
141 // For commands such as foo:bar, remove
142 // any alias 'foo-bar' from the alias list.
143 $aliasList = array_filter(
144 $command->getAliases(),
145 function ($aliasName) use ($name) {
146 return $aliasName != str_replace(':', '-', $name);
150 $aliases = implode(', ', $aliasList);
151 $suffix = $aliases ? " ($aliases)" : '';
152 $rows[] = [' ' . $name . $suffix, $description];
155 $formatterManager = new FormatterManager();
156 list($terminalWidth,) = $application->getTerminalDimensions();
158 FormatterOptions::INCLUDE_FIELD_LABELS => false,
159 FormatterOptions::TABLE_STYLE => 'compact',
160 FormatterOptions::TERMINAL_WIDTH => $terminalWidth,
162 $formatterOptions = new FormatterOptions([], $opts);
164 $formatterManager->write($output, 'table', new RowsOfFields($rows), $formatterOptions);
167 public function getTerminalWidth()
169 // From \Consolidation\AnnotatedCommand\Options\PrepareTerminalWidthOption::getTerminalWidth
170 $application = Drush::getApplication();
171 $dimensions = $application->getTerminalDimensions();
172 if ($dimensions[0] == null) {
175 return $dimensions[0];
179 * @param array $namespaced
181 public function renderListRaw($namespaced)
183 $table = new Table($this->output());
184 $table->setStyle('compact');
185 foreach ($namespaced as $namespace => $commands) {
186 foreach ($commands as $command) {
187 $table->addRow([$command->getName(), $command->getDescription()]);
194 * @param Command[] $all
195 * @param string $separator
199 public static function categorize($all, $separator = ':')
201 foreach ($all as $key => $command) {
202 if (!in_array($key, $command->getAliases()) && !$command->isHidden()) {
203 $parts = explode($separator, $key);
204 $namespace = count($parts) >= 2 ? array_shift($parts) : '_global';
205 $namespaced[$namespace][$key] = $command;
209 // Avoid solo namespaces.
210 foreach ($namespaced as $namespace => $commands) {
211 if (count($commands) == 1) {
212 $namespaced['_global'] += $commands;
213 unset($namespaced[$namespace]);