multiSelect<T> method

List<T> multiSelect<T>(
  1. String question,
  2. List<T> options, {
  3. required String label(
    1. T
    ),
  4. String describe(
    1. T
    )?,
  5. List<T> defaults = const [],
})

Asks the user to pick any number of options from a numbered list.

Accepts comma or space separated numbers, ranges like 1-3, and all. Answering nothing takes defaults; when there are no defaults the question is asked again, because an empty selection is never what the caller wanted — a job with no platform cannot be built.

Implementation

List<T> multiSelect<T>(
  String question,
  List<T> options, {
  required String Function(T) label,
  String Function(T)? describe,
  List<T> defaults = const [],
}) {
  _logger.logInfo('${ColorizeLogger.bold('?')} $question');
  for (var i = 0; i < options.length; i++) {
    final marker = defaults.contains(options[i]) ? '›' : ' ';
    final description = describe?.call(options[i]);
    _logger.logInfo(
      '  $marker ${i + 1}) ${label(options[i])}'
      '${description == null ? '' : '  ${ColorizeLogger.dim(description)}'}',
    );
  }

  final hint = defaults.isEmpty
      ? '1-${options.length}, comma separated, or "all"'
      : '1-${options.length} or "all" '
          '(${defaults.map((d) => options.indexOf(d) + 1).join(',')})';

  while (true) {
    stdout.write('  ${ColorizeLogger.dim(hint)} ');
    final answer = _readLine().trim().toLowerCase();

    if (answer.isEmpty) {
      if (defaults.isNotEmpty) return List<T>.from(defaults);
      _logger.logWarning('pick at least one');
      continue;
    }
    if (answer == 'all' || answer == 'a' || answer == '*') {
      return List<T>.from(options);
    }

    final picked = _parseIndexes(answer, options.length);
    if (picked == null) {
      _logger.logWarning(
        'use numbers between 1 and ${options.length}, e.g. "1,3" or "1-2"',
      );
      continue;
    }
    if (picked.isEmpty) {
      _logger.logWarning('pick at least one');
      continue;
    }
    return picked.map((i) => options[i]).toList();
  }
}