Skip to content

Commit 4403c36

Browse files
authored
feat(merge): use a consistent theme across ui and cli output (#78)
2 parents e9170ef + 3533418 commit 4403c36

26 files changed

+2274
-1000
lines changed

cmd/kat/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ func main() {
1616
fang.WithVersion(version.GetVersion()),
1717
fang.WithCommit(version.Revision),
1818
fang.WithErrorHandler(cli.ErrorHandler),
19+
fang.WithColorSchemeFunc(cli.ColorSchemeFunc),
1920
)
2021
if err != nil {
2122
os.Exit(1)

internal/cli/color_scheme.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package cli
2+
3+
import (
4+
"image/color"
5+
6+
"github.com/charmbracelet/fang"
7+
"github.com/charmbracelet/lipgloss/v2"
8+
"github.com/charmbracelet/x/exp/charmtone"
9+
10+
"github.com/macropower/kat/pkg/config"
11+
"github.com/macropower/kat/pkg/ui/theme"
12+
)
13+
14+
// Try to get the theme from the config, otherwise use the default color scheme.
15+
func ColorSchemeFunc(c lipgloss.LightDarkFunc) fang.ColorScheme {
16+
configPath := config.GetPath()
17+
18+
cl, err := config.NewConfigLoaderFromFile(configPath, config.WithThemeFromData())
19+
if err != nil {
20+
return ThemeColorScheme(theme.Default, c)
21+
}
22+
23+
return ThemeColorScheme(cl.GetTheme(), c)
24+
}
25+
26+
func ThemeColorScheme(t *theme.Theme, c lipgloss.LightDarkFunc) fang.ColorScheme {
27+
return fang.ColorScheme{
28+
Base: t.GenericTextStyle.GetForeground(),
29+
Title: t.LogoStyle.GetBackground(),
30+
Codeblock: c(charmtone.Salt, lipgloss.Color("#2F2E36")),
31+
Program: t.SelectedStyle.GetForeground(),
32+
Command: t.SelectedStyle.GetForeground(),
33+
DimmedArgument: t.SubtleStyle.GetForeground(),
34+
Comment: t.SubtleStyle.GetForeground(),
35+
Flag: t.SelectedStyle.GetForeground(),
36+
Argument: t.GenericTextStyle.GetForeground(),
37+
Description: t.GenericTextStyle.GetForeground(),
38+
FlagDefault: t.SelectedSubtleStyle.GetForeground(),
39+
QuotedString: t.GenericTextStyle.GetForeground(),
40+
ErrorHeader: [2]color.Color{
41+
t.ErrorTitleStyle.GetForeground(),
42+
t.ErrorTitleStyle.GetBackground(),
43+
},
44+
}
45+
}

internal/cli/run.go

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"os"
88
"time"
99

10-
"github.com/goccy/go-yaml"
1110
"github.com/spf13/cobra"
1211
"golang.org/x/term"
1312

@@ -18,6 +17,7 @@ import (
1817
"github.com/macropower/kat/pkg/ui"
1918
"github.com/macropower/kat/pkg/ui/common"
2019
"github.com/macropower/kat/pkg/ui/theme"
20+
"github.com/macropower/kat/pkg/ui/yamls"
2121
)
2222

2323
const (
@@ -133,12 +133,12 @@ func tryGetProfileNames(configPath string) []cobra.Completion {
133133
configPath = config.GetPath()
134134
}
135135

136-
cfgData, err := config.ReadConfig(configPath)
136+
cl, err := config.NewConfigLoaderFromFile(configPath)
137137
if err != nil {
138138
return nil
139139
}
140140

141-
cfg, err := config.LoadConfig(cfgData)
141+
cfg, err := cl.Load()
142142
if err != nil {
143143
return nil
144144
}
@@ -236,13 +236,18 @@ func run(cmd *cobra.Command, rc *RunArgs) error {
236236
return err
237237
}
238238

239-
cfgData, err := config.ReadConfig(configPath)
239+
cl, err := config.NewConfigLoaderFromFile(configPath, config.WithThemeFromData())
240240
if err != nil {
241241
slog.Warn("could not read config, using defaults", slog.Any("err", err))
242242
} else {
243-
cfg, err = config.LoadConfig(cfgData)
243+
err = cl.Validate()
244244
if err != nil {
245-
return fmt.Errorf("invalid config %q:\n%s", configPath, yaml.FormatError(err, true, true))
245+
return fmt.Errorf("invalid config %q: %w", configPath, err)
246+
}
247+
248+
cfg, err = cl.Load()
249+
if err != nil {
250+
return fmt.Errorf("invalid config %q: %w", configPath, err)
246251
}
247252
}
248253

@@ -253,13 +258,24 @@ func run(cmd *cobra.Command, rc *RunArgs) error {
253258

254259
if rc.ShowConfig {
255260
// Print the active configuration and exit.
256-
yamlConfig, err := cfg.MarshalYAML()
261+
slog.Info("active configuration", slog.String("path", configPath))
262+
263+
yamlBytes, err := cfg.MarshalYAML()
257264
if err != nil {
258265
return fmt.Errorf("marshal config yaml: %w", err)
259266
}
260267

261-
slog.Info("active configuration", slog.String("path", configPath))
262-
fmt.Printf("%s", yamlConfig)
268+
yamlConfig := string(yamlBytes)
269+
270+
cr := yamls.NewChromaRenderer(cl.GetTheme(), yamls.WithLineNumbersDisabled(true))
271+
prettyConfig, err := cr.RenderContent(yamlConfig, 0)
272+
if err != nil {
273+
mustN(fmt.Fprintln(cmd.OutOrStdout(), yamlConfig))
274+
275+
return err
276+
}
277+
278+
mustN(fmt.Fprintln(cmd.OutOrStdout(), prettyConfig))
263279

264280
return nil
265281
}

internal/schemagen/main.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,22 @@ import (
66
"os"
77

88
"github.com/macropower/kat/pkg/config"
9-
"github.com/macropower/kat/pkg/schema"
9+
"github.com/macropower/kat/pkg/yaml"
1010
)
1111

1212
var outFile = flag.String("o", "schema.json", "Output file for the generated schema")
1313

1414
func main() {
1515
flag.Parse()
1616

17-
gen := schema.NewGenerator(config.NewConfig(),
18-
"github.com/macropower/kat/pkg/config",
17+
gen := yaml.NewSchemaGenerator(config.NewConfig(),
1918
"github.com/macropower/kat/pkg/command",
20-
"github.com/macropower/kat/pkg/ui",
21-
"github.com/macropower/kat/pkg/profile",
22-
"github.com/macropower/kat/pkg/rule",
19+
"github.com/macropower/kat/pkg/config",
2320
"github.com/macropower/kat/pkg/execs",
2421
"github.com/macropower/kat/pkg/keys",
22+
"github.com/macropower/kat/pkg/profile",
23+
"github.com/macropower/kat/pkg/rule",
24+
"github.com/macropower/kat/pkg/ui",
2525
)
2626
jsData, err := gen.Generate()
2727
if err != nil {

pkg/command/config.go

Lines changed: 29 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@ package command
33
import (
44
"fmt"
55

6-
"github.com/goccy/go-yaml"
7-
86
"github.com/macropower/kat/pkg/execs"
97
"github.com/macropower/kat/pkg/profile"
108
"github.com/macropower/kat/pkg/rule"
9+
"github.com/macropower/kat/pkg/yaml"
1110
)
1211

1312
const (
@@ -106,15 +105,6 @@ type Config struct {
106105
Rules []*rule.Rule `json:"rules,omitempty" jsonschema:"title=Rules"`
107106
}
108107

109-
type ConfigError struct {
110-
Path *yaml.Path // YAML path to the error.
111-
Err error
112-
}
113-
114-
func (e ConfigError) Error() string {
115-
return fmt.Sprintf("error at %s: %v", e.Path.String(), e.Err)
116-
}
117-
118108
func NewConfig(ps map[string]*profile.Profile, rs []*rule.Rule) (*Config, error) {
119109
c := &Config{
120110
Profiles: ps,
@@ -146,16 +136,16 @@ func (c *Config) EnsureDefaults() {
146136
}
147137
}
148138

149-
func (c *Config) Validate() *ConfigError {
150-
pb := yaml.PathBuilder{}
139+
func (c *Config) Validate() error {
140+
pb := yaml.NewPathBuilder()
151141

152142
for name, p := range c.Profiles {
153143
err := p.CompileSource()
154144
if err != nil {
155-
return &ConfigError{
156-
Path: pb.Root().Child("profiles").Child(name).Child("source").Build(),
157-
Err: fmt.Errorf("invalid source: %w", err),
158-
}
145+
return yaml.NewError(
146+
fmt.Errorf("invalid source for profile %q: %w", name, err),
147+
yaml.WithPath(pb.Root().Child("profiles").Child(name).Child("source").Build()),
148+
)
159149
}
160150

161151
for i, env := range p.Command.Env {
@@ -166,18 +156,18 @@ func (c *Config) Validate() *ConfigError {
166156
uIdx := uint(i) //nolint:gosec // G115: integer overflow conversion int -> uint.
167157
err := env.ValueFrom.CallerRef.Compile()
168158
if err != nil {
169-
return &ConfigError{
170-
Path: pb.Root().
159+
return yaml.NewError(
160+
fmt.Errorf("invalid env pattern: %w", err),
161+
yaml.WithPath(pb.Root().
171162
Child("profiles").
172163
Child(name).
173164
Child("env").
174165
Index(uIdx).
175166
Child("valueFrom").
176167
Child("callerRef").
177168
Child("pattern").
178-
Build(),
179-
Err: fmt.Errorf("invalid env pattern: %w", err),
180-
}
169+
Build()),
170+
)
181171
}
182172
}
183173

@@ -189,45 +179,45 @@ func (c *Config) Validate() *ConfigError {
189179
uIdx := uint(i) //nolint:gosec // G115: integer overflow conversion int -> uint.
190180
err := envFrom.CallerRef.Compile()
191181
if err != nil {
192-
return &ConfigError{
193-
Path: pb.Root().
182+
return yaml.NewError(
183+
fmt.Errorf("invalid envFrom pattern: %w", err),
184+
yaml.WithPath(pb.Root().
194185
Child("profiles").
195186
Child(name).
196187
Child("envFrom").
197188
Index(uIdx).
198189
Child("callerRef").
199190
Child("pattern").
200-
Build(),
201-
Err: fmt.Errorf("invalid envFrom pattern: %w", err),
202-
}
191+
Build()),
192+
)
203193
}
204194
}
205195
// TODO: Build should return *ConfigError to avoid the duplicate validation above.
206196
err = p.Build()
207197
if err != nil {
208-
return &ConfigError{
209-
Path: pb.Root().Child("profiles").Child(name).Build(),
210-
Err: fmt.Errorf("invalid profile: %w", err),
211-
}
198+
return yaml.NewError(
199+
fmt.Errorf("invalid profile: %w", err),
200+
yaml.WithPath(pb.Root().Child("profiles").Child(name).Build()),
201+
)
212202
}
213203
}
214204

215205
for i, r := range c.Rules {
216206
uIdx := uint(i) //nolint:gosec // G115: integer overflow conversion int -> uint.
217207
err := r.CompileMatch()
218208
if err != nil {
219-
return &ConfigError{
220-
Path: pb.Root().Child("rules").Index(uIdx).Child("match").Build(),
221-
Err: fmt.Errorf("invalid match: %w", err),
222-
}
209+
return yaml.NewError(
210+
fmt.Errorf("invalid match: %w", err),
211+
yaml.WithPath(pb.Root().Child("rules").Index(uIdx).Child("match").Build()),
212+
)
223213
}
224214

225215
p, ok := c.Profiles[r.Profile]
226216
if !ok {
227-
return &ConfigError{
228-
Path: pb.Root().Child("rules").Index(uIdx).Child("profile").Build(),
229-
Err: fmt.Errorf("profile %q not found", r.Profile),
230-
}
217+
return yaml.NewError(
218+
fmt.Errorf("profile %q not found", r.Profile),
219+
yaml.WithPath(pb.Root().Child("rules").Index(uIdx).Child("profile").Build()),
220+
)
231221
}
232222

233223
r.SetProfile(p)

pkg/command/config_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -265,12 +265,12 @@ func TestConfig_Validate_EdgeCases(t *testing.T) {
265265
configErr := config.Validate()
266266

267267
if tc.expectError {
268-
require.NotNil(t, configErr)
268+
require.Error(t, configErr)
269269
if tc.errorPath != "" {
270270
assert.Contains(t, configErr.Error(), tc.errorPath)
271271
}
272272
} else {
273-
assert.Nil(t, configErr)
273+
assert.NoError(t, configErr)
274274
}
275275
})
276276
}

0 commit comments

Comments
 (0)