find static method

CompletionLevel? find(
  1. Iterable<String> rootArgs,
  2. ArgParser runnerGrammar,
  3. Map<String, Command> runnerCommands
)

Given a user input rootArgs and the runnerGrammar, it finds the innermost context that needs completion.

If the user input did not type any sub command, the runner itself will be taken as the completion context.

Example:

root_command -f command1 command2 -o

Consider root_command the cli executable and command1a sub command ofroot_commandandcommand2a sub command ofcommand1`.

In a scenario where the user requests completion for this line, all possible suggestions (options, flags and sub commands) should be declared under the ArgParser object belonging to command2, all the args preceding command2 are not considered for completion.

if the user input does not respect the known structure of commands, or if there is any error when parsing the command structure, the CompletionLevel will be null.

Implementation

static CompletionLevel? find(
  Iterable<String> rootArgs,
  ArgParser runnerGrammar,
  Map<String, Command<dynamic>> runnerCommands,
) {
  // Parse args regarding only commands
  final commandsOnlyResults = runnerGrammar.tryParseCommandsOnly(rootArgs);

  // If it cannot parse commands, bail out.
  if (commandsOnlyResults == null) {
    return null;
  }

  // Find the leaf-most parsed command, starting from the root level

  // The user-declared argParser in the current command, starting as the one
  // on the runner and substituted by the ones belonging to the
  // parsed subcommands, if any.
  var originalGrammar = runnerGrammar;

  // The available sub commands of the current level, starting as the
  // commands declared on the runner and substituted by the
  // parsed subcommands, if any.
  Map<String, Command<dynamic>>? subcommands = runnerCommands;

  var nextLevelResults = commandsOnlyResults.command;
  String? commandName;
  while (nextLevelResults != null) {
    originalGrammar = originalGrammar.commands[nextLevelResults.name]!;
    // This can be null if ArgParser.addSubcommand was used directly instead
    // of CommandRunner.addCommand or Command.addSubcommand
    // In these cases,
    subcommands = subcommands?[nextLevelResults.name]?.subcommands;
    commandName = nextLevelResults.name;
    nextLevelResults = nextLevelResults.command;
  }

  // rawArgs should be only the args after the last parsed command
  final List<String> rawArgs;
  if (commandName != null) {
    rawArgs =
        rootArgs.skipWhile((value) => value != commandName).skip(1).toList();
  } else {
    rawArgs = rootArgs.toList();
  }

  final validOptionsResult = originalGrammar.findValidOptions(rawArgs);

  final visibleSubcommands = subcommands?.values.where((command) {
        return !command.hidden;
      }).toList() ??
      [];

  final visibleOptions = originalGrammar.options.values.where((option) {
    final wasParsed = validOptionsResult?.wasParsed(option.name) ?? false;
    if (wasParsed) {
      return option.isMultiple;
    }
    return !option.hide;
  }).toList();

  return CompletionLevel(
    grammar: originalGrammar,
    parsedOptions: validOptionsResult,
    rawArgs: rawArgs,
    visibleSubcommands: visibleSubcommands,
    visibleOptions: visibleOptions,
  );
}