Skip to content

Conversation

@christerswahn
Copy link
Collaborator

@christerswahn christerswahn commented Sep 21, 2025

Adds a Yaml specification generator for another command line completion tool, carapace.

https://carapace.sh/

The benefit with Carapace, compared with Completely, is that it supports a lot of shells, including Bash, ZSH, Fish, Elvish, Powershell, Cmd, and more.

The drawback is that carapace requires end users to install it, rather than just distributing a pre-baked script with the command.

Example of a carapace Yaml spec file:

# yaml-language-server: $schema=https://carapace.sh/schemas/command.json
name: example
persistentFlags:
  -q, --quiet: Suppress all cli output. Is overridden by  -v, --verbose.
  -v, --verbose: Prints additional information useful for development. Overrides --q, --quiet.

commands:
  - name: completion
    flags:
      -t, --target=: 
      -e, --exec-name=: Override the name of the executable
      -f, --file=: Write the specification to a file instead of stdout
    completion:
      flag:
        target: ["completely", "carapace"]
        file: ["$files"]

  - name: show
    flags:
      -i, --interval=!: The time interval
      -n, --multi=*: The multioption
      -m, --mandatory: The mandatory flag
      --no-mandatory: The mandatory flag
    exclusiveFlags:
      - [mandatory, no-mandatory]
    completion:
      flag:
        multi: ["a", "b", "c", "d", "e", "f", "g"]

Summary by CodeRabbit

  • New Features

    • Experimental command-line completion support added; generate specs for Carapace and Completely.
    • Options to override executable name and write output to a file or stdout.
  • Documentation

    • Expanded "Command line completion" docs and a detailed completion guide covering prerequisites, generation, installation, updates, and Bash/Zsh setup for both tools.
  • Tests

    • End-to-end tests for completion command visibility and spec generation across targets and output modes.

@coderabbitai
Copy link

coderabbitai bot commented Sep 21, 2025

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

Modularizes experimental CLI completion: adds a completion module with a generate subcommand, target enum (completely/carapace), usage-representation API, two YAML generators, docs and tests; removes the previous monolithic completion implementation; updates a config MultiOption and an import path.

Changes

Cohort / File(s) Summary of edits
Documentation
packages/cli_tools/README.md, packages/cli_tools/README_completion.md
Rewrote and expanded completion docs to cover carapace and completely workflows, prerequisites, generation/installation steps, and distribution notes.
Completion commands & wiring
packages/cli_tools/lib/src/better_command_runner/better_command_runner.dart, packages/cli_tools/lib/src/better_command_runner/completion/completion_command.dart, packages/cli_tools/lib/src/better_command_runner/completion/completion_generate_command.dart, packages/cli_tools/lib/src/better_command_runner/completion/completion_target.dart
Introduced modular completion command structure: completion command, generate subcommand, CompletionTarget enum; updated import path in runner. generate reads config, compiles usage, selects generator, and writes output.
Usage representation API
packages/cli_tools/lib/src/better_command_runner/completion/usage_representation.dart
Added UsageRepresentationGenerator interface, CommandUsage data model, and UsageRepresentation.compile(...) to build hierarchical command/option trees for generators.
YAML generators
packages/cli_tools/lib/src/better_command_runner/completion/completely_generator.dart, packages/cli_tools/lib/src/better_command_runner/completion/carapace_generator.dart
Added CompletelyYamlGenerator and CarapaceYamlGenerator implementing usage generation for respective tools, emitting command/subcommand entries, flags/options, negations/exclusive groups, and value completions.
Generate command options
packages/cli_tools/lib/src/better_command_runner/completion/completion_generate_command.dart
Added CompletionGenerateOption enum wrapper, option wiring (target, exec-name, file) and run logic that selects generator and writes to stdout/file.
Removed legacy implementation
packages/cli_tools/lib/src/better_command_runner/completion_command.dart
Removed previous monolithic completion implementation (legacy CompletionCommand, CompletionOption, CompletionTarget enum, usage/ YAML generator logic).
Config changes
packages/config/lib/src/config/options.dart
MultiOption now exposes a multiParser field; constructor adjusted and allowedValues overridden; internal code uses multiParser instead of casting valueParser.
Tests
packages/cli_tools/test/better_command_runner/completion_test.dart
Added end-to-end tests for experimental completion: presence of completion command when enabled, generation for carapace/completely, exec-name overrides, and file/stdout output.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant CLI as BetterCommandRunner
  participant CC as CompletionCommand
  participant CG as CompletionGenerateCommand
  participant UR as UsageRepresentation
  participant Gen as YAML_Generator
  participant Out as Output_Sink

  U->>CLI: app completion generate --target {carapace|completely} [--exec-name X] [--file path]
  CLI->>CC: dispatch "completion"
  CC->>CG: dispatch "generate"
  CG->>UR: compile(runner, execNameOverride?)
  UR-->>CG: CommandUsage tree
  alt target == carapace
    CG->>Gen: CarapaceYamlGenerator.generate(out, usage)
  else target == completely
    CG->>Gen: CompletelyYamlGenerator.generate(out, usage)
  end
  alt file provided
    CG->>Out: open(file), write, flush, close
  else
    CG->>Out: write to stdout
  end
  Out-->>U: YAML spec
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • SandPod
  • nielsenko

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title “feat: Support the carapace CLI completions tool” precisely captures the primary change of introducing Carapace YAML specification generation for CLI completions, providing a clear, concise feature description that aligns with the pull request’s objectives and summary.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1392e6f and ca6dbe3.

📒 Files selected for processing (1)
  • packages/cli_tools/lib/src/better_command_runner/completion/completion_command.dart (1 hunks)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@christerswahn christerswahn marked this pull request as ready for review September 21, 2025 14:17
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/config/lib/src/config/options.dart (1)

672-686: Bug: multi-value args break when separator ≠ ',' (use multiParser.parse and flatten)

Current code parses each token with elementParser.parse, so --opt a;b with separator=';' yields a format error. Also remove the cast and use the new field.

Apply this diff:

   OptionResolution<List<T>>? _resolveNamedArg(final ArgResults? args) {
     final argOptName = argName;
     if (argOptName == null || args == null || !args.wasParsed(argOptName)) {
       return null;
     }
-    final multiParser = valueParser as MultiParser<T>;
-    return OptionResolution(
-      value: args
-          .multiOption(argOptName)
-          .map(multiParser.elementParser.parse)
-          .toList(),
-      source: ValueSourceType.arg,
-    );
+    final parser = multiParser; // use field; supports custom separators
+    final raw = args.multiOption(argOptName);
+    final parsed = <T>[];
+    for (final s in raw) {
+      // Parses 'a,b' (comma) or 'a;b' (custom sep) and repeated flags.
+      parsed.addAll(parser.parse(s));
+    }
+    return OptionResolution(
+      value: parsed,
+      source: ValueSourceType.arg,
+    );
   }
🧹 Nitpick comments (11)
packages/config/lib/src/config/options.dart (2)

665-666: Ensure defaults are a List<String> (add .toList())

addMultiOption expects defaultsTo as List<String>?; mapping returns an Iterable. Convert to a list for broader args compatibility.

-      defaultsTo: defaultValue()?.map(multiParser.elementParser.format),
+      defaultsTo: defaultValue()
+          ?.map(multiParser.elementParser.format)
+          .toList(),

644-649: Deprecate allowedValues on MultiOption and steer callers to element-level API

The override is fine, but it can mislead since list equality is identity-based and not useful here. Marking the getter as deprecated will guide users to allowedElementValues.

-  /// [MultiOption] does not properly support [allowedValues],
-  /// use [allowedElementValues] instead.
-  @override
-  List<List<T>>? get allowedValues {
+  /// [MultiOption] does not properly support [allowedValues],
+  /// use [allowedElementValues] instead.
+  @Deprecated('Use allowedElementValues; list-of-list allowedValues is not supported.')
+  @override
+  List<List<T>>? get allowedValues {
     return super.allowedValues;
   }

Additionally, consider renaming the constructor parameter allowedValues to allowedElementValues (and deprecate the old name) to avoid confusion with the base class property.

Would you like a follow-up patch that introduces a deprecated allowedValues parameter alias and a new allowedElementValues parameter?

packages/cli_tools/lib/src/better_command_runner/better_command_runner.dart (2)

73-75: Typo in docstring: “gloabl” → “global”.

Minor documentation nit.

-  /// The gloabl option definitions.
+  /// The global option definitions.

360-362: Help text: incorrect prefix for abbreviated flag.

Should be -$quietAbbr instead of --$quietAbbr.

-        'Overrides --$quietAbbr, --$quiet.',
+        'Overrides -$quietAbbr, --$quiet.',
packages/cli_tools/lib/src/better_command_runner/completion/completely_generator.dart (3)

51-55: Potential duplicate option entries when options are inherited + persistent/current.

If an option appears in both inherited and current lists, it will be emitted twice. Consider de-dup by (argName, argAbbrev).

-    final allOptions = [
-      ...inheritedOptions,
-      ...usage.persistentOptions,
-      ...usage.options
-    ];
+    final seen = <String>{};
+    final allOptions = <OptionDefinition>[
+      ...inheritedOptions,
+      ...usage.persistentOptions,
+      ...usage.options,
+    ].where((o) {
+      final key = '${o.option.argName ?? ''}|${o.option.argAbbrev ?? ''}';
+      return seen.add(key);
+    }).toList();

73-81: First positional completion supported; later ones omitted.

Callout is fine for now. Please track support for argPos > 0 as a follow-up.

Do you want me to open a small issue to extend positional completion beyond the first argument?


148-155: Quote values to keep YAML safe.

Unquoted values containing spaces/colon/hash can break YAML parsing. Safer to always quote.

-    for (final value in values) {
-      out.writeln('  - $value');
-    }
+    for (final value in values) {
+      out.writeln('  - "$value"');
+    }
packages/cli_tools/README_completion.md (2)

35-38: Likely filename copy/paste error.

You generate my-command.yaml but copy example.yaml.

-my-command completion -f my-command.yaml -t carapace
-cp example.yaml "${UserConfigDir}/carapace/specs/"
+my-command completion -f my-command.yaml -t carapace
+cp my-command.yaml "${UserConfigDir}/carapace/specs/"

16-16: Fix markdownlint MD034 (bare URLs).

Wrap bare URLs with angle brackets or link text.

-https://carapace.sh/
+<https://carapace.sh/>
-https://carapace-sh.github.io/carapace-bin/install.html
+<https://carapace-sh.github.io/carapace-bin/install.html>
-https://carapace-sh.github.io/carapace-bin/setup.html
+<https://carapace-sh.github.io/carapace-bin/setup.html>
-https://github.com/bashly-framework/completely
+<https://github.com/bashly-framework/completely>

Also applies to: 26-26, 61-61, 72-72

packages/cli_tools/lib/src/better_command_runner/completion/carapace_completion.dart (1)

28-33: Optional: add descriptions if available.

If we later carry command descriptions in UsageRepresentation, emitting them here would improve UX in Carapace help.

packages/cli_tools/lib/src/better_command_runner/completion/usage_representation.dart (1)

43-53: Return type can be explicit for clarity.

Consider returning CommandUsage<OptionDefinition> to avoid raw types.

-  static CommandUsage compile(
+  static CommandUsage<OptionDefinition> compile(
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9289ac7 and eda37c6.

📒 Files selected for processing (8)
  • packages/cli_tools/README_completion.md (3 hunks)
  • packages/cli_tools/lib/src/better_command_runner/better_command_runner.dart (1 hunks)
  • packages/cli_tools/lib/src/better_command_runner/completion/carapace_completion.dart (1 hunks)
  • packages/cli_tools/lib/src/better_command_runner/completion/completely_generator.dart (1 hunks)
  • packages/cli_tools/lib/src/better_command_runner/completion/completion_command.dart (1 hunks)
  • packages/cli_tools/lib/src/better_command_runner/completion/usage_representation.dart (1 hunks)
  • packages/cli_tools/lib/src/better_command_runner/completion_command.dart (0 hunks)
  • packages/config/lib/src/config/options.dart (2 hunks)
💤 Files with no reviewable changes (1)
  • packages/cli_tools/lib/src/better_command_runner/completion_command.dart
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-07T07:55:17.269Z
Learnt from: christerswahn
PR: serverpod/cli_tools#57
File: packages/config/test/better_command_runner/default_flags_test.dart:1-1
Timestamp: 2025-08-07T07:55:17.269Z
Learning: In the `config` package, `better_command_runner.dart` is intentionally kept as a separate import (`package:config/better_command_runner.dart`) rather than being re-exported through the main `packages/config/lib/config.dart` barrel file. This separation is by design according to the package maintainer christerswahn.

Applied to files:

  • packages/cli_tools/lib/src/better_command_runner/better_command_runner.dart
🪛 markdownlint-cli2 (0.18.1)
packages/cli_tools/README_completion.md

16-16: Bare URL used

(MD034, no-bare-urls)


26-26: Bare URL used

(MD034, no-bare-urls)


61-61: Bare URL used

(MD034, no-bare-urls)


72-72: Bare URL used

(MD034, no-bare-urls)

🔇 Additional comments (6)
packages/config/lib/src/config/options.dart (1)

618-623: Good addition: expose multiParser and forward to super

Public field + constructor forwarding improves ergonomics and removes the need for down‑casts elsewhere.

packages/cli_tools/lib/src/better_command_runner/better_command_runner.dart (1)

8-8: Import path update aligns with new completion module location.

Looks good and matches the new completion/ layout.

packages/cli_tools/lib/src/better_command_runner/completion/carapace_completion.dart (3)

186-193: LGTM: value sources cover enums, files, and directories.

The $files / $directories tokens align with Carapace conventions.


150-167: Confirmed — short single-letter keys are allowed for completion.flag

Verified: completion.flag keys are the flag "name" (no leading dashes) and may be a single-letter shorthand (examples use "v"); carapace-pflag supports shorthand-only flags.


133-148: Exclusive flag group format confirmed — use bare names (no leading dashes).

Carapace expects canonical flag names without dashes (e.g., "quiet", "no-quiet"), so emitting [argName, "no-$argName"] is correct; no change required.

packages/cli_tools/lib/src/better_command_runner/completion/completion_command.dart (1)

38-81: Solid orchestration and I/O handling.

Target dispatch, optional file output, and flushing/closing look correct. Casting null as T matches existing command patterns.

Copy link

@nielsenko nielsenko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is an experimental feature, but I did run into some problems trying to use it for the relic benchmark tool. Perhaps adding some tests would be beneficial at this point?

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eda37c6 and 11a152f.

📒 Files selected for processing (1)
  • packages/cli_tools/lib/src/better_command_runner/completion/completion_command.dart (1 hunks)
🔇 Additional comments (8)
packages/cli_tools/lib/src/better_command_runner/completion/completion_command.dart (8)

1-9: Good modular structure with clean imports.

The imports are well-organized and the modular approach with separate generator files follows good separation of concerns.


10-13: LGTM! Clear enumeration of supported completion targets.

The enum clearly defines the two supported completion tools with descriptive names.


15-38: Well-structured option definitions with comprehensive help text.

The enum-based option definitions are clear and provide good user guidance through help text and abbreviations. The mandatory flag on the target option ensures users must specify a completion target.


40-48: Clean command structure with descriptive metadata.

The command class properly extends the base class and provides clear name and description for the completion functionality.


50-65: Solid configuration extraction and usage compilation.

The configuration value extraction is clean, and the usage compilation approach provides a good abstraction for generating completion specifications.


56-59: Good defensive programming with clear error message.

The null check for the runner with a descriptive exception message is good defensive programming practice.


68-75: Clean generator selection with exhaustive switch.

The switch statement properly handles both completion targets and instantiates the appropriate generators. The pattern is clean and extensible.


66-80: File I/O looks correct — no action required

openWrite is only used in packages/cli_tools/lib/src/better_command_runner/completion/completion_command.dart (lines 66–80). Generators write to the provided IOSink and do not close it; completion_command flushes and await-closes the sink only when a file was opened and intentionally leaves stdout open. No resource-leak or incorrect-close issues found.

Copy link

@nielsenko nielsenko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 💯 Apart from a few suggestions for the README.

I have successfully used it for the relic benchmark tool.

@christerswahn christerswahn merged commit 46c7e61 into main Sep 27, 2025
9 checks passed
@christerswahn christerswahn deleted the completion branch September 27, 2025 19:43
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (4)
packages/cli_tools/README_completion.md (2)

23-23: Fix markdownlint MD034 (bare URLs) by wrapping links

Wrap bare URLs with angle brackets or convert to reference links to satisfy MD034 and improve rendering.

Apply this minimal diff:

-https://carapace.sh/
+<https://carapace.sh/>

-https://carapace-sh.github.io/carapace-bin/install.html
+<https://carapace-sh.github.io/carapace-bin/install.html>

-https://carapace-sh.github.io/carapace-bin/setup.html
+<https://carapace-sh.github.io/carapace-bin/setup.html>

-https://github.com/bashly-framework/completely
+<https://github.com/bashly-framework/completely>

Also applies to: 33-33, 71-71, 85-85


50-55: Clarify Windows path and copy command

You mention %APPDATA% but the example uses a POSIX cp. Consider adding a PowerShell equivalent and the full Windows path to reduce friction for users.

Example:

# PowerShell
$dest = Join-Path $env:APPDATA 'carapace\specs'
New-Item -ItemType Directory -Force -Path $dest | Out-Null
Copy-Item .\my-command.yaml -Destination $dest
packages/cli_tools/lib/src/better_command_runner/completion/carapace_generator.dart (1)

150-167: Deterministic ordering for completion specs (optional)

For reproducible outputs, consider sorting specs by flag name before writing. This reduces churn in diffs across SDK/tooling updates.

packages/cli_tools/lib/src/better_command_runner/completion/completely_generator.dart (1)

148-155: Optional: quote values that contain spaces

If any allowed value includes spaces or YAML-reserved characters, emitting unquoted lines may break parsing. Quoting here is a safe enhancement.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 11a152f and 1392e6f.

📒 Files selected for processing (9)
  • packages/cli_tools/README.md (1 hunks)
  • packages/cli_tools/README_completion.md (2 hunks)
  • packages/cli_tools/lib/src/better_command_runner/completion/carapace_generator.dart (1 hunks)
  • packages/cli_tools/lib/src/better_command_runner/completion/completely_generator.dart (1 hunks)
  • packages/cli_tools/lib/src/better_command_runner/completion/completion_command.dart (1 hunks)
  • packages/cli_tools/lib/src/better_command_runner/completion/completion_generate_command.dart (1 hunks)
  • packages/cli_tools/lib/src/better_command_runner/completion/completion_target.dart (1 hunks)
  • packages/cli_tools/lib/src/better_command_runner/completion/usage_representation.dart (1 hunks)
  • packages/cli_tools/test/better_command_runner/completion_test.dart (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/cli_tools/README.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/cli_tools/lib/src/better_command_runner/completion/usage_representation.dart
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-22T13:21:28.476Z
Learnt from: christerswahn
PR: serverpod/cli_tools#67
File: packages/cli_tools/lib/src/better_command_runner/completion/carapace_completion.dart:105-115
Timestamp: 2025-09-22T13:21:28.476Z
Learning: The `!` suffix is supported by Carapace for marking flags as required in the YAML command specification format, as documented in the official Carapace spec at carapace-sh.github.io/carapace-spec/carapace-spec/command/flags.html.

Applied to files:

  • packages/cli_tools/lib/src/better_command_runner/completion/carapace_generator.dart
📚 Learning: 2025-09-22T13:21:24.881Z
Learnt from: christerswahn
PR: serverpod/cli_tools#67
File: packages/cli_tools/lib/src/better_command_runner/completion/completion_command.dart:81-81
Timestamp: 2025-09-22T13:21:24.881Z
Learning: In the BetterCommand framework, using `return null as T` is a consistent pattern for commands that don't return meaningful values, rather than using void return types. The generic T parameter should be maintained even when the command doesn't produce a concrete return value.

Applied to files:

  • packages/cli_tools/lib/src/better_command_runner/completion/completion_command.dart
🪛 GitHub Actions: CLI Tools CI
packages/cli_tools/lib/src/better_command_runner/completion/completion_command.dart

[error] 36-36: The parameter name '_' doesn't match the name 'commandConfig' in the overridden method. Try changing the name to 'commandConfig'. - avoid_renaming_method_parameters. Command 'dart analyze --fatal-infos' failed with 1 issue.

🪛 markdownlint-cli2 (0.18.1)
packages/cli_tools/README_completion.md

23-23: Bare URL used

(MD034, no-bare-urls)


33-33: Bare URL used

(MD034, no-bare-urls)


71-71: Bare URL used

(MD034, no-bare-urls)


85-85: Bare URL used

(MD034, no-bare-urls)

🔇 Additional comments (2)
packages/cli_tools/lib/src/better_command_runner/completion/completion_target.dart (1)

1-4: LGTM: simple, clear enum for target selection

Names are concise and match the CLI flags/tests. No issues spotted.

packages/cli_tools/test/better_command_runner/completion_test.dart (1)

70-108: Good end-to-end coverage for both generators

Tests validate help wiring, file output, exec-name override, and key YAML fragments for both targets. Nice.

Consider adding two focused cases to catch regressions:

  • MultiOption completions for Completely (element allowedValues).
  • exclusiveFlags emission for a negatable flag pair (e.g., [mandatory, no-mandatory]) in the Carapace spec.

Also applies to: 149-185, 187-224

Comment on lines +105 to +114
String attributes = '';
if (option is! FlagOption) {
attributes += '=';
}
if (option is MultiOption) {
attributes += '*';
}
if (option.mandatory && option is! FlagOption) {
attributes += '!';
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Quote help text and support required flags ('!') too

  • Help text is written unquoted; colons, hashes, or brackets in help can break YAML.
  • Required flags (FlagOption.mandatory) aren’t marked with '!'. Carapace supports the '!' suffix for required flags as well, not only options. Based on learnings.

Apply this diff:

@@
-import 'dart:io' show IOSink;
+import 'dart:io' show IOSink;
+import 'dart:convert' show jsonEncode;
@@
   static void _declareOption(
@@
-    String attributes = '';
-    if (option is! FlagOption) {
-      attributes += '=';
-    }
-    if (option is MultiOption) {
-      attributes += '*';
-    }
-    if (option.mandatory && option is! FlagOption) {
-      attributes += '!';
-    }
+    String attributes = '';
+    if (option is! FlagOption) attributes += '=';
+    if (option is MultiOption) attributes += '*';
+    if (option.mandatory) attributes += '!';
@@
-      '${names.join(', ')}$attributes: ${option.helpText ?? ''}',
+      '${names.join(', ')}$attributes: ${_yamlQuote(option.helpText)}',
       indentLevel,
     );
@@
-          '--no-${option.argName}$attributes: ${option.helpText ?? ''}',
+          '--no-${option.argName}$attributes: ${_yamlQuote(option.helpText)}',
           indentLevel,
         );
@@
   }
+
+  static String _yamlQuote(final String? s) => jsonEncode(s ?? '');

Also applies to: 116-121, 122-130

🤖 Prompt for AI Agents
In
packages/cli_tools/lib/src/better_command_runner/completion/carapace_generator.dart
around lines 105-114 (and similarly apply the same changes to 116-121 and
122-130), the generated help text is emitted unquoted (which can break YAML when
it contains colons, hashes, or brackets) and required flags are only marked with
'!' for non-FlagOption types; update the generator to always quote/escape the
help text (e.g., wrap in quotes and escape internal quotes/newlines) when
emitting YAML so special characters are safe, and change the attribute-building
logic so that the '!' suffix is added whenever option.mandatory is true
regardless of whether the option is a FlagOption, while preserving the existing
'=' and '*' behavior for non-FlagOption and MultiOption respectively.

Comment on lines +128 to +146
static List<String> _getOptionValues(
final ConfigOptionBase option,
) {
if (option.allowedValues case final List allowedValues) {
return allowedValues.map(option.valueParser.format).toList();
}

switch (option.option) {
case EnumOption():
final enumParser = option.option.valueParser as EnumParser;
return enumParser.enumValues.map(enumParser.format).toList();
case FileOption():
return ['<file>'];
case DirOption():
return ['<directory>'];
default:
return [];
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add MultiOption element completions to match Carapace parity

Completely currently ignores MultiOption.allowedElementValues, so users won’t get element suggestions for multi-valued options. Carapace generator handles this. Bring parity by formatting element values via the MultiOption’s element parser.

Apply this diff:

   static List<String> _getOptionValues(
     final ConfigOptionBase option,
   ) {
-    if (option.allowedValues case final List allowedValues) {
+    if (option case final MultiOption multi) {
+      if (multi.allowedElementValues case final List allowedValues) {
+        return allowedValues
+            .map<String>(multi.multiParser.elementParser.format)
+            .toList();
+      }
+    } else if (option.allowedValues case final List allowedValues) {
       return allowedValues.map(option.valueParser.format).toList();
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
static List<String> _getOptionValues(
final ConfigOptionBase option,
) {
if (option.allowedValues case final List allowedValues) {
return allowedValues.map(option.valueParser.format).toList();
}
switch (option.option) {
case EnumOption():
final enumParser = option.option.valueParser as EnumParser;
return enumParser.enumValues.map(enumParser.format).toList();
case FileOption():
return ['<file>'];
case DirOption():
return ['<directory>'];
default:
return [];
}
}
static List<String> _getOptionValues(
final ConfigOptionBase option,
) {
if (option case final MultiOption multi) {
if (multi.allowedElementValues case final List allowedValues) {
return allowedValues
.map<String>(multi.multiParser.elementParser.format)
.toList();
}
} else if (option.allowedValues case final List allowedValues) {
return allowedValues.map(option.valueParser.format).toList();
}
switch (option.option) {
case EnumOption():
final enumParser = option.option.valueParser as EnumParser;
return enumParser.enumValues.map(enumParser.format).toList();
case FileOption():
return ['<file>'];
case DirOption():
return ['<directory>'];
default:
return [];
}
}
🤖 Prompt for AI Agents
In
packages/cli_tools/lib/src/better_command_runner/completion/completely_generator.dart
around lines 128 to 146, the function _getOptionValues ignores
MultiOption.allowedElementValues and therefore omits element completions for
multi-valued options; update the function to handle MultiOption by: when option
is a MultiOption (or when option.allowedElementValues is present), obtain the
element parser (option.option.elementParser or similar), map
option.allowedElementValues through that parser's format method and return the
formatted list; if allowedElementValues is absent but option.option is
MultiOption, fall back to returning a placeholder like ['<value>'] consistent
with FileOption/DirOption handling.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants