From 4ff8f5ea38b8ad00fb019f8c70bcfeab624d02cb Mon Sep 17 00:00:00 2001 From: Jon Gallant <2163001+jongio@users.noreply.github.com> Date: Sun, 26 Jul 2026 11:21:32 -0700 Subject: [PATCH] Show resolved theme details Closes #379 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3f892a21-0f5d-41e7-aebb-afbda057615d --- cmd/theme.go | 55 +++++++++++++++++++++++++++++++++++++++++++++++ cmd/theme_test.go | 37 +++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/cmd/theme.go b/cmd/theme.go index d43d4060..a95a1a59 100644 --- a/cmd/theme.go +++ b/cmd/theme.go @@ -16,6 +16,7 @@ func newThemeCmd() *cobra.Command { Short: "Inspect grut themes", } themeCmd.AddCommand(newThemeListCmd(theme.ListThemes)) + themeCmd.AddCommand(newThemeShowCmd(theme.Load)) return themeCmd } @@ -41,3 +42,57 @@ func newThemeListCmd(list themeListFunc) *cobra.Command { listCmd.Flags().BoolVar(&asJSON, "json", false, "Print theme names as JSON") return listCmd } + +type themeLoadFunc func(string) (*theme.Theme, error) + +type themeShowReport struct { + Name string `json:"name"` + Variant string `json:"variant"` + Mode string `json:"mode"` + Colors theme.Colors `json:"colors"` +} + +func newThemeShowCmd(load themeLoadFunc) *cobra.Command { + var asJSON bool + showCmd := &cobra.Command{ + Use: "show ", + Short: "Show resolved theme details", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + resolved, err := load(args[0]) + if err != nil { + return err + } + report := themeShowReport{ + Name: resolved.Name, + Variant: resolved.Variant, + Mode: string(resolved.Mode), + Colors: resolved.Colors, + } + if asJSON { + enc := json.NewEncoder(cmd.OutOrStdout()) + enc.SetIndent("", " ") + return enc.Encode(report) + } + _, _ = fmt.Fprintf(cmd.OutOrStdout(), "Name: %s\n", report.Name) + _, _ = fmt.Fprintf(cmd.OutOrStdout(), "Variant: %s\n", report.Variant) + _, _ = fmt.Fprintf(cmd.OutOrStdout(), "Mode: %s\n", report.Mode) + _, _ = fmt.Fprintf(cmd.OutOrStdout(), "Colors: %d slots\n", themeColorSlotCount(report.Colors)) + return nil + }, + } + showCmd.Flags().BoolVar(&asJSON, "json", false, "Print resolved theme details as JSON") + return showCmd +} + +func themeColorSlotCount(colors theme.Colors) int { + data, err := json.Marshal(colors) + if err != nil { + return 0 + } + var slots map[string]string + if err := json.Unmarshal(data, &slots); err != nil { + return 0 + } + return len(slots) +} diff --git a/cmd/theme_test.go b/cmd/theme_test.go index 91fef375..899b8e23 100644 --- a/cmd/theme_test.go +++ b/cmd/theme_test.go @@ -5,6 +5,8 @@ import ( "encoding/json" "testing" + "github.com/jongio/grut/internal/theme" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -34,6 +36,41 @@ func TestThemeListCommandPrintsJSON(t *testing.T) { assert.Equal(t, []string{"default", "gruvbox"}, names) } +func TestThemeShowCommandPrintsText(t *testing.T) { + cmd := newThemeShowCmd(func(string) (*theme.Theme, error) { + return &theme.Theme{Name: "default", Variant: "dark", Mode: theme.ModeColor, Colors: theme.Colors{Background: "#000000"}}, nil + }) + cmd.SetArgs([]string{"default"}) + var out bytes.Buffer + cmd.SetOut(&out) + + err := cmd.Execute() + + require.NoError(t, err) + assert.Contains(t, out.String(), "Name: default") + assert.Contains(t, out.String(), "Variant: dark") + assert.Contains(t, out.String(), "Mode: color") +} + +func TestThemeShowCommandPrintsJSON(t *testing.T) { + cmd := newThemeShowCmd(func(string) (*theme.Theme, error) { + return &theme.Theme{Name: "gruvbox", Variant: "dark", Mode: theme.ModeColor, Colors: theme.Colors{Background: "#282828"}}, nil + }) + cmd.SetArgs([]string{"gruvbox", "--json"}) + var out bytes.Buffer + cmd.SetOut(&out) + + err := cmd.Execute() + + require.NoError(t, err) + var report themeShowReport + require.NoError(t, json.Unmarshal(out.Bytes(), &report)) + assert.Equal(t, "gruvbox", report.Name) + assert.Equal(t, "dark", report.Variant) + assert.Equal(t, "color", report.Mode) + assert.Equal(t, "#282828", report.Colors.Background) +} + func TestRootRegistersThemeListCommand(t *testing.T) { root, cleanup := newRootCommand() defer cleanup()