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