'Drupal 7', 'yml' => 'Yaml', 'misc' => 'Miscellaneous', ]; /** * {@inheritdoc} */ protected function configure(): void { // As the navigation is default command the help should be relevant to the // entire DCG application. $help = <<<'EOT' dcg Display navigation dcg plugin:field:widget Run a specific generator dcg list List all available generators EOT; $this ->setName('navigation') ->setDescription('Command line code generator') ->setHelp($help) ->setHidden(); DefaultOptions::apply($this); } /** * {@inheritdoc} */ public function getSynopsis($short = FALSE): string { return 'dcg [options] '; } /** * {@inheritdoc} */ protected function initialize(InputInterface $input, OutputInterface $output): void { parent::initialize($input, $output); // Build the menu structure. $this->menuTree = []; if (!$application = $this->getApplication()) { throw new \LogicException('Navigation command cannot work without application'); } foreach ($application->all() as $command) { if ($command instanceof LabelInterface && !$command->isHidden()) { /** @var string $command_name */ $command_name = $command->getName(); self::arraySetNestedValue($this->menuTree, \explode(':', $command_name)); // Collect command labels. if ($label = $command->getLabel()) { $this->labels[$command_name] = $label; } } } self::recursiveKsort($this->menuTree); $style = new OutputFormatterStyle('white', 'blue', ['bold']); $output->getFormatter()->setStyle('title', $style); } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output): int { if ($command_name = $this->selectGenerator($input, $output)) { if (!$application = $this->getApplication()) { throw new \LogicException('Navigation command cannot work without application'); } return $application ->find($command_name) ->run($input, $output); } return 0; } /** * Selects a generator. * * Returns a generator selected by the user from a multilevel console menu or * null if user decided to exit the navigation. */ private function selectGenerator(InputInterface $input, OutputInterface $output, array $menu_trail = []): ?string { // Narrow down the menu tree using menu trail. $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 has 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) { $command_name = $menu_trail ? (\implode(':', $menu_trail) . ':' . $menu_item) : $menu_item; $label = $this->labels[$command_name] ?? \str_replace(['-', '_'], ' ', \ucfirst($menu_item)); \is_array($subtree) ? $sub_menu_labels[$menu_item] = "$label" : $command_labels[$menu_item] = $label; } // 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. \natcasesort($sub_menu_labels); \natcasesort($command_labels); $choices = ['..' => '..'] + $sub_menu_labels + $command_labels; $question = new ChoiceQuestion(' Select generator ', \array_values($choices)); $answer_label = $this->getHelper('question')->ask($input, $output, $question); $answer = \array_search($answer_label, $choices); if ($answer === '..') { // Exit the application if a user selected zero on the top menu level. if (\count($menu_trail) === 0) { return NULL; } // Level up. \array_pop($menu_trail); } else { // Level down. $menu_trail[] = $answer; } return $this->selectGenerator($input, $output, $menu_trail); } /** * Sort multidimensional array by keys. * * @param array $array * An array being sorted. */ private static function recursiveKsort(array &$array): void { foreach ($array as &$value) { if (\is_array($value)) { self::recursiveKsort($value); } } \ksort($array); } /** * Sets the property to true in nested array. * * @psalm-param list $parents * An array of parent keys, starting with the outermost key. * * @see https://api.drupal.org/api/drupal/includes!common.inc/function/drupal_array_set_nested_value/7 */ private static function arraySetNestedValue(array &$array, array $parents): void { $ref = &$array; foreach ($parents as $parent) { if (isset($ref) && !\is_array($ref)) { $ref = []; } // @todo Fix this. /** @psalm-suppress PossiblyNullArrayAccess */ $ref = &$ref[$parent]; } $ref ??= TRUE; } }