'Drupal 7', 'd8' => 'Drupal 8', ]; /** * Aliases for some sub-menus. * * @var array */ protected $defaultAliases = [ 'service' => 'd8:service', 'plugin' => 'd8:plugin', 'theme' => 'd8:theme', 'module' => 'd8:module', 'form' => 'd8:form', 'test' => 'd8:test', 'yml' => 'd8:yml', 'links' => 'd8:yml:links', ]; /** * Constructs menu command. * * @param \DrupalCodeGenerator\Command\GeneratorInterface[] $commands * List of registered commands. */ public function __construct(array $commands) { parent::__construct(); // Initialize the menu structure. $this->menuTree = []; $aliases = array_keys($this->defaultAliases); // Build aliases for the navigation based on command namespaces. foreach ($commands as $command) { $command_name = $command->getName(); $sub_names = explode(':', $command_name); $this->arraySetNestedValue($this->menuTree, $sub_names, TRUE); // The last sub-name is actual command name so it cannot be used as an // alias for navigation command. $last_sub_name = array_pop($sub_names); // Collect command labels. if ($label = $command->getLabel()) { $this->labels[$last_sub_name] = $label; } // We cannot use $application->getNamespaces() here because the // application is not available at this point. $alias = ''; foreach ($sub_names as $sub_name) { $alias = $alias ? $alias . ':' . $sub_name : $sub_name; $aliases[] = $alias; } } $this->setAliases(array_unique($aliases)); $this->recursiveKsort($this->menuTree); } /** * {@inheritdoc} */ public function getUsages() { return ['']; } /** * {@inheritdoc} */ protected function configure() { $this ->setName('navigation') ->setDescription('Provides an interactive menu to select generator') ->setHelp('Run `dcg list` to check out all available generators.') ->setHidden(TRUE) ->addOption( 'directory', '-d', InputOption::VALUE_OPTIONAL, 'Working directory' ) ->addOption( 'answers', '-a', InputOption::VALUE_OPTIONAL, 'Default JSON formatted answers' ); } /** * {@inheritdoc} */ protected function interact(InputInterface $input, OutputInterface $output) { $style = new OutputFormatterStyle('black', 'cyan', []); $output->getFormatter()->setStyle('title', $style); $command_name = $input->getFirstArgument(); // Before version 3.3.6 of Symfony console getFistArgument returned default // command name. $command_name = $command_name == 'navigation' ? NULL : $command_name; if (isset($this->defaultAliases[$command_name])) { $command_name = $this->defaultAliases[$command_name]; } $menu_trail = $command_name ? explode(':', $command_name) : []; $this->generatorName = $this->selectGenerator($input, $output, $menu_trail); } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { if (!$this->generatorName) { return 0; } // Run the generator. return $this->getApplication() ->find($this->generatorName) ->run($input, $output); } /** * Returns a generator selected by the user from a multilevel console menu. * * @param \Symfony\Component\Console\Input\InputInterface $input * Input instance. * @param \Symfony\Component\Console\Output\OutputInterface $output * Output instance. * @param array $menu_trail * Menu trail. * * @return string|null * Generator name or null if user decided to exit the navigation. */ protected function selectGenerator(InputInterface $input, OutputInterface $output, array $menu_trail) { // Narrow down menu tree. $active_menu_tree = $this->menuTree; foreach ($menu_trail as $active_menu_item) { $active_menu_tree = $active_menu_tree[$active_menu_item]; } // The $active_menu_tree can be either an array of menu items or TRUE if the // user reached the final menu point. if ($active_menu_tree === TRUE) { return implode(':', $menu_trail); } $sub_menu_labels = $command_labels = []; foreach ($active_menu_tree as $menu_item => $subtree) { if (is_array($subtree)) { $sub_menu_labels[$menu_item] = $this->createMenuItemLabel($menu_item, TRUE); } else { $command_labels[$menu_item] = $this->createMenuItemLabel($menu_item, FALSE); } } asort($sub_menu_labels); asort($command_labels); // Generally the choices array consists of the following parts: // - Reference to the parent menu level. // - Sorted list of nested menu levels. // - Sorted list of commands. $choices = ['..' => '..'] + $sub_menu_labels + $command_labels; $question = new ChoiceQuestion(' Select generator: ', array_values($choices)); $question->setPrompt(count($choices) <= 10 ? ' ➤➤➤ ' : ' ➤➤➤➤ '); $answer_label = $this->getHelper('question')->ask($input, $output, $question); $answer = array_search($answer_label, $choices); if ($answer == '..') { // Exit the application if the user selected zero on the top menu level. if (count($menu_trail) == 0) { return NULL; } // Decrease menu level. array_pop($menu_trail); } else { // Increase menu level. $menu_trail[] = $answer; } return $this->selectGenerator($input, $output, $menu_trail); } /** * Creates a human readable label for a given menu item. * * @param string $menu_item * Machine name of the menu item. * @param bool $comment * A boolean indicating that the label should be wrapped with comment tag. * * @return string * The menu label. */ protected function createMenuItemLabel($menu_item, $comment) { $label = isset($this->labels[$menu_item]) ? $this->labels[$menu_item] : str_replace(['-', '_'], ' ', ucfirst($menu_item)); return $comment ? "$label" : $label; } /** * Sort multi-dimensional array by keys. * * @param array $array * An array being sorted. * * @return array * Sorted array. */ protected function recursiveKsort(array &$array) { foreach ($array as &$value) { if (is_array($value)) { $this->recursiveKsort($value); } } return ksort($array); } /** * Sets a value in a nested array with variable depth. * * @param array $array * A reference to the array to modify. * @param array $parents * An array of parent keys, starting with the outermost key. * @param mixed $value * The value to set. * * @see https://api.drupal.org/api/drupal/includes!common.inc/function/drupal_array_set_nested_value/7 */ protected function arraySetNestedValue(array &$array, array $parents, $value) { $ref = &$array; foreach ($parents as $parent) { if (isset($ref) && !is_array($ref)) { $ref = []; } $ref = &$ref[$parent]; } if (!isset($ref)) { $ref = $value; } } }