Args.parse constructor

Args.parse(
  1. List<String> args
)

Implementation

factory Args.parse(List<String> args) {
  // --no-<key> should be false under <key>
  // --<key> should be true under <key>
  // --key=value should be value under key
  final mapped = <String, dynamic>{};
  final rest = <String>[];
  final path = <String>[];
  final abbr = <String, dynamic>{};
  final original = [...args];

  void add(String rawKey, dynamic rawValue) {
    final key = switch (rawKey.split('--')) {
      [final key] => key,
      [_, final key] => key,
      _ => throw ArgumentError('Invalid key: $rawKey'),
    };

    var value = switch (rawValue) {
      final String string
          when string.contains(RegExp(r'^".*"$')) ||
              string.contains(RegExp(r"^'.*'$")) =>
        string.substring(1, string.length - 1),
      final value => value,
    };

    if (int.tryParse('$value') case final int v) {
      value = v;
    } else if (double.tryParse('$value') case final double v) {
      value = v;
    } else {
      value = switch (value) {
        'true' => true,
        'false' => false,
        'null' => null,
        _ => value,
      };
    }

    if (mapped[key] case null) {
      mapped[key] = value;
      return;
    }

    // add to existing list of values
    // ignore: strict_raw_type
    if (mapped[key] case final Iterable list) {
      mapped[key] = list.followedBy([value]);
      return;
    }

    // starts a new list
    mapped[key] = [mapped[key], value];
  }

  var pathIsParsed = false;
  for (var i = 0; i < args.length; i++) {
    final arg = args[i];

    if (!arg.startsWith('-')) {
      if (pathIsParsed) {
        rest.add(arg);
      } else {
        path.add(arg);
      }
      continue;
    }

    if (!pathIsParsed) {
      pathIsParsed = true;
    }

    if (arg.startsWith(RegExp(r'-\w+'))) {
      var key = arg.substring(1);
      String? value;

      if (key.split('=') case [final k, final v]) {
        key = k;
        value = v;
      } else if (i + 1 < args.length) {
        value = switch (args[i + 1]) {
          final String value when !value.startsWith('-') => value,
          _ => null,
        };
      }

      final keys = key.split('');

      if (value != null) {
        abbr[keys.removeLast()] = value;
        i++;
      }

      for (final alias in keys) {
        abbr[alias] = true;
      }

      continue;
    }

    if (arg.split('=') case [final key, final value]) {
      add(key, value);
      continue;
    }

    if (arg.split('--no-') case [_, final key]) {
      mapped[key] = false;
      continue;
    }

    final key = arg.substring(2);

    if (i + 1 < args.length) {
      if (args[i + 1] case final String value when !value.startsWith('-')) {
        add(key, value);
        i++;
        continue;
      }
    }

    // Standalone flag e.g. --flag
    mapped[key] = true;
  }

  return Args._(
    args: mapped,
    rest: rest,
    path: path,
    abbr: abbr,
    original: original,
    rawArgs: [
      for (final arg in original)
        if (!path.contains(arg)) arg,
    ],
  );
}