resolveSelection function

KeySelection resolveSelection({
  1. required DialectProject project,
  2. required String command,
  3. required List<String> positionals,
  4. String? namespace,
  5. String? locale,
})

Resolve positionals + namespace + locale into a KeySelection against project. Throws SelectionFailure with a ready-to-print message when the request cannot be honoured.

Every positional is a key. Locale selection is --locale, because a variadic subject cannot also carry an optional trailing locale without guessing which one a bare vi is meant to be. Callers that pass a locale name positionally get told exactly that rather than "no such key".

Implementation

KeySelection resolveSelection({
  required DialectProject project,
  required String command,
  required List<String> positionals,
  String? namespace,
  String? locale,
}) {
  final targets = project.translations.keys.toList();

  if (locale != null && !project.translations.containsKey(locale)) {
    throw SelectionFailure(64, [
      'Locale `$locale` is not a configured target locale. '
          'Targets: ${targets.join(', ')}.',
    ]);
  }

  final selected = <String>{};

  if (namespace != null) {
    final inNamespace = [
      for (final e in project.source.entries)
        if (e.metadata?.namespace == namespace) e.key,
    ];
    if (inNamespace.isEmpty) {
      final known = {
        for (final e in project.source.entries)
          if (e.metadata?.namespace != null) e.metadata!.namespace!,
      }.toList()..sort();
      throw SelectionFailure(65, [
        'No source key declares `namespace: $namespace`.',
        known.isEmpty
            ? '  This source has no namespaced keys yet.'
            : '  Namespaces in this source: ${known.join(', ')}.',
      ]);
    }
    selected.addAll(inNamespace);
  }

  final unknown = <String>[];
  for (final key in positionals) {
    if (project.source.entryFor(key) == null) {
      unknown.add(key);
    } else {
      selected.add(key);
    }
  }

  if (unknown.isNotEmpty) {
    final lines = <String>[
      unknown.length == 1
          ? 'Key `${unknown.first}` is not in the source ARB — nothing to '
                '$command against. Add it to dialect/source first.'
          : '${unknown.length} keys are not in the source ARB: '
                '${unknown.join(', ')}. Add them to dialect/source first.',
    ];
    // The one migration everybody will hit: `dialect lock brand vi` used to
    // mean "vi only", and now reads as a second key.
    final asLocale = unknown.where(targets.contains).toList();
    if (asLocale.isNotEmpty) {
      lines.add(
        '  `${asLocale.first}` is a locale, not a key — $command takes any '
        'number of keys now, so locale selection moved to '
        '`--locale ${asLocale.first}`.',
      );
    }
    throw SelectionFailure(65, lines);
  }

  if (selected.isEmpty) {
    throw SelectionFailure(64, [
      '$command takes one or more <key>s, or --namespace <name>.',
      '  e.g. dialect $command brandTagline',
      '       dialect $command webAboutTitle webAboutBody --locale vi',
      '       dialect $command --namespace web',
    ]);
  }

  return KeySelection(
    keys: selected.toList()..sort(),
    locales: locale != null ? [locale] : targets,
  );
}