Skip to content

style lets output#348

Merged
kindermax merged 12 commits into
masterfrom
style-lets-output
Jun 13, 2026
Merged

style lets output#348
kindermax merged 12 commits into
masterfrom
style-lets-output

Conversation

@kindermax

@kindermax kindermax commented Jun 13, 2026

Copy link
Copy Markdown
Collaborator
  • move help output functions to help.go
  • Use Fang help renderer

Summary by Sourcery

Integrate a new themed help and error rendering system powered by the Fang library and docopt metadata, replacing the existing custom help output while enriching command documentation and visuals.

Enhancements:

  • Adopt Fang as the execution wrapper for the root Cobra command, centralizing help rendering, error handling, and color theming.
  • Introduce a customizable help renderer that formats usage, examples, grouped subcommands, and flags using docopt-derived metadata and lipgloss-based styling.
  • Add a theming package providing default, ANSI, and synthwave color schemes for CLI output via Fang.
  • Refine subcommand construction to parse docopt usage/options into annotations used for rich help output and to honor --help flags at the subcommand level.
  • Extract and enhance docopt parsing utilities to split usage/options/examples and derive structured help option data for commands.

Build:

  • Update module dependencies to include Fang, lipgloss, and related UI libraries, and bump several existing dependencies to newer versions.

Tests:

  • Add tests for the custom help renderer to verify subgroup display, docopt flag usage, root command listings, usage de-duplication, and error output formatting.
  • Add tests for subcommand creation to ensure docopt-derived help metadata and --help behavior are correctly wired.
  • Add tests for docopt parsing helpers to validate extraction of usage, options, examples, and structured help options data.

@sourcery-ai

sourcery-ai Bot commented Jun 13, 2026

Copy link
Copy Markdown

Reviewer's Guide

Refactors lets CLI help and error output to use the external Fang renderer with a new theming system, parsing docopt metadata into command annotations to drive richer, subgroup-aware help screens while cleaning up legacy help logic and wiring Fang into the main CLI entrypoint.

Sequence diagram for CLI execution with Fang help and error handling

sequenceDiagram
    actor User
    participant Main as cli.Main
    participant RootCmd as cobra.Command_root
    participant Fang as fang.Execute
    participant HelpR as cmd.HelpRenderer
    participant ErrH as cmd.ErrorHandler

    User->>Main: run lets command
    Main->>RootCmd: newRootCmd(version, buildDate)
    Main->>Fang: Execute(ctx, RootCmd, WithColorSchemeFunc(theme.DefaultColorScheme), WithErrorHandler(cmd.ErrorHandler), WithHelpRenderer(cmd.HelpRenderer))

    alt command runs normally
        Fang->>RootCmd: ExecuteContext(ctx)
        RootCmd-->>Fang: nil error
        Fang-->>Main: nil
    else user requests help (e.g. --help)
        Fang->>RootCmd: ExecuteContext(ctx)
        RootCmd->>HelpR: Help(ctx.Writer, ctx.Styles, ctx.Width)
        HelpR-->>RootCmd: rendered help output
        RootCmd-->>Fang: nil error
        Fang-->>Main: nil
    else command or flag error
        Fang->>RootCmd: ExecuteContext(ctx)
        RootCmd-->>Fang: error
        Fang->>ErrH: ErrorHandler(writer, styles, error)
        ErrH-->>Fang: styled error output
        Fang-->>Main: error
    end

    Main-->>User: exit code
Loading

File-Level Changes

Change Details Files
Replace custom Cobra help and error handling with Fang-based renderer and centralized theming.
  • Remove bespoke root and subcommand help functions and handlers from the Cobra setup.
  • Introduce HelpRenderer and ErrorHandler implementations that render usage, examples, grouped commands, and options using Fang styles, including improved usage formatting and error messages.
  • Add a theme package that defines default, ANSI, and Synthwave color schemes consumed by Fang.
  • Wire Fang execution into the CLI Main function, configuring color scheme, error handler, and help renderer instead of calling rootCmd.ExecuteContext directly.
internal/cmd/root.go
internal/cmd/help.go
internal/cli/cli.go
internal/theme/theme.go
Derive help metadata from docopt definitions to drive command usage, examples, and options rendering.
  • Add utilities to split a docopt string into Usage, Options, and Example sections and to parse those options into structured HelpOption definitions.
  • Enhance newSubcommand to normalize docopt command-name placeholders, extract docopt usage and examples, and store them as Cobra command fields and annotations (usage and serialized help options).
  • Teach subcommands to handle --help/-h directly by invoking Cobra help before executing command logic.
internal/docopt/docopts.go
internal/cmd/subcommand.go
Add tests around new help renderer, docopt parsing, and subcommand metadata behavior.
  • Test that the help renderer shows subgroup titles and uses docopt-derived flags and usage in both root command lists and per-command help.
  • Test that newSubcommand preserves configured command names when docopt usage differs and correctly populates annotations and examples from docopt strings.
  • Test that help invocation via --help on subcommands calls the help function, and that ErrorHandler formats error headers and usage hints correctly.
  • Test docopt parsing helpers for correctly extracting usage, options, examples, and structured HelpOption data.
internal/cmd/help_test.go
internal/cmd/subcommand_test.go
internal/docopt/docopts_test.go
Update module dependencies to pull in Fang and its styling stack and bump some existing libraries.
  • Add github.com/lets-cli/fang and various Charmbracelet / lipgloss-related dependencies plus ancillary libraries for rendering and terminal support.
  • Update versions of golang.org/x/sync, go-colorful, go-runewidth, uniseg, golang.org/x/sys, and add golang.org/x/text.
  • Add a local replace directive for fang to point to ../fang for development.
go.mod
go.sum

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="internal/cmd/subcommand_test.go" line_range="90" />
<code_context>
+	}
+}
+
+func TestSubcommandHelpArg(t *testing.T) {
+	command := &configpkg.Command{
+		Name:        "release",
</code_context>
<issue_to_address>
**suggestion (testing):** Consider also testing `-h` in addition to `--help` for the subcommand help path

Since `hasHelpArg` treats both `--help` and `-h` as help triggers, please also cover the `-h` form (either in this test or a small additional one) so both aliases remain protected against regressions.

Suggested implementation:

```golang
Example:
  lets release 1.0.0 -m "Release 1.0.0"`,
}

func TestHasHelpArgShortFlag(t *testing.T) {
	t.Helper()

	if !hasHelpArg([]string{"release", "-h"}) {
		t.Fatalf("expected -h to be treated as help arg")
	}
}

```

This change assumes `hasHelpArg` is in the same package as `subcommand_test.go` and has the signature `func hasHelpArg(args []string) bool`. If the signature differs (e.g., different parameter type or additional parameters), update the call in `TestHasHelpArgShortFlag` accordingly.

If you prefer to keep all coverage inside `TestSubcommandHelpArg`, you could instead convert that test to table-driven and assert behavior for both `--help` and `-h` arguments using whatever invocation pattern it currently uses.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

}
}

func TestSubcommandHelpArg(t *testing.T) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (testing): Consider also testing -h in addition to --help for the subcommand help path

Since hasHelpArg treats both --help and -h as help triggers, please also cover the -h form (either in this test or a small additional one) so both aliases remain protected against regressions.

Suggested implementation:

Example:
  lets release 1.0.0 -m "Release 1.0.0"`,
}

func TestHasHelpArgShortFlag(t *testing.T) {
	t.Helper()

	if !hasHelpArg([]string{"release", "-h"}) {
		t.Fatalf("expected -h to be treated as help arg")
	}
}

This change assumes hasHelpArg is in the same package as subcommand_test.go and has the signature func hasHelpArg(args []string) bool. If the signature differs (e.g., different parameter type or additional parameters), update the call in TestHasHelpArgShortFlag accordingly.

If you prefer to keep all coverage inside TestSubcommandHelpArg, you could instead convert that test to table-driven and assert behavior for both --help and -h arguments using whatever invocation pattern it currently uses.

@kindermax kindermax force-pushed the style-lets-output branch from 427d586 to b52cc2b Compare June 13, 2026 13:06
kindermax added 10 commits June 13, 2026 16:31
- Replace hand-rolled cmpOr with stdlib cmp.Or
- Hoist paddedLeft2 style to package var; avoids alloc per help row
- Fix renderCommandGroup O(n²) double-scan: partition into map once
- Remove no-op JoinHorizontal in renderDocoptFlagPart
- Fix misleading capacity hint in renderHelpDescription
- Style subgroup names with title color/transform, no top padding
- Drop usageTitle; reuse sectionTitle for "usage" label
- Extract subgroupTitleStyle constructor; remove inline style overrides
- Inline styleHelpUsageLine into its only caller and delete it
- Remove redundant MarginBottom(0) from compactTitleStyle
- renderCobraFlag: return directly instead of JoinHorizontal on one string
- Inline errorHeader (used once)
- Use var-decl nil slices instead of make([]string, 0) where no cap is known
- renderHelpDescription: fast-path single-line descriptions
- Replace sort.Slice/sort.Strings with slices.SortFunc/slices.Sort
The fang renderer changed the output format significantly from cobra defaults:
- Help output starts with a blank line (renderLongShort adds leading newline)
- Section titles are uppercase (Title style uses strings.ToUpper)
- Descriptions capitalize first word (FlagDescription uses titleFirstWord)
- Error output uses styled ERROR header with Margin(1), adding a leading blank line
- Error messages capitalize first word via titleFirstWord on ErrorText
- No trailing "Use lets help [command]..." footer
- No type annotations on flags

Update all affected tests to use assert_output --partial instead of exact
fixture matching or assert_line --index 0, which were both fragile against
the new styled output.

Also add fang.WithVersion(rootCmd.Version) to pass the binary version into
fang so version output shows "0.0.0-dev" instead of "unknown (built from source)".

Publish fang v0.1.0 and remove go.mod replace directive so Docker-based
bats tests can resolve the dependency.
Replace bats format assertions with snapshot tests in internal/cmd/
using github.com/charmbracelet/x/exp/golden. Ten golden files cover
root help, subcommand help, grouped commands, and error output
(unknown command, dependency chain).

Bats tests are simplified to behavioural checks only (exit codes,
suggestion presence). Format regressions are caught by the golden
files instead.

Adds --update-golden flag to `lets test-unit` to regenerate snapshots
when rendering changes intentionally.
Replace programmatic command construction with config.Load + InitSubCommands
so golden tests exercise the full YAML → config → render pipeline.

Fixtures are copied from the corresponding bats test directories into
testdata/fixtures/ and loaded by path. Error renderer tests stay
synthetic since they inject DependencyError directly rather than
running shell commands.
Rename errorOutput.println to writeln to avoid forbidigo match on the
builtin println pattern. Replace HasPrefix+TrimPrefix pair in
buildCommandUse with an unconditional TrimPrefix (staticcheck S1017).
@kindermax kindermax force-pushed the style-lets-output branch from 05502c0 to 06e6037 Compare June 13, 2026 18:04
@kindermax kindermax merged commit de55873 into master Jun 13, 2026
5 checks passed
@kindermax kindermax deleted the style-lets-output branch June 13, 2026 18:11
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.

1 participant