annotate vendor/chi-teck/drupal-code-generator/src/Command/Navigation.php @ 0:c75dbcec494b

Initial commit from drush-created site
author Chris Cannam
date Thu, 05 Jul 2018 14:24:15 +0000
parents
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace DrupalCodeGenerator\Command;
Chris@0 4
Chris@0 5 use Symfony\Component\Console\Command\Command;
Chris@0 6 use Symfony\Component\Console\Formatter\OutputFormatterStyle;
Chris@0 7 use Symfony\Component\Console\Input\InputInterface;
Chris@0 8 use Symfony\Component\Console\Input\InputOption;
Chris@0 9 use Symfony\Component\Console\Output\OutputInterface;
Chris@0 10 use Symfony\Component\Console\Question\ChoiceQuestion;
Chris@0 11
Chris@0 12 /**
Chris@0 13 * Implements generate command.
Chris@0 14 */
Chris@0 15 class Navigation extends Command {
Chris@0 16
Chris@0 17 /**
Chris@0 18 * Menu tree.
Chris@0 19 *
Chris@0 20 * @var array
Chris@0 21 */
Chris@0 22 protected $menuTree;
Chris@0 23
Chris@0 24 /**
Chris@0 25 * Name of the generator to execute.
Chris@0 26 *
Chris@0 27 * @var string
Chris@0 28 */
Chris@0 29 protected $generatorName;
Chris@0 30
Chris@0 31 /**
Chris@0 32 * Command labels.
Chris@0 33 *
Chris@0 34 * @var array
Chris@0 35 */
Chris@0 36 protected $labels = [
Chris@0 37 'd7' => 'Drupal 7',
Chris@0 38 'd8' => 'Drupal 8',
Chris@0 39 ];
Chris@0 40
Chris@0 41 /**
Chris@0 42 * Aliases for some sub-menus.
Chris@0 43 *
Chris@0 44 * @var array
Chris@0 45 */
Chris@0 46 protected $defaultAliases = [
Chris@0 47 'service' => 'd8:service',
Chris@0 48 'plugin' => 'd8:plugin',
Chris@0 49 'theme' => 'd8:theme',
Chris@0 50 'module' => 'd8:module',
Chris@0 51 'form' => 'd8:form',
Chris@0 52 'test' => 'd8:test',
Chris@0 53 'yml' => 'd8:yml',
Chris@0 54 'links' => 'd8:yml:links',
Chris@0 55 ];
Chris@0 56
Chris@0 57 /**
Chris@0 58 * Constructs menu command.
Chris@0 59 *
Chris@0 60 * @param \DrupalCodeGenerator\Command\GeneratorInterface[] $commands
Chris@0 61 * List of registered commands.
Chris@0 62 */
Chris@0 63 public function __construct(array $commands) {
Chris@0 64 parent::__construct();
Chris@0 65
Chris@0 66 // Initialize the menu structure.
Chris@0 67 $this->menuTree = [];
Chris@0 68 $aliases = array_keys($this->defaultAliases);
Chris@0 69
Chris@0 70 // Build aliases for the navigation based on command namespaces.
Chris@0 71 foreach ($commands as $command) {
Chris@0 72 $command_name = $command->getName();
Chris@0 73 $sub_names = explode(':', $command_name);
Chris@0 74
Chris@0 75 $this->arraySetNestedValue($this->menuTree, $sub_names, TRUE);
Chris@0 76
Chris@0 77 // The last sub-name is actual command name so it cannot be used as an
Chris@0 78 // alias for navigation command.
Chris@0 79 $last_sub_name = array_pop($sub_names);
Chris@0 80
Chris@0 81 // Collect command labels.
Chris@0 82 if ($label = $command->getLabel()) {
Chris@0 83 $this->labels[$last_sub_name] = $label;
Chris@0 84 }
Chris@0 85
Chris@0 86 // We cannot use $application->getNamespaces() here because the
Chris@0 87 // application is not available at this point.
Chris@0 88 $alias = '';
Chris@0 89 foreach ($sub_names as $sub_name) {
Chris@0 90 $alias = $alias ? $alias . ':' . $sub_name : $sub_name;
Chris@0 91 $aliases[] = $alias;
Chris@0 92 }
Chris@0 93 }
Chris@0 94
Chris@0 95 $this->setAliases(array_unique($aliases));
Chris@0 96 $this->recursiveKsort($this->menuTree);
Chris@0 97 }
Chris@0 98
Chris@0 99 /**
Chris@0 100 * {@inheritdoc}
Chris@0 101 */
Chris@0 102 public function getUsages() {
Chris@0 103 return ['<generator>'];
Chris@0 104 }
Chris@0 105
Chris@0 106 /**
Chris@0 107 * {@inheritdoc}
Chris@0 108 */
Chris@0 109 protected function configure() {
Chris@0 110 $this
Chris@0 111 ->setName('navigation')
Chris@0 112 ->setDescription('Provides an interactive menu to select generator')
Chris@0 113 ->setHelp('Run `dcg list` to check out all available generators.')
Chris@0 114 ->setHidden(TRUE)
Chris@0 115 ->addOption(
Chris@0 116 'directory',
Chris@0 117 '-d',
Chris@0 118 InputOption::VALUE_OPTIONAL,
Chris@0 119 'Working directory'
Chris@0 120 )
Chris@0 121 ->addOption(
Chris@0 122 'answers',
Chris@0 123 '-a',
Chris@0 124 InputOption::VALUE_OPTIONAL,
Chris@0 125 'Default JSON formatted answers'
Chris@0 126 );
Chris@0 127 }
Chris@0 128
Chris@0 129 /**
Chris@0 130 * {@inheritdoc}
Chris@0 131 */
Chris@0 132 protected function interact(InputInterface $input, OutputInterface $output) {
Chris@0 133 $style = new OutputFormatterStyle('black', 'cyan', []);
Chris@0 134 $output->getFormatter()->setStyle('title', $style);
Chris@0 135
Chris@0 136 $command_name = $input->getFirstArgument();
Chris@0 137
Chris@0 138 // Before version 3.3.6 of Symfony console getFistArgument returned default
Chris@0 139 // command name.
Chris@0 140 $command_name = $command_name == 'navigation' ? NULL : $command_name;
Chris@0 141
Chris@0 142 if (isset($this->defaultAliases[$command_name])) {
Chris@0 143 $command_name = $this->defaultAliases[$command_name];
Chris@0 144 }
Chris@0 145
Chris@0 146 $menu_trail = $command_name ? explode(':', $command_name) : [];
Chris@0 147
Chris@0 148 $this->generatorName = $this->selectGenerator($input, $output, $menu_trail);
Chris@0 149 }
Chris@0 150
Chris@0 151 /**
Chris@0 152 * {@inheritdoc}
Chris@0 153 */
Chris@0 154 protected function execute(InputInterface $input, OutputInterface $output) {
Chris@0 155
Chris@0 156 if (!$this->generatorName) {
Chris@0 157 return 0;
Chris@0 158 }
Chris@0 159
Chris@0 160 // Run the generator.
Chris@0 161 return $this->getApplication()
Chris@0 162 ->find($this->generatorName)
Chris@0 163 ->run($input, $output);
Chris@0 164 }
Chris@0 165
Chris@0 166 /**
Chris@0 167 * Returns a generator selected by the user from a multilevel console menu.
Chris@0 168 *
Chris@0 169 * @param \Symfony\Component\Console\Input\InputInterface $input
Chris@0 170 * Input instance.
Chris@0 171 * @param \Symfony\Component\Console\Output\OutputInterface $output
Chris@0 172 * Output instance.
Chris@0 173 * @param array $menu_trail
Chris@0 174 * Menu trail.
Chris@0 175 *
Chris@0 176 * @return string|null
Chris@0 177 * Generator name or null if user decided to exit the navigation.
Chris@0 178 */
Chris@0 179 protected function selectGenerator(InputInterface $input, OutputInterface $output, array $menu_trail) {
Chris@0 180
Chris@0 181 // Narrow down menu tree.
Chris@0 182 $active_menu_tree = $this->menuTree;
Chris@0 183 foreach ($menu_trail as $active_menu_item) {
Chris@0 184 $active_menu_tree = $active_menu_tree[$active_menu_item];
Chris@0 185 }
Chris@0 186
Chris@0 187 // The $active_menu_tree can be either an array of menu items or TRUE if the
Chris@0 188 // user reached the final menu point.
Chris@0 189 if ($active_menu_tree === TRUE) {
Chris@0 190 return implode(':', $menu_trail);
Chris@0 191 }
Chris@0 192
Chris@0 193 $sub_menu_labels = $command_labels = [];
Chris@0 194 foreach ($active_menu_tree as $menu_item => $subtree) {
Chris@0 195 if (is_array($subtree)) {
Chris@0 196 $sub_menu_labels[$menu_item] = $this->createMenuItemLabel($menu_item, TRUE);
Chris@0 197 }
Chris@0 198 else {
Chris@0 199 $command_labels[$menu_item] = $this->createMenuItemLabel($menu_item, FALSE);
Chris@0 200 }
Chris@0 201 }
Chris@0 202 asort($sub_menu_labels);
Chris@0 203 asort($command_labels);
Chris@0 204
Chris@0 205 // Generally the choices array consists of the following parts:
Chris@0 206 // - Reference to the parent menu level.
Chris@0 207 // - Sorted list of nested menu levels.
Chris@0 208 // - Sorted list of commands.
Chris@0 209 $choices = ['..' => '..'] + $sub_menu_labels + $command_labels;
Chris@0 210 $question = new ChoiceQuestion('<title> Select generator: </title>', array_values($choices));
Chris@0 211 $question->setPrompt(count($choices) <= 10 ? ' ➤➤➤ ' : ' ➤➤➤➤ ');
Chris@0 212
Chris@0 213 $answer_label = $this->getHelper('question')->ask($input, $output, $question);
Chris@0 214 $answer = array_search($answer_label, $choices);
Chris@0 215
Chris@0 216 if ($answer == '..') {
Chris@0 217 // Exit the application if the user selected zero on the top menu level.
Chris@0 218 if (count($menu_trail) == 0) {
Chris@0 219 return NULL;
Chris@0 220 }
Chris@0 221 // Decrease menu level.
Chris@0 222 array_pop($menu_trail);
Chris@0 223 }
Chris@0 224 else {
Chris@0 225 // Increase menu level.
Chris@0 226 $menu_trail[] = $answer;
Chris@0 227 }
Chris@0 228
Chris@0 229 return $this->selectGenerator($input, $output, $menu_trail);
Chris@0 230 }
Chris@0 231
Chris@0 232 /**
Chris@0 233 * Creates a human readable label for a given menu item.
Chris@0 234 *
Chris@0 235 * @param string $menu_item
Chris@0 236 * Machine name of the menu item.
Chris@0 237 * @param bool $comment
Chris@0 238 * A boolean indicating that the label should be wrapped with comment tag.
Chris@0 239 *
Chris@0 240 * @return string
Chris@0 241 * The menu label.
Chris@0 242 */
Chris@0 243 protected function createMenuItemLabel($menu_item, $comment) {
Chris@0 244 $label = isset($this->labels[$menu_item]) ?
Chris@0 245 $this->labels[$menu_item] : str_replace(['-', '_'], ' ', ucfirst($menu_item));
Chris@0 246 return $comment ? "<comment>$label</comment>" : $label;
Chris@0 247 }
Chris@0 248
Chris@0 249 /**
Chris@0 250 * Sort multi-dimensional array by keys.
Chris@0 251 *
Chris@0 252 * @param array $array
Chris@0 253 * An array being sorted.
Chris@0 254 *
Chris@0 255 * @return array
Chris@0 256 * Sorted array.
Chris@0 257 */
Chris@0 258 protected function recursiveKsort(array &$array) {
Chris@0 259 foreach ($array as &$value) {
Chris@0 260 if (is_array($value)) {
Chris@0 261 $this->recursiveKsort($value);
Chris@0 262 }
Chris@0 263 }
Chris@0 264 return ksort($array);
Chris@0 265 }
Chris@0 266
Chris@0 267 /**
Chris@0 268 * Sets a value in a nested array with variable depth.
Chris@0 269 *
Chris@0 270 * @param array $array
Chris@0 271 * A reference to the array to modify.
Chris@0 272 * @param array $parents
Chris@0 273 * An array of parent keys, starting with the outermost key.
Chris@0 274 * @param mixed $value
Chris@0 275 * The value to set.
Chris@0 276 *
Chris@0 277 * @see https://api.drupal.org/api/drupal/includes!common.inc/function/drupal_array_set_nested_value/7
Chris@0 278 */
Chris@0 279 protected function arraySetNestedValue(array &$array, array $parents, $value) {
Chris@0 280 $ref = &$array;
Chris@0 281 foreach ($parents as $parent) {
Chris@0 282 if (isset($ref) && !is_array($ref)) {
Chris@0 283 $ref = [];
Chris@0 284 }
Chris@0 285 $ref = &$ref[$parent];
Chris@0 286 }
Chris@0 287 if (!isset($ref)) {
Chris@0 288 $ref = $value;
Chris@0 289 }
Chris@0 290 }
Chris@0 291
Chris@0 292 }