parse method

ParsedArguments parse(
  1. List<String> tokens, {
  2. List<String>? defaultPath,
  3. CommandRegistry defaultTarget(
    1. CommandRegistry
    )?,
})

Reads tokens against the resolved registry and returns typed values.

A --help or --version token ends validation for everything after it: the remaining tokens are skipped rather than rejected, so --typo --help fails while --help --typo succeeds. This is deliberate — an invocation that is only asking for help is answered, not corrected — but it does mean validation depends on argument order.

Implementation

ParsedArguments parse(
  List<String> tokens, {
  List<String>? defaultPath,
  CommandRegistry Function(CommandRegistry)? defaultTarget,
}) {
  final resolution = _registry.resolveCommandPath(
    tokens,
    defaultTarget: defaultTarget,
  );
  final commandPath = defaultPath ?? resolution.path;
  final registry = defaultPath == null
      ? resolution.registry
      : _registry.descendant(defaultPath);
  final values = <Object, Object?>{};
  final positionals = <String>[];
  final trailing = <String>[];
  var help = false;
  var version = false;
  final consumed = <int>{};
  final optionInputs = [
    ...registry.applicableOptions,
    for (final group in registry.pairedOptionGroups) ...group.options,
    for (final group in registry.selectedOptions) ...group.options,
  ];
  for (var index = 0; index < tokens.length; index++) {
    if (consumed.contains(index) || resolution.tokenIndices.contains(index)) {
      continue;
    }
    final token = tokens[index];
    if (token == '--') {
      trailing.addAll(tokens.skip(index + 1));
      break;
    }
    if (MambaBuiltInFlags.isHelp(token)) {
      values[MambaBuiltInFlags.help] = true;
      help = true;
      continue;
    }
    if (MambaBuiltInFlags.isVersion(token)) {
      values[MambaBuiltInFlags.version] = true;
      version = true;
      continue;
    }
    if ((help || version) && !MambaBuiltInFlags.isControl(token)) continue;
    if (token.startsWith('--') && token.length > 2) {
      final (name, inline) = _split(token.substring(2));
      final accessor = _accessorFor(name, registry.applicableAccessors);
      if (accessor != null) {
        values[accessor] = _parseValue(
          accessor,
          _takeValue(tokens, index, consumed, name, inline, accessor),
        );
        continue;
      }
      final input = optionInputs
          .where((item) => item.name == name)
          .firstOrNull;
      if (input != null) {
        _put(
          values,
          input,
          _parseValue(
            input,
            _takeValue(tokens, index, consumed, name, inline, input),
          ),
        );
        continue;
      }
      final flag =
          registry.applicableFlags
              .where((item) => item.name == name)
              .firstOrNull ??
          registry.applicableFlags
              .where(
                (item) =>
                    item is BooleanFlag &&
                    item.negatable &&
                    name == 'no-${item.name}',
              )
              .firstOrNull;
      if (flag == null) {
        throw _unknownInput(registry, name);
      }
      if (inline != null) {
        throw MambaParseException('Flag --$name does not accept a value');
      }
      if (flag is BooleanFlag) {
        values[flag] = name == 'no-${flag.name}' ? false : true;
      } else if (flag is CountFlag) {
        values[flag] = ((values[flag] as int?) ?? 0) + 1;
      }
      continue;
    }
    if (token.startsWith('-') && token.length > 1) {
      final (short, inline) = _split(token.substring(1));
      if (short.isEmpty) {
        throw MambaParseException(
          'An attached value requires a short option name.',
        );
      }
      final letters = short.split('');
      for (final (letterIndex, letter) in letters.indexed) {
        final input = optionInputs
            .where((item) => _shortOf(item) == letter)
            .firstOrNull;
        if (input != null) {
          if (letterIndex != letters.length - 1 ||
              (letters.length > 1 && inline == null)) {
            throw MambaParseException(
              'A bundled value option must be final and use equals-attached supply.',
            );
          }
          _put(
            values,
            input,
            _parseValue(
              input,
              _takeValue(tokens, index, consumed, input.name, inline, input),
            ),
          );
          continue;
        }
        if (letterIndex == letters.length - 1 && inline != null) {
          throw MambaParseException('Flag -$letter does not accept a value');
        }
        if (letter == MambaBuiltInFlags.help.short) {
          // A group does not publish the built-in help flag to its
          // descendants, so the clustered path cannot find it by lookup. The
          // handle is recorded here so a clustered `-h` reads the same as a
          // lone one.
          values[MambaBuiltInFlags.help] = true;
          help = true;
          continue;
        }
        final flag = registry.applicableFlags
            .where((item) => item.short == letter)
            .firstOrNull;
        if (flag == null) {
          throw MambaParseException(
            '"-$letter" isn\'t a registered short flag or option.'
            '${_shortInventory(registry)}',
          );
        }
        if (identical(flag, MambaBuiltInFlags.version)) version = true;
        if (flag is BooleanFlag) values[flag] = true;
        if (flag is CountFlag) {
          values[flag] = ((values[flag] as int?) ?? 0) + 1;
        }
      }
      continue;
    }
    positionals.add(token);
  }
  if (!help && !version) {
    final occurrences = values.keys.toSet();
    _addDefaults(registry, values);
    _validateRequired(registry, values);
    _validateConflicts(registry, occurrences);
    _validateGroups(registry, values);
    _parsePositionals(registry, positionals, values);
    _validateVariadic(registry.variadic, trailing);
  }
  for (final flag in registry.applicableFlags) {
    if (flag is BooleanFlag) {
      values.putIfAbsent(flag, () => flag.defaultValue);
    }
    if (flag is CountFlag) values.putIfAbsent(flag, () => 0);
  }
  _addAccessorMaps(registry, values);
  for (final entry in registry.inputLineage.entries) {
    if (values.containsKey(entry.value)) {
      values[entry.key] = values[entry.value];
    }
  }
  return (
    commandPath,
    ParsedInputs(values, _knownInputs(registry)),
    List.unmodifiable(trailing),
    help: help,
    version: version,
  );
}