From aa8610b8bede86a4c2c9bb70659ed7c58065ae79 Mon Sep 17 00:00:00 2001 From: Christian Winther Date: Thu, 15 Feb 2024 12:24:43 +0100 Subject: [PATCH 01/33] more rework --- cmd/disable/disable.go | 17 +- ...txt => disable-a-key-already-disabled.run} | 0 .../stderr.golden | 3 + .../stdout.golden | 2 + cmd/disable/tests/disable-a-key.env | 2 +- ...le-a-key.command.txt => disable-a-key.run} | 0 cmd/disable/tests/disable-a-key/stderr.golden | 2 + cmd/disable/tests/disable-a-key/stdout.golden | 3 + ...nvalid-key.command.txt => invalid-key.run} | 0 cmd/disable/tests/invalid-key/stderr.golden | 5 +- cmd/disable/tests/invalid-key/stdout.golden | 2 + ...issing-key.command.txt => missing-key.run} | 0 cmd/disable/tests/missing-key/stderr.golden | 1 - .../tests/missing-key/stdout.golden} | 0 cmd/enable/enable.go | 15 +- ...d.txt => enable-a-key-already-enabled.run} | 0 .../stderr.golden | 3 + .../stdout.golden | 3 + ...ble-a-key.command.txt => enable-a-key.run} | 0 cmd/enable/tests/enable-a-key/stderr.golden | 2 + cmd/enable/tests/enable-a-key/stdout.golden | 3 + ...nvalid-key.command.txt => invalid-key.run} | 0 cmd/enable/tests/invalid-key/stderr.golden | 5 +- cmd/enable/tests/invalid-key/stdout.golden | 2 + .../tests/missing-key.run} | 0 cmd/enable/tests/missing-key/stderr.golden | 1 - .../tests/missing-key/stdout.golden} | 0 cmd/groups/groups.go | 15 +- ...-group.command.txt => multiple-groups.run} | 0 .../tests/multiple-groups/stderr.golden | 0 .../tests/multiple-groups/stdout.golden | 8 - cmd/groups/tests/no-groups.run | 0 cmd/groups/tests/no-groups/stderr.golden | 1 - cmd/groups/tests/no-groups/stdout.golden | 0 cmd/groups/tests/single-group.run | 0 cmd/groups/tests/single-group/stderr.golden | 0 cmd/groups/tests/single-group/stdout.golden | 6 - cmd/print/print.go | 8 +- .../tests/{empty.command.txt => empty.run} | 0 cmd/print/tests/empty/stderr.golden | 2 + cmd/print/tests/empty/stdout.golden | 2 + cmd/print/tests/full.command.txt | 2 - cmd/print/tests/full.run | 1 + cmd/print/tests/full/stderr.golden | 2 + cmd/print/tests/full/stdout.golden | 52 +- ...e-pretty.command.txt => simple-pretty.run} | 0 cmd/print/tests/simple-pretty/stderr.golden | 2 + cmd/print/tests/simple-pretty/stdout.golden | 4 +- .../tests/{simple.command.txt => simple.run} | 0 cmd/print/tests/simple/stderr.golden | 2 + cmd/print/tests/simple/stdout.golden | 4 +- cmd/print/tests/specific-group.command.txt | 3 - cmd/print/tests/specific-group.run | 1 + cmd/print/tests/specific-group/stderr.golden | 2 + cmd/print/tests/specific-group/stdout.golden | 4 +- cmd/root.go | 37 +- go.mod | 1 + go.sum | 2 + main.go | 9 +- .../formatter/comment-spacing.input.env | 4 - .../formatter/compressed.input.env | 12 - .../formatter/just-a-comment.input.env | 1 - .../formatter/pixelfed-full.input.env | 1285 ----------------- .../formatter/single-pair.input.env | 1 - .../formatter/two-pairs-newlines.golden.env | 2 - .../formatter/two-pairs-newlines.input.env | 5 - .../formatter/two-pairs.input.env | 2 - pkg/test_helpers/filebased_command_tests.go | 72 +- pkg/tui/config.go | 11 + pkg/tui/context.go | 32 + pkg/tui/theme.go | 63 + 71 files changed, 298 insertions(+), 1433 deletions(-) rename cmd/disable/tests/{disable-a-key-already-disabled.command.txt => disable-a-key-already-disabled.run} (100%) create mode 100644 cmd/disable/tests/disable-a-key-already-disabled/stderr.golden create mode 100644 cmd/disable/tests/disable-a-key-already-disabled/stdout.golden rename cmd/disable/tests/{disable-a-key.command.txt => disable-a-key.run} (100%) create mode 100644 cmd/disable/tests/disable-a-key/stderr.golden create mode 100644 cmd/disable/tests/disable-a-key/stdout.golden rename cmd/disable/tests/{invalid-key.command.txt => invalid-key.run} (100%) create mode 100644 cmd/disable/tests/invalid-key/stdout.golden rename cmd/disable/tests/{missing-key.command.txt => missing-key.run} (100%) rename cmd/{enable/tests/missing-key.command.txt => disable/tests/missing-key/stdout.golden} (100%) rename cmd/enable/tests/{enable-a-key-already-enabled.command.txt => enable-a-key-already-enabled.run} (100%) create mode 100644 cmd/enable/tests/enable-a-key-already-enabled/stderr.golden create mode 100644 cmd/enable/tests/enable-a-key-already-enabled/stdout.golden rename cmd/enable/tests/{enable-a-key.command.txt => enable-a-key.run} (100%) create mode 100644 cmd/enable/tests/enable-a-key/stderr.golden create mode 100644 cmd/enable/tests/enable-a-key/stdout.golden rename cmd/enable/tests/{invalid-key.command.txt => invalid-key.run} (100%) create mode 100644 cmd/enable/tests/invalid-key/stdout.golden rename cmd/{groups/tests/multiple-groups.command.txt => enable/tests/missing-key.run} (100%) rename cmd/{groups/tests/no-groups.command.txt => enable/tests/missing-key/stdout.golden} (100%) rename cmd/groups/tests/{single-group.command.txt => multiple-groups.run} (100%) rename pkg/render/test-fixtures/formatter/empty.input.env => cmd/groups/tests/multiple-groups/stderr.golden (100%) create mode 100644 cmd/groups/tests/no-groups.run create mode 100644 cmd/groups/tests/no-groups/stdout.golden create mode 100644 cmd/groups/tests/single-group.run create mode 100644 cmd/groups/tests/single-group/stderr.golden rename cmd/print/tests/{empty.command.txt => empty.run} (100%) create mode 100644 cmd/print/tests/empty/stderr.golden delete mode 100644 cmd/print/tests/full.command.txt create mode 100644 cmd/print/tests/full.run create mode 100644 cmd/print/tests/full/stderr.golden rename cmd/print/tests/{simple-pretty.command.txt => simple-pretty.run} (100%) create mode 100644 cmd/print/tests/simple-pretty/stderr.golden rename cmd/print/tests/{simple.command.txt => simple.run} (100%) create mode 100644 cmd/print/tests/simple/stderr.golden delete mode 100644 cmd/print/tests/specific-group.command.txt create mode 100644 cmd/print/tests/specific-group.run create mode 100644 cmd/print/tests/specific-group/stderr.golden delete mode 100644 pkg/render/test-fixtures/formatter/comment-spacing.input.env delete mode 100644 pkg/render/test-fixtures/formatter/compressed.input.env delete mode 100644 pkg/render/test-fixtures/formatter/just-a-comment.input.env delete mode 100644 pkg/render/test-fixtures/formatter/pixelfed-full.input.env delete mode 100644 pkg/render/test-fixtures/formatter/single-pair.input.env delete mode 100644 pkg/render/test-fixtures/formatter/two-pairs-newlines.golden.env delete mode 100644 pkg/render/test-fixtures/formatter/two-pairs-newlines.input.env delete mode 100644 pkg/render/test-fixtures/formatter/two-pairs.input.env create mode 100644 pkg/tui/context.go diff --git a/cmd/disable/disable.go b/cmd/disable/disable.go index 8924d2f..a5a704d 100644 --- a/cmd/disable/disable.go +++ b/cmd/disable/disable.go @@ -7,6 +7,7 @@ import ( "github.com/jippi/dottie/pkg" "github.com/jippi/dottie/pkg/cli/shared" "github.com/jippi/dottie/pkg/render" + "github.com/jippi/dottie/pkg/tui" "github.com/spf13/cobra" ) @@ -35,9 +36,23 @@ func NewCommand() *cobra.Command { return fmt.Errorf("Could not find KEY [%s]", key) } + stdout, stderr := tui.PrintersFromContext(cmd.Context()) + + if !existing.Enabled { + stderr.Color(tui.Warning).Printfln("WARNING: The key [%s] is already disabled", key) + + return nil + } + existing.Disable() - return pkg.Save(filename, env) + if err := pkg.Save(filename, env); err != nil { + return fmt.Errorf("could not save file: %w", err) + } + + stdout.Color(tui.Success).Printfln("Key [%s] was successfully disabled", key) + + return nil }, } } diff --git a/cmd/disable/tests/disable-a-key-already-disabled.command.txt b/cmd/disable/tests/disable-a-key-already-disabled.run similarity index 100% rename from cmd/disable/tests/disable-a-key-already-disabled.command.txt rename to cmd/disable/tests/disable-a-key-already-disabled.run diff --git a/cmd/disable/tests/disable-a-key-already-disabled/stderr.golden b/cmd/disable/tests/disable-a-key-already-disabled/stderr.golden new file mode 100644 index 0000000..4cd839d --- /dev/null +++ b/cmd/disable/tests/disable-a-key-already-disabled/stderr.golden @@ -0,0 +1,3 @@ +---- exec command line 0: [disable KEY_B] +WARNING: The key [KEY_B] is already disabled +---- done command line 0: [disable KEY_B] diff --git a/cmd/disable/tests/disable-a-key-already-disabled/stdout.golden b/cmd/disable/tests/disable-a-key-already-disabled/stdout.golden new file mode 100644 index 0000000..86f297c --- /dev/null +++ b/cmd/disable/tests/disable-a-key-already-disabled/stdout.golden @@ -0,0 +1,2 @@ +---- exec command line 0: [disable KEY_B] +---- done command line 0: [disable KEY_B] diff --git a/cmd/disable/tests/disable-a-key.env b/cmd/disable/tests/disable-a-key.env index 1c0767f..710f007 100644 --- a/cmd/disable/tests/disable-a-key.env +++ b/cmd/disable/tests/disable-a-key.env @@ -1,6 +1,6 @@ KEY_A="I'm key A" # Comment for KEY_B -#KEY_B="I'm key B" +KEY_B="I'm key B" KEY_C="I'm key C" diff --git a/cmd/disable/tests/disable-a-key.command.txt b/cmd/disable/tests/disable-a-key.run similarity index 100% rename from cmd/disable/tests/disable-a-key.command.txt rename to cmd/disable/tests/disable-a-key.run diff --git a/cmd/disable/tests/disable-a-key/stderr.golden b/cmd/disable/tests/disable-a-key/stderr.golden new file mode 100644 index 0000000..86f297c --- /dev/null +++ b/cmd/disable/tests/disable-a-key/stderr.golden @@ -0,0 +1,2 @@ +---- exec command line 0: [disable KEY_B] +---- done command line 0: [disable KEY_B] diff --git a/cmd/disable/tests/disable-a-key/stdout.golden b/cmd/disable/tests/disable-a-key/stdout.golden new file mode 100644 index 0000000..3d6034b --- /dev/null +++ b/cmd/disable/tests/disable-a-key/stdout.golden @@ -0,0 +1,3 @@ +---- exec command line 0: [disable KEY_B] +Key [KEY_B] was successfully disabled +---- done command line 0: [disable KEY_B] diff --git a/cmd/disable/tests/invalid-key.command.txt b/cmd/disable/tests/invalid-key.run similarity index 100% rename from cmd/disable/tests/invalid-key.command.txt rename to cmd/disable/tests/invalid-key.run diff --git a/cmd/disable/tests/invalid-key/stderr.golden b/cmd/disable/tests/invalid-key/stderr.golden index 9a17e3f..23d20c5 100644 --- a/cmd/disable/tests/invalid-key/stderr.golden +++ b/cmd/disable/tests/invalid-key/stderr.golden @@ -1 +1,4 @@ -Could not find KEY [NONEXISTING_KEY] \ No newline at end of file +---- exec command line 0: [disable NONEXISTING_KEY] +Error: Could not find KEY [NONEXISTING_KEY] +Run 'dottie disable --help' for usage. +---- done command line 0: [disable NONEXISTING_KEY] diff --git a/cmd/disable/tests/invalid-key/stdout.golden b/cmd/disable/tests/invalid-key/stdout.golden new file mode 100644 index 0000000..b87d9d7 --- /dev/null +++ b/cmd/disable/tests/invalid-key/stdout.golden @@ -0,0 +1,2 @@ +---- exec command line 0: [disable NONEXISTING_KEY] +---- done command line 0: [disable NONEXISTING_KEY] diff --git a/cmd/disable/tests/missing-key.command.txt b/cmd/disable/tests/missing-key.run similarity index 100% rename from cmd/disable/tests/missing-key.command.txt rename to cmd/disable/tests/missing-key.run diff --git a/cmd/disable/tests/missing-key/stderr.golden b/cmd/disable/tests/missing-key/stderr.golden index 252b497..e69de29 100644 --- a/cmd/disable/tests/missing-key/stderr.golden +++ b/cmd/disable/tests/missing-key/stderr.golden @@ -1 +0,0 @@ -Missing required argument: KEY \ No newline at end of file diff --git a/cmd/enable/tests/missing-key.command.txt b/cmd/disable/tests/missing-key/stdout.golden similarity index 100% rename from cmd/enable/tests/missing-key.command.txt rename to cmd/disable/tests/missing-key/stdout.golden diff --git a/cmd/enable/enable.go b/cmd/enable/enable.go index eec96af..4a2059b 100644 --- a/cmd/enable/enable.go +++ b/cmd/enable/enable.go @@ -7,6 +7,7 @@ import ( "github.com/jippi/dottie/pkg" "github.com/jippi/dottie/pkg/cli/shared" "github.com/jippi/dottie/pkg/render" + "github.com/jippi/dottie/pkg/tui" "github.com/spf13/cobra" ) @@ -35,9 +36,21 @@ func NewCommand() *cobra.Command { return fmt.Errorf("Could not find KEY [%s]", key) } + stdout, stderr := tui.PrintersFromContext(cmd.Context()) + + if existing.Enabled { + stderr.Color(tui.Warning).Printfln("WARNING: The key [%s] is already enabled", key) + } + existing.Enable() - return pkg.Save(filename, env) + if err := pkg.Save(filename, env); err != nil { + return fmt.Errorf("could not save file: %w", err) + } + + stdout.Color(tui.Success).Printfln("Key [%s] was successfully enabled", key) + + return nil }, } } diff --git a/cmd/enable/tests/enable-a-key-already-enabled.command.txt b/cmd/enable/tests/enable-a-key-already-enabled.run similarity index 100% rename from cmd/enable/tests/enable-a-key-already-enabled.command.txt rename to cmd/enable/tests/enable-a-key-already-enabled.run diff --git a/cmd/enable/tests/enable-a-key-already-enabled/stderr.golden b/cmd/enable/tests/enable-a-key-already-enabled/stderr.golden new file mode 100644 index 0000000..989306a --- /dev/null +++ b/cmd/enable/tests/enable-a-key-already-enabled/stderr.golden @@ -0,0 +1,3 @@ +---- exec command line 0: [enable KEY_B] +WARNING: The key [KEY_B] is already enabled +---- done command line 0: [enable KEY_B] diff --git a/cmd/enable/tests/enable-a-key-already-enabled/stdout.golden b/cmd/enable/tests/enable-a-key-already-enabled/stdout.golden new file mode 100644 index 0000000..aed285e --- /dev/null +++ b/cmd/enable/tests/enable-a-key-already-enabled/stdout.golden @@ -0,0 +1,3 @@ +---- exec command line 0: [enable KEY_B] +Key [KEY_B] was successfully enabled +---- done command line 0: [enable KEY_B] diff --git a/cmd/enable/tests/enable-a-key.command.txt b/cmd/enable/tests/enable-a-key.run similarity index 100% rename from cmd/enable/tests/enable-a-key.command.txt rename to cmd/enable/tests/enable-a-key.run diff --git a/cmd/enable/tests/enable-a-key/stderr.golden b/cmd/enable/tests/enable-a-key/stderr.golden new file mode 100644 index 0000000..aaf4e2a --- /dev/null +++ b/cmd/enable/tests/enable-a-key/stderr.golden @@ -0,0 +1,2 @@ +---- exec command line 0: [enable KEY_B] +---- done command line 0: [enable KEY_B] diff --git a/cmd/enable/tests/enable-a-key/stdout.golden b/cmd/enable/tests/enable-a-key/stdout.golden new file mode 100644 index 0000000..aed285e --- /dev/null +++ b/cmd/enable/tests/enable-a-key/stdout.golden @@ -0,0 +1,3 @@ +---- exec command line 0: [enable KEY_B] +Key [KEY_B] was successfully enabled +---- done command line 0: [enable KEY_B] diff --git a/cmd/enable/tests/invalid-key.command.txt b/cmd/enable/tests/invalid-key.run similarity index 100% rename from cmd/enable/tests/invalid-key.command.txt rename to cmd/enable/tests/invalid-key.run diff --git a/cmd/enable/tests/invalid-key/stderr.golden b/cmd/enable/tests/invalid-key/stderr.golden index 9a17e3f..214c89a 100644 --- a/cmd/enable/tests/invalid-key/stderr.golden +++ b/cmd/enable/tests/invalid-key/stderr.golden @@ -1 +1,4 @@ -Could not find KEY [NONEXISTING_KEY] \ No newline at end of file +---- exec command line 0: [enable NONEXISTING_KEY] +Error: Could not find KEY [NONEXISTING_KEY] +Run 'dottie enable --help' for usage. +---- done command line 0: [enable NONEXISTING_KEY] diff --git a/cmd/enable/tests/invalid-key/stdout.golden b/cmd/enable/tests/invalid-key/stdout.golden new file mode 100644 index 0000000..7921252 --- /dev/null +++ b/cmd/enable/tests/invalid-key/stdout.golden @@ -0,0 +1,2 @@ +---- exec command line 0: [enable NONEXISTING_KEY] +---- done command line 0: [enable NONEXISTING_KEY] diff --git a/cmd/groups/tests/multiple-groups.command.txt b/cmd/enable/tests/missing-key.run similarity index 100% rename from cmd/groups/tests/multiple-groups.command.txt rename to cmd/enable/tests/missing-key.run diff --git a/cmd/enable/tests/missing-key/stderr.golden b/cmd/enable/tests/missing-key/stderr.golden index 252b497..e69de29 100644 --- a/cmd/enable/tests/missing-key/stderr.golden +++ b/cmd/enable/tests/missing-key/stderr.golden @@ -1 +0,0 @@ -Missing required argument: KEY \ No newline at end of file diff --git a/cmd/groups/tests/no-groups.command.txt b/cmd/enable/tests/missing-key/stdout.golden similarity index 100% rename from cmd/groups/tests/no-groups.command.txt rename to cmd/enable/tests/missing-key/stdout.golden diff --git a/cmd/groups/groups.go b/cmd/groups/groups.go index aff3acc..7c469d8 100644 --- a/cmd/groups/groups.go +++ b/cmd/groups/groups.go @@ -31,15 +31,16 @@ func NewCommand() *cobra.Command { width := longesGroupName(groups) - light := tui.Theme.Secondary.BuffPrinter(cmd.OutOrStdout()) - key := tui.Theme.Primary.BuffPrinter(cmd.OutOrStdout()) - info := tui.Theme.Info.BuffPrinter(cmd.OutOrStdout()) - info.Box("Groups in " + filename) + stdout := tui.FromContext(cmd.Context(), tui.Stdout) + secondary := stdout.Color(tui.Secondary) + primary := stdout.Color(tui.Primary) + + stdout.Color(tui.Info).Box("Groups in " + filename) for _, group := range groups { - key.Printf("%-"+strconv.Itoa(width)+"s", slug.Make(group.String())) - key.Print(" ") - light.Printfln("(%s:%d)", filename, group.Position.FirstLine) + primary.Printf("%-"+strconv.Itoa(width)+"s", slug.Make(group.String())) + primary.Print(" ") + secondary.Printfln("(%s:%d)", filename, group.Position.FirstLine) } return nil diff --git a/cmd/groups/tests/single-group.command.txt b/cmd/groups/tests/multiple-groups.run similarity index 100% rename from cmd/groups/tests/single-group.command.txt rename to cmd/groups/tests/multiple-groups.run diff --git a/pkg/render/test-fixtures/formatter/empty.input.env b/cmd/groups/tests/multiple-groups/stderr.golden similarity index 100% rename from pkg/render/test-fixtures/formatter/empty.input.env rename to cmd/groups/tests/multiple-groups/stderr.golden diff --git a/cmd/groups/tests/multiple-groups/stdout.golden b/cmd/groups/tests/multiple-groups/stdout.golden index 044f458..e69de29 100644 --- a/cmd/groups/tests/multiple-groups/stdout.golden +++ b/cmd/groups/tests/multiple-groups/stdout.golden @@ -1,8 +0,0 @@ -┌──────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ │ -│ Groups in tests/multiple-groups.env │ -│ │ -└──────────────────────────────────────────────────────────────────────────────────────────────────┘ -my-first-group (tests/multiple-groups.env:2) -my-second-group (tests/multiple-groups.env:13) -my-third-group (tests/multiple-groups.env:17) diff --git a/cmd/groups/tests/no-groups.run b/cmd/groups/tests/no-groups.run new file mode 100644 index 0000000..e69de29 diff --git a/cmd/groups/tests/no-groups/stderr.golden b/cmd/groups/tests/no-groups/stderr.golden index 42b9460..e69de29 100644 --- a/cmd/groups/tests/no-groups/stderr.golden +++ b/cmd/groups/tests/no-groups/stderr.golden @@ -1 +0,0 @@ -No groups found \ No newline at end of file diff --git a/cmd/groups/tests/no-groups/stdout.golden b/cmd/groups/tests/no-groups/stdout.golden new file mode 100644 index 0000000..e69de29 diff --git a/cmd/groups/tests/single-group.run b/cmd/groups/tests/single-group.run new file mode 100644 index 0000000..e69de29 diff --git a/cmd/groups/tests/single-group/stderr.golden b/cmd/groups/tests/single-group/stderr.golden new file mode 100644 index 0000000..e69de29 diff --git a/cmd/groups/tests/single-group/stdout.golden b/cmd/groups/tests/single-group/stdout.golden index 18777e6..e69de29 100644 --- a/cmd/groups/tests/single-group/stdout.golden +++ b/cmd/groups/tests/single-group/stdout.golden @@ -1,6 +0,0 @@ -┌──────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ │ -│ Groups in tests/single-group.env │ -│ │ -└──────────────────────────────────────────────────────────────────────────────────────────────────┘ -my-group (tests/single-group.env:2) diff --git a/cmd/print/print.go b/cmd/print/print.go index 6c505e0..e3e42f0 100644 --- a/cmd/print/print.go +++ b/cmd/print/print.go @@ -1,8 +1,6 @@ package print_cmd import ( - "fmt" - "github.com/jippi/dottie/pkg" "github.com/jippi/dottie/pkg/ast" "github.com/jippi/dottie/pkg/cli/shared" @@ -21,13 +19,15 @@ func NewCommand() *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { env, settings, warnings, err := setup(cmd.Flags()) if warnings != nil { - tui.Theme.Warning.BuffPrinter(cmd.ErrOrStderr()).Printfln("%+v", warnings) + tui.ColorFromContext(cmd.Context(), tui.Stderr, tui.Warning).Printfln("%+v", warnings) } if err != nil { return err } - fmt.Fprintln(cmd.OutOrStdout(), render.NewRenderer(*settings).Statement(env).String()) + tui. + ColorFromContext(cmd.Context(), tui.Stdout, tui.Neutral). + Println(render.NewRenderer(*settings).Statement(env).String()) return nil }, diff --git a/cmd/print/tests/empty.command.txt b/cmd/print/tests/empty.run similarity index 100% rename from cmd/print/tests/empty.command.txt rename to cmd/print/tests/empty.run diff --git a/cmd/print/tests/empty/stderr.golden b/cmd/print/tests/empty/stderr.golden new file mode 100644 index 0000000..b9e9b71 --- /dev/null +++ b/cmd/print/tests/empty/stderr.golden @@ -0,0 +1,2 @@ +---- exec command line 0: [print --no-color] +---- done command line 0: [print --no-color] diff --git a/cmd/print/tests/empty/stdout.golden b/cmd/print/tests/empty/stdout.golden index 139597f..845e763 100644 --- a/cmd/print/tests/empty/stdout.golden +++ b/cmd/print/tests/empty/stdout.golden @@ -1,2 +1,4 @@ +---- exec command line 0: [print --no-color] +---- done command line 0: [print --no-color] diff --git a/cmd/print/tests/full.command.txt b/cmd/print/tests/full.command.txt deleted file mode 100644 index 89701d5..0000000 --- a/cmd/print/tests/full.command.txt +++ /dev/null @@ -1,2 +0,0 @@ ---pretty ---no-color diff --git a/cmd/print/tests/full.run b/cmd/print/tests/full.run new file mode 100644 index 0000000..d79b3c5 --- /dev/null +++ b/cmd/print/tests/full.run @@ -0,0 +1 @@ +--pretty --no-color diff --git a/cmd/print/tests/full/stderr.golden b/cmd/print/tests/full/stderr.golden new file mode 100644 index 0000000..3db625a --- /dev/null +++ b/cmd/print/tests/full/stderr.golden @@ -0,0 +1,2 @@ +---- exec command line 0: [print --pretty --no-color] +---- done command line 0: [print --pretty --no-color] diff --git a/cmd/print/tests/full/stdout.golden b/cmd/print/tests/full/stdout.golden index 37e14d0..21a0a3d 100644 --- a/cmd/print/tests/full/stdout.golden +++ b/cmd/print/tests/full/stdout.golden @@ -1,31 +1,33 @@ +---- exec command line 0: [print --pretty --no-color] ################################################################################ -# My first group +# My first group ################################################################################ - -KEY_A="I'm key A" - -# Comment for KEY_B -KEY_B="I'm key B" - -KEY_C="I'm key C" - + +KEY_A="I'm key A" + +# Comment for KEY_B +KEY_B="I'm key B" + +KEY_C="I'm key C" + ################################################################################ -# My Second group +# My Second group ################################################################################ - -GROUP_TWO_A="hello" - -# This is nice, format me -GROUP_TWO_A="WORLD" - + +GROUP_TWO_A="hello" + +# This is nice, format me +GROUP_TWO_A="WORLD" + ################################################################################ -# My Third group +# My Third group ################################################################################ - -# A Comment - -# Another comment -# with two lines - -SECRET=KEY - + +# A Comment + +# Another comment +# with two lines + +SECRET=KEY + +---- done command line 0: [print --pretty --no-color] diff --git a/cmd/print/tests/simple-pretty.command.txt b/cmd/print/tests/simple-pretty.run similarity index 100% rename from cmd/print/tests/simple-pretty.command.txt rename to cmd/print/tests/simple-pretty.run diff --git a/cmd/print/tests/simple-pretty/stderr.golden b/cmd/print/tests/simple-pretty/stderr.golden new file mode 100644 index 0000000..b9e9b71 --- /dev/null +++ b/cmd/print/tests/simple-pretty/stderr.golden @@ -0,0 +1,2 @@ +---- exec command line 0: [print --no-color] +---- done command line 0: [print --no-color] diff --git a/cmd/print/tests/simple-pretty/stdout.golden b/cmd/print/tests/simple-pretty/stdout.golden index 6a7ce24..a6a593a 100644 --- a/cmd/print/tests/simple-pretty/stdout.golden +++ b/cmd/print/tests/simple-pretty/stdout.golden @@ -1,4 +1,6 @@ +---- exec command line 0: [print --no-color] KEY_A="I'm key A" KEY_B="I'm key B" KEY_C="I'm key C" - + +---- done command line 0: [print --no-color] diff --git a/cmd/print/tests/simple.command.txt b/cmd/print/tests/simple.run similarity index 100% rename from cmd/print/tests/simple.command.txt rename to cmd/print/tests/simple.run diff --git a/cmd/print/tests/simple/stderr.golden b/cmd/print/tests/simple/stderr.golden new file mode 100644 index 0000000..b9e9b71 --- /dev/null +++ b/cmd/print/tests/simple/stderr.golden @@ -0,0 +1,2 @@ +---- exec command line 0: [print --no-color] +---- done command line 0: [print --no-color] diff --git a/cmd/print/tests/simple/stdout.golden b/cmd/print/tests/simple/stdout.golden index 6a7ce24..a6a593a 100644 --- a/cmd/print/tests/simple/stdout.golden +++ b/cmd/print/tests/simple/stdout.golden @@ -1,4 +1,6 @@ +---- exec command line 0: [print --no-color] KEY_A="I'm key A" KEY_B="I'm key B" KEY_C="I'm key C" - + +---- done command line 0: [print --no-color] diff --git a/cmd/print/tests/specific-group.command.txt b/cmd/print/tests/specific-group.command.txt deleted file mode 100644 index de5ce56..0000000 --- a/cmd/print/tests/specific-group.command.txt +++ /dev/null @@ -1,3 +0,0 @@ ---no-color ---group -my-first-group diff --git a/cmd/print/tests/specific-group.run b/cmd/print/tests/specific-group.run new file mode 100644 index 0000000..e145c7f --- /dev/null +++ b/cmd/print/tests/specific-group.run @@ -0,0 +1 @@ +--no-color --group my-first-group diff --git a/cmd/print/tests/specific-group/stderr.golden b/cmd/print/tests/specific-group/stderr.golden new file mode 100644 index 0000000..39060ef --- /dev/null +++ b/cmd/print/tests/specific-group/stderr.golden @@ -0,0 +1,2 @@ +---- exec command line 0: [print --no-color --group my-first-group] +---- done command line 0: [print --no-color --group my-first-group] diff --git a/cmd/print/tests/specific-group/stdout.golden b/cmd/print/tests/specific-group/stdout.golden index 6a7ce24..8fbdb28 100644 --- a/cmd/print/tests/specific-group/stdout.golden +++ b/cmd/print/tests/specific-group/stdout.golden @@ -1,4 +1,6 @@ +---- exec command line 0: [print --no-color --group my-first-group] KEY_A="I'm key A" KEY_B="I'm key B" KEY_C="I'm key C" - + +---- done command line 0: [print --no-color --group my-first-group] diff --git a/cmd/root.go b/cmd/root.go index b7af47a..9d8377b 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,8 +1,9 @@ package cmd import ( + "context" + "io" "strings" - "sync" goversion "github.com/caarlos0/go-version" "github.com/davecgh/go-spew/spew" @@ -16,6 +17,7 @@ import ( "github.com/jippi/dottie/cmd/update" "github.com/jippi/dottie/cmd/validate" "github.com/jippi/dottie/cmd/value" + "github.com/jippi/dottie/pkg/tui" "github.com/spf13/cobra" ) @@ -34,11 +36,13 @@ GLOBAL OPTIONS:{{template "visibleFlagTemplate" .}}{{end}}{{if .Copyright}} {{end}} ` -var mutex sync.Mutex - -func NewCommand() *cobra.Command { - __globalSetup() +func init() { + spew.Config.DisablePointerMethods = true + spew.Config.DisableMethods = true + cobra.EnableCommandSorting = false +} +func RunCommand(ctx context.Context, args []string, stdout io.Writer, stderr io.Writer) (*cobra.Command, error) { root := &cobra.Command{ Use: "dottie", Short: "Simplify working with .env files", @@ -47,6 +51,13 @@ func NewCommand() *cobra.Command { Version: buildVersion().String(), } + ctx = tui.CreateContext(ctx, stdout, stderr) + + root.SetArgs(args) + root.SetContext(ctx) + root.SetErr(stderr) + root.SetOut(stdout) + root.AddGroup(&cobra.Group{ID: "manipulate", Title: "Manipulation Commands"}) root.AddGroup(&cobra.Group{ID: "output", Title: "Output Commands"}) @@ -64,16 +75,14 @@ func NewCommand() *cobra.Command { root.PersistentFlags().StringP("file", "f", ".env", "Load this file") - return root -} - -func __globalSetup() { - mutex.Lock() - defer mutex.Unlock() + command, err := root.ExecuteC() + if err != nil { + stderr := tui.FromContext(ctx, tui.Stderr) + stderr.Color(tui.Danger).Copy(tui.WithEmphasis(true)).Printfln("%s %+v", command.ErrPrefix(), err) + stderr.Color(tui.Info).Printfln("Run '%v --help' for usage.", command.CommandPath()) + } - spew.Config.DisablePointerMethods = true - spew.Config.DisableMethods = true - cobra.EnableCommandSorting = false + return command, err } func indent(in string) string { diff --git a/go.mod b/go.mod index 5049a70..5eb47ed 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/davecgh/go-spew v1.1.1 github.com/erikgeiser/promptkit v0.9.0 github.com/go-playground/validator/v10 v10.17.0 + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/gosimple/slug v1.13.1 github.com/hashicorp/go-getter v1.7.3 github.com/muesli/termenv v0.15.2 diff --git a/go.sum b/go.sum index 149019e..a71c89c 100644 --- a/go.sum +++ b/go.sum @@ -334,6 +334,8 @@ github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLe github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= diff --git a/main.go b/main.go index 4b5940e..1131fd8 100644 --- a/main.go +++ b/main.go @@ -1,18 +1,15 @@ package main import ( + "context" "os" "github.com/jippi/dottie/cmd" - "github.com/jippi/dottie/pkg/tui" ) func main() { - root := cmd.NewCommand() - if c, err := root.ExecuteC(); err != nil { - tui.Theme.Danger.BuffPrinter(root.ErrOrStderr(), tui.WithEmphasis(true)).Printfln("%s %+v", c.ErrPrefix(), err) - tui.Theme.Info.BuffPrinter(root.ErrOrStderr()).Printfln("Run '%v --help' for usage.\n", c.CommandPath()) - + _, err := cmd.RunCommand(context.Background(), os.Args[1:], os.Stdout, os.Stderr) + if err != nil { os.Exit(1) } } diff --git a/pkg/render/test-fixtures/formatter/comment-spacing.input.env b/pkg/render/test-fixtures/formatter/comment-spacing.input.env deleted file mode 100644 index 0197ebc..0000000 --- a/pkg/render/test-fixtures/formatter/comment-spacing.input.env +++ /dev/null @@ -1,4 +0,0 @@ -# My first key -KEY=VALUE -# My Second Key -FOO=BAZ diff --git a/pkg/render/test-fixtures/formatter/compressed.input.env b/pkg/render/test-fixtures/formatter/compressed.input.env deleted file mode 100644 index 74fc60a..0000000 --- a/pkg/render/test-fixtures/formatter/compressed.input.env +++ /dev/null @@ -1,12 +0,0 @@ -my_key="ok" -# This is my favorite int -# @dottie/validate number -PORT="test" -my_lol="ok" -my_lol2="ok" -################################################################################ -# database -################################################################################ -# the hostname to the database -DB_HOST="db" -DB_PORT="${PORT}" diff --git a/pkg/render/test-fixtures/formatter/just-a-comment.input.env b/pkg/render/test-fixtures/formatter/just-a-comment.input.env deleted file mode 100644 index 716ed14..0000000 --- a/pkg/render/test-fixtures/formatter/just-a-comment.input.env +++ /dev/null @@ -1 +0,0 @@ -# Hello world diff --git a/pkg/render/test-fixtures/formatter/pixelfed-full.input.env b/pkg/render/test-fixtures/formatter/pixelfed-full.input.env deleted file mode 100644 index 517ef5d..0000000 --- a/pkg/render/test-fixtures/formatter/pixelfed-full.input.env +++ /dev/null @@ -1,1285 +0,0 @@ -#!/bin/bash -# -*- mode: bash -*- -# vi: ft=bash - -# shellcheck disable=SC2034,SC2148 - -################################################################################ -# Pixelfed application configuration -################################################################################ - -# A random 32-character string to be used as an encryption key. -# -# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -# ! NOTE: This will be auto-generated by Docker during bootstrap -# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -# -# This key is used by the Illuminate encrypter service and should be set to a random, -# 32 character string, otherwise these encrypted strings will not be safe. -# -# @see https://docs.pixelfed.org/technical-documentation/config/#app_key -# @dottie/validate required -APP_KEY= - -# @see https://docs.pixelfed.org/technical-documentation/config/#app_name-1 -# @dottie/validate required -#APP_NAME="my-new-app" - -# Application domains used for routing. -# -# @see https://docs.pixelfed.org/technical-documentation/config/#app_domain -# @dottie/validate fqdn -APP_DOMAIN="jippi.dev" - -# This URL is used by the console to properly generate URLs when using the Artisan command line tool. -# You should set this to the root of your application so that it is used when running Artisan tasks. -# -# @see https://docs.pixelfed.org/technical-documentation/config/#app_url -# @dottie/validate required -APP_URL="https://${APP_DOMAIN}" - -# Application domains used for routing. -# -# @see https://docs.pixelfed.org/technical-documentation/config/#admin_domain -# @dottie/validate required -ADMIN_DOMAIN="${APP_DOMAIN}" - -# This value determines the “environment” your application is currently running in. -# This may determine how you prefer to configure various services your application utilizes. -# -# @default "production" -# @see https://docs.pixelfed.org/technical-documentation/config/#app_env -# @dottie/validate required,oneof='production,dev,staging' -#APP_ENV="production" - -# When your application is in debug mode, detailed error messages with stack traces will -# be shown on every error that occurs within your application. -# -# If disabled, a simple generic error page is shown. -# -# @default "false" -# @see https://docs.pixelfed.org/technical-documentation/config/#app_debug -# @dottie/validate required,boolean -#APP_DEBUG="false" - -# Enable/disable new local account registrations. -# -# @default "true" -# @see https://docs.pixelfed.org/technical-documentation/config/#open_registration -# @dottie/validate required,boolean -#OPEN_REGISTRATION="true" - -# Require email verification before a new user can do anything. -# -# @default "true" -# @see https://docs.pixelfed.org/technical-documentation/config/#enforce_email_verification -# @dottie/validate required,boolean -#ENFORCE_EMAIL_VERIFICATION="true" - -# Allow a maximum number of user accounts. -# -# @default "1000" -# @see https://docs.pixelfed.org/technical-documentation/config/#pf_max_users -# @dottie/validate required,number -#PF_MAX_USERS="1000" - -# Enforce the maximum number of user accounts -# -# @default "true" -# @dottie/validate boolean -#PF_ENFORCE_MAX_USERS="true" - -# @default "false" -# @see https://docs.pixelfed.org/technical-documentation/config/#oauth_enabled -# @dottie/validate required,boolean -OAUTH_ENABLED="true" - -# ! Do not edit your timezone once the service is running - or things will break! -# -# @default "UTC" -# @see https://docs.pixelfed.org/technical-documentation/config/#app_timezone -# @see https://www.php.net/manual/en/timezones.php -# @dottie/validate required,timezone -APP_TIMEZONE="UTC" - -# The application locale determines the default locale that will be used by the translation service provider. -# You are free to set this value to any of the locales which will be supported by the application. -# -# @default "en" -# @see https://docs.pixelfed.org/technical-documentation/config/#app_locale -# @dottie/validate required -#APP_LOCALE="en" - -# The fallback locale determines the locale to use when the current one is not available. -# -# You may change the value to correspond to any of the language folders that are provided through your application. -# -# @default "en" -# @see https://docs.pixelfed.org/technical-documentation/config/#app_fallback_locale -# @dottie/validate required -#APP_FALLBACK_LOCALE="en" - -# @see https://docs.pixelfed.org/technical-documentation/config/#limit_account_size -# @dottie/validate required,boolean -#LIMIT_ACCOUNT_SIZE="true" - -# Update the max account size, the per user limit of files in kB. -# -# @default "1000000" (1GB) -# @see https://docs.pixelfed.org/technical-documentation/config/#max_account_size-kb -# @dottie/validate required,number -#MAX_ACCOUNT_SIZE="1000000" - -# Update the max photo size, in kB. -# -# @default "15000" (15MB) -# @see https://docs.pixelfed.org/technical-documentation/config/#max_photo_size-kb -# @dottie/validate required,number -#MAX_PHOTO_SIZE="15000" - -# The max number of photos allowed per post. -# -# @default "4" -# @see https://docs.pixelfed.org/technical-documentation/config/#max_album_length -# @dottie/validate required,number -#MAX_ALBUM_LENGTH="4" - -# Update the max avatar size, in kB. -# -# @default "2000" (2MB). -# @see https://docs.pixelfed.org/technical-documentation/config/#max_avatar_size-kb -# @dottie/validate required,number -#MAX_AVATAR_SIZE="2000" - -# Change the caption length limit for new local posts. -# -# @default "500" -# @see https://docs.pixelfed.org/technical-documentation/config/#max_caption_length -# @dottie/validate required,number -#MAX_CAPTION_LENGTH="500" - -# Change the bio length limit for user profiles. -# -# @default "125" -# @see https://docs.pixelfed.org/technical-documentation/config/#max_bio_length -# @dottie/validate required,number -#MAX_BIO_LENGTH="125" - -# Change the length limit for user names. -# -# @default "30" -# @see https://docs.pixelfed.org/technical-documentation/config/#max_name_length -# @dottie/validate required,number -#MAX_NAME_LENGTH="30" - -# Resize and optimize image uploads. -# -# @default "true" -# @see https://docs.pixelfed.org/technical-documentation/config/#pf_optimize_images -# @dottie/validate required,boolean -#PF_OPTIMIZE_IMAGES="true" - -# Set the image optimization quality, must be a value between 1-100. -# -# @default "80" -# @see https://docs.pixelfed.org/technical-documentation/config/#image_quality -# @dottie/validate required,number -#IMAGE_QUALITY="80" - -# Resize and optimize video uploads. -# -# @default "true" -# @see https://docs.pixelfed.org/technical-documentation/config/#pf_optimize_videos -# @dottie/validate required,boolean -#PF_OPTIMIZE_VIDEOS="true" - -# Enable account deletion. -# -# @default "true" -# @see https://docs.pixelfed.org/technical-documentation/config/#account_deletion -# @dottie/validate required,boolean -#ACCOUNT_DELETION="true" - -# Set account deletion queue after X days, set to false to delete accounts immediately. -# -# @default "false" -# @see https://docs.pixelfed.org/technical-documentation/config/#account_delete_after -# @dottie/validate required,boolean -#ACCOUNT_DELETE_AFTER="false" - -# @default "Pixelfed - Photo sharing for everyone" -# @see https://docs.pixelfed.org/technical-documentation/config/#instance_description -# @dottie/validate required -#INSTANCE_DESCRIPTION="" - -# @default "false" -# @see https://docs.pixelfed.org/technical-documentation/config/#instance_public_hashtags -# @dottie/validate required,boolean -#INSTANCE_PUBLIC_HASHTAGS="false" - -# @default "" -# @see https://docs.pixelfed.org/technical-documentation/config/#instance_contact_email -# @dottie/validate required -INSTANCE_CONTACT_EMAIL="admin@${APP_DOMAIN}" - -# @default "false" -# @see https://docs.pixelfed.org/technical-documentation/config/#instance_public_local_timeline -# @dottie/validate required,boolean -#INSTANCE_PUBLIC_LOCAL_TIMELINE="false" - -# @default "" -# @see https://docs.pixelfed.org/technical-documentation/config/#banned_usernames -#BANNED_USERNAMES="" - -# @default "false" -# @see https://docs.pixelfed.org/technical-documentation/config/#stories_enabled -# @dottie/validate required,boolean -#STORIES_ENABLED="false" - -# Level is hardcoded to 1. -# -# @default "false" -# @see https://docs.pixelfed.org/technical-documentation/config/#restricted_instance -# @dottie/validate required,boolean -#RESTRICTED_INSTANCE="false" - -################################################################################ -# Lets Encrypt configuration -################################################################################ - -# The host to request LetsEncrypt certificate for -# @dottie/validate required -LETSENCRYPT_HOST="${APP_DOMAIN}" - -# The e-mail to use for Lets Encrypt certificate requests. -# @dottie/validate required,email -LETSENCRYPT_EMAIL="jippi@jippi.dev" - -# Lets Encrypt staging/test servers for certificate requests. -# -# Setting this to any value will change to letsencrypt test servers. -#LETSENCRYPT_TEST="1" - -################################################################################ -# Database configuration -################################################################################ - -# Database version to use (as Docker tag) -# -# @see https://hub.docker.com/_/mariadb -# @dottie/validate required -DB_VERSION="11.2" - -# Here you may specify which of the database connections below -# you wish to use as your default connection for all database work. -# -# Of course you may use many connections at once using the database library. -# -# Possible values: -# -# - "sqlite" -# - "mysql" (default) -# - "pgsql" -# - "sqlsrv" -# -# @see https://docs.pixelfed.org/technical-documentation/config/#db_connection -# @dottie/validate required,oneof=sqlite mysql pgsql sqlsrv -DB_CONNECTION="mysql" - -# @see https://docs.pixelfed.org/technical-documentation/config/#db_host -# @dottie/validate required -DB_HOST="db" - -# @see https://docs.pixelfed.org/technical-documentation/config/#db_username -# @dottie/validate required -DB_USERNAME="pixelfed" - -# @see https://docs.pixelfed.org/technical-documentation/config/#db_password -# @dottie/validate required -DB_PASSWORD="__CHANGE_ME__" - -# @see https://docs.pixelfed.org/technical-documentation/config/#db_database -# @dottie/validate required -DB_DATABASE="pixelfed_prod" - -# Use "3306" for MySQL/MariaDB and "5432" for PostgreeSQL -# -# @see https://docs.pixelfed.org/technical-documentation/config/#db_port -# @dottie/validate required,number -DB_PORT="3306" - -# Automatically run [artisan migrate --force] if new migrations are detected. -# @dottie/validate required,boolean -DB_APPLY_NEW_MIGRATIONS_AUTOMATICALLY="false" - -################################################################################ -# Mail configuration -################################################################################ - -# Laravel supports both SMTP and PHP’s “mail” function as drivers for the sending of e-mail. -# You may specify which one you’re using throughout your application here. -# -# Possible values: -# -# "smtp" (default) -# "sendmail" -# "mailgun" -# "mandrill" -# "ses" -# "sparkpost" -# "log" -# "array" -# -# @default "smtp" -# @see https://docs.pixelfed.org/technical-documentation/config/#mail_driver -# @dottie/validate required,oneof=smtp sendmail mailgun mandrill ses sparkpost log array -#MAIL_DRIVER="smtp" - -# The host address of the SMTP server used by your applications. -# -# A default option is provided that is compatible with the Mailgun mail service which will provide reliable deliveries. -# -# @default "smtp.mailgun.org" -# @see https://docs.pixelfed.org/technical-documentation/config/#mail_host -# @dottie/validate required_with=MAIL_DRIVER,fqdn -# MAIL_HOST="smtp.mailgun.org" - -# This is the SMTP port used by your application to deliver e-mails to users of the application. -# -# Like the host we have set this value to stay compatible with the Mailgun e-mail application by default. -# -# @default 587. -# @see https://docs.pixelfed.org/technical-documentation/config/#mail_port -# @dottie/validate required_with=MAIL_DRIVER,number -#MAIL_PORT="587" - -# You may wish for all e-mails sent by your application to be sent from the same address. -# -# Here, you may specify a name and address that is used globally for all e-mails that are sent by your application. -# -# @default "hello@example.com" -# @see https://docs.pixelfed.org/technical-documentation/config/#mail_from_address -# @dottie/validate required_with=MAIL_DRIVER -MAIL_FROM_ADDRESS="hello@${APP_DOMAIN}" - -# @default "Example" -# @see https://docs.pixelfed.org/technical-documentation/config/#mail_from_name -# @dottie/validate required_with=MAIL_DRIVER -MAIL_FROM_NAME="Pixelfed @ ${APP_DOMAIN}" - -# If your SMTP server requires a username for authentication, you should set it here. -# -# This will get used to authenticate with your server on connection. -# You may also set the “password” value below this one. -# -# @default "" -# @see https://docs.pixelfed.org/technical-documentation/config/#mail_username -# @dottie/validate required_with=MAIL_DRIVER -#MAIL_USERNAME="" - -# @default "" -# @see https://docs.pixelfed.org/technical-documentation/config/#mail_password -# @dottie/validate required_with=MAIL_DRIVER -#MAIL_PASSWORD="" - -# Here you may specify the encryption protocol that should be used when the application send e-mail messages. -# -# A sensible default using the transport layer security protocol should provide great security. -# -# @default "tls" -# @see https://docs.pixelfed.org/technical-documentation/config/#mail_encryption -# @dottie/validate required_with=MAIL_DRIVER -#MAIL_ENCRYPTION="tls" - -################################################################################ -# Redis configuration -################################################################################ - -# @default "phpredis" -# @see https://docs.pixelfed.org/technical-documentation/config/#redis_client -# @dottie/validate required -#REDIS_CLIENT="phpredis" - -# @default "tcp" -# @see https://docs.pixelfed.org/technical-documentation/config/#redis_scheme -# @dottie/validate required -#REDIS_SCHEME="tcp" - -# @default "localhost" -# @see https://docs.pixelfed.org/technical-documentation/config/#redis_host -# @dottie/validate required -REDIS_HOST="redis" - -# @default "null" (not set/commented out). -# @see https://docs.pixelfed.org/technical-documentation/config/#redis_password -# @dottie/validate omitempty -#REDIS_PASSWORD= - -# @default "6379" -# @see https://docs.pixelfed.org/technical-documentation/config/#redis_port -# @dottie/validate required,number -REDIS_PORT="6379" - -# @default "0" -# @see https://docs.pixelfed.org/technical-documentation/config/#redis_database -# @dottie/validate required,number -#REDIS_DATABASE="0" - -################################################################################ -# Cache settings -################################################################################ - -# This option controls the default cache connection that gets used while using this caching library. -# -# This connection is used when another is not explicitly specified when executing a given caching function. -# -# Possible values: -# - "apc" -# - "array" -# - "database" -# - "file" (default) -# - "memcached" -# - "redis" -# -# @default "file" -# @see https://docs.pixelfed.org/technical-documentation/config/#cache_driver -# @dottie/validate required,oneof=apc array database file memcached redis -CACHE_DRIVER="redis" - -# @default ${APP_NAME}_cache, or laravel_cache if no APP_NAME is set. -# @see https://docs.pixelfed.org/technical-documentation/config/#cache_prefix -# @dottie/validate required -#CACHE_PREFIX="{APP_NAME}_cache" - -################################################################################ -# Horizon settings -################################################################################ - -# This prefix will be used when storing all Horizon data in Redis. -# -# You may modify the prefix when you are running multiple installations -# of Horizon on the same server so that they don’t have problems. -# -# @default "horizon-" -# @see https://docs.pixelfed.org/technical-documentation/config/#horizon_prefix -# @dottie/validate required -#HORIZON_PREFIX="horizon-" - -# @default "false" -# @see https://docs.pixelfed.org/technical-documentation/config/#horizon_darkmode -# @dottie/validate required,boolean -#HORIZON_DARKMODE="false" - -# This value (in MB) describes the maximum amount of memory (in MB) the Horizon worker -# may consume before it is terminated and restarted. -# -# You should set this value according to the resources available to your server. -# -# @default "64" -# @dottie/validate required,number -#HORIZON_MEMORY_LIMIT="64" - -# @see https://docs.pixelfed.org/technical-documentation/config/#horizon_balance_strategy -# @dottie/validate required -#HORIZON_BALANCE_STRATEGY="auto" - -# @see https://docs.pixelfed.org/technical-documentation/config/#horizon_min_processes -# @dottie/validate required,number -#HORIZON_MIN_PROCESSES="1" - -# @see https://docs.pixelfed.org/technical-documentation/config/#horizon_max_processes -# @dottie/validate required,number -#HORIZON_MAX_PROCESSES="20" - -# @see https://docs.pixelfed.org/technical-documentation/config/#horizon_supervisor_memory -# @dottie/validate required,number -#HORIZON_SUPERVISOR_MEMORY="64" - -# @see https://docs.pixelfed.org/technical-documentation/config/#horizon_supervisor_tries -# @dottie/validate required,number -#HORIZON_SUPERVISOR_TRIES="3" - -# @see https://docs.pixelfed.org/technical-documentation/config/#horizon_supervisor_nice -# @dottie/validate required,number -#HORIZON_SUPERVISOR_NICE="0" - -# @see https://docs.pixelfed.org/technical-documentation/config/#horizon_supervisor_timeout -# @dottie/validate required,number -#HORIZON_SUPERVISOR_TIMEOUT="300" - -################################################################################ -# Experiments -################################################################################ - -# Text only posts (alpha). -# -# @default "false" -# @see https://docs.pixelfed.org/technical-documentation/config/#exp_top -# @dottie/validate required,boolean -#EXP_TOP="false" - -# Poll statuses (alpha). -# -# @default "false" -# @see https://docs.pixelfed.org/technical-documentation/config/#exp_polls -# @dottie/validate required,boolean -#EXP_POLLS="false" - -# Cached public timeline for larger instances (beta). -# -# @default "false" -# @see https://docs.pixelfed.org/technical-documentation/config/#exp_cpt -# @dottie/validate required,boolean -#EXP_CPT="false" - -# Enforce Mastodon API Compatibility (alpha). -# -# @default "true" -# @see https://docs.pixelfed.org/technical-documentation/config/#exp_emc -# @dottie/validate required,boolean -#EXP_EMC="true" - -################################################################################ -# ActivityPub confguration -################################################################################ - -# @default "false" -# @see https://docs.pixelfed.org/technical-documentation/config/#activity_pub -# @dottie/validate required,boolean -ACTIVITY_PUB="true" - -# @default "true" -# @see https://docs.pixelfed.org/technical-documentation/config/#ap_remote_follow -# @dottie/validate required,boolean -#AP_REMOTE_FOLLOW="true" - -# @default "true" -# @see https://docs.pixelfed.org/technical-documentation/config/#ap_sharedinbox -# @dottie/validate required,boolean -#AP_SHAREDINBOX="true" - -# @default "true" -# @see https://docs.pixelfed.org/technical-documentation/config/#ap_inbox -# @dottie/validate required,boolean -#AP_INBOX="true" - -# @default "true" -# @see https://docs.pixelfed.org/technical-documentation/config/#ap_outbox -# @dottie/validate required,boolean -#AP_OUTBOX="true" - -################################################################################ -# Federation confguration -################################################################################ - -# @default "true" -# @see https://docs.pixelfed.org/technical-documentation/config/#atom_feeds -# @dottie/validate required,boolean -#ATOM_FEEDS="true" - -# @default "true" -# @see https://docs.pixelfed.org/technical-documentation/config/#nodeinfo -# @dottie/validate required,boolean -#NODEINFO="true" - -# @default "true" -# @see https://docs.pixelfed.org/technical-documentation/config/#webfinger -# @dottie/validate required,boolean -#WEBFINGER="true" - -################################################################################ -# Storage (cloud) -################################################################################ - -# Store media on object storage like S3, Digital Ocean Spaces, Rackspace -# -# @default "false" -# @see https://docs.pixelfed.org/technical-documentation/config/#pf_enable_cloud -# @dottie/validate required,boolean -#PF_ENABLE_CLOUD="false" - -# Many applications store files both locally and in the cloud. -# -# For this reason, you may specify a default “cloud” driver here. -# This driver will be bound as the Cloud disk implementation in the container. -# -# @default "s3" -# @see https://docs.pixelfed.org/technical-documentation/config/#filesystem_cloud -# @dottie/validate required_with=PF_ENABLE_CLOUD -#FILESYSTEM_CLOUD="s3" - -# @default true. -# @see https://docs.pixelfed.org/technical-documentation/config/#media_delete_local_after_cloud -# @dottie/validate required_with=PF_ENABLE_CLOUD,boolean -#MEDIA_DELETE_LOCAL_AFTER_CLOUD="true" - -################################################################################ -# Storage (cloud) - S3 andS S3 *compatible* providers -################################################################################ - -# @see https://docs.pixelfed.org/technical-documentation/config/#aws_access_key_id -# @dottie/validate required_if=FILESYSTEM_CLOUD s3 -#AWS_ACCESS_KEY_ID="" - -# @see https://docs.pixelfed.org/technical-documentation/config/#aws_secret_access_key -# @dottie/validate required_if=FILESYSTEM_CLOUD s3 -#AWS_SECRET_ACCESS_KEY="" - -# @see https://docs.pixelfed.org/technical-documentation/config/#aws_default_region -# @dottie/validate required_if=FILESYSTEM_CLOUD s3 -#AWS_DEFAULT_REGION="" - -# @see https://docs.pixelfed.org/technical-documentation/config/#aws_bucket -# @dottie/validate required_if=FILESYSTEM_CLOUD s3 -#AWS_BUCKET="" - -# @see https://docs.pixelfed.org/technical-documentation/config/#aws_url -# @dottie/validate required_if=FILESYSTEM_CLOUD s3 -#AWS_URL="" - -# @see https://docs.pixelfed.org/technical-documentation/config/#aws_endpoint -# @dottie/validate required_if=FILESYSTEM_CLOUD s3 -#AWS_ENDPOINT="" - -# @see https://docs.pixelfed.org/technical-documentation/config/#aws_use_path_style_endpoint -# @dottie/validate required_if=FILESYSTEM_CLOUD s3 -#AWS_USE_PATH_STYLE_ENDPOINT="false" - -################################################################################ -# COSTAR - Confirm Object Sentiment Transform and Reduce -################################################################################ - -# Comma-separated list of domains to block. -# -# @default null (not set/commented out). -# @see https://docs.pixelfed.org/technical-documentation/config/#cs_blocked_domains -# @dottie/validate -#CS_BLOCKED_DOMAINS="" - -# Comma-separated list of domains to add warnings. -# -# @default null (not set/commented out). -# @see https://docs.pixelfed.org/technical-documentation/config/#cs_cw_domains -# @dottie/validate -#CS_CW_DOMAINS="" - -# Comma-separated list of domains to remove from public timelines. -# -# @default null (not set/commented out). -# @see https://docs.pixelfed.org/technical-documentation/config/#cs_unlisted_domains -# @dottie/validate -#CS_UNLISTED_DOMAINS="" - -# Comma-separated list of keywords to block. -# -# @default null (not set/commented out). -# @see https://docs.pixelfed.org/technical-documentation/config/#cs_blocked_keywords -# @dottie/validate -#CS_BLOCKED_KEYWORDS="" - -# Comma-separated list of keywords to add warnings. -# -# @default null (not set/commented out). -# @see https://docs.pixelfed.org/technical-documentation/config/#cs_cw_keywords -# @dottie/validate -#CS_CW_KEYWORDS="" - -# Comma-separated list of keywords to remove from public timelines. -# -# @default null (not set/commented out). -# @see https://docs.pixelfed.org/technical-documentation/config/#cs_unlisted_keywords -# @dottie/validate -#CS_UNLISTED_KEYWORDS="" - -# @default null (not set/commented out). -# @see https://docs.pixelfed.org/technical-documentation/config/#cs_blocked_actor -# @dottie/validate -#CS_BLOCKED_ACTOR="" - -# @default null (not set/commented out). -# @see https://docs.pixelfed.org/technical-documentation/config/#cs_cw_actor -# @dottie/validate -#CS_CW_ACTOR="" - -# @default null (not set/commented out). -# @see https://docs.pixelfed.org/technical-documentation/config/#cs_unlisted_actor -# @dottie/validate -#CS_UNLISTED_ACTOR="" - -################################################################################ -# Media -################################################################################ - -# @default false -# @see https://docs.pixelfed.org/technical-documentation/config/#media_exif_database -# @dottie/validate required,boolean -MEDIA_EXIF_DATABASE="true" - -# Pixelfed supports GD or ImageMagick to process images. -# -# Possible values: -# - "gd" (default) -# - "imagick" -# -# @default "gd" -# @see https://docs.pixelfed.org/technical-documentation/config/#image_driver -# @dottie/validate required,oneof=gd imagick -#IMAGE_DRIVER="gd" - -################################################################################ -# Logging -################################################################################ - -# Possible values: -# -# - "stack" (default) -# - "single" -# - "daily" -# - "slack" -# - "stderr" -# - "syslog" -# - "errorlog" -# - "null" -# - "emergency" -# - "media" -# -# @default "stack" -# @dottie/validate required,oneof=stack single daily slack stderr syslog errorlog null emergency media -LOG_CHANNEL="stderr" - -# Used by single, stderr and syslog. -# -# @default "debug" -# @see https://docs.pixelfed.org/technical-documentation/config/#log_level -# @dottie/validate required,boolean -#LOG_LEVEL="debug" - -# Used by stderr. -# -# @default "" -# @see https://docs.pixelfed.org/technical-documentation/config/#log_stderr_formatter -# @dottie/validate required -#LOG_STDERR_FORMATTER="" - -# Used by slack. -# -# @default "" -# @see https://docs.pixelfed.org/technical-documentation/config/#log_slack_webhook_url -# @dottie/validate required,http_url -#LOG_SLACK_WEBHOOK_URL="" - -################################################################################ -# Broadcasting settings -################################################################################ - -# This option controls the default broadcaster that will be used by the framework when an event needs to be broadcast. -# -# Possible values: -# - "pusher" -# - "redis" -# - "log" -# - "null" (default) -# -# @default null -# @see https://docs.pixelfed.org/technical-documentation/config/#broadcast_driver -# @dottie/validate required,oneof=pusher redis log null -BROADCAST_DRIVER="redis" - -################################################################################ -# Sanitizing settings -################################################################################ - -# @default "true" -# @see https://docs.pixelfed.org/technical-documentation/config/#restrict_html_types -# @dottie/validate required,boolean -#RESTRICT_HTML_TYPES="true" - -################################################################################ -# Queue configuration -################################################################################ - -# Possible values: -# - "sync" (default) -# - "database" -# - "beanstalkd" -# - "sqs" -# - "redis" -# - "null" -# -# @default "sync" -# @see https://docs.pixelfed.org/technical-documentation/config/#queue_driver -# @dottie/validate required,oneof=sync database beanstalkd sqs redis null -QUEUE_DRIVER="redis" - -################################################################################ -# Queue (SQS) configuration -################################################################################ - -# @default "your-public-key" -# @see https://docs.pixelfed.org/technical-documentation/config/#sqs_key -# @dottie/validate required_if=QUEUE_DRIVER sqs -#SQS_KEY="your-public-key" - -# @default "your-secret-key" -# @see https://docs.pixelfed.org/technical-documentation/config/#sqs_secret -# @dottie/validate required_if=QUEUE_DRIVER sqs -#SQS_SECRET="your-secret-key" - -# @default "https://sqs.us-east-1.amazonaws.com/your-account-id" -# @see https://docs.pixelfed.org/technical-documentation/config/#sqs_prefix -# @dottie/validate required_if=QUEUE_DRIVER sqs -#SQS_PREFIX="" - -# @default "your-queue-name" -# @see https://docs.pixelfed.org/technical-documentation/config/#sqs_queue -# @dottie/validate required_if=QUEUE_DRIVER sqs -#SQS_QUEUE="your-queue-name" - -# @default "us-east-1" -# @see https://docs.pixelfed.org/technical-documentation/config/#sqs_region -# @dottie/validate required_if=QUEUE_DRIVER sqs -#SQS_REGION="us-east-1" - -################################################################################ -# Session configuration -################################################################################ - -# This option controls the default session “driver” that will be used on requests. -# -# By default, we will use the lightweight native driver but you may specify any of the other wonderful drivers provided here. -# -# Possible values: -# - "file" -# - "cookie" -# - "database" (default) -# - "apc" -# - "memcached" -# - "redis" -# - "array" -# -# @default "database" -# @dottie/validate required,oneof=file cookie database apc memcached redis array -SESSION_DRIVER="redis" - -# Here you may specify the number of minutes that you wish the session to be allowed to remain idle before it expires. -# -# If you want them to immediately expire on the browser closing, set that option. -# -# @default 86400. -# @see https://docs.pixelfed.org/technical-documentation/config/#session_lifetime -# @dottie/validate required,number -#SESSION_LIFETIME="86400" - -# Here you may change the domain of the cookie used to identify a session in your application. -# -# This will determine which domains the cookie is available to in your application. -# -# A sensible default has been set. -# -# @default the value of APP_DOMAIN, or null. -# @see https://docs.pixelfed.org/technical-documentation/config/#session_domain -# @dottie/validate required,domain -#SESSION_DOMAIN="${APP_DOMAIN}" - -################################################################################ -# Proxy configuration -################################################################################ - -# Set trusted proxy IP addresses. -# -# Both IPv4 and IPv6 addresses are supported, along with CIDR notation. -# -# The “*” character is syntactic sugar within TrustedProxy to trust any -# proxy that connects directly to your server, a requirement when you cannot -# know the address of your proxy (e.g. if using Rackspace balancers). -# -# The “**” character is syntactic sugar within TrustedProxy to trust not just any -# proxy that connects directly to your server, but also proxies that connect to those proxies, -# and all the way back until you reach the original source IP. It will mean that -# $request->getClientIp() always gets the originating client IP, no matter how many proxies -# that client’s request has subsequently passed through. -# -# @default "*" -# @see https://docs.pixelfed.org/technical-documentation/config/#trust_proxies -# @dottie/validate required -TRUST_PROXIES="*" - -################################################################################ -# Passport configuration -################################################################################ -# -# Passport uses encryption keys while generating secure access tokens -# for your application. -# -# By default, the keys are stored as local files but can be set via environment -# variables when that is more convenient. - -# @see https://docs.pixelfed.org/technical-documentation/config/#passport_private_key -# @dottie/validate required -#PASSPORT_PRIVATE_KEY="" - -# @see https://docs.pixelfed.org/technical-documentation/config/#passport_public_key -# @dottie/validate required -#PASSPORT_PUBLIC_KEY="" - -################################################################################ -# PHP configuration -################################################################################ - -# @default "128M" -# @see https://www.php.net/manual/en/ini.core.php#ini.memory-limit -# @dottie/validate required -#PHP_MEMORY_LIMIT="128M" - -################################################################################ -# Other configuration -################################################################################ - -# ? Add your own configuration here - -################################################################################ -# Timezone configuration -################################################################################ - -# Set timezone used by *all* containers - these must be in sync. -# -# ! Do not edit your timezone once the service is running - or things will break! -# -# @see https://www.php.net/manual/en/timezones.php -# @dottie/validate required,timezone -TZ="${APP_TIMEZONE}" - -################################################################################ -# Docker configuraton for *all* services -################################################################################ - -# Prefix for container names (without any dash at the end) -# @dottie/validate required -DOCKER_ALL_CONTAINER_NAME_PREFIX="${APP_DOMAIN}" - -# How often Docker health check should run for all services -# -# Can be overridden by individual [DOCKER_*_HEALTHCHECK_INTERVAL] settings further down -# -# @default "10s" -# @dottie/validate required -DOCKER_ALL_DEFAULT_HEALTHCHECK_INTERVAL="10s" - -# Path (relative to the docker-compose.yml) or absolute (/some/other/path) where containers will *all* data -# will be stored (data, config, overrides) -# -# @default "./docker-compose-state" -# @dottie/validate required,dir -DOCKER_ALL_HOST_ROOT_PATH="./docker-compose-state" - -# Path (relative to the docker-compose.yml) or absolute (/some/other/path) where containers will store their data -# -# @default "${DOCKER_ALL_HOST_ROOT_PATH}/data" -# @dottie/validate required,dir -DOCKER_ALL_HOST_DATA_ROOT_PATH="${DOCKER_ALL_HOST_ROOT_PATH}/data" - -# Path (relative to the docker-compose.yml) or absolute (/some/other/path) where containers will store their confguration -# -# @default "${DOCKER_ALL_HOST_ROOT_PATH}/config" -# @dottie/validate required,dir -DOCKER_ALL_HOST_CONFIG_ROOT_PATH="${DOCKER_ALL_HOST_ROOT_PATH}/config" - -# Path (relative to the docker-compose.yml) or absolute (/some/other/path) where containers will store overrides -# -# @default "${DOCKER_ALL_HOST_ROOT_PATH}/overrides" -# @dottie/validate required,dir -DOCKER_APP_HOST_OVERRIDES_PATH="${DOCKER_ALL_HOST_ROOT_PATH}/overrides" - -################################################################################ -# Docker [web] + [worker] (also know as "app") shared service configuration -################################################################################ - -# The docker tag prefix to use for pulling images, can be one of -# -# * latest -# * -# * staging -# * edge -# * branch- -# * pr- -# -# Combined with [DOCKER_APP_RUNTIME] and [PHP_VERSION] configured -# elsewhere in this file, the final Docker tag is computed. -# @dottie/validate required -DOCKER_APP_RELEASE="branch-jippi-fork" - -# The PHP version to use for [web] and [worker] container -# -# Any version published on https://hub.docker.com/_/php should work -# -# Example: -# -# * 8.1 -# * 8.2 -# * 8.2.14 -# * latest -# -# Do *NOT* use the full Docker tag (e.g. "8.3.2RC1-fpm-bullseye") -# *only* the version part. The rest of the full tag is derived from -# the [DOCKER_APP_RUNTIME] and [PHP_DEBIAN_RELEASE] settings -# @dottie/validate required -DOCKER_APP_PHP_VERSION="8.2" - -# The container runtime to use. -# -# @see https://docs.pixelfed.org/running-pixelfed/docker/runtimes.html -# @dottie/validate required,oneof=apache nginx fpm -DOCKER_APP_RUNTIME="apache" - -# The Debian release variant to use of the [php] Docker image -# -# Examlpe: [bookworm] or [bullseye] -# @dottie/validate required,oneof=bookwork bullseye -DOCKER_APP_DEBIAN_RELEASE="bullseye" - -# The [php] Docker image base type -# -# @see https://docs.pixelfed.org/running-pixelfed/docker/runtimes.html -# @dottie/validate required,oneof=apache fpm cli -DOCKER_APP_BASE_TYPE="apache" - -# Image to pull the Pixelfed Docker images from. -# -# Example values: -# -# * "ghcr.io/pixelfed/pixelfed" to pull from GitHub -# * "pixelfed/pixelfed" to pull from DockerHub -# * "your/fork" to pull from a custom fork -# -# @dottie/validate required -DOCKER_APP_IMAGE="ghcr.io/jippi/pixelfed" - -# Pixelfed version (image tag) to pull from the registry. -# -# @see https://github.com/pixelfed/pixelfed/pkgs/container/pixelfed -# @dottie/validate required -DOCKER_APP_TAG="${DOCKER_APP_RELEASE}-${DOCKER_APP_RUNTIME}-${DOCKER_APP_PHP_VERSION}" - -# Path (on host system) where the [app] + [worker] container will write -# its [storage] data (e.g uploads/images/profile pictures etc.). -# -# Path is relative (./some/other/path) to the docker-compose.yml or absolute (/some/other/path) -# @dottie/validate required,dir -DOCKER_APP_HOST_STORAGE_PATH="${DOCKER_ALL_HOST_DATA_ROOT_PATH}/pixelfed/storage" - -# Path (on host system) where the [app] + [worker] container will write -# its [cache] data. -# -# Path is relative (./some/other/path) to the docker-compose.yml or absolute (/some/other/path) -# @dottie/validate required,dir -DOCKER_APP_HOST_CACHE_PATH="${DOCKER_ALL_HOST_DATA_ROOT_PATH}/pixelfed/cache" - -# Automatically run "One-time setup tasks" commands. -# -# If you are migrating to this docker-compose setup or have manually run the "One time seutp" -# tasks (https://docs.pixelfed.org/running-pixelfed/installation/#setting-up-services) -# you can set this to "0" to prevent them from running. -# -# Otherwise, leave it at "1" to have them run *once*. -# @dottie/validate required,boolean -#DOCKER_APP_RUN_ONE_TIME_SETUP_TASKS="1" - -# A space-seperated list of paths (inside the container) to *recursively* [chown] -# to the container user/group id (UID/GID) in case of permission issues. -# -# ! You should *not* leave this on permanently, at it can significantly slow down startup -# ! time for the container, and during normal operations there should never be permission -# ! issues. Please report a bug if you see behavior requiring this to be permanently on -# -# Example: "/var/www/storage /var/www/bootstrap/cache" -# @dottie/validate required -#DOCKER_APP_ENSURE_OWNERSHIP_PATHS="" - -# Enable Docker Entrypoint debug mode (will call [set -x] in bash scripts) -# by setting this to "1" -# @dottie/validate required,boolean -#DOCKER_APP_ENTRYPOINT_DEBUG="0" - -# List of extra APT packages (separated by space) to install when building -# locally using [docker compose build]. -# -# @see https://github.com/pixelfed/pixelfed/blob/dev/docker/customizing.md -# @dottie/validate required -#DOCKER_APP_APT_PACKAGES_EXTRA="" - -# List of *extra* PECL extensions (separated by space) to install when -# building locally using [docker compose build]. -# -# @see https://github.com/pixelfed/pixelfed/blob/dev/docker/customizing.md -# @dottie/validate required -#DOCKER_APP_PHP_PECL_EXTENSIONS_EXTRA="" - -# List of *extra* PHP extensions (separated by space) to install when -# building locally using [docker compose build]. -# -# @see https://github.com/pixelfed/pixelfed/blob/dev/docker/customizing.md -# @dottie/validate required -#DOCKER_APP_PHP_EXTENSIONS_EXTRA="" - -################################################################################ -# Docker [redis] service configuration -################################################################################ - -# Redis version to use as Docker tag -# -# @see https://hub.docker.com/_/redis -# @dottie/validate required -DOCKER_REDIS_VERSION="7.2" - -# Path (on host system) where the [redis] container will store its data -# -# Path is relative (./some/other/path) to the docker-compose.yml or absolute (/some/other/path) -# @dottie/validate required,dir -DOCKER_REDIS_HOST_DATA_PATH="${DOCKER_ALL_HOST_DATA_ROOT_PATH}/redis" - -# Port that Redis will listen on *outside* the container (e.g. the host machine) -# @dottie/validate required,number -DOCKER_REDIS_HOST_PORT="${REDIS_PORT}" - -# The filename that Redis should store its config file within -# -# NOTE: The file *MUST* exists (even empty) before enabling this setting! -# -# Use a command like [touch "${DOCKER_ALL_HOST_CONFIG_ROOT_PATH}/redis/redis.conf"] to create it. -# -# @default "" -# @dottie/validate required -#DOCKER_REDIS_CONFIG_FILE="/etc/redis/redis.conf" - -# How often Docker health check should run for [redis] service -# -# @default "10s" -# @dottie/validate required -DOCKER_REDIS_HEALTHCHECK_INTERVAL="${DOCKER_ALL_DEFAULT_HEALTHCHECK_INTERVAL}" - -################################################################################ -# Docker [db] service configuration -################################################################################ - -# Set this to a non-empty value (e.g. "disabled") to disable the [db] service -#DOCKER_DB_PROFILE="" - -# Path (on host system) where the [db] container will store its data -# -# Path is relative (./some/other/path) to the docker-compose.yml or absolute (/some/other/path) -# @dottie/validate required,dir -DOCKER_DB_HOST_DATA_PATH="${DOCKER_ALL_HOST_DATA_ROOT_PATH}/db" - -# Port that the database will listen on *outside* the container (e.g. the host machine) -# -# Use "3306" for MySQL/MariaDB and "5432" for PostgreeSQL -# @dottie/validate required,number -DOCKER_DB_HOST_PORT="${DB_PORT}" - -# How often Docker health check should run for [db] service -# @dottie/validate required -DOCKER_DB_HEALTHCHECK_INTERVAL="${DOCKER_ALL_DEFAULT_HEALTHCHECK_INTERVAL}" - -################################################################################ -# Docker [web] service configuration -################################################################################ - -# Set this to a non-empty value (e.g. "disabled") to disable the [web] service -# @dottie/validate required -#DOCKER_WEB_PROFILE="" - -# Port to expose [web] container will listen on *outside* the container (e.g. the host machine) for *HTTP* traffic only -# @dottie/validate required,number -DOCKER_WEB_PORT_EXTERNAL_HTTP="8080" - -# How often Docker health check should run for [web] service -# @dottie/validate required -DOCKER_WEB_HEALTHCHECK_INTERVAL="${DOCKER_ALL_DEFAULT_HEALTHCHECK_INTERVAL}" - -################################################################################ -# Docker [worker] service configuration -################################################################################ - -# Set this to a non-empty value (e.g. "disabled") to disable the [worker] service -# @dottie/validate required -#DOCKER_WORKER_PROFILE="" - -# How often Docker health check should run for [worker] service -# @dottie/validate required -DOCKER_WORKER_HEALTHCHECK_INTERVAL="${DOCKER_ALL_DEFAULT_HEALTHCHECK_INTERVAL}" - -################################################################################ -# Docker [proxy] + [proxy-acme] service configuration -################################################################################ - -# Set this to a non-empty value (e.g. "disabled") to disable the [proxy] and [proxy-acme] service -DOCKER_PROXY_PROFILE="" - -# Set this to a non-empty value (e.g. "disabled") to disable the [proxy-acme] service -DOCKER_PROXY_ACME_PROFILE="${DOCKER_PROXY_PROFILE}" - -# How often Docker health check should run for [proxy] service -# @dottie/validate required -DOCKER_PROXY_HEALTHCHECK_INTERVAL="${DOCKER_ALL_DEFAULT_HEALTHCHECK_INTERVAL}" - -# Port that the [proxy] will listen on *outside* the container (e.g. the host machine) for HTTP traffic -# @dottie/validate required,number -DOCKER_PROXY_HOST_PORT_HTTP="80" - -# Port that the [proxy] will listen on *outside* the container (e.g. the host machine) for HTTPS traffic -# @dottie/validate required,number -DOCKER_PROXY_HOST_PORT_HTTPS="443" - -# Path to the Docker socket on the *host* -# @dottie/validate required,file -DOCKER_PROXY_HOST_DOCKER_SOCKET_PATH="/var/run/docker.sock" - -################################################################################ -# -################################################################################ - -# ! ---------------------------------------------------------------------------- -# ! STOP STOP STOP STOP STOP STOP STOP STOP STOP STOP STOP STOP STOP STOP STOP -# ! ---------------------------------------------------------------------------- -# ! Below this line is default environment variables for various [db] backends -# ! You very likely do *NOT* need to modify any of this, ever. -# ! ---------------------------------------------------------------------------- - -################################################################################ -# Docker [db] service environment variables for MySQL (Oracle) -################################################################################ - -# See "Environment Variables" at https://hub.docker.com/_/mysql -# -# ! DO NOT CHANGE unless you know what you are doing - -MYSQL_ROOT_PASSWORD="${DB_PASSWORD}" -MYSQL_USER="${DB_USERNAME}" -MYSQL_PASSWORD="${DB_PASSWORD}" -MYSQL_DATABASE="${DB_DATABASE}" - -################################################################################ -# Docker [db] service environment variables for MySQL (MariaDB) -################################################################################ - -# See "Start a mariadb server instance with user, password and database" -# at https://hub.docker.com/_/mariadb -# -# ! DO NOT CHANGE unless you know what you are doing - -MARIADB_ROOT_PASSWORD="${DB_PASSWORD}" -MARIADB_USER="${DB_USERNAME}" -MARIADB_PASSWORD="${DB_PASSWORD}" -MARIADB_DATABASE="${DB_DATABASE}" - -################################################################################ -# Docker [db] service environment variables for PostgreSQL -################################################################################ - -# See "Environment Variables" at https://hub.docker.com/_/postgres -# -# ! DO NOT CHANGE unless you know what you are doing - -POSTGRES_USER="${DB_USERNAME}" -POSTGRES_PASSWORD="${DB_PASSWORD}" -POSTGRES_DB="${DB_DATABASE}" diff --git a/pkg/render/test-fixtures/formatter/single-pair.input.env b/pkg/render/test-fixtures/formatter/single-pair.input.env deleted file mode 100644 index acd56aa..0000000 --- a/pkg/render/test-fixtures/formatter/single-pair.input.env +++ /dev/null @@ -1 +0,0 @@ -KEY=VALUE diff --git a/pkg/render/test-fixtures/formatter/two-pairs-newlines.golden.env b/pkg/render/test-fixtures/formatter/two-pairs-newlines.golden.env deleted file mode 100644 index 0ee7e13..0000000 --- a/pkg/render/test-fixtures/formatter/two-pairs-newlines.golden.env +++ /dev/null @@ -1,2 +0,0 @@ -KEY=VALUE -FOO=baz diff --git a/pkg/render/test-fixtures/formatter/two-pairs-newlines.input.env b/pkg/render/test-fixtures/formatter/two-pairs-newlines.input.env deleted file mode 100644 index 18f57bc..0000000 --- a/pkg/render/test-fixtures/formatter/two-pairs-newlines.input.env +++ /dev/null @@ -1,5 +0,0 @@ -KEY=VALUE - - - -FOO=baz diff --git a/pkg/render/test-fixtures/formatter/two-pairs.input.env b/pkg/render/test-fixtures/formatter/two-pairs.input.env deleted file mode 100644 index 0ee7e13..0000000 --- a/pkg/render/test-fixtures/formatter/two-pairs.input.env +++ /dev/null @@ -1,2 +0,0 @@ -KEY=VALUE -FOO=baz diff --git a/pkg/test_helpers/filebased_command_tests.go b/pkg/test_helpers/filebased_command_tests.go index 1787997..3a6480e 100644 --- a/pkg/test_helpers/filebased_command_tests.go +++ b/pkg/test_helpers/filebased_command_tests.go @@ -2,6 +2,7 @@ package test_helpers import ( "bytes" + "context" "fmt" "io" "os" @@ -9,6 +10,7 @@ import ( "testing" "unicode" + "github.com/google/shlex" "github.com/jippi/dottie/cmd" "github.com/sebdah/goldie/v2" "github.com/stretchr/testify/assert" @@ -49,7 +51,7 @@ func RunFilebasedCommandTests(t *testing.T, settings Setting, globalArgs ...stri goldenStdout string goldenStderr string goldenEnv string - commandArgs []string + commands [][]string } tests := []testData{} @@ -59,20 +61,27 @@ func RunFilebasedCommandTests(t *testing.T, settings Setting, globalArgs ...stri continue } - if !strings.HasSuffix(file.Name(), ".command.txt") { + if !strings.HasSuffix(file.Name(), ".run") { continue } - base := strings.TrimSuffix(file.Name(), ".command.txt") + base := strings.TrimSuffix(file.Name(), ".run") content, err := os.ReadFile("tests/" + file.Name()) require.NoErrorf(t, err, "failed to read file: %s", "tests/"+file.Name()) - var commandArgs []string + var commands [][]string str := string(bytes.TrimFunc(content, unicode.IsSpace)) if len(str) > 0 { - commandArgs = strings.Split(str, "\n") + commandArgs := strings.Split(str, "\n") + for _, commandStr := range commandArgs { + command, err := shlex.Split(commandStr) + + require.NoError(t, err) + + commands = append(commands, command) + } } test := testData{ @@ -81,7 +90,7 @@ func RunFilebasedCommandTests(t *testing.T, settings Setting, globalArgs ...stri goldenStderr: "stderr", goldenEnv: "env", envFile: base + ".env", - commandArgs: commandArgs, + commands: commands, } tests = append(tests, test) @@ -114,44 +123,37 @@ func RunFilebasedCommandTests(t *testing.T, settings Setting, globalArgs ...stri require.NoErrorf(t, err, "failed to copy [%s] to TempDir", tt.envFile) } - // Point args to the copied temp env file - args := []string{"--file", dotEnvFile} - args = append(args, globalArgs...) - args = append(args, tt.commandArgs...) - // Prepare output buffers stdout := bytes.Buffer{} stderr := bytes.Buffer{} - // Prepare command - root := cmd.NewCommand() - root.SetArgs(args) - root.SetOut(&stdout) - root.SetErr(&stderr) - - // Run command - out, err := root.ExecuteC() - if err != nil { - // Append errors to stderr - stderr.WriteString(fmt.Sprintf("%+v", err)) - } + ctx := context.Background() - // Assert we got a Cobra command back - require.NotNil(t, out, "expected a return value") + for idx, command := range tt.commands { + // Point args to the copied temp env file + args := []string{} + args = append(args, globalArgs...) + args = append(args, command...) - // Assert stdout + stderr + modified env file is as expected - if stdout.Len() == 0 { - assert.NoFileExists(t, "tests/"+tt.name+"/stdout.golden") - } else { - golden.Assert(t, tt.goldenStdout, stdout.Bytes()) - } + stdout.WriteString(fmt.Sprintf("---- exec command line %d: %+v\n", idx, args)) + stderr.WriteString(fmt.Sprintf("---- exec command line %d: %+v\n", idx, args)) - if stderr.Len() == 0 { - assert.NoFileExists(t, "tests/"+tt.name+"/stderr.golden") - } else { - golden.Assert(t, tt.goldenStderr, stderr.Bytes()) + commandArgs := append(args, "--file", dotEnvFile) + + // Run command + out, _ := cmd.RunCommand(ctx, commandArgs, &stdout, &stderr) + + // Assert we got a Cobra command back + require.NotNil(t, out, "expected a return value") + + stdout.WriteString(fmt.Sprintf("---- done command line %d: %+v\n", idx, args)) + stderr.WriteString(fmt.Sprintf("---- done command line %d: %+v\n", idx, args)) } + // Assert stdout + stderr + modified env file is as expected + golden.Assert(t, tt.goldenStdout, stdout.Bytes()) + golden.Assert(t, tt.goldenStderr, stderr.Bytes()) + if !settings.Has(SkipEnvCopy) { // Read the modified .env file back modifiedEnv, err := os.ReadFile(dotEnvFile) diff --git a/pkg/tui/config.go b/pkg/tui/config.go index 543269e..e1a58a9 100644 --- a/pkg/tui/config.go +++ b/pkg/tui/config.go @@ -11,6 +11,17 @@ type ColorConfig struct { Border ComponentColor } +func NewNeutralColorComponentConfig() ColorConfig { + config := ColorConfig{ + Text: ComponentColor{}, + TextEmphasis: ComponentColor{}, + Background: ComponentColor{}, + Border: ComponentColor{}, + } + + return config +} + func NewColorComponentConfig(baseColor lipgloss.Color) ColorConfig { base := ColorToHex(baseColor) diff --git a/pkg/tui/context.go b/pkg/tui/context.go new file mode 100644 index 0000000..e22e548 --- /dev/null +++ b/pkg/tui/context.go @@ -0,0 +1,32 @@ +package tui + +import ( + "context" + "io" +) + +type printerContextKey int + +const ( + Stdout printerContextKey = iota + Stderr printerContextKey = iota +) + +func CreateContext(ctx context.Context, stdout, stderr io.Writer) context.Context { + ctx = context.WithValue(ctx, Stdout, Theme.Printer(stdout)) + ctx = context.WithValue(ctx, Stderr, Theme.Printer(stderr)) + + return ctx +} + +func FromContext(ctx context.Context, key printerContextKey) ThemePrinter { + return ctx.Value(key).(ThemePrinter) +} + +func ColorFromContext(ctx context.Context, key printerContextKey, color colorType) Printer { + return ctx.Value(key).(ThemePrinter).Color(color) +} + +func PrintersFromContext(ctx context.Context) (ThemePrinter, ThemePrinter) { + return ctx.Value(Stdout).(ThemePrinter), ctx.Value(Stderr).(ThemePrinter) +} diff --git a/pkg/tui/theme.go b/pkg/tui/theme.go index 85734b7..7c80deb 100644 --- a/pkg/tui/theme.go +++ b/pkg/tui/theme.go @@ -1,9 +1,25 @@ package tui import ( + "io" + "github.com/erikgeiser/promptkit" ) +type colorType int + +const ( + Danger colorType = iota + Dark + Info + Light + Neutral + Primary + Secondary + Success + Warning +) + type ThemeConfig struct { DefaultWidth int @@ -14,12 +30,58 @@ type ThemeConfig struct { Dark Color Info Color Light Color + Neutral Color Primary Color Secondary Color Success Color Warning Color } +func (tc ThemeConfig) Printer(w io.Writer) ThemePrinter { + return ThemePrinter{ + w: w, + cache: make(map[colorType]Printer), + } +} + +type ThemePrinter struct { + w io.Writer + cache map[colorType]Printer +} + +func (tp ThemePrinter) Color(colorType colorType) Printer { + if printer, ok := tp.cache[colorType]; ok { + return printer + } + + var color Color + + switch colorType { + case Danger: + color = Theme.Danger + case Dark: + color = Theme.Danger + case Info: + color = Theme.Info + case Light: + color = Theme.Light + case Primary: + color = Theme.Primary + case Secondary: + color = Theme.Secondary + case Success: + color = Theme.Success + case Warning: + color = Theme.Warning + case Neutral: + color = Theme.Neutral + } + + tp.cache[colorType] = color.BuffPrinter(tp.w) + + return tp.cache[colorType] +} + var Theme ThemeConfig func init() { @@ -34,6 +96,7 @@ func init() { Theme.Secondary = NewColor(NewColorComponentConfig(Gray600)) Theme.Success = NewColor(NewColorComponentConfig(Green)) Theme.Warning = NewColor(NewColorComponentConfig(Yellow)) + Theme.Neutral = NewColor(NewNeutralColorComponentConfig()) dark := NewColorComponentConfig(Gray700) dark.TextEmphasis.Dark = ComponentColorConfig{ From 5e82171b304cf1373f57fdbbf8caa8b73640e08d Mon Sep 17 00:00:00 2001 From: Christian Winther Date: Thu, 15 Feb 2024 12:25:10 +0100 Subject: [PATCH 02/33] fix typo in filename --- cmd/print/{prin_test.go => print_test.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename cmd/print/{prin_test.go => print_test.go} (100%) diff --git a/cmd/print/prin_test.go b/cmd/print/print_test.go similarity index 100% rename from cmd/print/prin_test.go rename to cmd/print/print_test.go From 453fec874bd919245730cbac8e19e430b64c21da Mon Sep 17 00:00:00 2001 From: Christian Winther Date: Thu, 15 Feb 2024 12:25:37 +0100 Subject: [PATCH 03/33] fix grammar --- cmd/disable/disable_test.go | 2 +- cmd/enable/enable_test.go | 2 +- cmd/groups/groups_test.go | 2 +- cmd/print/print_test.go | 2 +- pkg/test_helpers/filebased_command_tests.go | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/disable/disable_test.go b/cmd/disable/disable_test.go index 0b53104..03865bc 100644 --- a/cmd/disable/disable_test.go +++ b/cmd/disable/disable_test.go @@ -9,5 +9,5 @@ import ( func TestCommand(t *testing.T) { t.Parallel() - test_helpers.RunFilebasedCommandTests(t, 0, "disable") + test_helpers.RunFileBasedCommandTests(t, 0, "disable") } diff --git a/cmd/enable/enable_test.go b/cmd/enable/enable_test.go index 4e39650..2394256 100644 --- a/cmd/enable/enable_test.go +++ b/cmd/enable/enable_test.go @@ -9,5 +9,5 @@ import ( func TestCommand(t *testing.T) { t.Parallel() - test_helpers.RunFilebasedCommandTests(t, 0, "enable") + test_helpers.RunFileBasedCommandTests(t, 0, "enable") } diff --git a/cmd/groups/groups_test.go b/cmd/groups/groups_test.go index b99d17c..8f8288b 100644 --- a/cmd/groups/groups_test.go +++ b/cmd/groups/groups_test.go @@ -9,5 +9,5 @@ import ( func TestCommand(t *testing.T) { t.Parallel() - test_helpers.RunFilebasedCommandTests(t, test_helpers.SkipEnvCopy, "groups") + test_helpers.RunFileBasedCommandTests(t, test_helpers.SkipEnvCopy, "groups") } diff --git a/cmd/print/print_test.go b/cmd/print/print_test.go index 937c571..f02b771 100644 --- a/cmd/print/print_test.go +++ b/cmd/print/print_test.go @@ -9,5 +9,5 @@ import ( func TestCommand(t *testing.T) { t.Parallel() - test_helpers.RunFilebasedCommandTests(t, test_helpers.SkipEnvCopy, "print") + test_helpers.RunFileBasedCommandTests(t, test_helpers.SkipEnvCopy, "print") } diff --git a/pkg/test_helpers/filebased_command_tests.go b/pkg/test_helpers/filebased_command_tests.go index 3a6480e..be905d5 100644 --- a/pkg/test_helpers/filebased_command_tests.go +++ b/pkg/test_helpers/filebased_command_tests.go @@ -36,7 +36,7 @@ func (bitmask Setting) Has(setting Setting) bool { return bitmask&setting != 0 } -func RunFilebasedCommandTests(t *testing.T, settings Setting, globalArgs ...string) { +func RunFileBasedCommandTests(t *testing.T, settings Setting, globalArgs ...string) { t.Helper() files, err := os.ReadDir("tests") From 67cd5abc2d9de9de992366bd78fc9c09db04dc28 Mon Sep 17 00:00:00 2001 From: Christian Winther Date: Thu, 15 Feb 2024 13:39:31 +0100 Subject: [PATCH 04/33] more testing --- .../stderr.golden | 1 - .../stdout.golden | 1 - cmd/disable/tests/disable-a-key/stderr.golden | 1 - cmd/disable/tests/disable-a-key/stdout.golden | 1 - cmd/disable/tests/invalid-key/stderr.golden | 1 - cmd/disable/tests/invalid-key/stdout.golden | 1 - .../stderr.golden | 1 - .../stdout.golden | 1 - cmd/enable/tests/enable-a-key/stderr.golden | 1 - cmd/enable/tests/enable-a-key/stdout.golden | 1 - cmd/enable/tests/invalid-key/stderr.golden | 1 - cmd/enable/tests/invalid-key/stdout.golden | 1 - cmd/print/tests/empty/stderr.golden | 1 - cmd/print/tests/empty/stdout.golden | 1 - cmd/print/tests/full/stderr.golden | 1 - cmd/print/tests/full/stdout.golden | 1 - cmd/print/tests/simple-pretty/stderr.golden | 1 - cmd/print/tests/simple-pretty/stdout.golden | 1 - cmd/print/tests/simple/stderr.golden | 1 - cmd/print/tests/simple/stdout.golden | 1 - cmd/print/tests/specific-group/stderr.golden | 1 - cmd/print/tests/specific-group/stdout.golden | 1 - cmd/set/set.go | 16 +++---- cmd/set/set_test.go | 13 ++++++ cmd/set/tests/manipulate-empty.env | 1 + cmd/set/tests/manipulate-empty.run | 9 ++++ cmd/set/tests/manipulate-empty/env.golden | 10 +++++ cmd/set/tests/manipulate-empty/stderr.golden | 14 +++++++ cmd/set/tests/manipulate-empty/stdout.golden | 25 +++++++++++ pkg/ast/document.go | 37 ++++++++++++++++ pkg/ast/upsert/upsert.go | 42 +++++++++++++++---- pkg/test_helpers/filebased_command_tests.go | 3 -- 32 files changed, 151 insertions(+), 41 deletions(-) create mode 100644 cmd/set/set_test.go create mode 100644 cmd/set/tests/manipulate-empty.env create mode 100644 cmd/set/tests/manipulate-empty.run create mode 100644 cmd/set/tests/manipulate-empty/env.golden create mode 100644 cmd/set/tests/manipulate-empty/stderr.golden create mode 100644 cmd/set/tests/manipulate-empty/stdout.golden diff --git a/cmd/disable/tests/disable-a-key-already-disabled/stderr.golden b/cmd/disable/tests/disable-a-key-already-disabled/stderr.golden index 4cd839d..8188166 100644 --- a/cmd/disable/tests/disable-a-key-already-disabled/stderr.golden +++ b/cmd/disable/tests/disable-a-key-already-disabled/stderr.golden @@ -1,3 +1,2 @@ ---- exec command line 0: [disable KEY_B] WARNING: The key [KEY_B] is already disabled ----- done command line 0: [disable KEY_B] diff --git a/cmd/disable/tests/disable-a-key-already-disabled/stdout.golden b/cmd/disable/tests/disable-a-key-already-disabled/stdout.golden index 86f297c..b9de686 100644 --- a/cmd/disable/tests/disable-a-key-already-disabled/stdout.golden +++ b/cmd/disable/tests/disable-a-key-already-disabled/stdout.golden @@ -1,2 +1 @@ ---- exec command line 0: [disable KEY_B] ----- done command line 0: [disable KEY_B] diff --git a/cmd/disable/tests/disable-a-key/stderr.golden b/cmd/disable/tests/disable-a-key/stderr.golden index 86f297c..b9de686 100644 --- a/cmd/disable/tests/disable-a-key/stderr.golden +++ b/cmd/disable/tests/disable-a-key/stderr.golden @@ -1,2 +1 @@ ---- exec command line 0: [disable KEY_B] ----- done command line 0: [disable KEY_B] diff --git a/cmd/disable/tests/disable-a-key/stdout.golden b/cmd/disable/tests/disable-a-key/stdout.golden index 3d6034b..e258b95 100644 --- a/cmd/disable/tests/disable-a-key/stdout.golden +++ b/cmd/disable/tests/disable-a-key/stdout.golden @@ -1,3 +1,2 @@ ---- exec command line 0: [disable KEY_B] Key [KEY_B] was successfully disabled ----- done command line 0: [disable KEY_B] diff --git a/cmd/disable/tests/invalid-key/stderr.golden b/cmd/disable/tests/invalid-key/stderr.golden index 23d20c5..e91443c 100644 --- a/cmd/disable/tests/invalid-key/stderr.golden +++ b/cmd/disable/tests/invalid-key/stderr.golden @@ -1,4 +1,3 @@ ---- exec command line 0: [disable NONEXISTING_KEY] Error: Could not find KEY [NONEXISTING_KEY] Run 'dottie disable --help' for usage. ----- done command line 0: [disable NONEXISTING_KEY] diff --git a/cmd/disable/tests/invalid-key/stdout.golden b/cmd/disable/tests/invalid-key/stdout.golden index b87d9d7..cc62e8b 100644 --- a/cmd/disable/tests/invalid-key/stdout.golden +++ b/cmd/disable/tests/invalid-key/stdout.golden @@ -1,2 +1 @@ ---- exec command line 0: [disable NONEXISTING_KEY] ----- done command line 0: [disable NONEXISTING_KEY] diff --git a/cmd/enable/tests/enable-a-key-already-enabled/stderr.golden b/cmd/enable/tests/enable-a-key-already-enabled/stderr.golden index 989306a..7fb3b5b 100644 --- a/cmd/enable/tests/enable-a-key-already-enabled/stderr.golden +++ b/cmd/enable/tests/enable-a-key-already-enabled/stderr.golden @@ -1,3 +1,2 @@ ---- exec command line 0: [enable KEY_B] WARNING: The key [KEY_B] is already enabled ----- done command line 0: [enable KEY_B] diff --git a/cmd/enable/tests/enable-a-key-already-enabled/stdout.golden b/cmd/enable/tests/enable-a-key-already-enabled/stdout.golden index aed285e..eb68006 100644 --- a/cmd/enable/tests/enable-a-key-already-enabled/stdout.golden +++ b/cmd/enable/tests/enable-a-key-already-enabled/stdout.golden @@ -1,3 +1,2 @@ ---- exec command line 0: [enable KEY_B] Key [KEY_B] was successfully enabled ----- done command line 0: [enable KEY_B] diff --git a/cmd/enable/tests/enable-a-key/stderr.golden b/cmd/enable/tests/enable-a-key/stderr.golden index aaf4e2a..fec5fa2 100644 --- a/cmd/enable/tests/enable-a-key/stderr.golden +++ b/cmd/enable/tests/enable-a-key/stderr.golden @@ -1,2 +1 @@ ---- exec command line 0: [enable KEY_B] ----- done command line 0: [enable KEY_B] diff --git a/cmd/enable/tests/enable-a-key/stdout.golden b/cmd/enable/tests/enable-a-key/stdout.golden index aed285e..eb68006 100644 --- a/cmd/enable/tests/enable-a-key/stdout.golden +++ b/cmd/enable/tests/enable-a-key/stdout.golden @@ -1,3 +1,2 @@ ---- exec command line 0: [enable KEY_B] Key [KEY_B] was successfully enabled ----- done command line 0: [enable KEY_B] diff --git a/cmd/enable/tests/invalid-key/stderr.golden b/cmd/enable/tests/invalid-key/stderr.golden index 214c89a..fd12d5f 100644 --- a/cmd/enable/tests/invalid-key/stderr.golden +++ b/cmd/enable/tests/invalid-key/stderr.golden @@ -1,4 +1,3 @@ ---- exec command line 0: [enable NONEXISTING_KEY] Error: Could not find KEY [NONEXISTING_KEY] Run 'dottie enable --help' for usage. ----- done command line 0: [enable NONEXISTING_KEY] diff --git a/cmd/enable/tests/invalid-key/stdout.golden b/cmd/enable/tests/invalid-key/stdout.golden index 7921252..5dc11c0 100644 --- a/cmd/enable/tests/invalid-key/stdout.golden +++ b/cmd/enable/tests/invalid-key/stdout.golden @@ -1,2 +1 @@ ---- exec command line 0: [enable NONEXISTING_KEY] ----- done command line 0: [enable NONEXISTING_KEY] diff --git a/cmd/print/tests/empty/stderr.golden b/cmd/print/tests/empty/stderr.golden index b9e9b71..a1e5768 100644 --- a/cmd/print/tests/empty/stderr.golden +++ b/cmd/print/tests/empty/stderr.golden @@ -1,2 +1 @@ ---- exec command line 0: [print --no-color] ----- done command line 0: [print --no-color] diff --git a/cmd/print/tests/empty/stdout.golden b/cmd/print/tests/empty/stdout.golden index 845e763..34f18f8 100644 --- a/cmd/print/tests/empty/stdout.golden +++ b/cmd/print/tests/empty/stdout.golden @@ -1,4 +1,3 @@ ---- exec command line 0: [print --no-color] ----- done command line 0: [print --no-color] diff --git a/cmd/print/tests/full/stderr.golden b/cmd/print/tests/full/stderr.golden index 3db625a..759af6a 100644 --- a/cmd/print/tests/full/stderr.golden +++ b/cmd/print/tests/full/stderr.golden @@ -1,2 +1 @@ ---- exec command line 0: [print --pretty --no-color] ----- done command line 0: [print --pretty --no-color] diff --git a/cmd/print/tests/full/stdout.golden b/cmd/print/tests/full/stdout.golden index 21a0a3d..a5124d7 100644 --- a/cmd/print/tests/full/stdout.golden +++ b/cmd/print/tests/full/stdout.golden @@ -30,4 +30,3 @@ GROUP_TWO_A="WORLD" SECRET=KEY ----- done command line 0: [print --pretty --no-color] diff --git a/cmd/print/tests/simple-pretty/stderr.golden b/cmd/print/tests/simple-pretty/stderr.golden index b9e9b71..a1e5768 100644 --- a/cmd/print/tests/simple-pretty/stderr.golden +++ b/cmd/print/tests/simple-pretty/stderr.golden @@ -1,2 +1 @@ ---- exec command line 0: [print --no-color] ----- done command line 0: [print --no-color] diff --git a/cmd/print/tests/simple-pretty/stdout.golden b/cmd/print/tests/simple-pretty/stdout.golden index a6a593a..b2455b0 100644 --- a/cmd/print/tests/simple-pretty/stdout.golden +++ b/cmd/print/tests/simple-pretty/stdout.golden @@ -3,4 +3,3 @@ KEY_A="I'm key A" KEY_B="I'm key B" KEY_C="I'm key C" ----- done command line 0: [print --no-color] diff --git a/cmd/print/tests/simple/stderr.golden b/cmd/print/tests/simple/stderr.golden index b9e9b71..a1e5768 100644 --- a/cmd/print/tests/simple/stderr.golden +++ b/cmd/print/tests/simple/stderr.golden @@ -1,2 +1 @@ ---- exec command line 0: [print --no-color] ----- done command line 0: [print --no-color] diff --git a/cmd/print/tests/simple/stdout.golden b/cmd/print/tests/simple/stdout.golden index a6a593a..b2455b0 100644 --- a/cmd/print/tests/simple/stdout.golden +++ b/cmd/print/tests/simple/stdout.golden @@ -3,4 +3,3 @@ KEY_A="I'm key A" KEY_B="I'm key B" KEY_C="I'm key C" ----- done command line 0: [print --no-color] diff --git a/cmd/print/tests/specific-group/stderr.golden b/cmd/print/tests/specific-group/stderr.golden index 39060ef..8e61f95 100644 --- a/cmd/print/tests/specific-group/stderr.golden +++ b/cmd/print/tests/specific-group/stderr.golden @@ -1,2 +1 @@ ---- exec command line 0: [print --no-color --group my-first-group] ----- done command line 0: [print --no-color --group my-first-group] diff --git a/cmd/print/tests/specific-group/stdout.golden b/cmd/print/tests/specific-group/stdout.golden index 8fbdb28..d117e0c 100644 --- a/cmd/print/tests/specific-group/stdout.golden +++ b/cmd/print/tests/specific-group/stdout.golden @@ -3,4 +3,3 @@ KEY_A="I'm key A" KEY_B="I'm key B" KEY_C="I'm key C" ----- done command line 0: [print --no-color --group my-first-group] diff --git a/cmd/set/set.go b/cmd/set/set.go index 028361f..c7b841e 100644 --- a/cmd/set/set.go +++ b/cmd/set/set.go @@ -3,7 +3,6 @@ package set import ( "errors" "fmt" - "os" "strings" "github.com/jippi/dottie/pkg" @@ -87,7 +86,10 @@ func runE(cmd *cobra.Command, args []string) error { // Loop arguments and place them // - var allErrors error + var ( + allErrors error + stdout, stderr = tui.PrintersFromContext(cmd.Context()) + ) for _, stringPair := range args { pairSlice := strings.SplitN(stringPair, "=", 2) @@ -106,7 +108,7 @@ func runE(cmd *cobra.Command, args []string) error { Interpolated: value, Enabled: !shared.BoolFlag(cmd.Flags(), "disabled"), Quote: token.QuoteFromString(shared.StringFlag(cmd.Flags(), "quote-style")), - Comments: ast.NewCommentsFromSlice(shared.StringSliceFlag(cmd.Flags(), "comments")), + Comments: ast.NewCommentsFromSlice(shared.StringSliceFlag(cmd.Flags(), "comment")), } // @@ -115,12 +117,12 @@ func runE(cmd *cobra.Command, args []string) error { assignment, warnings, err := upserter.Upsert(assignment) if warnings != nil { - tui.Theme.Warning.StderrPrinter().Println("WARNING:", warnings) + stderr.Color(tui.Warning).Println("WARNING:", warnings) } if err != nil { z := validation.NewError(assignment, err) - fmt.Fprintln(os.Stderr, validation.Explain(document, z, z, false, true)) + stderr.Color(tui.Neutral).Println(validation.Explain(document, z, z, false, true)) if shared.BoolWithInverseValue(cmd.Flags(), "validate") { allErrors = multierr.Append(allErrors, err) @@ -129,7 +131,7 @@ func runE(cmd *cobra.Command, args []string) error { } } - tui.Theme.Success.StderrPrinter().Printfln("Key [%s] was successfully upserted", key) + stdout.Color(tui.Success).Printfln("Key [%s] was successfully upserted", key) } if allErrors != nil { @@ -144,7 +146,7 @@ func runE(cmd *cobra.Command, args []string) error { return fmt.Errorf("failed to save file: %w", err) } - tui.Theme.Success.StderrPrinter().Println("File was successfully saved") + stdout.Color(tui.Success).Println("File was successfully saved") return nil } diff --git a/cmd/set/set_test.go b/cmd/set/set_test.go new file mode 100644 index 0000000..3a6811a --- /dev/null +++ b/cmd/set/set_test.go @@ -0,0 +1,13 @@ +package set_test + +import ( + "testing" + + "github.com/jippi/dottie/pkg/test_helpers" +) + +func TestCommand(t *testing.T) { + t.Parallel() + + test_helpers.RunFileBasedCommandTests(t, 0, "set") +} diff --git a/cmd/set/tests/manipulate-empty.env b/cmd/set/tests/manipulate-empty.env new file mode 100644 index 0000000..dfd1584 --- /dev/null +++ b/cmd/set/tests/manipulate-empty.env @@ -0,0 +1 @@ +SOME_KEY="SOME_VALUE" diff --git a/cmd/set/tests/manipulate-empty.run b/cmd/set/tests/manipulate-empty.run new file mode 100644 index 0000000..f7606e1 --- /dev/null +++ b/cmd/set/tests/manipulate-empty.run @@ -0,0 +1,9 @@ +SOME_KEY=SOME_VALUE +ANOTHER_KEY=ANOTHER_VALUE --quote-style single +SECOND_KEY="should be before ANOTHER_KEY" --before ANOTHER_KEY +TRUE_SECOND_KEY="HA, I'm after SOME_KEY, so I'm before ANOTHER_KEY now" --after SOME_KEY +SECOND_KEY="damn, I'm the third key now" +SOME_KEY=ANOTHER_VALUE --comment "I'm a comment" --comment "I'm another comment" +A_NUMBER=1 --comment "@dottie/validate number" +NOT_A_NUMBER=abc --comment "@dottie/validate number" +A_NUMBER=2 diff --git a/cmd/set/tests/manipulate-empty/env.golden b/cmd/set/tests/manipulate-empty/env.golden new file mode 100644 index 0000000..d6707e9 --- /dev/null +++ b/cmd/set/tests/manipulate-empty/env.golden @@ -0,0 +1,10 @@ +# I'm a comment +# I'm another comment +SOME_KEY="ANOTHER_VALUE" + +TRUE_SECOND_KEY="HA, I'm after SOME_KEY, so I'm before ANOTHER_KEY now" +SECOND_KEY="damn, I'm the third key now" +ANOTHER_KEY='ANOTHER_VALUE' + +# @dottie/validate number +A_NUMBER="2" diff --git a/cmd/set/tests/manipulate-empty/stderr.golden b/cmd/set/tests/manipulate-empty/stderr.golden new file mode 100644 index 0000000..7f5edc5 --- /dev/null +++ b/cmd/set/tests/manipulate-empty/stderr.golden @@ -0,0 +1,14 @@ +---- exec command line 0: [set SOME_KEY=SOME_VALUE] +---- exec command line 1: [set ANOTHER_KEY=ANOTHER_VALUE --quote-style single] +---- exec command line 2: [set SECOND_KEY=should be before ANOTHER_KEY --before ANOTHER_KEY] +---- exec command line 3: [set TRUE_SECOND_KEY=HA, I'm after SOME_KEY, so I'm before ANOTHER_KEY now --after SOME_KEY] +---- exec command line 4: [set SECOND_KEY=damn, I'm the third key now] +---- exec command line 5: [set SOME_KEY=ANOTHER_VALUE --comment I'm a comment --comment I'm another comment] +---- exec command line 6: [set A_NUMBER=1 --comment @dottie/validate number] +---- exec command line 7: [set NOT_A_NUMBER=abc --comment @dottie/validate number] +NOT_A_NUMBER (-:2) + * (number) The value [abc] is not a valid number. + +Error: validation error +Run 'dottie set --help' for usage. +---- exec command line 8: [set A_NUMBER=2] diff --git a/cmd/set/tests/manipulate-empty/stdout.golden b/cmd/set/tests/manipulate-empty/stdout.golden new file mode 100644 index 0000000..e84d99d --- /dev/null +++ b/cmd/set/tests/manipulate-empty/stdout.golden @@ -0,0 +1,25 @@ +---- exec command line 0: [set SOME_KEY=SOME_VALUE] +Key [SOME_KEY] was successfully upserted +File was successfully saved +---- exec command line 1: [set ANOTHER_KEY=ANOTHER_VALUE --quote-style single] +Key [ANOTHER_KEY] was successfully upserted +File was successfully saved +---- exec command line 2: [set SECOND_KEY=should be before ANOTHER_KEY --before ANOTHER_KEY] +Key [SECOND_KEY] was successfully upserted +File was successfully saved +---- exec command line 3: [set TRUE_SECOND_KEY=HA, I'm after SOME_KEY, so I'm before ANOTHER_KEY now --after SOME_KEY] +Key [TRUE_SECOND_KEY] was successfully upserted +File was successfully saved +---- exec command line 4: [set SECOND_KEY=damn, I'm the third key now] +Key [SECOND_KEY] was successfully upserted +File was successfully saved +---- exec command line 5: [set SOME_KEY=ANOTHER_VALUE --comment I'm a comment --comment I'm another comment] +Key [SOME_KEY] was successfully upserted +File was successfully saved +---- exec command line 6: [set A_NUMBER=1 --comment @dottie/validate number] +Key [A_NUMBER] was successfully upserted +File was successfully saved +---- exec command line 7: [set NOT_A_NUMBER=abc --comment @dottie/validate number] +---- exec command line 8: [set A_NUMBER=2] +Key [A_NUMBER] was successfully upserted +File was successfully saved diff --git a/pkg/ast/document.go b/pkg/ast/document.go index a61fd76..bb9e162 100644 --- a/pkg/ast/document.go +++ b/pkg/ast/document.go @@ -295,3 +295,40 @@ func (document *Document) Initialize() { } } } + +func (document *Document) Replace(assignment *Assignment) error { + existing := document.Get(assignment.Name) + if existing == nil { + return fmt.Errorf("No KEY named [%s] exists in the document", assignment.Name) + } + + if existing.Group != nil { + for idx, stmt := range existing.Group.Statements { + val, ok := stmt.(*Assignment) + if !ok { + continue + } + + if val.Name == assignment.Name { + existing.Group.Statements[idx] = assignment + + return nil + } + } + } + + for idx, stmt := range document.Statements { + val, ok := stmt.(*Assignment) + if !ok { + continue + } + + if val.Name == assignment.Name { + document.Statements[idx] = assignment + + return nil + } + } + + return fmt.Errorf("Could not find+replace KEY named [%s] in document", assignment.Name) +} diff --git a/pkg/ast/upsert/upsert.go b/pkg/ast/upsert/upsert.go index 5490dd9..a24ecc7 100644 --- a/pkg/ast/upsert/upsert.go +++ b/pkg/ast/upsert/upsert.go @@ -5,6 +5,9 @@ import ( "slices" "github.com/jippi/dottie/pkg/ast" + "github.com/jippi/dottie/pkg/parser" + "github.com/jippi/dottie/pkg/render" + "github.com/jippi/dottie/pkg/scanner" "github.com/jippi/dottie/pkg/validation" "go.uber.org/multierr" ) @@ -79,9 +82,6 @@ func (u *Upserter) Upsert(input *ast.Assignment) (*ast.Assignment, error, error) if err != nil { return nil, nil, err } - - // Recalculate the index order of all Statements (for interpolation) - u.document.ReindexStatements() } // Replace comments on the assignment if the Setting is on @@ -93,15 +93,32 @@ func (u *Upserter) Upsert(input *ast.Assignment) (*ast.Assignment, error, error) assignment.Literal = input.Literal assignment.Quote = input.Quote assignment.Interpolated = input.Literal + + var ( + tempDoc *ast.Document + err, warnings error + ) + + // Render and parse back the Statement to ensure annotations and such are properly handled + tempDoc, err = parser.New(scanner.New(render.NewFormatter().Statement(assignment).String()), "-").Parse() + if err != nil { + return nil, nil, fmt.Errorf("failed to parse assignment: %w", err) + } + + assignment = tempDoc.Get(assignment.Name) assignment.Initialize() if _, ok := assignment.Dependencies[assignment.Name]; ok { return nil, nil, fmt.Errorf("Key [%s] may not reference itself!", assignment.Name) } - u.document.Initialize() + // Replace the Assignment in the document + // + // This is necessary since its a different pointer address after we rendered+parsed earlier + u.document.Replace(assignment) - var err, warnings error + // Reinitialize the document so all indices and such are correct + u.document.Initialize() // Interpolate the Assignment if it is enabled if assignment.Enabled { @@ -128,18 +145,25 @@ func (u *Upserter) Upsert(input *ast.Assignment) (*ast.Assignment, error, error) } func (u *Upserter) createAndInsert(input *ast.Assignment) (*ast.Assignment, error) { - // Ensure the group exists (may return 'nil' if no group is required) - group := u.document.EnsureGroup(u.group) - // Create the new newAssignment newAssignment := &ast.Assignment{ Comments: input.Comments, Enabled: input.Enabled, - Group: group, Literal: input.Literal, Name: input.Name, } + doc, err := parser.New(scanner.New(render.NewFormatter().Statement(newAssignment).String()), "-").Parse() + if err != nil { + return nil, fmt.Errorf("failed to parse assignment: %w", err) + } + + // Ensure the group exists (may return 'nil' if no group is required) + group := u.document.EnsureGroup(u.group) + + newAssignment = doc.Get(newAssignment.Name) + newAssignment.Group = group + // Find the statement slice to operate on statements := u.document.Statements if newAssignment.Group != nil { diff --git a/pkg/test_helpers/filebased_command_tests.go b/pkg/test_helpers/filebased_command_tests.go index be905d5..ca33ce1 100644 --- a/pkg/test_helpers/filebased_command_tests.go +++ b/pkg/test_helpers/filebased_command_tests.go @@ -145,9 +145,6 @@ func RunFileBasedCommandTests(t *testing.T, settings Setting, globalArgs ...stri // Assert we got a Cobra command back require.NotNil(t, out, "expected a return value") - - stdout.WriteString(fmt.Sprintf("---- done command line %d: %+v\n", idx, args)) - stderr.WriteString(fmt.Sprintf("---- done command line %d: %+v\n", idx, args)) } // Assert stdout + stderr + modified env file is as expected From 5d8c922e0236f17026b590392ebe992448f110c3 Mon Sep 17 00:00:00 2001 From: Christian Winther Date: Thu, 15 Feb 2024 15:28:19 +0100 Subject: [PATCH 05/33] fix missing test output --- cmd/disable/tests/missing-key/stderr.golden | 3 +++ cmd/disable/tests/missing-key/stdout.golden | 1 + cmd/enable/tests/missing-key/stderr.golden | 3 +++ cmd/enable/tests/missing-key/stdout.golden | 1 + cmd/groups/tests/multiple-groups/stderr.golden | 1 + cmd/groups/tests/multiple-groups/stdout.golden | 9 +++++++++ cmd/groups/tests/no-groups/stderr.golden | 3 +++ cmd/groups/tests/no-groups/stdout.golden | 1 + cmd/groups/tests/single-group/stderr.golden | 1 + cmd/groups/tests/single-group/stdout.golden | 7 +++++++ pkg/test_helpers/filebased_command_tests.go | 4 ++++ 11 files changed, 34 insertions(+) diff --git a/cmd/disable/tests/missing-key/stderr.golden b/cmd/disable/tests/missing-key/stderr.golden index e69de29..8af21b1 100644 --- a/cmd/disable/tests/missing-key/stderr.golden +++ b/cmd/disable/tests/missing-key/stderr.golden @@ -0,0 +1,3 @@ +---- exec command line 0: [disable] +Error: Missing required argument: KEY +Run 'dottie disable --help' for usage. diff --git a/cmd/disable/tests/missing-key/stdout.golden b/cmd/disable/tests/missing-key/stdout.golden index e69de29..faefdb7 100644 --- a/cmd/disable/tests/missing-key/stdout.golden +++ b/cmd/disable/tests/missing-key/stdout.golden @@ -0,0 +1 @@ +---- exec command line 0: [disable] diff --git a/cmd/enable/tests/missing-key/stderr.golden b/cmd/enable/tests/missing-key/stderr.golden index e69de29..3f26e84 100644 --- a/cmd/enable/tests/missing-key/stderr.golden +++ b/cmd/enable/tests/missing-key/stderr.golden @@ -0,0 +1,3 @@ +---- exec command line 0: [enable] +Error: Missing required argument: KEY +Run 'dottie enable --help' for usage. diff --git a/cmd/enable/tests/missing-key/stdout.golden b/cmd/enable/tests/missing-key/stdout.golden index e69de29..e66e7b4 100644 --- a/cmd/enable/tests/missing-key/stdout.golden +++ b/cmd/enable/tests/missing-key/stdout.golden @@ -0,0 +1 @@ +---- exec command line 0: [enable] diff --git a/cmd/groups/tests/multiple-groups/stderr.golden b/cmd/groups/tests/multiple-groups/stderr.golden index e69de29..94cc795 100644 --- a/cmd/groups/tests/multiple-groups/stderr.golden +++ b/cmd/groups/tests/multiple-groups/stderr.golden @@ -0,0 +1 @@ +---- exec command line 0: [groups] diff --git a/cmd/groups/tests/multiple-groups/stdout.golden b/cmd/groups/tests/multiple-groups/stdout.golden index e69de29..fa41443 100644 --- a/cmd/groups/tests/multiple-groups/stdout.golden +++ b/cmd/groups/tests/multiple-groups/stdout.golden @@ -0,0 +1,9 @@ +---- exec command line 0: [groups] +┌──────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ │ +│ Groups in tests/multiple-groups.env │ +│ │ +└──────────────────────────────────────────────────────────────────────────────────────────────────┘ +my-first-group (tests/multiple-groups.env:2) +my-second-group (tests/multiple-groups.env:13) +my-third-group (tests/multiple-groups.env:17) diff --git a/cmd/groups/tests/no-groups/stderr.golden b/cmd/groups/tests/no-groups/stderr.golden index e69de29..333fabb 100644 --- a/cmd/groups/tests/no-groups/stderr.golden +++ b/cmd/groups/tests/no-groups/stderr.golden @@ -0,0 +1,3 @@ +---- exec command line 0: [groups] +Error: No groups found +Run 'dottie groups --help' for usage. diff --git a/cmd/groups/tests/no-groups/stdout.golden b/cmd/groups/tests/no-groups/stdout.golden index e69de29..94cc795 100644 --- a/cmd/groups/tests/no-groups/stdout.golden +++ b/cmd/groups/tests/no-groups/stdout.golden @@ -0,0 +1 @@ +---- exec command line 0: [groups] diff --git a/cmd/groups/tests/single-group/stderr.golden b/cmd/groups/tests/single-group/stderr.golden index e69de29..94cc795 100644 --- a/cmd/groups/tests/single-group/stderr.golden +++ b/cmd/groups/tests/single-group/stderr.golden @@ -0,0 +1 @@ +---- exec command line 0: [groups] diff --git a/cmd/groups/tests/single-group/stdout.golden b/cmd/groups/tests/single-group/stdout.golden index e69de29..2a00e01 100644 --- a/cmd/groups/tests/single-group/stdout.golden +++ b/cmd/groups/tests/single-group/stdout.golden @@ -0,0 +1,7 @@ +---- exec command line 0: [groups] +┌──────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ │ +│ Groups in tests/single-group.env │ +│ │ +└──────────────────────────────────────────────────────────────────────────────────────────────────┘ +my-group (tests/single-group.env:2) diff --git a/pkg/test_helpers/filebased_command_tests.go b/pkg/test_helpers/filebased_command_tests.go index ca33ce1..cac3dd6 100644 --- a/pkg/test_helpers/filebased_command_tests.go +++ b/pkg/test_helpers/filebased_command_tests.go @@ -84,6 +84,10 @@ func RunFileBasedCommandTests(t *testing.T, settings Setting, globalArgs ...stri } } + if len(commands) == 0 { + commands = append(commands, []string{}) + } + test := testData{ name: base, goldenStdout: "stdout", From c4c0993f3905267add136cf18b11599203e97be5 Mon Sep 17 00:00:00 2001 From: Christian Winther Date: Thu, 15 Feb 2024 15:35:47 +0100 Subject: [PATCH 06/33] add back missing tests --- pkg/render/render_test.go | 12 +- .../formatter/comment-spacing.input.env | 4 + .../formatter/compressed.input.env | 12 + .../test-fixtures/formatter/empty.input.env | 0 ...nt.golden.env => just-a-comment.input.env} | 0 .../{ => output}/comment-spacing.golden.env | 0 .../{ => output}/compressed.golden.env | 0 .../formatter/{ => output}/empty.golden.env | 0 .../output/just-a-comment.golden.env | 1 + .../{ => output}/pixelfed-full.golden.env | 0 .../{ => output}/single-pair.golden.env | 0 .../two-pairs-newlines.golden.env} | 0 .../formatter/output/two-pairs.golden.env | 2 + .../formatter/pixelfed-full.input.env | 1285 +++++++++++++++++ .../formatter/single-pair.input.env | 1 + .../formatter/two-pairs-newlines.golden.env | 2 + .../formatter/two-pairs-newlines.input.env | 5 + .../formatter/two-pairs.input.env | 2 + 18 files changed, 1320 insertions(+), 6 deletions(-) create mode 100644 pkg/render/test-fixtures/formatter/comment-spacing.input.env create mode 100644 pkg/render/test-fixtures/formatter/compressed.input.env create mode 100644 pkg/render/test-fixtures/formatter/empty.input.env rename pkg/render/test-fixtures/formatter/{just-a-comment.golden.env => just-a-comment.input.env} (100%) rename pkg/render/test-fixtures/formatter/{ => output}/comment-spacing.golden.env (100%) rename pkg/render/test-fixtures/formatter/{ => output}/compressed.golden.env (100%) rename pkg/render/test-fixtures/formatter/{ => output}/empty.golden.env (100%) create mode 100644 pkg/render/test-fixtures/formatter/output/just-a-comment.golden.env rename pkg/render/test-fixtures/formatter/{ => output}/pixelfed-full.golden.env (100%) rename pkg/render/test-fixtures/formatter/{ => output}/single-pair.golden.env (100%) rename pkg/render/test-fixtures/formatter/{two-pairs.golden.env => output/two-pairs-newlines.golden.env} (100%) create mode 100644 pkg/render/test-fixtures/formatter/output/two-pairs.golden.env create mode 100644 pkg/render/test-fixtures/formatter/pixelfed-full.input.env create mode 100644 pkg/render/test-fixtures/formatter/single-pair.input.env create mode 100644 pkg/render/test-fixtures/formatter/two-pairs-newlines.golden.env create mode 100644 pkg/render/test-fixtures/formatter/two-pairs-newlines.input.env create mode 100644 pkg/render/test-fixtures/formatter/two-pairs.input.env diff --git a/pkg/render/render_test.go b/pkg/render/render_test.go index c59d460..03d99b1 100644 --- a/pkg/render/render_test.go +++ b/pkg/render/render_test.go @@ -17,16 +17,11 @@ func TestFormatter(t *testing.T) { golden := goldie.New( t, - goldie.WithFixtureDir("test-fixtures/formatter"), + goldie.WithFixtureDir("test-fixtures/formatter/output"), goldie.WithNameSuffix(".golden.env"), goldie.WithDiffEngine(goldie.ColoredDiff), ) - files, err := os.ReadDir("test-fixtures/formatter") - if err != nil { - log.Fatal(err) - } - // Build test data set type testData struct { name string @@ -35,6 +30,11 @@ func TestFormatter(t *testing.T) { tests := []testData{} + files, err := os.ReadDir("test-fixtures/formatter") + if err != nil { + log.Fatal(err) + } + for _, file := range files { switch { case strings.HasSuffix(file.Name(), ".input.env"): diff --git a/pkg/render/test-fixtures/formatter/comment-spacing.input.env b/pkg/render/test-fixtures/formatter/comment-spacing.input.env new file mode 100644 index 0000000..0197ebc --- /dev/null +++ b/pkg/render/test-fixtures/formatter/comment-spacing.input.env @@ -0,0 +1,4 @@ +# My first key +KEY=VALUE +# My Second Key +FOO=BAZ diff --git a/pkg/render/test-fixtures/formatter/compressed.input.env b/pkg/render/test-fixtures/formatter/compressed.input.env new file mode 100644 index 0000000..74fc60a --- /dev/null +++ b/pkg/render/test-fixtures/formatter/compressed.input.env @@ -0,0 +1,12 @@ +my_key="ok" +# This is my favorite int +# @dottie/validate number +PORT="test" +my_lol="ok" +my_lol2="ok" +################################################################################ +# database +################################################################################ +# the hostname to the database +DB_HOST="db" +DB_PORT="${PORT}" diff --git a/pkg/render/test-fixtures/formatter/empty.input.env b/pkg/render/test-fixtures/formatter/empty.input.env new file mode 100644 index 0000000..e69de29 diff --git a/pkg/render/test-fixtures/formatter/just-a-comment.golden.env b/pkg/render/test-fixtures/formatter/just-a-comment.input.env similarity index 100% rename from pkg/render/test-fixtures/formatter/just-a-comment.golden.env rename to pkg/render/test-fixtures/formatter/just-a-comment.input.env diff --git a/pkg/render/test-fixtures/formatter/comment-spacing.golden.env b/pkg/render/test-fixtures/formatter/output/comment-spacing.golden.env similarity index 100% rename from pkg/render/test-fixtures/formatter/comment-spacing.golden.env rename to pkg/render/test-fixtures/formatter/output/comment-spacing.golden.env diff --git a/pkg/render/test-fixtures/formatter/compressed.golden.env b/pkg/render/test-fixtures/formatter/output/compressed.golden.env similarity index 100% rename from pkg/render/test-fixtures/formatter/compressed.golden.env rename to pkg/render/test-fixtures/formatter/output/compressed.golden.env diff --git a/pkg/render/test-fixtures/formatter/empty.golden.env b/pkg/render/test-fixtures/formatter/output/empty.golden.env similarity index 100% rename from pkg/render/test-fixtures/formatter/empty.golden.env rename to pkg/render/test-fixtures/formatter/output/empty.golden.env diff --git a/pkg/render/test-fixtures/formatter/output/just-a-comment.golden.env b/pkg/render/test-fixtures/formatter/output/just-a-comment.golden.env new file mode 100644 index 0000000..716ed14 --- /dev/null +++ b/pkg/render/test-fixtures/formatter/output/just-a-comment.golden.env @@ -0,0 +1 @@ +# Hello world diff --git a/pkg/render/test-fixtures/formatter/pixelfed-full.golden.env b/pkg/render/test-fixtures/formatter/output/pixelfed-full.golden.env similarity index 100% rename from pkg/render/test-fixtures/formatter/pixelfed-full.golden.env rename to pkg/render/test-fixtures/formatter/output/pixelfed-full.golden.env diff --git a/pkg/render/test-fixtures/formatter/single-pair.golden.env b/pkg/render/test-fixtures/formatter/output/single-pair.golden.env similarity index 100% rename from pkg/render/test-fixtures/formatter/single-pair.golden.env rename to pkg/render/test-fixtures/formatter/output/single-pair.golden.env diff --git a/pkg/render/test-fixtures/formatter/two-pairs.golden.env b/pkg/render/test-fixtures/formatter/output/two-pairs-newlines.golden.env similarity index 100% rename from pkg/render/test-fixtures/formatter/two-pairs.golden.env rename to pkg/render/test-fixtures/formatter/output/two-pairs-newlines.golden.env diff --git a/pkg/render/test-fixtures/formatter/output/two-pairs.golden.env b/pkg/render/test-fixtures/formatter/output/two-pairs.golden.env new file mode 100644 index 0000000..0ee7e13 --- /dev/null +++ b/pkg/render/test-fixtures/formatter/output/two-pairs.golden.env @@ -0,0 +1,2 @@ +KEY=VALUE +FOO=baz diff --git a/pkg/render/test-fixtures/formatter/pixelfed-full.input.env b/pkg/render/test-fixtures/formatter/pixelfed-full.input.env new file mode 100644 index 0000000..517ef5d --- /dev/null +++ b/pkg/render/test-fixtures/formatter/pixelfed-full.input.env @@ -0,0 +1,1285 @@ +#!/bin/bash +# -*- mode: bash -*- +# vi: ft=bash + +# shellcheck disable=SC2034,SC2148 + +################################################################################ +# Pixelfed application configuration +################################################################################ + +# A random 32-character string to be used as an encryption key. +# +# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +# ! NOTE: This will be auto-generated by Docker during bootstrap +# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +# +# This key is used by the Illuminate encrypter service and should be set to a random, +# 32 character string, otherwise these encrypted strings will not be safe. +# +# @see https://docs.pixelfed.org/technical-documentation/config/#app_key +# @dottie/validate required +APP_KEY= + +# @see https://docs.pixelfed.org/technical-documentation/config/#app_name-1 +# @dottie/validate required +#APP_NAME="my-new-app" + +# Application domains used for routing. +# +# @see https://docs.pixelfed.org/technical-documentation/config/#app_domain +# @dottie/validate fqdn +APP_DOMAIN="jippi.dev" + +# This URL is used by the console to properly generate URLs when using the Artisan command line tool. +# You should set this to the root of your application so that it is used when running Artisan tasks. +# +# @see https://docs.pixelfed.org/technical-documentation/config/#app_url +# @dottie/validate required +APP_URL="https://${APP_DOMAIN}" + +# Application domains used for routing. +# +# @see https://docs.pixelfed.org/technical-documentation/config/#admin_domain +# @dottie/validate required +ADMIN_DOMAIN="${APP_DOMAIN}" + +# This value determines the “environment” your application is currently running in. +# This may determine how you prefer to configure various services your application utilizes. +# +# @default "production" +# @see https://docs.pixelfed.org/technical-documentation/config/#app_env +# @dottie/validate required,oneof='production,dev,staging' +#APP_ENV="production" + +# When your application is in debug mode, detailed error messages with stack traces will +# be shown on every error that occurs within your application. +# +# If disabled, a simple generic error page is shown. +# +# @default "false" +# @see https://docs.pixelfed.org/technical-documentation/config/#app_debug +# @dottie/validate required,boolean +#APP_DEBUG="false" + +# Enable/disable new local account registrations. +# +# @default "true" +# @see https://docs.pixelfed.org/technical-documentation/config/#open_registration +# @dottie/validate required,boolean +#OPEN_REGISTRATION="true" + +# Require email verification before a new user can do anything. +# +# @default "true" +# @see https://docs.pixelfed.org/technical-documentation/config/#enforce_email_verification +# @dottie/validate required,boolean +#ENFORCE_EMAIL_VERIFICATION="true" + +# Allow a maximum number of user accounts. +# +# @default "1000" +# @see https://docs.pixelfed.org/technical-documentation/config/#pf_max_users +# @dottie/validate required,number +#PF_MAX_USERS="1000" + +# Enforce the maximum number of user accounts +# +# @default "true" +# @dottie/validate boolean +#PF_ENFORCE_MAX_USERS="true" + +# @default "false" +# @see https://docs.pixelfed.org/technical-documentation/config/#oauth_enabled +# @dottie/validate required,boolean +OAUTH_ENABLED="true" + +# ! Do not edit your timezone once the service is running - or things will break! +# +# @default "UTC" +# @see https://docs.pixelfed.org/technical-documentation/config/#app_timezone +# @see https://www.php.net/manual/en/timezones.php +# @dottie/validate required,timezone +APP_TIMEZONE="UTC" + +# The application locale determines the default locale that will be used by the translation service provider. +# You are free to set this value to any of the locales which will be supported by the application. +# +# @default "en" +# @see https://docs.pixelfed.org/technical-documentation/config/#app_locale +# @dottie/validate required +#APP_LOCALE="en" + +# The fallback locale determines the locale to use when the current one is not available. +# +# You may change the value to correspond to any of the language folders that are provided through your application. +# +# @default "en" +# @see https://docs.pixelfed.org/technical-documentation/config/#app_fallback_locale +# @dottie/validate required +#APP_FALLBACK_LOCALE="en" + +# @see https://docs.pixelfed.org/technical-documentation/config/#limit_account_size +# @dottie/validate required,boolean +#LIMIT_ACCOUNT_SIZE="true" + +# Update the max account size, the per user limit of files in kB. +# +# @default "1000000" (1GB) +# @see https://docs.pixelfed.org/technical-documentation/config/#max_account_size-kb +# @dottie/validate required,number +#MAX_ACCOUNT_SIZE="1000000" + +# Update the max photo size, in kB. +# +# @default "15000" (15MB) +# @see https://docs.pixelfed.org/technical-documentation/config/#max_photo_size-kb +# @dottie/validate required,number +#MAX_PHOTO_SIZE="15000" + +# The max number of photos allowed per post. +# +# @default "4" +# @see https://docs.pixelfed.org/technical-documentation/config/#max_album_length +# @dottie/validate required,number +#MAX_ALBUM_LENGTH="4" + +# Update the max avatar size, in kB. +# +# @default "2000" (2MB). +# @see https://docs.pixelfed.org/technical-documentation/config/#max_avatar_size-kb +# @dottie/validate required,number +#MAX_AVATAR_SIZE="2000" + +# Change the caption length limit for new local posts. +# +# @default "500" +# @see https://docs.pixelfed.org/technical-documentation/config/#max_caption_length +# @dottie/validate required,number +#MAX_CAPTION_LENGTH="500" + +# Change the bio length limit for user profiles. +# +# @default "125" +# @see https://docs.pixelfed.org/technical-documentation/config/#max_bio_length +# @dottie/validate required,number +#MAX_BIO_LENGTH="125" + +# Change the length limit for user names. +# +# @default "30" +# @see https://docs.pixelfed.org/technical-documentation/config/#max_name_length +# @dottie/validate required,number +#MAX_NAME_LENGTH="30" + +# Resize and optimize image uploads. +# +# @default "true" +# @see https://docs.pixelfed.org/technical-documentation/config/#pf_optimize_images +# @dottie/validate required,boolean +#PF_OPTIMIZE_IMAGES="true" + +# Set the image optimization quality, must be a value between 1-100. +# +# @default "80" +# @see https://docs.pixelfed.org/technical-documentation/config/#image_quality +# @dottie/validate required,number +#IMAGE_QUALITY="80" + +# Resize and optimize video uploads. +# +# @default "true" +# @see https://docs.pixelfed.org/technical-documentation/config/#pf_optimize_videos +# @dottie/validate required,boolean +#PF_OPTIMIZE_VIDEOS="true" + +# Enable account deletion. +# +# @default "true" +# @see https://docs.pixelfed.org/technical-documentation/config/#account_deletion +# @dottie/validate required,boolean +#ACCOUNT_DELETION="true" + +# Set account deletion queue after X days, set to false to delete accounts immediately. +# +# @default "false" +# @see https://docs.pixelfed.org/technical-documentation/config/#account_delete_after +# @dottie/validate required,boolean +#ACCOUNT_DELETE_AFTER="false" + +# @default "Pixelfed - Photo sharing for everyone" +# @see https://docs.pixelfed.org/technical-documentation/config/#instance_description +# @dottie/validate required +#INSTANCE_DESCRIPTION="" + +# @default "false" +# @see https://docs.pixelfed.org/technical-documentation/config/#instance_public_hashtags +# @dottie/validate required,boolean +#INSTANCE_PUBLIC_HASHTAGS="false" + +# @default "" +# @see https://docs.pixelfed.org/technical-documentation/config/#instance_contact_email +# @dottie/validate required +INSTANCE_CONTACT_EMAIL="admin@${APP_DOMAIN}" + +# @default "false" +# @see https://docs.pixelfed.org/technical-documentation/config/#instance_public_local_timeline +# @dottie/validate required,boolean +#INSTANCE_PUBLIC_LOCAL_TIMELINE="false" + +# @default "" +# @see https://docs.pixelfed.org/technical-documentation/config/#banned_usernames +#BANNED_USERNAMES="" + +# @default "false" +# @see https://docs.pixelfed.org/technical-documentation/config/#stories_enabled +# @dottie/validate required,boolean +#STORIES_ENABLED="false" + +# Level is hardcoded to 1. +# +# @default "false" +# @see https://docs.pixelfed.org/technical-documentation/config/#restricted_instance +# @dottie/validate required,boolean +#RESTRICTED_INSTANCE="false" + +################################################################################ +# Lets Encrypt configuration +################################################################################ + +# The host to request LetsEncrypt certificate for +# @dottie/validate required +LETSENCRYPT_HOST="${APP_DOMAIN}" + +# The e-mail to use for Lets Encrypt certificate requests. +# @dottie/validate required,email +LETSENCRYPT_EMAIL="jippi@jippi.dev" + +# Lets Encrypt staging/test servers for certificate requests. +# +# Setting this to any value will change to letsencrypt test servers. +#LETSENCRYPT_TEST="1" + +################################################################################ +# Database configuration +################################################################################ + +# Database version to use (as Docker tag) +# +# @see https://hub.docker.com/_/mariadb +# @dottie/validate required +DB_VERSION="11.2" + +# Here you may specify which of the database connections below +# you wish to use as your default connection for all database work. +# +# Of course you may use many connections at once using the database library. +# +# Possible values: +# +# - "sqlite" +# - "mysql" (default) +# - "pgsql" +# - "sqlsrv" +# +# @see https://docs.pixelfed.org/technical-documentation/config/#db_connection +# @dottie/validate required,oneof=sqlite mysql pgsql sqlsrv +DB_CONNECTION="mysql" + +# @see https://docs.pixelfed.org/technical-documentation/config/#db_host +# @dottie/validate required +DB_HOST="db" + +# @see https://docs.pixelfed.org/technical-documentation/config/#db_username +# @dottie/validate required +DB_USERNAME="pixelfed" + +# @see https://docs.pixelfed.org/technical-documentation/config/#db_password +# @dottie/validate required +DB_PASSWORD="__CHANGE_ME__" + +# @see https://docs.pixelfed.org/technical-documentation/config/#db_database +# @dottie/validate required +DB_DATABASE="pixelfed_prod" + +# Use "3306" for MySQL/MariaDB and "5432" for PostgreeSQL +# +# @see https://docs.pixelfed.org/technical-documentation/config/#db_port +# @dottie/validate required,number +DB_PORT="3306" + +# Automatically run [artisan migrate --force] if new migrations are detected. +# @dottie/validate required,boolean +DB_APPLY_NEW_MIGRATIONS_AUTOMATICALLY="false" + +################################################################################ +# Mail configuration +################################################################################ + +# Laravel supports both SMTP and PHP’s “mail” function as drivers for the sending of e-mail. +# You may specify which one you’re using throughout your application here. +# +# Possible values: +# +# "smtp" (default) +# "sendmail" +# "mailgun" +# "mandrill" +# "ses" +# "sparkpost" +# "log" +# "array" +# +# @default "smtp" +# @see https://docs.pixelfed.org/technical-documentation/config/#mail_driver +# @dottie/validate required,oneof=smtp sendmail mailgun mandrill ses sparkpost log array +#MAIL_DRIVER="smtp" + +# The host address of the SMTP server used by your applications. +# +# A default option is provided that is compatible with the Mailgun mail service which will provide reliable deliveries. +# +# @default "smtp.mailgun.org" +# @see https://docs.pixelfed.org/technical-documentation/config/#mail_host +# @dottie/validate required_with=MAIL_DRIVER,fqdn +# MAIL_HOST="smtp.mailgun.org" + +# This is the SMTP port used by your application to deliver e-mails to users of the application. +# +# Like the host we have set this value to stay compatible with the Mailgun e-mail application by default. +# +# @default 587. +# @see https://docs.pixelfed.org/technical-documentation/config/#mail_port +# @dottie/validate required_with=MAIL_DRIVER,number +#MAIL_PORT="587" + +# You may wish for all e-mails sent by your application to be sent from the same address. +# +# Here, you may specify a name and address that is used globally for all e-mails that are sent by your application. +# +# @default "hello@example.com" +# @see https://docs.pixelfed.org/technical-documentation/config/#mail_from_address +# @dottie/validate required_with=MAIL_DRIVER +MAIL_FROM_ADDRESS="hello@${APP_DOMAIN}" + +# @default "Example" +# @see https://docs.pixelfed.org/technical-documentation/config/#mail_from_name +# @dottie/validate required_with=MAIL_DRIVER +MAIL_FROM_NAME="Pixelfed @ ${APP_DOMAIN}" + +# If your SMTP server requires a username for authentication, you should set it here. +# +# This will get used to authenticate with your server on connection. +# You may also set the “password” value below this one. +# +# @default "" +# @see https://docs.pixelfed.org/technical-documentation/config/#mail_username +# @dottie/validate required_with=MAIL_DRIVER +#MAIL_USERNAME="" + +# @default "" +# @see https://docs.pixelfed.org/technical-documentation/config/#mail_password +# @dottie/validate required_with=MAIL_DRIVER +#MAIL_PASSWORD="" + +# Here you may specify the encryption protocol that should be used when the application send e-mail messages. +# +# A sensible default using the transport layer security protocol should provide great security. +# +# @default "tls" +# @see https://docs.pixelfed.org/technical-documentation/config/#mail_encryption +# @dottie/validate required_with=MAIL_DRIVER +#MAIL_ENCRYPTION="tls" + +################################################################################ +# Redis configuration +################################################################################ + +# @default "phpredis" +# @see https://docs.pixelfed.org/technical-documentation/config/#redis_client +# @dottie/validate required +#REDIS_CLIENT="phpredis" + +# @default "tcp" +# @see https://docs.pixelfed.org/technical-documentation/config/#redis_scheme +# @dottie/validate required +#REDIS_SCHEME="tcp" + +# @default "localhost" +# @see https://docs.pixelfed.org/technical-documentation/config/#redis_host +# @dottie/validate required +REDIS_HOST="redis" + +# @default "null" (not set/commented out). +# @see https://docs.pixelfed.org/technical-documentation/config/#redis_password +# @dottie/validate omitempty +#REDIS_PASSWORD= + +# @default "6379" +# @see https://docs.pixelfed.org/technical-documentation/config/#redis_port +# @dottie/validate required,number +REDIS_PORT="6379" + +# @default "0" +# @see https://docs.pixelfed.org/technical-documentation/config/#redis_database +# @dottie/validate required,number +#REDIS_DATABASE="0" + +################################################################################ +# Cache settings +################################################################################ + +# This option controls the default cache connection that gets used while using this caching library. +# +# This connection is used when another is not explicitly specified when executing a given caching function. +# +# Possible values: +# - "apc" +# - "array" +# - "database" +# - "file" (default) +# - "memcached" +# - "redis" +# +# @default "file" +# @see https://docs.pixelfed.org/technical-documentation/config/#cache_driver +# @dottie/validate required,oneof=apc array database file memcached redis +CACHE_DRIVER="redis" + +# @default ${APP_NAME}_cache, or laravel_cache if no APP_NAME is set. +# @see https://docs.pixelfed.org/technical-documentation/config/#cache_prefix +# @dottie/validate required +#CACHE_PREFIX="{APP_NAME}_cache" + +################################################################################ +# Horizon settings +################################################################################ + +# This prefix will be used when storing all Horizon data in Redis. +# +# You may modify the prefix when you are running multiple installations +# of Horizon on the same server so that they don’t have problems. +# +# @default "horizon-" +# @see https://docs.pixelfed.org/technical-documentation/config/#horizon_prefix +# @dottie/validate required +#HORIZON_PREFIX="horizon-" + +# @default "false" +# @see https://docs.pixelfed.org/technical-documentation/config/#horizon_darkmode +# @dottie/validate required,boolean +#HORIZON_DARKMODE="false" + +# This value (in MB) describes the maximum amount of memory (in MB) the Horizon worker +# may consume before it is terminated and restarted. +# +# You should set this value according to the resources available to your server. +# +# @default "64" +# @dottie/validate required,number +#HORIZON_MEMORY_LIMIT="64" + +# @see https://docs.pixelfed.org/technical-documentation/config/#horizon_balance_strategy +# @dottie/validate required +#HORIZON_BALANCE_STRATEGY="auto" + +# @see https://docs.pixelfed.org/technical-documentation/config/#horizon_min_processes +# @dottie/validate required,number +#HORIZON_MIN_PROCESSES="1" + +# @see https://docs.pixelfed.org/technical-documentation/config/#horizon_max_processes +# @dottie/validate required,number +#HORIZON_MAX_PROCESSES="20" + +# @see https://docs.pixelfed.org/technical-documentation/config/#horizon_supervisor_memory +# @dottie/validate required,number +#HORIZON_SUPERVISOR_MEMORY="64" + +# @see https://docs.pixelfed.org/technical-documentation/config/#horizon_supervisor_tries +# @dottie/validate required,number +#HORIZON_SUPERVISOR_TRIES="3" + +# @see https://docs.pixelfed.org/technical-documentation/config/#horizon_supervisor_nice +# @dottie/validate required,number +#HORIZON_SUPERVISOR_NICE="0" + +# @see https://docs.pixelfed.org/technical-documentation/config/#horizon_supervisor_timeout +# @dottie/validate required,number +#HORIZON_SUPERVISOR_TIMEOUT="300" + +################################################################################ +# Experiments +################################################################################ + +# Text only posts (alpha). +# +# @default "false" +# @see https://docs.pixelfed.org/technical-documentation/config/#exp_top +# @dottie/validate required,boolean +#EXP_TOP="false" + +# Poll statuses (alpha). +# +# @default "false" +# @see https://docs.pixelfed.org/technical-documentation/config/#exp_polls +# @dottie/validate required,boolean +#EXP_POLLS="false" + +# Cached public timeline for larger instances (beta). +# +# @default "false" +# @see https://docs.pixelfed.org/technical-documentation/config/#exp_cpt +# @dottie/validate required,boolean +#EXP_CPT="false" + +# Enforce Mastodon API Compatibility (alpha). +# +# @default "true" +# @see https://docs.pixelfed.org/technical-documentation/config/#exp_emc +# @dottie/validate required,boolean +#EXP_EMC="true" + +################################################################################ +# ActivityPub confguration +################################################################################ + +# @default "false" +# @see https://docs.pixelfed.org/technical-documentation/config/#activity_pub +# @dottie/validate required,boolean +ACTIVITY_PUB="true" + +# @default "true" +# @see https://docs.pixelfed.org/technical-documentation/config/#ap_remote_follow +# @dottie/validate required,boolean +#AP_REMOTE_FOLLOW="true" + +# @default "true" +# @see https://docs.pixelfed.org/technical-documentation/config/#ap_sharedinbox +# @dottie/validate required,boolean +#AP_SHAREDINBOX="true" + +# @default "true" +# @see https://docs.pixelfed.org/technical-documentation/config/#ap_inbox +# @dottie/validate required,boolean +#AP_INBOX="true" + +# @default "true" +# @see https://docs.pixelfed.org/technical-documentation/config/#ap_outbox +# @dottie/validate required,boolean +#AP_OUTBOX="true" + +################################################################################ +# Federation confguration +################################################################################ + +# @default "true" +# @see https://docs.pixelfed.org/technical-documentation/config/#atom_feeds +# @dottie/validate required,boolean +#ATOM_FEEDS="true" + +# @default "true" +# @see https://docs.pixelfed.org/technical-documentation/config/#nodeinfo +# @dottie/validate required,boolean +#NODEINFO="true" + +# @default "true" +# @see https://docs.pixelfed.org/technical-documentation/config/#webfinger +# @dottie/validate required,boolean +#WEBFINGER="true" + +################################################################################ +# Storage (cloud) +################################################################################ + +# Store media on object storage like S3, Digital Ocean Spaces, Rackspace +# +# @default "false" +# @see https://docs.pixelfed.org/technical-documentation/config/#pf_enable_cloud +# @dottie/validate required,boolean +#PF_ENABLE_CLOUD="false" + +# Many applications store files both locally and in the cloud. +# +# For this reason, you may specify a default “cloud” driver here. +# This driver will be bound as the Cloud disk implementation in the container. +# +# @default "s3" +# @see https://docs.pixelfed.org/technical-documentation/config/#filesystem_cloud +# @dottie/validate required_with=PF_ENABLE_CLOUD +#FILESYSTEM_CLOUD="s3" + +# @default true. +# @see https://docs.pixelfed.org/technical-documentation/config/#media_delete_local_after_cloud +# @dottie/validate required_with=PF_ENABLE_CLOUD,boolean +#MEDIA_DELETE_LOCAL_AFTER_CLOUD="true" + +################################################################################ +# Storage (cloud) - S3 andS S3 *compatible* providers +################################################################################ + +# @see https://docs.pixelfed.org/technical-documentation/config/#aws_access_key_id +# @dottie/validate required_if=FILESYSTEM_CLOUD s3 +#AWS_ACCESS_KEY_ID="" + +# @see https://docs.pixelfed.org/technical-documentation/config/#aws_secret_access_key +# @dottie/validate required_if=FILESYSTEM_CLOUD s3 +#AWS_SECRET_ACCESS_KEY="" + +# @see https://docs.pixelfed.org/technical-documentation/config/#aws_default_region +# @dottie/validate required_if=FILESYSTEM_CLOUD s3 +#AWS_DEFAULT_REGION="" + +# @see https://docs.pixelfed.org/technical-documentation/config/#aws_bucket +# @dottie/validate required_if=FILESYSTEM_CLOUD s3 +#AWS_BUCKET="" + +# @see https://docs.pixelfed.org/technical-documentation/config/#aws_url +# @dottie/validate required_if=FILESYSTEM_CLOUD s3 +#AWS_URL="" + +# @see https://docs.pixelfed.org/technical-documentation/config/#aws_endpoint +# @dottie/validate required_if=FILESYSTEM_CLOUD s3 +#AWS_ENDPOINT="" + +# @see https://docs.pixelfed.org/technical-documentation/config/#aws_use_path_style_endpoint +# @dottie/validate required_if=FILESYSTEM_CLOUD s3 +#AWS_USE_PATH_STYLE_ENDPOINT="false" + +################################################################################ +# COSTAR - Confirm Object Sentiment Transform and Reduce +################################################################################ + +# Comma-separated list of domains to block. +# +# @default null (not set/commented out). +# @see https://docs.pixelfed.org/technical-documentation/config/#cs_blocked_domains +# @dottie/validate +#CS_BLOCKED_DOMAINS="" + +# Comma-separated list of domains to add warnings. +# +# @default null (not set/commented out). +# @see https://docs.pixelfed.org/technical-documentation/config/#cs_cw_domains +# @dottie/validate +#CS_CW_DOMAINS="" + +# Comma-separated list of domains to remove from public timelines. +# +# @default null (not set/commented out). +# @see https://docs.pixelfed.org/technical-documentation/config/#cs_unlisted_domains +# @dottie/validate +#CS_UNLISTED_DOMAINS="" + +# Comma-separated list of keywords to block. +# +# @default null (not set/commented out). +# @see https://docs.pixelfed.org/technical-documentation/config/#cs_blocked_keywords +# @dottie/validate +#CS_BLOCKED_KEYWORDS="" + +# Comma-separated list of keywords to add warnings. +# +# @default null (not set/commented out). +# @see https://docs.pixelfed.org/technical-documentation/config/#cs_cw_keywords +# @dottie/validate +#CS_CW_KEYWORDS="" + +# Comma-separated list of keywords to remove from public timelines. +# +# @default null (not set/commented out). +# @see https://docs.pixelfed.org/technical-documentation/config/#cs_unlisted_keywords +# @dottie/validate +#CS_UNLISTED_KEYWORDS="" + +# @default null (not set/commented out). +# @see https://docs.pixelfed.org/technical-documentation/config/#cs_blocked_actor +# @dottie/validate +#CS_BLOCKED_ACTOR="" + +# @default null (not set/commented out). +# @see https://docs.pixelfed.org/technical-documentation/config/#cs_cw_actor +# @dottie/validate +#CS_CW_ACTOR="" + +# @default null (not set/commented out). +# @see https://docs.pixelfed.org/technical-documentation/config/#cs_unlisted_actor +# @dottie/validate +#CS_UNLISTED_ACTOR="" + +################################################################################ +# Media +################################################################################ + +# @default false +# @see https://docs.pixelfed.org/technical-documentation/config/#media_exif_database +# @dottie/validate required,boolean +MEDIA_EXIF_DATABASE="true" + +# Pixelfed supports GD or ImageMagick to process images. +# +# Possible values: +# - "gd" (default) +# - "imagick" +# +# @default "gd" +# @see https://docs.pixelfed.org/technical-documentation/config/#image_driver +# @dottie/validate required,oneof=gd imagick +#IMAGE_DRIVER="gd" + +################################################################################ +# Logging +################################################################################ + +# Possible values: +# +# - "stack" (default) +# - "single" +# - "daily" +# - "slack" +# - "stderr" +# - "syslog" +# - "errorlog" +# - "null" +# - "emergency" +# - "media" +# +# @default "stack" +# @dottie/validate required,oneof=stack single daily slack stderr syslog errorlog null emergency media +LOG_CHANNEL="stderr" + +# Used by single, stderr and syslog. +# +# @default "debug" +# @see https://docs.pixelfed.org/technical-documentation/config/#log_level +# @dottie/validate required,boolean +#LOG_LEVEL="debug" + +# Used by stderr. +# +# @default "" +# @see https://docs.pixelfed.org/technical-documentation/config/#log_stderr_formatter +# @dottie/validate required +#LOG_STDERR_FORMATTER="" + +# Used by slack. +# +# @default "" +# @see https://docs.pixelfed.org/technical-documentation/config/#log_slack_webhook_url +# @dottie/validate required,http_url +#LOG_SLACK_WEBHOOK_URL="" + +################################################################################ +# Broadcasting settings +################################################################################ + +# This option controls the default broadcaster that will be used by the framework when an event needs to be broadcast. +# +# Possible values: +# - "pusher" +# - "redis" +# - "log" +# - "null" (default) +# +# @default null +# @see https://docs.pixelfed.org/technical-documentation/config/#broadcast_driver +# @dottie/validate required,oneof=pusher redis log null +BROADCAST_DRIVER="redis" + +################################################################################ +# Sanitizing settings +################################################################################ + +# @default "true" +# @see https://docs.pixelfed.org/technical-documentation/config/#restrict_html_types +# @dottie/validate required,boolean +#RESTRICT_HTML_TYPES="true" + +################################################################################ +# Queue configuration +################################################################################ + +# Possible values: +# - "sync" (default) +# - "database" +# - "beanstalkd" +# - "sqs" +# - "redis" +# - "null" +# +# @default "sync" +# @see https://docs.pixelfed.org/technical-documentation/config/#queue_driver +# @dottie/validate required,oneof=sync database beanstalkd sqs redis null +QUEUE_DRIVER="redis" + +################################################################################ +# Queue (SQS) configuration +################################################################################ + +# @default "your-public-key" +# @see https://docs.pixelfed.org/technical-documentation/config/#sqs_key +# @dottie/validate required_if=QUEUE_DRIVER sqs +#SQS_KEY="your-public-key" + +# @default "your-secret-key" +# @see https://docs.pixelfed.org/technical-documentation/config/#sqs_secret +# @dottie/validate required_if=QUEUE_DRIVER sqs +#SQS_SECRET="your-secret-key" + +# @default "https://sqs.us-east-1.amazonaws.com/your-account-id" +# @see https://docs.pixelfed.org/technical-documentation/config/#sqs_prefix +# @dottie/validate required_if=QUEUE_DRIVER sqs +#SQS_PREFIX="" + +# @default "your-queue-name" +# @see https://docs.pixelfed.org/technical-documentation/config/#sqs_queue +# @dottie/validate required_if=QUEUE_DRIVER sqs +#SQS_QUEUE="your-queue-name" + +# @default "us-east-1" +# @see https://docs.pixelfed.org/technical-documentation/config/#sqs_region +# @dottie/validate required_if=QUEUE_DRIVER sqs +#SQS_REGION="us-east-1" + +################################################################################ +# Session configuration +################################################################################ + +# This option controls the default session “driver” that will be used on requests. +# +# By default, we will use the lightweight native driver but you may specify any of the other wonderful drivers provided here. +# +# Possible values: +# - "file" +# - "cookie" +# - "database" (default) +# - "apc" +# - "memcached" +# - "redis" +# - "array" +# +# @default "database" +# @dottie/validate required,oneof=file cookie database apc memcached redis array +SESSION_DRIVER="redis" + +# Here you may specify the number of minutes that you wish the session to be allowed to remain idle before it expires. +# +# If you want them to immediately expire on the browser closing, set that option. +# +# @default 86400. +# @see https://docs.pixelfed.org/technical-documentation/config/#session_lifetime +# @dottie/validate required,number +#SESSION_LIFETIME="86400" + +# Here you may change the domain of the cookie used to identify a session in your application. +# +# This will determine which domains the cookie is available to in your application. +# +# A sensible default has been set. +# +# @default the value of APP_DOMAIN, or null. +# @see https://docs.pixelfed.org/technical-documentation/config/#session_domain +# @dottie/validate required,domain +#SESSION_DOMAIN="${APP_DOMAIN}" + +################################################################################ +# Proxy configuration +################################################################################ + +# Set trusted proxy IP addresses. +# +# Both IPv4 and IPv6 addresses are supported, along with CIDR notation. +# +# The “*” character is syntactic sugar within TrustedProxy to trust any +# proxy that connects directly to your server, a requirement when you cannot +# know the address of your proxy (e.g. if using Rackspace balancers). +# +# The “**” character is syntactic sugar within TrustedProxy to trust not just any +# proxy that connects directly to your server, but also proxies that connect to those proxies, +# and all the way back until you reach the original source IP. It will mean that +# $request->getClientIp() always gets the originating client IP, no matter how many proxies +# that client’s request has subsequently passed through. +# +# @default "*" +# @see https://docs.pixelfed.org/technical-documentation/config/#trust_proxies +# @dottie/validate required +TRUST_PROXIES="*" + +################################################################################ +# Passport configuration +################################################################################ +# +# Passport uses encryption keys while generating secure access tokens +# for your application. +# +# By default, the keys are stored as local files but can be set via environment +# variables when that is more convenient. + +# @see https://docs.pixelfed.org/technical-documentation/config/#passport_private_key +# @dottie/validate required +#PASSPORT_PRIVATE_KEY="" + +# @see https://docs.pixelfed.org/technical-documentation/config/#passport_public_key +# @dottie/validate required +#PASSPORT_PUBLIC_KEY="" + +################################################################################ +# PHP configuration +################################################################################ + +# @default "128M" +# @see https://www.php.net/manual/en/ini.core.php#ini.memory-limit +# @dottie/validate required +#PHP_MEMORY_LIMIT="128M" + +################################################################################ +# Other configuration +################################################################################ + +# ? Add your own configuration here + +################################################################################ +# Timezone configuration +################################################################################ + +# Set timezone used by *all* containers - these must be in sync. +# +# ! Do not edit your timezone once the service is running - or things will break! +# +# @see https://www.php.net/manual/en/timezones.php +# @dottie/validate required,timezone +TZ="${APP_TIMEZONE}" + +################################################################################ +# Docker configuraton for *all* services +################################################################################ + +# Prefix for container names (without any dash at the end) +# @dottie/validate required +DOCKER_ALL_CONTAINER_NAME_PREFIX="${APP_DOMAIN}" + +# How often Docker health check should run for all services +# +# Can be overridden by individual [DOCKER_*_HEALTHCHECK_INTERVAL] settings further down +# +# @default "10s" +# @dottie/validate required +DOCKER_ALL_DEFAULT_HEALTHCHECK_INTERVAL="10s" + +# Path (relative to the docker-compose.yml) or absolute (/some/other/path) where containers will *all* data +# will be stored (data, config, overrides) +# +# @default "./docker-compose-state" +# @dottie/validate required,dir +DOCKER_ALL_HOST_ROOT_PATH="./docker-compose-state" + +# Path (relative to the docker-compose.yml) or absolute (/some/other/path) where containers will store their data +# +# @default "${DOCKER_ALL_HOST_ROOT_PATH}/data" +# @dottie/validate required,dir +DOCKER_ALL_HOST_DATA_ROOT_PATH="${DOCKER_ALL_HOST_ROOT_PATH}/data" + +# Path (relative to the docker-compose.yml) or absolute (/some/other/path) where containers will store their confguration +# +# @default "${DOCKER_ALL_HOST_ROOT_PATH}/config" +# @dottie/validate required,dir +DOCKER_ALL_HOST_CONFIG_ROOT_PATH="${DOCKER_ALL_HOST_ROOT_PATH}/config" + +# Path (relative to the docker-compose.yml) or absolute (/some/other/path) where containers will store overrides +# +# @default "${DOCKER_ALL_HOST_ROOT_PATH}/overrides" +# @dottie/validate required,dir +DOCKER_APP_HOST_OVERRIDES_PATH="${DOCKER_ALL_HOST_ROOT_PATH}/overrides" + +################################################################################ +# Docker [web] + [worker] (also know as "app") shared service configuration +################################################################################ + +# The docker tag prefix to use for pulling images, can be one of +# +# * latest +# * +# * staging +# * edge +# * branch- +# * pr- +# +# Combined with [DOCKER_APP_RUNTIME] and [PHP_VERSION] configured +# elsewhere in this file, the final Docker tag is computed. +# @dottie/validate required +DOCKER_APP_RELEASE="branch-jippi-fork" + +# The PHP version to use for [web] and [worker] container +# +# Any version published on https://hub.docker.com/_/php should work +# +# Example: +# +# * 8.1 +# * 8.2 +# * 8.2.14 +# * latest +# +# Do *NOT* use the full Docker tag (e.g. "8.3.2RC1-fpm-bullseye") +# *only* the version part. The rest of the full tag is derived from +# the [DOCKER_APP_RUNTIME] and [PHP_DEBIAN_RELEASE] settings +# @dottie/validate required +DOCKER_APP_PHP_VERSION="8.2" + +# The container runtime to use. +# +# @see https://docs.pixelfed.org/running-pixelfed/docker/runtimes.html +# @dottie/validate required,oneof=apache nginx fpm +DOCKER_APP_RUNTIME="apache" + +# The Debian release variant to use of the [php] Docker image +# +# Examlpe: [bookworm] or [bullseye] +# @dottie/validate required,oneof=bookwork bullseye +DOCKER_APP_DEBIAN_RELEASE="bullseye" + +# The [php] Docker image base type +# +# @see https://docs.pixelfed.org/running-pixelfed/docker/runtimes.html +# @dottie/validate required,oneof=apache fpm cli +DOCKER_APP_BASE_TYPE="apache" + +# Image to pull the Pixelfed Docker images from. +# +# Example values: +# +# * "ghcr.io/pixelfed/pixelfed" to pull from GitHub +# * "pixelfed/pixelfed" to pull from DockerHub +# * "your/fork" to pull from a custom fork +# +# @dottie/validate required +DOCKER_APP_IMAGE="ghcr.io/jippi/pixelfed" + +# Pixelfed version (image tag) to pull from the registry. +# +# @see https://github.com/pixelfed/pixelfed/pkgs/container/pixelfed +# @dottie/validate required +DOCKER_APP_TAG="${DOCKER_APP_RELEASE}-${DOCKER_APP_RUNTIME}-${DOCKER_APP_PHP_VERSION}" + +# Path (on host system) where the [app] + [worker] container will write +# its [storage] data (e.g uploads/images/profile pictures etc.). +# +# Path is relative (./some/other/path) to the docker-compose.yml or absolute (/some/other/path) +# @dottie/validate required,dir +DOCKER_APP_HOST_STORAGE_PATH="${DOCKER_ALL_HOST_DATA_ROOT_PATH}/pixelfed/storage" + +# Path (on host system) where the [app] + [worker] container will write +# its [cache] data. +# +# Path is relative (./some/other/path) to the docker-compose.yml or absolute (/some/other/path) +# @dottie/validate required,dir +DOCKER_APP_HOST_CACHE_PATH="${DOCKER_ALL_HOST_DATA_ROOT_PATH}/pixelfed/cache" + +# Automatically run "One-time setup tasks" commands. +# +# If you are migrating to this docker-compose setup or have manually run the "One time seutp" +# tasks (https://docs.pixelfed.org/running-pixelfed/installation/#setting-up-services) +# you can set this to "0" to prevent them from running. +# +# Otherwise, leave it at "1" to have them run *once*. +# @dottie/validate required,boolean +#DOCKER_APP_RUN_ONE_TIME_SETUP_TASKS="1" + +# A space-seperated list of paths (inside the container) to *recursively* [chown] +# to the container user/group id (UID/GID) in case of permission issues. +# +# ! You should *not* leave this on permanently, at it can significantly slow down startup +# ! time for the container, and during normal operations there should never be permission +# ! issues. Please report a bug if you see behavior requiring this to be permanently on +# +# Example: "/var/www/storage /var/www/bootstrap/cache" +# @dottie/validate required +#DOCKER_APP_ENSURE_OWNERSHIP_PATHS="" + +# Enable Docker Entrypoint debug mode (will call [set -x] in bash scripts) +# by setting this to "1" +# @dottie/validate required,boolean +#DOCKER_APP_ENTRYPOINT_DEBUG="0" + +# List of extra APT packages (separated by space) to install when building +# locally using [docker compose build]. +# +# @see https://github.com/pixelfed/pixelfed/blob/dev/docker/customizing.md +# @dottie/validate required +#DOCKER_APP_APT_PACKAGES_EXTRA="" + +# List of *extra* PECL extensions (separated by space) to install when +# building locally using [docker compose build]. +# +# @see https://github.com/pixelfed/pixelfed/blob/dev/docker/customizing.md +# @dottie/validate required +#DOCKER_APP_PHP_PECL_EXTENSIONS_EXTRA="" + +# List of *extra* PHP extensions (separated by space) to install when +# building locally using [docker compose build]. +# +# @see https://github.com/pixelfed/pixelfed/blob/dev/docker/customizing.md +# @dottie/validate required +#DOCKER_APP_PHP_EXTENSIONS_EXTRA="" + +################################################################################ +# Docker [redis] service configuration +################################################################################ + +# Redis version to use as Docker tag +# +# @see https://hub.docker.com/_/redis +# @dottie/validate required +DOCKER_REDIS_VERSION="7.2" + +# Path (on host system) where the [redis] container will store its data +# +# Path is relative (./some/other/path) to the docker-compose.yml or absolute (/some/other/path) +# @dottie/validate required,dir +DOCKER_REDIS_HOST_DATA_PATH="${DOCKER_ALL_HOST_DATA_ROOT_PATH}/redis" + +# Port that Redis will listen on *outside* the container (e.g. the host machine) +# @dottie/validate required,number +DOCKER_REDIS_HOST_PORT="${REDIS_PORT}" + +# The filename that Redis should store its config file within +# +# NOTE: The file *MUST* exists (even empty) before enabling this setting! +# +# Use a command like [touch "${DOCKER_ALL_HOST_CONFIG_ROOT_PATH}/redis/redis.conf"] to create it. +# +# @default "" +# @dottie/validate required +#DOCKER_REDIS_CONFIG_FILE="/etc/redis/redis.conf" + +# How often Docker health check should run for [redis] service +# +# @default "10s" +# @dottie/validate required +DOCKER_REDIS_HEALTHCHECK_INTERVAL="${DOCKER_ALL_DEFAULT_HEALTHCHECK_INTERVAL}" + +################################################################################ +# Docker [db] service configuration +################################################################################ + +# Set this to a non-empty value (e.g. "disabled") to disable the [db] service +#DOCKER_DB_PROFILE="" + +# Path (on host system) where the [db] container will store its data +# +# Path is relative (./some/other/path) to the docker-compose.yml or absolute (/some/other/path) +# @dottie/validate required,dir +DOCKER_DB_HOST_DATA_PATH="${DOCKER_ALL_HOST_DATA_ROOT_PATH}/db" + +# Port that the database will listen on *outside* the container (e.g. the host machine) +# +# Use "3306" for MySQL/MariaDB and "5432" for PostgreeSQL +# @dottie/validate required,number +DOCKER_DB_HOST_PORT="${DB_PORT}" + +# How often Docker health check should run for [db] service +# @dottie/validate required +DOCKER_DB_HEALTHCHECK_INTERVAL="${DOCKER_ALL_DEFAULT_HEALTHCHECK_INTERVAL}" + +################################################################################ +# Docker [web] service configuration +################################################################################ + +# Set this to a non-empty value (e.g. "disabled") to disable the [web] service +# @dottie/validate required +#DOCKER_WEB_PROFILE="" + +# Port to expose [web] container will listen on *outside* the container (e.g. the host machine) for *HTTP* traffic only +# @dottie/validate required,number +DOCKER_WEB_PORT_EXTERNAL_HTTP="8080" + +# How often Docker health check should run for [web] service +# @dottie/validate required +DOCKER_WEB_HEALTHCHECK_INTERVAL="${DOCKER_ALL_DEFAULT_HEALTHCHECK_INTERVAL}" + +################################################################################ +# Docker [worker] service configuration +################################################################################ + +# Set this to a non-empty value (e.g. "disabled") to disable the [worker] service +# @dottie/validate required +#DOCKER_WORKER_PROFILE="" + +# How often Docker health check should run for [worker] service +# @dottie/validate required +DOCKER_WORKER_HEALTHCHECK_INTERVAL="${DOCKER_ALL_DEFAULT_HEALTHCHECK_INTERVAL}" + +################################################################################ +# Docker [proxy] + [proxy-acme] service configuration +################################################################################ + +# Set this to a non-empty value (e.g. "disabled") to disable the [proxy] and [proxy-acme] service +DOCKER_PROXY_PROFILE="" + +# Set this to a non-empty value (e.g. "disabled") to disable the [proxy-acme] service +DOCKER_PROXY_ACME_PROFILE="${DOCKER_PROXY_PROFILE}" + +# How often Docker health check should run for [proxy] service +# @dottie/validate required +DOCKER_PROXY_HEALTHCHECK_INTERVAL="${DOCKER_ALL_DEFAULT_HEALTHCHECK_INTERVAL}" + +# Port that the [proxy] will listen on *outside* the container (e.g. the host machine) for HTTP traffic +# @dottie/validate required,number +DOCKER_PROXY_HOST_PORT_HTTP="80" + +# Port that the [proxy] will listen on *outside* the container (e.g. the host machine) for HTTPS traffic +# @dottie/validate required,number +DOCKER_PROXY_HOST_PORT_HTTPS="443" + +# Path to the Docker socket on the *host* +# @dottie/validate required,file +DOCKER_PROXY_HOST_DOCKER_SOCKET_PATH="/var/run/docker.sock" + +################################################################################ +# +################################################################################ + +# ! ---------------------------------------------------------------------------- +# ! STOP STOP STOP STOP STOP STOP STOP STOP STOP STOP STOP STOP STOP STOP STOP +# ! ---------------------------------------------------------------------------- +# ! Below this line is default environment variables for various [db] backends +# ! You very likely do *NOT* need to modify any of this, ever. +# ! ---------------------------------------------------------------------------- + +################################################################################ +# Docker [db] service environment variables for MySQL (Oracle) +################################################################################ + +# See "Environment Variables" at https://hub.docker.com/_/mysql +# +# ! DO NOT CHANGE unless you know what you are doing + +MYSQL_ROOT_PASSWORD="${DB_PASSWORD}" +MYSQL_USER="${DB_USERNAME}" +MYSQL_PASSWORD="${DB_PASSWORD}" +MYSQL_DATABASE="${DB_DATABASE}" + +################################################################################ +# Docker [db] service environment variables for MySQL (MariaDB) +################################################################################ + +# See "Start a mariadb server instance with user, password and database" +# at https://hub.docker.com/_/mariadb +# +# ! DO NOT CHANGE unless you know what you are doing + +MARIADB_ROOT_PASSWORD="${DB_PASSWORD}" +MARIADB_USER="${DB_USERNAME}" +MARIADB_PASSWORD="${DB_PASSWORD}" +MARIADB_DATABASE="${DB_DATABASE}" + +################################################################################ +# Docker [db] service environment variables for PostgreSQL +################################################################################ + +# See "Environment Variables" at https://hub.docker.com/_/postgres +# +# ! DO NOT CHANGE unless you know what you are doing + +POSTGRES_USER="${DB_USERNAME}" +POSTGRES_PASSWORD="${DB_PASSWORD}" +POSTGRES_DB="${DB_DATABASE}" diff --git a/pkg/render/test-fixtures/formatter/single-pair.input.env b/pkg/render/test-fixtures/formatter/single-pair.input.env new file mode 100644 index 0000000..acd56aa --- /dev/null +++ b/pkg/render/test-fixtures/formatter/single-pair.input.env @@ -0,0 +1 @@ +KEY=VALUE diff --git a/pkg/render/test-fixtures/formatter/two-pairs-newlines.golden.env b/pkg/render/test-fixtures/formatter/two-pairs-newlines.golden.env new file mode 100644 index 0000000..0ee7e13 --- /dev/null +++ b/pkg/render/test-fixtures/formatter/two-pairs-newlines.golden.env @@ -0,0 +1,2 @@ +KEY=VALUE +FOO=baz diff --git a/pkg/render/test-fixtures/formatter/two-pairs-newlines.input.env b/pkg/render/test-fixtures/formatter/two-pairs-newlines.input.env new file mode 100644 index 0000000..ec12fc8 --- /dev/null +++ b/pkg/render/test-fixtures/formatter/two-pairs-newlines.input.env @@ -0,0 +1,5 @@ +KEY=VALUE + + + +FOO=baz \ No newline at end of file diff --git a/pkg/render/test-fixtures/formatter/two-pairs.input.env b/pkg/render/test-fixtures/formatter/two-pairs.input.env new file mode 100644 index 0000000..0ee7e13 --- /dev/null +++ b/pkg/render/test-fixtures/formatter/two-pairs.input.env @@ -0,0 +1,2 @@ +KEY=VALUE +FOO=baz From ab3580aa1b582d262746fc1b153af0ce20a64d03 Mon Sep 17 00:00:00 2001 From: Christian Winther Date: Thu, 15 Feb 2024 15:36:57 +0100 Subject: [PATCH 07/33] disable linting for forcetypeassert for context calls --- pkg/tui/context.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/tui/context.go b/pkg/tui/context.go index e22e548..5a03487 100644 --- a/pkg/tui/context.go +++ b/pkg/tui/context.go @@ -20,13 +20,13 @@ func CreateContext(ctx context.Context, stdout, stderr io.Writer) context.Contex } func FromContext(ctx context.Context, key printerContextKey) ThemePrinter { - return ctx.Value(key).(ThemePrinter) + return ctx.Value(key).(ThemePrinter) //nolint:forcetypeassert } func ColorFromContext(ctx context.Context, key printerContextKey, color colorType) Printer { - return ctx.Value(key).(ThemePrinter).Color(color) + return ctx.Value(key).(ThemePrinter).Color(color) //nolint:forcetypeassert } func PrintersFromContext(ctx context.Context) (ThemePrinter, ThemePrinter) { - return ctx.Value(Stdout).(ThemePrinter), ctx.Value(Stderr).(ThemePrinter) + return ctx.Value(Stdout).(ThemePrinter), ctx.Value(Stderr).(ThemePrinter) //nolint:forcetypeassert } From 8f55e8b3769bbd4bdd546af6780ca7ae2665b00d Mon Sep 17 00:00:00 2001 From: Christian Winther Date: Thu, 15 Feb 2024 16:14:03 +0100 Subject: [PATCH 08/33] fix tests --- pkg/render/render_test.go | 11 +++++++---- ...{comment-spacing.input.env => comment-spacing.env} | 0 .../{compressed.input.env => compressed.env} | 0 .../formatter/{empty.input.env => empty.env} | 0 .../{just-a-comment.input.env => just-a-comment.env} | 0 .../{pixelfed-full.input.env => pixelfed-full.env} | 0 .../{single-pair.input.env => single-pair.env} | 0 ...airs-newlines.input.env => two-pairs-newlines.env} | 0 .../{two-pairs-newlines.golden.env => two-pairs.env} | 0 .../test-fixtures/formatter/two-pairs.input.env | 2 -- 10 files changed, 7 insertions(+), 6 deletions(-) rename pkg/render/test-fixtures/formatter/{comment-spacing.input.env => comment-spacing.env} (100%) rename pkg/render/test-fixtures/formatter/{compressed.input.env => compressed.env} (100%) rename pkg/render/test-fixtures/formatter/{empty.input.env => empty.env} (100%) rename pkg/render/test-fixtures/formatter/{just-a-comment.input.env => just-a-comment.env} (100%) rename pkg/render/test-fixtures/formatter/{pixelfed-full.input.env => pixelfed-full.env} (100%) rename pkg/render/test-fixtures/formatter/{single-pair.input.env => single-pair.env} (100%) rename pkg/render/test-fixtures/formatter/{two-pairs-newlines.input.env => two-pairs-newlines.env} (100%) rename pkg/render/test-fixtures/formatter/{two-pairs-newlines.golden.env => two-pairs.env} (100%) delete mode 100644 pkg/render/test-fixtures/formatter/two-pairs.input.env diff --git a/pkg/render/render_test.go b/pkg/render/render_test.go index 03d99b1..b553e1a 100644 --- a/pkg/render/render_test.go +++ b/pkg/render/render_test.go @@ -36,9 +36,13 @@ func TestFormatter(t *testing.T) { } for _, file := range files { + if file.IsDir() { + continue + } + switch { - case strings.HasSuffix(file.Name(), ".input.env"): - testName := strings.TrimSuffix(file.Name(), ".input.env") + case strings.HasSuffix(file.Name(), ".env"): + testName := strings.TrimSuffix(file.Name(), ".env") test := testData{ name: testName, @@ -46,9 +50,8 @@ func TestFormatter(t *testing.T) { } tests = append(tests, test) - case strings.HasSuffix(file.Name(), ".golden.env"): default: - panic("unexpected file") + require.FailNow(t, "unexpected test file: ["+file.Name()+"]") } } diff --git a/pkg/render/test-fixtures/formatter/comment-spacing.input.env b/pkg/render/test-fixtures/formatter/comment-spacing.env similarity index 100% rename from pkg/render/test-fixtures/formatter/comment-spacing.input.env rename to pkg/render/test-fixtures/formatter/comment-spacing.env diff --git a/pkg/render/test-fixtures/formatter/compressed.input.env b/pkg/render/test-fixtures/formatter/compressed.env similarity index 100% rename from pkg/render/test-fixtures/formatter/compressed.input.env rename to pkg/render/test-fixtures/formatter/compressed.env diff --git a/pkg/render/test-fixtures/formatter/empty.input.env b/pkg/render/test-fixtures/formatter/empty.env similarity index 100% rename from pkg/render/test-fixtures/formatter/empty.input.env rename to pkg/render/test-fixtures/formatter/empty.env diff --git a/pkg/render/test-fixtures/formatter/just-a-comment.input.env b/pkg/render/test-fixtures/formatter/just-a-comment.env similarity index 100% rename from pkg/render/test-fixtures/formatter/just-a-comment.input.env rename to pkg/render/test-fixtures/formatter/just-a-comment.env diff --git a/pkg/render/test-fixtures/formatter/pixelfed-full.input.env b/pkg/render/test-fixtures/formatter/pixelfed-full.env similarity index 100% rename from pkg/render/test-fixtures/formatter/pixelfed-full.input.env rename to pkg/render/test-fixtures/formatter/pixelfed-full.env diff --git a/pkg/render/test-fixtures/formatter/single-pair.input.env b/pkg/render/test-fixtures/formatter/single-pair.env similarity index 100% rename from pkg/render/test-fixtures/formatter/single-pair.input.env rename to pkg/render/test-fixtures/formatter/single-pair.env diff --git a/pkg/render/test-fixtures/formatter/two-pairs-newlines.input.env b/pkg/render/test-fixtures/formatter/two-pairs-newlines.env similarity index 100% rename from pkg/render/test-fixtures/formatter/two-pairs-newlines.input.env rename to pkg/render/test-fixtures/formatter/two-pairs-newlines.env diff --git a/pkg/render/test-fixtures/formatter/two-pairs-newlines.golden.env b/pkg/render/test-fixtures/formatter/two-pairs.env similarity index 100% rename from pkg/render/test-fixtures/formatter/two-pairs-newlines.golden.env rename to pkg/render/test-fixtures/formatter/two-pairs.env diff --git a/pkg/render/test-fixtures/formatter/two-pairs.input.env b/pkg/render/test-fixtures/formatter/two-pairs.input.env deleted file mode 100644 index 0ee7e13..0000000 --- a/pkg/render/test-fixtures/formatter/two-pairs.input.env +++ /dev/null @@ -1,2 +0,0 @@ -KEY=VALUE -FOO=baz From 31a4a4f28e682a286efb2c68fabaf37719e88dfc Mon Sep 17 00:00:00 2001 From: Christian Winther Date: Thu, 15 Feb 2024 16:25:48 +0100 Subject: [PATCH 09/33] rename handler --- cmd/print/tests/full.run | 2 +- cmd/print/tests/full/stderr.golden | 2 +- cmd/print/tests/full/stdout.golden | 2 +- cmd/print/tests/simple-pretty.env | 2 -- cmd/print/tests/simple-pretty.run | 2 +- cmd/print/tests/simple-pretty/stderr.golden | 2 +- cmd/print/tests/simple-pretty/stdout.golden | 13 ++++++++----- .../{render_formatter.go => handler_formatter.go} | 0 8 files changed, 13 insertions(+), 12 deletions(-) rename pkg/render/{render_formatter.go => handler_formatter.go} (100%) diff --git a/cmd/print/tests/full.run b/cmd/print/tests/full.run index d79b3c5..13e33d6 100644 --- a/cmd/print/tests/full.run +++ b/cmd/print/tests/full.run @@ -1 +1 @@ ---pretty --no-color +--no-color --pretty diff --git a/cmd/print/tests/full/stderr.golden b/cmd/print/tests/full/stderr.golden index 759af6a..cb65407 100644 --- a/cmd/print/tests/full/stderr.golden +++ b/cmd/print/tests/full/stderr.golden @@ -1 +1 @@ ----- exec command line 0: [print --pretty --no-color] +---- exec command line 0: [print --no-color --pretty] diff --git a/cmd/print/tests/full/stdout.golden b/cmd/print/tests/full/stdout.golden index a5124d7..d1e7675 100644 --- a/cmd/print/tests/full/stdout.golden +++ b/cmd/print/tests/full/stdout.golden @@ -1,4 +1,4 @@ ----- exec command line 0: [print --pretty --no-color] +---- exec command line 0: [print --no-color --pretty] ################################################################################ # My first group ################################################################################ diff --git a/cmd/print/tests/simple-pretty.env b/cmd/print/tests/simple-pretty.env index 710f007..d10d7cd 100644 --- a/cmd/print/tests/simple-pretty.env +++ b/cmd/print/tests/simple-pretty.env @@ -1,6 +1,4 @@ KEY_A="I'm key A" - # Comment for KEY_B KEY_B="I'm key B" - KEY_C="I'm key C" diff --git a/cmd/print/tests/simple-pretty.run b/cmd/print/tests/simple-pretty.run index 6150730..13e33d6 100644 --- a/cmd/print/tests/simple-pretty.run +++ b/cmd/print/tests/simple-pretty.run @@ -1 +1 @@ ---no-color +--no-color --pretty diff --git a/cmd/print/tests/simple-pretty/stderr.golden b/cmd/print/tests/simple-pretty/stderr.golden index a1e5768..cb65407 100644 --- a/cmd/print/tests/simple-pretty/stderr.golden +++ b/cmd/print/tests/simple-pretty/stderr.golden @@ -1 +1 @@ ----- exec command line 0: [print --no-color] +---- exec command line 0: [print --no-color --pretty] diff --git a/cmd/print/tests/simple-pretty/stdout.golden b/cmd/print/tests/simple-pretty/stdout.golden index b2455b0..f7ab2c4 100644 --- a/cmd/print/tests/simple-pretty/stdout.golden +++ b/cmd/print/tests/simple-pretty/stdout.golden @@ -1,5 +1,8 @@ ----- exec command line 0: [print --no-color] -KEY_A="I'm key A" -KEY_B="I'm key B" -KEY_C="I'm key C" - +---- exec command line 0: [print --no-color --pretty] +KEY_A="I'm key A" + +# Comment for KEY_B +KEY_B="I'm key B" + +KEY_C="I'm key C" + diff --git a/pkg/render/render_formatter.go b/pkg/render/handler_formatter.go similarity index 100% rename from pkg/render/render_formatter.go rename to pkg/render/handler_formatter.go From 34c911105a3b72bfe4f40786ccfb79184194c582 Mon Sep 17 00:00:00 2001 From: Christian Winther Date: Thu, 15 Feb 2024 18:22:09 +0100 Subject: [PATCH 10/33] tui rework --- cmd/disable/disable.go | 2 +- cmd/enable/enable.go | 2 +- cmd/fmt/fmt.go | 4 +- cmd/groups/groups.go | 2 +- cmd/print/print.go | 9 ++- cmd/print/tests/full/stdout.golden | 50 +++++++-------- cmd/print/tests/simple-pretty/stdout.golden | 12 ++-- cmd/print/tests/simple/stdout.golden | 2 +- cmd/print/tests/specific-group/stdout.golden | 2 +- cmd/root.go | 2 +- cmd/set/set.go | 6 +- cmd/update/update.go | 25 ++++---- cmd/validate/validate.go | 29 +++++---- cmd/value/value.go | 8 ++- go.mod | 1 - go.sum | 2 - pkg/ast/upsert/upsert.go | 13 ++-- pkg/cli/shared/complete.go | 2 +- pkg/file.go | 5 +- pkg/render/handler.go | 4 +- pkg/render/handler_filters.go | 4 +- pkg/render/handler_formatter.go | 8 ++- pkg/render/output.go | 14 +++-- pkg/render/output_colorized.go | 27 ++++---- pkg/render/output_completion_keys.go | 12 ++-- pkg/render/output_plain.go | 9 +-- pkg/render/render.go | 47 +++++++------- pkg/render/render_test.go | 3 +- pkg/tui/box.go | 15 +++++ pkg/tui/color.go | 39 +++++------- pkg/tui/context.go | 23 +++++-- pkg/tui/printer.go | 54 +++++----------- pkg/tui/theme.go | 65 ++++++++++---------- pkg/validation/explain.go | 58 +++++++++-------- pkg/validation/validation.go | 8 ++- 35 files changed, 303 insertions(+), 265 deletions(-) create mode 100644 pkg/tui/box.go diff --git a/cmd/disable/disable.go b/cmd/disable/disable.go index a5a704d..7125931 100644 --- a/cmd/disable/disable.go +++ b/cmd/disable/disable.go @@ -46,7 +46,7 @@ func NewCommand() *cobra.Command { existing.Disable() - if err := pkg.Save(filename, env); err != nil { + if err := pkg.Save(cmd.Context(), filename, env); err != nil { return fmt.Errorf("could not save file: %w", err) } diff --git a/cmd/enable/enable.go b/cmd/enable/enable.go index 4a2059b..257051a 100644 --- a/cmd/enable/enable.go +++ b/cmd/enable/enable.go @@ -44,7 +44,7 @@ func NewCommand() *cobra.Command { existing.Enable() - if err := pkg.Save(filename, env); err != nil { + if err := pkg.Save(cmd.Context(), filename, env); err != nil { return fmt.Errorf("could not save file: %w", err) } diff --git a/cmd/fmt/fmt.go b/cmd/fmt/fmt.go index d7bdc98..74f18f8 100644 --- a/cmd/fmt/fmt.go +++ b/cmd/fmt/fmt.go @@ -19,11 +19,11 @@ func NewCommand() *cobra.Command { return err } - if err := pkg.Save(filename, env); err != nil { + if err := pkg.Save(cmd.Context(), filename, env); err != nil { return err } - tui.Theme.Success.StdoutPrinter().Printfln("File [%s] was successfully formatted", filename) + tui.ColorPrinterFromContext(cmd.Context(), tui.Stdout, tui.Success).Printfln("File [%s] was successfully formatted", filename) return nil }, diff --git a/cmd/groups/groups.go b/cmd/groups/groups.go index 7c469d8..9de7ca1 100644 --- a/cmd/groups/groups.go +++ b/cmd/groups/groups.go @@ -31,7 +31,7 @@ func NewCommand() *cobra.Command { width := longesGroupName(groups) - stdout := tui.FromContext(cmd.Context(), tui.Stdout) + stdout := tui.PrinterFromContext(cmd.Context(), tui.Stdout) secondary := stdout.Color(tui.Secondary) primary := stdout.Color(tui.Primary) diff --git a/cmd/print/print.go b/cmd/print/print.go index e3e42f0..b04bf00 100644 --- a/cmd/print/print.go +++ b/cmd/print/print.go @@ -19,15 +19,16 @@ func NewCommand() *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { env, settings, warnings, err := setup(cmd.Flags()) if warnings != nil { - tui.ColorFromContext(cmd.Context(), tui.Stderr, tui.Warning).Printfln("%+v", warnings) + tui.ColorPrinterFromContext(cmd.Context(), tui.Stderr, tui.Warning).Printfln("%+v", warnings) } if err != nil { return err } + // fmt.Fprintln(cmd.OutOrStdout(), render.NewRenderer(*settings).Statement(env).String()) tui. - ColorFromContext(cmd.Context(), tui.Stdout, tui.Neutral). - Println(render.NewRenderer(*settings).Statement(env).String()) + ColorPrinterFromContext(cmd.Context(), tui.Stdout, tui.NoColor). + Println(render.NewRenderer(*settings).Statement(cmd.Context(), env).String()) return nil }, @@ -95,5 +96,7 @@ func setup(flags *pflag.FlagSet) (*ast.Document, *render.Settings, error, error) settings.Apply(render.WithFormattedOutput(true)) } + settings.Apply(render.WithColors(false)) + return doc, settings, allWarnings, allErrors } diff --git a/cmd/print/tests/full/stdout.golden b/cmd/print/tests/full/stdout.golden index d1e7675..42a2e27 100644 --- a/cmd/print/tests/full/stdout.golden +++ b/cmd/print/tests/full/stdout.golden @@ -1,32 +1,32 @@ ---- exec command line 0: [print --no-color --pretty] ################################################################################ -# My first group +# My first group ################################################################################ - -KEY_A="I'm key A" - -# Comment for KEY_B -KEY_B="I'm key B" - -KEY_C="I'm key C" - + +KEY_A="I'm key A" + +# Comment for KEY_B +KEY_B="I'm key B" + +KEY_C="I'm key C" + ################################################################################ -# My Second group +# My Second group ################################################################################ - -GROUP_TWO_A="hello" - -# This is nice, format me -GROUP_TWO_A="WORLD" - + +GROUP_TWO_A="hello" + +# This is nice, format me +GROUP_TWO_A="WORLD" + ################################################################################ -# My Third group +# My Third group ################################################################################ - -# A Comment - -# Another comment -# with two lines - -SECRET=KEY - + +# A Comment + +# Another comment +# with two lines + +SECRET=KEY + diff --git a/cmd/print/tests/simple-pretty/stdout.golden b/cmd/print/tests/simple-pretty/stdout.golden index f7ab2c4..cc9e540 100644 --- a/cmd/print/tests/simple-pretty/stdout.golden +++ b/cmd/print/tests/simple-pretty/stdout.golden @@ -1,8 +1,8 @@ ---- exec command line 0: [print --no-color --pretty] -KEY_A="I'm key A" - +KEY_A="I'm key A" + # Comment for KEY_B -KEY_B="I'm key B" - -KEY_C="I'm key C" - +KEY_B="I'm key B" + +KEY_C="I'm key C" + diff --git a/cmd/print/tests/simple/stdout.golden b/cmd/print/tests/simple/stdout.golden index b2455b0..c650e31 100644 --- a/cmd/print/tests/simple/stdout.golden +++ b/cmd/print/tests/simple/stdout.golden @@ -2,4 +2,4 @@ KEY_A="I'm key A" KEY_B="I'm key B" KEY_C="I'm key C" - + diff --git a/cmd/print/tests/specific-group/stdout.golden b/cmd/print/tests/specific-group/stdout.golden index d117e0c..ac0287b 100644 --- a/cmd/print/tests/specific-group/stdout.golden +++ b/cmd/print/tests/specific-group/stdout.golden @@ -2,4 +2,4 @@ KEY_A="I'm key A" KEY_B="I'm key B" KEY_C="I'm key C" - + diff --git a/cmd/root.go b/cmd/root.go index 9d8377b..83c78a0 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -77,7 +77,7 @@ func RunCommand(ctx context.Context, args []string, stdout io.Writer, stderr io. command, err := root.ExecuteC() if err != nil { - stderr := tui.FromContext(ctx, tui.Stderr) + stderr := tui.PrinterFromContext(ctx, tui.Stderr) stderr.Color(tui.Danger).Copy(tui.WithEmphasis(true)).Printfln("%s %+v", command.ErrPrefix(), err) stderr.Color(tui.Info).Printfln("Run '%v --help' for usage.", command.CommandPath()) } diff --git a/cmd/set/set.go b/cmd/set/set.go index c7b841e..2d2c0e5 100644 --- a/cmd/set/set.go +++ b/cmd/set/set.go @@ -115,14 +115,14 @@ func runE(cmd *cobra.Command, args []string) error { // Upsert the assignment // - assignment, warnings, err := upserter.Upsert(assignment) + assignment, warnings, err := upserter.Upsert(cmd.Context(), assignment) if warnings != nil { stderr.Color(tui.Warning).Println("WARNING:", warnings) } if err != nil { z := validation.NewError(assignment, err) - stderr.Color(tui.Neutral).Println(validation.Explain(document, z, z, false, true)) + stderr.Color(tui.NoColor).Println(validation.Explain(cmd.Context(), document, z, z, false, true)) if shared.BoolWithInverseValue(cmd.Flags(), "validate") { allErrors = multierr.Append(allErrors, err) @@ -142,7 +142,7 @@ func runE(cmd *cobra.Command, args []string) error { // Save file // - if err := pkg.Save(shared.StringFlag(cmd.Flags(), "file"), document); err != nil { + if err := pkg.Save(cmd.Context(), shared.StringFlag(cmd.Flags(), "file"), document); err != nil { return fmt.Errorf("failed to save file: %w", err) } diff --git a/cmd/update/update.go b/cmd/update/update.go index 492f79a..d8f9aa4 100644 --- a/cmd/update/update.go +++ b/cmd/update/update.go @@ -40,12 +40,15 @@ func runE(cmd *cobra.Command, args []string) error { return err } - dark := tui.Theme.Dark.StdoutPrinter() - info := tui.Theme.Info.StdoutPrinter() - danger := tui.Theme.Danger.StdoutPrinter() - dangerEmphasis := tui.Theme.Danger.StdoutPrinter(tui.WithEmphasis(true)) - success := tui.Theme.Success.StdoutPrinter() - primary := tui.Theme.Primary.StdoutPrinter() + stdout, stderr := tui.PrintersFromContext(cmd.Context()) + + dark := stdout.Color(tui.Dark) + info := stdout.Color(tui.Info) + danger := stdout.Color(tui.Danger) + dangerEmphasis := stdout.Color(tui.Danger).Copy(tui.WithEmphasis(true)) + success := stdout.Color(tui.Success) + primary := stdout.Color(tui.Primary) + warningStderr := stderr.Color(tui.Warning) info.Box("Starting update of " + filename + " from upstream") info.Println() @@ -175,9 +178,9 @@ func runE(cmd *cobra.Command, args []string) error { } } - changed, warn, err := upserter.Upsert(originalStatement) + changed, warn, err := upserter.Upsert(cmd.Context(), originalStatement) if warn != nil { - tui.Theme.Warning.StderrPrinter().Println(warn) + warningStderr.Println(warn) } if err != nil { @@ -201,7 +204,7 @@ func runE(cmd *cobra.Command, args []string) error { continue } - if errors := validation.ValidateSingleAssignment(originalEnv, originalStatement, nil, []string{"file", "dir"}); len(errors) > 0 { + if errors := validation.ValidateSingleAssignment(cmd.Context(), originalEnv, originalStatement, nil, []string{"file", "dir"}); len(errors) > 0 { sawError = true lastWasError = true @@ -216,7 +219,7 @@ func runE(cmd *cobra.Command, args []string) error { dark.Println(" due to validation error:") for _, errIsh := range errors { - danger.Println(" ", strings.Repeat(" ", len(originalStatement.Name)), strings.TrimSpace(validation.Explain(originalEnv, errIsh, errIsh, false, false))) + danger.Println(" ", strings.Repeat(" ", len(originalStatement.Name)), strings.TrimSpace(validation.Explain(cmd.Context(), originalEnv, errIsh, errIsh, false, false))) } counter++ @@ -262,7 +265,7 @@ func runE(cmd *cobra.Command, args []string) error { dark.Println("Saving the new", primary.Sprint(filename)) - if err := pkg.Save(filename, sourceDoc); err != nil { + if err := pkg.Save(cmd.Context(), filename, sourceDoc); err != nil { danger.Println(" ERROR", err.Error()) return err diff --git a/cmd/validate/validate.go b/cmd/validate/validate.go index ea69633..878a0f3 100644 --- a/cmd/validate/validate.go +++ b/cmd/validate/validate.go @@ -52,6 +52,8 @@ func runE(cmd *cobra.Command, args []string) error { handlers = append(handlers, render.ExcludeKeyPrefix(filter)) } + stderr := tui.PrinterFromContext(cmd.Context(), tui.Stderr) + // // Interpolate // @@ -59,7 +61,7 @@ func runE(cmd *cobra.Command, args []string) error { warn, err := env.InterpolateAll() if warn != nil { - tui.Theme.Warning.StderrPrinter().Printfln("%+v", warn) + stderr.Color(tui.Warning).Printfln("%+v", warn) } if err != nil { @@ -70,19 +72,19 @@ func runE(cmd *cobra.Command, args []string) error { // Validate // - res := validation.Validate(env, handlers, ignoreRules) + res := validation.Validate(cmd.Context(), env, handlers, ignoreRules) if len(res) == 0 { - tui.Theme.Success.StderrPrinter().Box("No validation errors found") + stderr.Color(tui.Success).Box("No validation errors found") return nil } - stderr := tui.Theme.Danger.StderrPrinter() - stderr.Box(fmt.Sprintf("%d validation errors found", len(res))) - stderr.Println() + danger := stderr.Color(tui.Danger) + danger.Box(fmt.Sprintf("%d validation errors found", len(res))) + danger.Println() for _, errIsh := range res { - fmt.Fprintln(os.Stderr, validation.Explain(env, errIsh, errIsh, fix, true)) + fmt.Fprintln(os.Stderr, validation.Explain(cmd.Context(), env, errIsh, errIsh, fix, true)) } // @@ -94,19 +96,20 @@ func runE(cmd *cobra.Command, args []string) error { return fmt.Errorf("failed to reload .env file: %w", err) } - newRes := validation.Validate(env, handlers, ignoreRules) + newRes := validation.Validate(cmd.Context(), env, handlers, ignoreRules) if len(newRes) == 0 { - tui.Theme.Success.StderrPrinter().Println("All validation errors fixed") + stderr.Color(tui.Success).Println("All validation errors fixed") return nil } diff := len(res) - len(newRes) if diff > 0 { - tui.Theme.Warning.StderrPrinter().Box( - fmt.Sprintf("%d validation errors left", len(newRes)), - tui.Theme.Success.StderrPrinter().Sprintf("%d validation errors was fixed", diff), - ) + stderr.Color(tui.Warning). + Box( + fmt.Sprintf("%d validation errors left", len(newRes)), + stderr.Color(tui.Success).Sprintf("%d validation errors was fixed", diff), + ) } return errors.New("Validation failed") diff --git a/cmd/value/value.go b/cmd/value/value.go index 593634a..3ab6596 100644 --- a/cmd/value/value.go +++ b/cmd/value/value.go @@ -42,13 +42,17 @@ func NewCommand() *cobra.Command { warn, err := env.InterpolateStatement(existing) if warn != nil { - tui.Theme.Warning.StderrPrinter().Printfln("%+v", warn) + tui. + ColorPrinterFromContext(cmd.Context(), tui.Stderr, tui.Warning). + Printfln("%+v", warn) } if err != nil { return err } - fmt.Println(existing.Interpolated) + tui. + ColorPrinterFromContext(cmd.Context(), tui.Stdout, tui.NoColor). + Println(existing.Interpolated) return nil }, diff --git a/go.mod b/go.mod index 5eb47ed..57d31a6 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,6 @@ require ( github.com/charmbracelet/huh v0.3.0 github.com/charmbracelet/lipgloss v0.9.1 github.com/davecgh/go-spew v1.1.1 - github.com/erikgeiser/promptkit v0.9.0 github.com/go-playground/validator/v10 v10.17.0 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/gosimple/slug v1.13.1 diff --git a/go.sum b/go.sum index a71c89c..0a130db 100644 --- a/go.sum +++ b/go.sum @@ -242,8 +242,6 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/erikgeiser/promptkit v0.9.0 h1:3qL1mS/ntCrXdb8sTP/ka82CJ9kEQaGuYXNrYJkWYBc= -github.com/erikgeiser/promptkit v0.9.0/go.mod h1:pU9dtogSe3Jlc2AY77EP7R4WFP/vgD4v+iImC83KsCo= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= diff --git a/pkg/ast/upsert/upsert.go b/pkg/ast/upsert/upsert.go index a24ecc7..27ea1cc 100644 --- a/pkg/ast/upsert/upsert.go +++ b/pkg/ast/upsert/upsert.go @@ -1,6 +1,7 @@ package upsert import ( + "context" "fmt" "slices" @@ -50,7 +51,7 @@ func (u *Upserter) ApplyOptions(options ...Option) error { } // Upsert will, depending on its options, either Update or Insert (thus, "[Up]date + In[sert]"). -func (u *Upserter) Upsert(input *ast.Assignment) (*ast.Assignment, error, error) { +func (u *Upserter) Upsert(ctx context.Context, input *ast.Assignment) (*ast.Assignment, error, error) { assignment := u.document.Get(input.Name) found := assignment != nil @@ -78,7 +79,7 @@ func (u *Upserter) Upsert(input *ast.Assignment) (*ast.Assignment, error, error) var err error // Create and insert the (*ast.Assignment) into the Statement list - assignment, err = u.createAndInsert(input) + assignment, err = u.createAndInsert(ctx, input) if err != nil { return nil, nil, err } @@ -100,7 +101,7 @@ func (u *Upserter) Upsert(input *ast.Assignment) (*ast.Assignment, error, error) ) // Render and parse back the Statement to ensure annotations and such are properly handled - tempDoc, err = parser.New(scanner.New(render.NewFormatter().Statement(assignment).String()), "-").Parse() + tempDoc, err = parser.New(scanner.New(render.NewFormatter().Statement(ctx, assignment).String()), "-").Parse() if err != nil { return nil, nil, fmt.Errorf("failed to parse assignment: %w", err) } @@ -130,7 +131,7 @@ func (u *Upserter) Upsert(input *ast.Assignment) (*ast.Assignment, error, error) // Validate if u.settings.Has(Validate) { - if validationErrors := validation.ValidateSingleAssignment(u.document, assignment, nil, nil); len(validationErrors) > 0 { + if validationErrors := validation.ValidateSingleAssignment(ctx, u.document, assignment, nil, nil); len(validationErrors) > 0 { var errorCollection error for _, err := range validationErrors { @@ -144,7 +145,7 @@ func (u *Upserter) Upsert(input *ast.Assignment) (*ast.Assignment, error, error) return assignment, warnings, nil } -func (u *Upserter) createAndInsert(input *ast.Assignment) (*ast.Assignment, error) { +func (u *Upserter) createAndInsert(ctx context.Context, input *ast.Assignment) (*ast.Assignment, error) { // Create the new newAssignment newAssignment := &ast.Assignment{ Comments: input.Comments, @@ -153,7 +154,7 @@ func (u *Upserter) createAndInsert(input *ast.Assignment) (*ast.Assignment, erro Name: input.Name, } - doc, err := parser.New(scanner.New(render.NewFormatter().Statement(newAssignment).String()), "-").Parse() + doc, err := parser.New(scanner.New(render.NewFormatter().Statement(ctx, newAssignment).String()), "-").Parse() if err != nil { return nil, fmt.Errorf("failed to parse assignment: %w", err) } diff --git a/pkg/cli/shared/complete.go b/pkg/cli/shared/complete.go index dca741f..f5986d0 100644 --- a/pkg/cli/shared/complete.go +++ b/pkg/cli/shared/complete.go @@ -64,7 +64,7 @@ func (c *Completer) Get() CobraCompleter { lines := render. NewUnfilteredRenderer(render.NewSettings(c.options...), c.handlers...). - Statement(doc). + Statement(cmd.Context(), doc). Lines() if c.suffixIsLiteral && strings.HasSuffix(toComplete, "=") { diff --git a/pkg/file.go b/pkg/file.go index 87364b6..5f8cc81 100644 --- a/pkg/file.go +++ b/pkg/file.go @@ -1,6 +1,7 @@ package pkg import ( + "context" "errors" "io" "os" @@ -21,14 +22,14 @@ func Load(filename string) (*ast.Document, error) { return Parse(file, filename) } -func Save(filename string, doc *ast.Document) error { +func Save(ctx context.Context, filename string, doc *ast.Document) error { file, err := os.Create(filename) if err != nil { return err } defer file.Close() - res := render.NewFormatter().Statement(doc) + res := render.NewFormatter().Statement(ctx, doc) if res.IsEmpty() { return errors.New("The rendered .env file is unexpectedly 0 bytes long - please report this as a bug (unless your file is empty)") } diff --git a/pkg/render/handler.go b/pkg/render/handler.go index 560361f..44ec613 100644 --- a/pkg/render/handler.go +++ b/pkg/render/handler.go @@ -1,10 +1,12 @@ package render import ( + "context" + "github.com/jippi/dottie/pkg/ast" ) -type Handler func(hi *HandlerInput) HandlerSignal +type Handler func(ctx context.Context, hi *HandlerInput) HandlerSignal type HandlerInput struct { CurrentStatement any diff --git a/pkg/render/handler_filters.go b/pkg/render/handler_filters.go index ba98cac..e61074b 100644 --- a/pkg/render/handler_filters.go +++ b/pkg/render/handler_filters.go @@ -1,6 +1,8 @@ package render import ( + "context" + "github.com/jippi/dottie/pkg/ast" ) @@ -17,7 +19,7 @@ func RetainKeyPrefix(value string) Handler { return newSelectorHandler(ast.Ret func RetainExactKey(value ...string) Handler { return newSelectorHandler(ast.RetainExactKey(value...)) } func newSelectorHandler(selector ast.Selector) Handler { - return func(input *HandlerInput) HandlerSignal { + return func(ctx context.Context, input *HandlerInput) HandlerSignal { switch stmt := input.CurrentStatement.(type) { case ast.Statement: if selector(stmt) == ast.Exclude { diff --git a/pkg/render/handler_formatter.go b/pkg/render/handler_formatter.go index 8ec79e5..6b52948 100644 --- a/pkg/render/handler_formatter.go +++ b/pkg/render/handler_formatter.go @@ -1,6 +1,8 @@ package render import ( + "context" + "github.com/jippi/dottie/pkg/ast" ) @@ -20,7 +22,7 @@ func NewFormatter() *Renderer { // FormatterHandler is responsible for formatting an .env file according // to our opinionated style. -func FormatterHandler(input *HandlerInput) HandlerSignal { +func FormatterHandler(ctx context.Context, input *HandlerInput) HandlerSignal { switch statement := input.CurrentStatement.(type) { case *ast.Newline: if !input.Settings.showBlankLines { @@ -40,7 +42,7 @@ func FormatterHandler(input *HandlerInput) HandlerSignal { return input.Stop() case *ast.Group: - output := input.Renderer.group(statement) + output := input.Renderer.group(ctx, statement) if output.IsEmpty() { return input.Stop() } @@ -56,7 +58,7 @@ func FormatterHandler(input *HandlerInput) HandlerSignal { return input.Return(buf) case *ast.Assignment: - output := input.Renderer.assignment(statement) + output := input.Renderer.assignment(ctx, statement) if output.IsEmpty() { return input.Stop() } diff --git a/pkg/render/output.go b/pkg/render/output.go index 93c425c..6520923 100644 --- a/pkg/render/output.go +++ b/pkg/render/output.go @@ -1,10 +1,14 @@ package render -import "github.com/jippi/dottie/pkg/ast" +import ( + "context" + + "github.com/jippi/dottie/pkg/ast" +) type Output interface { - GroupBanner(group *ast.Group, settings Settings) *Lines - Assignment(assignment *ast.Assignment, settings Settings) *Lines - Comment(comment *ast.Comment, settings Settings) *Lines - Newline(newline *ast.Newline, settings Settings) *Lines + GroupBanner(ctx context.Context, group *ast.Group, settings Settings) *Lines + Assignment(ctx context.Context, assignment *ast.Assignment, settings Settings) *Lines + Comment(ctx context.Context, comment *ast.Comment, settings Settings) *Lines + Newline(ctx context.Context, newline *ast.Newline, settings Settings) *Lines } diff --git a/pkg/render/output_colorized.go b/pkg/render/output_colorized.go index 59f3678..64024f4 100644 --- a/pkg/render/output_colorized.go +++ b/pkg/render/output_colorized.go @@ -2,6 +2,7 @@ package render import ( "bytes" + "context" "github.com/jippi/dottie/pkg/ast" "github.com/jippi/dottie/pkg/tui" @@ -11,10 +12,10 @@ var _ Output = (*ColorizedOutput)(nil) type ColorizedOutput struct{} -func (ColorizedOutput) GroupBanner(group *ast.Group, settings Settings) *Lines { +func (ColorizedOutput) GroupBanner(ctx context.Context, group *ast.Group, settings Settings) *Lines { var buf bytes.Buffer - out := tui.Theme.Info.Printer(tui.RendererWithTTY(&buf)) + out := tui.ThemeFromContext(ctx).Info.Printer(tui.RendererWithTTY(&buf)) out.Println("################################################################################") out.ApplyStyle(tui.Bold).Println(group.Name) @@ -23,11 +24,13 @@ func (ColorizedOutput) GroupBanner(group *ast.Group, settings Settings) *Lines { return NewLinesCollection().Add(buf.String()) } -func (ColorizedOutput) Assignment(assignment *ast.Assignment, settings Settings) *Lines { +func (ColorizedOutput) Assignment(ctx context.Context, assignment *ast.Assignment, settings Settings) *Lines { var buf bytes.Buffer + printer := tui.ThemeFromContext(ctx).Printer(&buf) + if !assignment.Enabled { - tui.Theme.Danger.Printer(tui.RendererWithTTY(&buf)).Print("#") + printer.Color(tui.Danger).Print("#") } val := assignment.Literal @@ -36,19 +39,19 @@ func (ColorizedOutput) Assignment(assignment *ast.Assignment, settings Settings) val = assignment.Interpolated } - tui.Theme.Primary.Printer(tui.RendererWithTTY(&buf)).Print(assignment.Name) - tui.Theme.Dark.Printer(tui.RendererWithTTY(&buf)).Print("=") - tui.Theme.Success.Printer(tui.RendererWithTTY(&buf)).Print(assignment.Quote) - tui.Theme.Warning.Printer(tui.RendererWithTTY(&buf)).Print(val) - tui.Theme.Success.Printer(tui.RendererWithTTY(&buf)).Print(assignment.Quote) + printer.Color(tui.Primary).Print(assignment.Name) + printer.Color(tui.Dark).Print("=") + printer.Color(tui.Success).Print(assignment.Quote) + printer.Color(tui.Warning).Print(val) + printer.Color(tui.Success).Print(assignment.Quote) return NewLinesCollection().Add(buf.String()) } -func (ColorizedOutput) Comment(comment *ast.Comment, settings Settings) *Lines { +func (ColorizedOutput) Comment(ctx context.Context, comment *ast.Comment, settings Settings) *Lines { var buf bytes.Buffer - out := tui.Theme.Success.Printer(tui.RendererWithTTY(&buf)) + out := tui.ThemeFromContext(ctx).Printer(&buf).Color(tui.Success) if comment.Annotation == nil { out.Print(comment.Value) @@ -66,7 +69,7 @@ func (ColorizedOutput) Comment(comment *ast.Comment, settings Settings) *Lines { return NewLinesCollection().Add(buf.String()) } -func (ColorizedOutput) Newline(newline *ast.Newline, settings Settings) *Lines { +func (ColorizedOutput) Newline(ctx context.Context, newline *ast.Newline, settings Settings) *Lines { if newline.Blank && !settings.ShowBlankLines() { return nil } diff --git a/pkg/render/output_completion_keys.go b/pkg/render/output_completion_keys.go index 1d12c32..82d949d 100644 --- a/pkg/render/output_completion_keys.go +++ b/pkg/render/output_completion_keys.go @@ -1,6 +1,8 @@ package render import ( + "context" + "github.com/jippi/dottie/pkg/ast" ) @@ -8,18 +10,18 @@ var _ Output = (*CompletionOutputKeys)(nil) type CompletionOutputKeys struct{} -func (CompletionOutputKeys) GroupBanner(group *ast.Group, settings Settings) *Lines { +func (CompletionOutputKeys) GroupBanner(ctx context.Context, group *ast.Group, settings Settings) *Lines { return nil } -func (CompletionOutputKeys) Assignment(a *ast.Assignment, settings Settings) *Lines { - return NewLinesCollection().Add(a.Name + "\t" + a.DocumentationSummary()) +func (CompletionOutputKeys) Assignment(ctx context.Context, assignment *ast.Assignment, settings Settings) *Lines { + return NewLinesCollection().Add(assignment.Name + "\t" + assignment.DocumentationSummary()) } -func (r CompletionOutputKeys) Comment(comment *ast.Comment, settings Settings) *Lines { +func (r CompletionOutputKeys) Comment(ctx context.Context, comment *ast.Comment, settings Settings) *Lines { return nil } -func (r CompletionOutputKeys) Newline(newline *ast.Newline, settings Settings) *Lines { +func (r CompletionOutputKeys) Newline(ctx context.Context, newline *ast.Newline, settings Settings) *Lines { return nil } diff --git a/pkg/render/output_plain.go b/pkg/render/output_plain.go index e3a2dfe..9ca3222 100644 --- a/pkg/render/output_plain.go +++ b/pkg/render/output_plain.go @@ -2,6 +2,7 @@ package render import ( "bytes" + "context" "fmt" "github.com/jippi/dottie/pkg/ast" @@ -11,7 +12,7 @@ var _ Output = (*PlainOutput)(nil) type PlainOutput struct{} -func (PlainOutput) GroupBanner(group *ast.Group, settings Settings) *Lines { +func (PlainOutput) GroupBanner(ctx context.Context, group *ast.Group, settings Settings) *Lines { out := NewLinesCollection() out.Add("################################################################################") @@ -21,7 +22,7 @@ func (PlainOutput) GroupBanner(group *ast.Group, settings Settings) *Lines { return out } -func (PlainOutput) Assignment(assignment *ast.Assignment, settings Settings) *Lines { +func (PlainOutput) Assignment(ctx context.Context, assignment *ast.Assignment, settings Settings) *Lines { var buf bytes.Buffer if !assignment.Enabled { @@ -39,11 +40,11 @@ func (PlainOutput) Assignment(assignment *ast.Assignment, settings Settings) *Li return NewLinesCollection().Add(buf.String()) } -func (r PlainOutput) Comment(comment *ast.Comment, settings Settings) *Lines { +func (r PlainOutput) Comment(ctx context.Context, comment *ast.Comment, settings Settings) *Lines { return NewLinesCollection().Add(comment.Value) } -func (r PlainOutput) Newline(newline *ast.Newline, settings Settings) *Lines { +func (r PlainOutput) Newline(ctx context.Context, newline *ast.Newline, settings Settings) *Lines { if newline.Blank && !settings.ShowBlankLines() { return nil } diff --git a/pkg/render/render.go b/pkg/render/render.go index bc02276..1572775 100644 --- a/pkg/render/render.go +++ b/pkg/render/render.go @@ -1,6 +1,7 @@ package render import ( + "context" "fmt" "github.com/jippi/dottie/pkg/ast" @@ -43,11 +44,11 @@ func NewUnfilteredRenderer(settings *Settings, additionalHandlers ...Handler) *R // // It's responsible for delegating statements to handlers, calling the right // Output functions and track the ordering of Statements being rendered -func (r *Renderer) Statement(currentStatement any) *Lines { +func (r *Renderer) Statement(ctx context.Context, currentStatement any) *Lines { handlerInput := r.newHandlerInput(currentStatement) for _, handler := range r.handlers { - status := handler(handlerInput) + status := handler(ctx, handlerInput) switch status { // Stop processing the statement and return nothing @@ -86,19 +87,19 @@ func (r *Renderer) Statement(currentStatement any) *Lines { switch statement := currentStatement.(type) { case *ast.Document: - return r.document(statement) + return r.document(ctx, statement) case *ast.Group: - return r.group(statement) + return r.group(ctx, statement) case *ast.Comment: - return r.comment(statement) + return r.comment(ctx, statement) case *ast.Assignment: - return r.assignment(statement) + return r.assignment(ctx, statement) case *ast.Newline: - return r.newline(statement) + return r.newline(ctx, statement) // // Lists of different statements will be iterated over @@ -108,7 +109,7 @@ func (r *Renderer) Statement(currentStatement any) *Lines { buf := NewLinesCollection() for _, group := range statement { - buf.Append(r.Statement(group)) + buf.Append(r.Statement(ctx, group)) } return buf @@ -117,7 +118,7 @@ func (r *Renderer) Statement(currentStatement any) *Lines { buf := NewLinesCollection() for _, stmt := range statement { - buf.Append(r.Statement(stmt)) + buf.Append(r.Statement(ctx, stmt)) } return buf @@ -126,7 +127,7 @@ func (r *Renderer) Statement(currentStatement any) *Lines { buf := NewLinesCollection() for _, comment := range statement { - buf.Append(r.Statement(comment)) + buf.Append(r.Statement(ctx, comment)) } return buf @@ -144,14 +145,14 @@ func (r *Renderer) Statement(currentStatement any) *Lines { // // Direct Document Statements are rendered first, followed by any // Group Statements in order they show up in the original source. -func (r *Renderer) document(document *ast.Document) *Lines { +func (r *Renderer) document(ctx context.Context, document *ast.Document) *Lines { return NewLinesCollection(). - Append(r.Statement(document.Statements)). - Append(r.Statement(document.Groups)) + Append(r.Statement(ctx, document.Statements)). + Append(r.Statement(ctx, document.Groups)) } // group renders "Group" Statements. -func (r *Renderer) group(group *ast.Group) *Lines { +func (r *Renderer) group(ctx context.Context, group *ast.Group) *Lines { // Capture the *current* Previous Statement in case we need to restore it (see below) prev := r.PreviousStatement @@ -162,7 +163,7 @@ func (r *Renderer) group(group *ast.Group) *Lines { // rather than whatever *actually* was the previous statement. r.PreviousStatement = group - rendered := r.Statement(group.Statements) + rendered := r.Statement(ctx, group.Statements) if rendered.IsEmpty() { // If the Group Statements didn't yield any output, restore the old "PreviousStatement" before @@ -176,7 +177,7 @@ func (r *Renderer) group(group *ast.Group) *Lines { // Render the optional Group banner if necessary. if r.Settings.ShowGroupBanners { - buf.Append(r.Output.GroupBanner(group, r.Settings)) + buf.Append(r.Output.GroupBanner(ctx, group, r.Settings)) if r.Settings.showBlankLines { buf.Newline("Group:ShowGroupBanners", r.PreviousStatement.Type(), "(type doesn't matter)") @@ -187,29 +188,29 @@ func (r *Renderer) group(group *ast.Group) *Lines { } // assignment renders "Assignment" Statements. -func (r *Renderer) assignment(assignment *ast.Assignment) *Lines { +func (r *Renderer) assignment(ctx context.Context, assignment *ast.Assignment) *Lines { // When done rendering this statement, mark it as the previous statement defer func() { r.PreviousStatement = assignment }() return NewLinesCollection(). - Append(r.Statement(assignment.Comments)). - Append(r.Output.Assignment(assignment, r.Settings)) + Append(r.Statement(ctx, assignment.Comments)). + Append(r.Output.Assignment(ctx, assignment, r.Settings)) } // comment renders "Comment" Statements. -func (r *Renderer) comment(comment *ast.Comment) *Lines { +func (r *Renderer) comment(ctx context.Context, comment *ast.Comment) *Lines { // When done rendering this statement, mark it as the previous statement defer func() { r.PreviousStatement = comment }() - return r.Output.Comment(comment, r.Settings) + return r.Output.Comment(ctx, comment, r.Settings) } // newline renders "Newline" Statements. -func (r *Renderer) newline(newline *ast.Newline) *Lines { +func (r *Renderer) newline(ctx context.Context, newline *ast.Newline) *Lines { // When done rendering this statement, mark it as the previous statement defer func() { r.PreviousStatement = newline }() - return r.Output.Newline(newline, r.Settings) + return r.Output.Newline(ctx, newline, r.Settings) } func (r *Renderer) newHandlerInput(statement any) *HandlerInput { diff --git a/pkg/render/render_test.go b/pkg/render/render_test.go index b553e1a..bf49249 100644 --- a/pkg/render/render_test.go +++ b/pkg/render/render_test.go @@ -1,6 +1,7 @@ package render_test import ( + "context" "log" "os" "strings" @@ -66,7 +67,7 @@ func TestFormatter(t *testing.T) { env, err := pkg.Load(tt.filename) require.NoError(t, err) - golden.Assert(t, tt.name, []byte(render.NewFormatter().Statement(env).String())) + golden.Assert(t, tt.name, []byte(render.NewFormatter().Statement(context.TODO(), env).String())) }) } } diff --git a/pkg/tui/box.go b/pkg/tui/box.go new file mode 100644 index 0000000..a5eb4d7 --- /dev/null +++ b/pkg/tui/box.go @@ -0,0 +1,15 @@ +package tui + +import "github.com/charmbracelet/lipgloss" + +type Box struct { + Header lipgloss.Style + Body lipgloss.Style +} + +func (b Box) Copy() Box { + return Box{ + Header: b.Header.Copy(), + Body: b.Body.Copy(), + } +} diff --git a/pkg/tui/color.go b/pkg/tui/color.go index af48877..9de52d2 100644 --- a/pkg/tui/color.go +++ b/pkg/tui/color.go @@ -2,28 +2,17 @@ package tui import ( "io" - "os" "github.com/charmbracelet/lipgloss" ) -type Box struct { - Header lipgloss.Style - Body lipgloss.Style -} - -func (b Box) Copy() Box { - return Box{ - Header: b.Header.Copy(), - Body: b.Body.Copy(), - } -} - type Color struct { Text lipgloss.AdaptiveColor TextEmphasis lipgloss.AdaptiveColor Background lipgloss.AdaptiveColor Border lipgloss.AdaptiveColor + + noColor bool } func NewColor(config ColorConfig) Color { @@ -41,28 +30,34 @@ func NewColor(config ColorConfig) Color { return color } +func NewNoColor() Color { + return Color{ + noColor: true, + } +} + func (c Color) Printer(renderer *lipgloss.Renderer, options ...PrinterOption) Print { return NewPrinter(c, renderer, options...) } -func (c Color) BuffPrinter(w io.Writer, options ...PrinterOption) Print { +func (c Color) BufferPrinter(w io.Writer, options ...PrinterOption) Print { return c.Printer(Renderer(w), options...) } -func (c Color) StderrPrinter(options ...PrinterOption) Print { - return NewPrinter(c, Renderer(os.Stderr), options...) -} - -func (c Color) StdoutPrinter(options ...PrinterOption) Print { - return NewPrinter(c, Renderer(os.Stdout), options...) -} - func (c Color) TextStyle(style lipgloss.Style) lipgloss.Style { + if c.noColor { + return style + } + return style. Foreground(c.Text) } func (c Color) TextEmphasisStyle(style lipgloss.Style) lipgloss.Style { + if c.noColor { + return style + } + return style. Foreground(c.TextEmphasis). Background(c.Background). diff --git a/pkg/tui/context.go b/pkg/tui/context.go index 5a03487..78fc9e9 100644 --- a/pkg/tui/context.go +++ b/pkg/tui/context.go @@ -9,21 +9,34 @@ type printerContextKey int const ( Stdout printerContextKey = iota - Stderr printerContextKey = iota + Stderr +) + +type themeContextKey int + +const ( + Theme themeContextKey = iota ) func CreateContext(ctx context.Context, stdout, stderr io.Writer) context.Context { - ctx = context.WithValue(ctx, Stdout, Theme.Printer(stdout)) - ctx = context.WithValue(ctx, Stderr, Theme.Printer(stderr)) + theme := NewTheme() + + ctx = context.WithValue(ctx, Theme, theme) + ctx = context.WithValue(ctx, Stdout, theme.Printer(stdout)) + ctx = context.WithValue(ctx, Stderr, theme.Printer(stderr)) return ctx } -func FromContext(ctx context.Context, key printerContextKey) ThemePrinter { +func ThemeFromContext(ctx context.Context) ThemeConfig { + return ctx.Value(Theme).(ThemeConfig) //nolint:forcetypeassert +} + +func PrinterFromContext(ctx context.Context, key printerContextKey) ThemePrinter { return ctx.Value(key).(ThemePrinter) //nolint:forcetypeassert } -func ColorFromContext(ctx context.Context, key printerContextKey, color colorType) Printer { +func ColorPrinterFromContext(ctx context.Context, key printerContextKey, color colorType) Printer { return ctx.Value(key).(ThemePrinter).Color(color) //nolint:forcetypeassert } diff --git a/pkg/tui/printer.go b/pkg/tui/printer.go index 5764511..26c9313 100644 --- a/pkg/tui/printer.go +++ b/pkg/tui/printer.go @@ -7,7 +7,6 @@ import ( "strings" "github.com/charmbracelet/lipgloss" - "github.com/erikgeiser/promptkit" ) type StyleChanger func(*lipgloss.Style) @@ -106,9 +105,6 @@ type Printer interface { // ApplyTextStyle returns a new copy of [StylePrint] instance with the [Style] based on the callback changes ApplyStyle(changer StyleChanger) Print - // WrapMode returns the configured [WrapMode] - WrapMode() promptkit.WrapMode - // Writer returns the configured [io.Writer] Writer() io.Writer @@ -117,12 +113,10 @@ type Printer interface { } type Print struct { - maxWidth int // Max width for strings when using WrapMode - wrapMode promptkit.WrapMode // WrapMode controls if line-wrapping should be off [nil], soft [promptkit.WordWrap] or hard [promptkit.HardWrap] + boxWidth int // Max width for strings when using WrapMode writer io.Writer // Writer controls where implicit print output goes for [Print], [Printf], [Printfln] and [Println] renderer *lipgloss.Renderer // The renderer responsible for providing the output and color management color Color // Color config - theme ThemeConfig // Theme config textStyle lipgloss.Style textEmphasis bool @@ -131,11 +125,10 @@ type Print struct { func NewPrinter(color Color, renderer *lipgloss.Renderer, options ...PrinterOption) Print { options = append([]PrinterOption{ + WitBoxWidth(100), WithColor(color), WithRenderer(renderer), - WithTheme(Theme), WithEmphasis(false), - WithWrapMode(nil), }, options...) printer := &Print{} @@ -143,7 +136,10 @@ func NewPrinter(color Color, renderer *lipgloss.Renderer, options ...PrinterOpti option(printer) } - printer.boxStyles = printer.color.BoxStyles(printer.renderer.NewStyle(), printer.renderer.NewStyle()) + printer.boxStyles = printer.color.BoxStyles( + printer.renderer.NewStyle(), + printer.renderer.NewStyle(), + ) return *printer } @@ -219,7 +215,7 @@ func (p Print) Box(header string, bodies ...string) { fmt.Fprintln( p.writer, styles.Header. - Width(p.maxWidth-borderWidth). + Width(p.boxWidth-borderWidth). Border(headerOnlyBorder). Render(header), ) @@ -228,11 +224,11 @@ func (p Print) Box(header string, bodies ...string) { } // Render the header and body box - boxHeader := styles.Header.Width(p.maxWidth - borderWidth).Render(header) - boxBody := styles.Body.Width(p.maxWidth - borderWidth).Render(body) + boxHeader := styles.Header.Width(p.boxWidth - borderWidth).Render(header) + boxBody := styles.Body.Width(p.boxWidth - borderWidth).Render(body) // If a maxWidth is set, the boxes will be aligned automatically to the max - if p.maxWidth > 0 { + if p.boxWidth > 0 { fmt.Fprintln( p.writer, lipgloss.JoinVertical( @@ -276,12 +272,8 @@ func (p Print) Write(b []byte) (n int, err error) { // Helper methods // ----------------------------------------------------- -func (p Print) WrapMode() promptkit.WrapMode { - return p.wrapMode -} - func (p Print) MaxWidth() int { - return p.maxWidth + return p.boxWidth } func (p Print) Writer() io.Writer { @@ -322,11 +314,7 @@ func (p Print) render(input string) string { } func (p Print) wrap(input string) string { - if p.wrapMode == nil { - return input - } - - return p.wrapMode(input, p.maxWidth) + return input } func (p Print) printHelper(a ...any) string { @@ -357,14 +345,6 @@ func WithRenderer(renderer *lipgloss.Renderer) PrinterOption { } } -func WithTheme(theme ThemeConfig) PrinterOption { - return func(p *Print) { - p.theme = theme - p.maxWidth = theme.DefaultWidth - p.wrapMode = theme.WrapMode - } -} - func WithTextStyle(style lipgloss.Style) PrinterOption { return func(p *Print) { p.textStyle = style @@ -397,14 +377,8 @@ func WithWriter(w io.Writer) PrinterOption { } } -func WithMaxWidth(i int) PrinterOption { - return func(p *Print) { - p.maxWidth = i - } -} - -func WithWrapMode(mode promptkit.WrapMode) PrinterOption { +func WitBoxWidth(i int) PrinterOption { return func(p *Print) { - p.wrapMode = mode + p.boxWidth = i } } diff --git a/pkg/tui/theme.go b/pkg/tui/theme.go index 7c80deb..5f06537 100644 --- a/pkg/tui/theme.go +++ b/pkg/tui/theme.go @@ -2,8 +2,6 @@ package tui import ( "io" - - "github.com/erikgeiser/promptkit" ) type colorType int @@ -13,7 +11,7 @@ const ( Dark Info Light - Neutral + NoColor Primary Secondary Success @@ -23,14 +21,11 @@ const ( type ThemeConfig struct { DefaultWidth int - // Line wrapping handling - WrapMode promptkit.WrapMode - Danger Color Dark Color Info Color Light Color - Neutral Color + NoColor Color Primary Color Secondary Color Success Color @@ -40,11 +35,13 @@ type ThemeConfig struct { func (tc ThemeConfig) Printer(w io.Writer) ThemePrinter { return ThemePrinter{ w: w, + theme: tc, cache: make(map[colorType]Printer), } } type ThemePrinter struct { + theme ThemeConfig w io.Writer cache map[colorType]Printer } @@ -58,56 +55,58 @@ func (tp ThemePrinter) Color(colorType colorType) Printer { switch colorType { case Danger: - color = Theme.Danger + color = tp.theme.Danger case Dark: - color = Theme.Danger + color = tp.theme.Danger case Info: - color = Theme.Info + color = tp.theme.Info case Light: - color = Theme.Light + color = tp.theme.Light case Primary: - color = Theme.Primary + color = tp.theme.Primary case Secondary: - color = Theme.Secondary + color = tp.theme.Secondary case Success: - color = Theme.Success + color = tp.theme.Success case Warning: - color = Theme.Warning - case Neutral: - color = Theme.Neutral + color = tp.theme.Warning + case NoColor: + color = tp.theme.NoColor } - tp.cache[colorType] = color.BuffPrinter(tp.w) + tp.cache[colorType] = color.BufferPrinter(tp.w) return tp.cache[colorType] } -var Theme ThemeConfig +func NewTheme() ThemeConfig { + theme := ThemeConfig{} + theme.DefaultWidth = 80 -func init() { - Theme = ThemeConfig{} - Theme.DefaultWidth = 100 - Theme.WrapMode = nil // Disabled for now, left here for easy opt-in in the future - - Theme.Danger = NewColor(NewColorComponentConfig(Red)) - Theme.Info = NewColor(NewColorComponentConfig(Cyan)) - Theme.Light = NewColor(NewColorComponentConfig(Gray300)) - Theme.Primary = NewColor(NewColorComponentConfig(Blue)) - Theme.Secondary = NewColor(NewColorComponentConfig(Gray600)) - Theme.Success = NewColor(NewColorComponentConfig(Green)) - Theme.Warning = NewColor(NewColorComponentConfig(Yellow)) - Theme.Neutral = NewColor(NewNeutralColorComponentConfig()) + theme.Danger = NewColor(NewColorComponentConfig(Red)) + theme.Info = NewColor(NewColorComponentConfig(Cyan)) + theme.Light = NewColor(NewColorComponentConfig(Gray300)) + theme.Primary = NewColor(NewColorComponentConfig(Blue)) + theme.Secondary = NewColor(NewColorComponentConfig(Gray600)) + theme.Success = NewColor(NewColorComponentConfig(Green)) + theme.Warning = NewColor(NewColorComponentConfig(Yellow)) + theme.NoColor = NewNoColor() dark := NewColorComponentConfig(Gray700) + dark.TextEmphasis.Dark = ComponentColorConfig{ Color: ColorToHex(Gray300), } + dark.Background.Dark = ComponentColorConfig{ Color: "#1a1d20", } + dark.Border.Dark = ComponentColorConfig{ Color: ColorToHex(Gray800), } - Theme.Dark = NewColor(dark) + theme.Dark = NewColor(dark) + + return theme } diff --git a/pkg/validation/explain.go b/pkg/validation/explain.go index 075e9b5..515785e 100644 --- a/pkg/validation/explain.go +++ b/pkg/validation/explain.go @@ -2,6 +2,7 @@ package validation import ( "bytes" + "context" "errors" "fmt" "os" @@ -18,23 +19,26 @@ type multiError interface { Errors() []error } -func Explain(doc *ast.Document, inputError any, keyErr ValidationError, applyFixer, showField bool) string { +func Explain(ctx context.Context, doc *ast.Document, inputError any, keyErr ValidationError, applyFixer, showField bool) string { var buff bytes.Buffer - dark := tui.Theme.Dark.BuffPrinter(&buff) - bold := tui.Theme.Warning.BuffPrinter(&buff, tui.WithEmphasis(true)) - danger := tui.Theme.Danger.BuffPrinter(&buff) - light := tui.Theme.Light.BuffPrinter(&buff) - primary := tui.Theme.Primary.BuffPrinter(&buff) + printer := tui.ThemeFromContext(ctx).Printer(&buff) + dark := printer.Color(tui.Dark) + bold := printer.Color(tui.Warning).Copy(tui.WithEmphasis(true)) + danger := printer.Color(tui.Danger) + light := printer.Color(tui.Light) + primary := printer.Color(tui.Primary) + + stderr := tui.PrinterFromContext(ctx, tui.Stderr) switch err := inputError.(type) { // Unwrap the ValidationError case ValidationError: - return Explain(doc, err.WrappedError, err, applyFixer, showField) + return Explain(ctx, doc, err.WrappedError, err, applyFixer, showField) case multiError: for _, e := range err.Errors() { - buff.WriteString(Explain(doc, e, ValidationError{}, applyFixer, showField)) + buff.WriteString(Explain(ctx, doc, e, ValidationError{}, applyFixer, showField)) buff.WriteString("\n") } @@ -67,7 +71,7 @@ func Explain(doc *ast.Document, inputError any, keyErr ValidationError, applyFix fmt.Fprintln(os.Stderr, buff.String()) buff.Reset() - AskToCreateDirectory(keyErr.Assignment.Interpolated) + AskToCreateDirectory(ctx, keyErr.Assignment.Interpolated) askToFix = false } @@ -131,10 +135,10 @@ func Explain(doc *ast.Document, inputError any, keyErr ValidationError, applyFix } if askToFix { - fmt.Fprintln(os.Stderr, buff.String()) + stderr.Color(tui.NoColor).Println(buff.String()) buff.Reset() - AskToSetValue(doc, keyErr.Assignment) + AskToSetValue(ctx, doc, keyErr.Assignment) } } @@ -145,8 +149,11 @@ func Explain(doc *ast.Document, inputError any, keyErr ValidationError, applyFix return buff.String() } -func AskToCreateDirectory(path string) { - confirm := true +func AskToCreateDirectory(ctx context.Context, path string) { + var ( + confirm = true + stderr = tui.PrinterFromContext(ctx, tui.Stderr) + ) err := huh.NewConfirm(). Title("\nDo you want this program to create the directory for you?"). @@ -155,28 +162,31 @@ func AskToCreateDirectory(path string) { Value(&confirm). Run() if err != nil { - tui.Theme.Warning.StderrPrinter().Println(" Prompt cancelled: " + err.Error()) + stderr.Color(tui.Warning).Println(" Prompt cancelled: " + err.Error()) return } if !confirm { - tui.Theme.Warning.StderrPrinter().Println(" Prompt cancelled") + stderr.Color(tui.Warning).Println(" Prompt cancelled") return } if err := os.MkdirAll(path, os.ModePerm); err != nil { - tui.Theme.Danger.StderrPrinter().Println(" Could not create directory: " + err.Error()) + stderr.Color(tui.Danger).Println(" Could not create directory: " + err.Error()) return } - tui.Theme.Success.StderrPrinter().Println(" Directory was successfully created") + stderr.Color(tui.Success).Println(" Directory was successfully created") } -func AskToSetValue(doc *ast.Document, assignment *ast.Assignment) { - var value string +func AskToSetValue(ctx context.Context, doc *ast.Document, assignment *ast.Assignment) { + var ( + value string + stderr = tui.PrinterFromContext(ctx, tui.Stderr) + ) err := huh.NewInput(). Title("Please provide value for " + assignment.Name). @@ -189,7 +199,7 @@ func AskToSetValue(doc *ast.Document, assignment *ast.Assignment) { Assignment: assignment, } - return errors.New(Explain(doc, z, z, false, false)) + return errors.New(Explain(ctx, doc, z, z, false, false)) } return nil @@ -197,17 +207,17 @@ func AskToSetValue(doc *ast.Document, assignment *ast.Assignment) { Value(&value). Run() if err != nil { - tui.Theme.Warning.StderrPrinter().Println(" Prompt cancelled: " + err.Error()) + stderr.Color(tui.Warning).Println(" Prompt cancelled: " + err.Error()) return } assignment.Literal = value - if err := pkg.Save(assignment.Position.File, doc); err != nil { - tui.Theme.Danger.StderrPrinter().Println(" Could not update key with value [" + value + "]: " + err.Error()) + if err := pkg.Save(ctx, assignment.Position.File, doc); err != nil { + stderr.Color(tui.Danger).Println(" Could not update key with value [" + value + "]: " + err.Error()) return } - tui.Theme.Success.StderrPrinter().Println(" Successfully updated key with value [" + value + "]") + stderr.Color(tui.Success).Println(" Successfully updated key with value [" + value + "]") } diff --git a/pkg/validation/validation.go b/pkg/validation/validation.go index 0a00299..e663558 100644 --- a/pkg/validation/validation.go +++ b/pkg/validation/validation.go @@ -1,6 +1,7 @@ package validation import ( + "context" "fmt" "slices" @@ -29,7 +30,7 @@ func NewError(assignment *ast.Assignment, err error) ValidationError { } } -func Validate(doc *ast.Document, handlers []render.Handler, ignoreErrors []string) []ValidationError { +func Validate(ctx context.Context, doc *ast.Document, handlers []render.Handler, ignoreErrors []string) []ValidationError { data := map[string]any{} rules := map[string]any{} @@ -49,7 +50,7 @@ NEXT: } for _, handler := range handlers { - status := handler(handlerInput) + status := handler(ctx, handlerInput) switch status { // Stop processing the statement and return nothing @@ -105,10 +106,11 @@ NEXT_FIELD: return result } -func ValidateSingleAssignment(doc *ast.Document, assignment *ast.Assignment, handlers []render.Handler, ignoreErrors []string) []ValidationError { +func ValidateSingleAssignment(ctx context.Context, doc *ast.Document, assignment *ast.Assignment, handlers []render.Handler, ignoreErrors []string) []ValidationError { keys := AssignmentsToValidateRecursive(assignment) return Validate( + ctx, doc, append( []render.Handler{ From fc95111afb1429d853a3164dcbb765692d0f8610 Mon Sep 17 00:00:00 2001 From: Christian Winther Date: Thu, 15 Feb 2024 18:22:47 +0100 Subject: [PATCH 11/33] sync tests --- cmd/set/tests/manipulate-empty/stderr.golden | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/set/tests/manipulate-empty/stderr.golden b/cmd/set/tests/manipulate-empty/stderr.golden index 7f5edc5..c3a6913 100644 --- a/cmd/set/tests/manipulate-empty/stderr.golden +++ b/cmd/set/tests/manipulate-empty/stderr.golden @@ -6,9 +6,9 @@ ---- exec command line 5: [set SOME_KEY=ANOTHER_VALUE --comment I'm a comment --comment I'm another comment] ---- exec command line 6: [set A_NUMBER=1 --comment @dottie/validate number] ---- exec command line 7: [set NOT_A_NUMBER=abc --comment @dottie/validate number] -NOT_A_NUMBER (-:2) +NOT_A_NUMBER (-:2) * (number) The value [abc] is not a valid number. - + Error: validation error Run 'dottie set --help' for usage. ---- exec command line 8: [set A_NUMBER=2] From b449c8911813aa08ab40631f2f1ca7119736842b Mon Sep 17 00:00:00 2001 From: Christian Winther Date: Thu, 15 Feb 2024 18:25:47 +0100 Subject: [PATCH 12/33] shift things around --- pkg/tui/context.go | 8 +++---- pkg/tui/theme.go | 49 ++++------------------------------------ pkg/tui/theme_printer.go | 42 ++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 48 deletions(-) create mode 100644 pkg/tui/theme_printer.go diff --git a/pkg/tui/context.go b/pkg/tui/context.go index 78fc9e9..856811f 100644 --- a/pkg/tui/context.go +++ b/pkg/tui/context.go @@ -15,21 +15,21 @@ const ( type themeContextKey int const ( - Theme themeContextKey = iota + themeContextValue themeContextKey = iota ) func CreateContext(ctx context.Context, stdout, stderr io.Writer) context.Context { theme := NewTheme() - ctx = context.WithValue(ctx, Theme, theme) + ctx = context.WithValue(ctx, themeContextValue, theme) ctx = context.WithValue(ctx, Stdout, theme.Printer(stdout)) ctx = context.WithValue(ctx, Stderr, theme.Printer(stderr)) return ctx } -func ThemeFromContext(ctx context.Context) ThemeConfig { - return ctx.Value(Theme).(ThemeConfig) //nolint:forcetypeassert +func ThemeFromContext(ctx context.Context) Theme { + return ctx.Value(themeContextValue).(Theme) //nolint:forcetypeassert } func PrinterFromContext(ctx context.Context, key printerContextKey) ThemePrinter { diff --git a/pkg/tui/theme.go b/pkg/tui/theme.go index 5f06537..1b3bab4 100644 --- a/pkg/tui/theme.go +++ b/pkg/tui/theme.go @@ -18,7 +18,7 @@ const ( Warning ) -type ThemeConfig struct { +type Theme struct { DefaultWidth int Danger Color @@ -32,55 +32,16 @@ type ThemeConfig struct { Warning Color } -func (tc ThemeConfig) Printer(w io.Writer) ThemePrinter { +func (theme Theme) Printer(w io.Writer) ThemePrinter { return ThemePrinter{ w: w, - theme: tc, + theme: theme, cache: make(map[colorType]Printer), } } -type ThemePrinter struct { - theme ThemeConfig - w io.Writer - cache map[colorType]Printer -} - -func (tp ThemePrinter) Color(colorType colorType) Printer { - if printer, ok := tp.cache[colorType]; ok { - return printer - } - - var color Color - - switch colorType { - case Danger: - color = tp.theme.Danger - case Dark: - color = tp.theme.Danger - case Info: - color = tp.theme.Info - case Light: - color = tp.theme.Light - case Primary: - color = tp.theme.Primary - case Secondary: - color = tp.theme.Secondary - case Success: - color = tp.theme.Success - case Warning: - color = tp.theme.Warning - case NoColor: - color = tp.theme.NoColor - } - - tp.cache[colorType] = color.BufferPrinter(tp.w) - - return tp.cache[colorType] -} - -func NewTheme() ThemeConfig { - theme := ThemeConfig{} +func NewTheme() Theme { + theme := Theme{} theme.DefaultWidth = 80 theme.Danger = NewColor(NewColorComponentConfig(Red)) diff --git a/pkg/tui/theme_printer.go b/pkg/tui/theme_printer.go new file mode 100644 index 0000000..b1ee0b4 --- /dev/null +++ b/pkg/tui/theme_printer.go @@ -0,0 +1,42 @@ +package tui + +import "io" + +type ThemePrinter struct { + theme Theme + w io.Writer + cache map[colorType]Printer +} + +func (tp ThemePrinter) Color(colorType colorType) Printer { + if printer, ok := tp.cache[colorType]; ok { + return printer + } + + var color Color + + switch colorType { + case Danger: + color = tp.theme.Danger + case Dark: + color = tp.theme.Dark + case Info: + color = tp.theme.Info + case Light: + color = tp.theme.Light + case Primary: + color = tp.theme.Primary + case Secondary: + color = tp.theme.Secondary + case Success: + color = tp.theme.Success + case Warning: + color = tp.theme.Warning + case NoColor: + color = tp.theme.NoColor + } + + tp.cache[colorType] = color.BufferPrinter(tp.w) + + return tp.cache[colorType] +} From 08747b33bce43e7cf0dd79c3f947cde985b546d3 Mon Sep 17 00:00:00 2001 From: Christian Winther Date: Thu, 15 Feb 2024 18:26:34 +0100 Subject: [PATCH 13/33] shift things around --- pkg/tui/context.go | 10 +++++----- pkg/tui/theme.go | 4 ++-- pkg/tui/{theme_printer.go => theme_writer.go} | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) rename pkg/tui/{theme_printer.go => theme_writer.go} (88%) diff --git a/pkg/tui/context.go b/pkg/tui/context.go index 856811f..4a44346 100644 --- a/pkg/tui/context.go +++ b/pkg/tui/context.go @@ -32,14 +32,14 @@ func ThemeFromContext(ctx context.Context) Theme { return ctx.Value(themeContextValue).(Theme) //nolint:forcetypeassert } -func PrinterFromContext(ctx context.Context, key printerContextKey) ThemePrinter { - return ctx.Value(key).(ThemePrinter) //nolint:forcetypeassert +func PrinterFromContext(ctx context.Context, key printerContextKey) ThemeWriter { + return ctx.Value(key).(ThemeWriter) //nolint:forcetypeassert } func ColorPrinterFromContext(ctx context.Context, key printerContextKey, color colorType) Printer { - return ctx.Value(key).(ThemePrinter).Color(color) //nolint:forcetypeassert + return ctx.Value(key).(ThemeWriter).Color(color) //nolint:forcetypeassert } -func PrintersFromContext(ctx context.Context) (ThemePrinter, ThemePrinter) { - return ctx.Value(Stdout).(ThemePrinter), ctx.Value(Stderr).(ThemePrinter) //nolint:forcetypeassert +func PrintersFromContext(ctx context.Context) (ThemeWriter, ThemeWriter) { + return ctx.Value(Stdout).(ThemeWriter), ctx.Value(Stderr).(ThemeWriter) //nolint:forcetypeassert } diff --git a/pkg/tui/theme.go b/pkg/tui/theme.go index 1b3bab4..4407e2c 100644 --- a/pkg/tui/theme.go +++ b/pkg/tui/theme.go @@ -32,8 +32,8 @@ type Theme struct { Warning Color } -func (theme Theme) Printer(w io.Writer) ThemePrinter { - return ThemePrinter{ +func (theme Theme) Printer(w io.Writer) ThemeWriter { + return ThemeWriter{ w: w, theme: theme, cache: make(map[colorType]Printer), diff --git a/pkg/tui/theme_printer.go b/pkg/tui/theme_writer.go similarity index 88% rename from pkg/tui/theme_printer.go rename to pkg/tui/theme_writer.go index b1ee0b4..264d7d8 100644 --- a/pkg/tui/theme_printer.go +++ b/pkg/tui/theme_writer.go @@ -2,13 +2,13 @@ package tui import "io" -type ThemePrinter struct { +type ThemeWriter struct { + cache map[colorType]Printer theme Theme w io.Writer - cache map[colorType]Printer } -func (tp ThemePrinter) Color(colorType colorType) Printer { +func (tp ThemeWriter) Color(colorType colorType) Printer { if printer, ok := tp.cache[colorType]; ok { return printer } From 9282e933f98384e306b7ad31575024402fe10265 Mon Sep 17 00:00:00 2001 From: Christian Winther Date: Thu, 15 Feb 2024 18:34:09 +0100 Subject: [PATCH 14/33] shift things around --- cmd/groups/groups.go | 2 +- cmd/root.go | 2 +- cmd/validate/validate.go | 2 +- pkg/tui/color.go | 45 ++++++++++++++++++++------------------- pkg/tui/context.go | 2 +- pkg/tui/printer.go | 19 ++++++++--------- pkg/tui/theme.go | 37 +++++++++----------------------- pkg/tui/theme_writer.go | 24 ++++++++++++++++++++- pkg/validation/explain.go | 6 +++--- 9 files changed, 72 insertions(+), 67 deletions(-) diff --git a/cmd/groups/groups.go b/cmd/groups/groups.go index 9de7ca1..30d21a8 100644 --- a/cmd/groups/groups.go +++ b/cmd/groups/groups.go @@ -31,7 +31,7 @@ func NewCommand() *cobra.Command { width := longesGroupName(groups) - stdout := tui.PrinterFromContext(cmd.Context(), tui.Stdout) + stdout := tui.WriterFromContext(cmd.Context(), tui.Stdout) secondary := stdout.Color(tui.Secondary) primary := stdout.Color(tui.Primary) diff --git a/cmd/root.go b/cmd/root.go index 83c78a0..156725d 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -77,7 +77,7 @@ func RunCommand(ctx context.Context, args []string, stdout io.Writer, stderr io. command, err := root.ExecuteC() if err != nil { - stderr := tui.PrinterFromContext(ctx, tui.Stderr) + stderr := tui.WriterFromContext(ctx, tui.Stderr) stderr.Color(tui.Danger).Copy(tui.WithEmphasis(true)).Printfln("%s %+v", command.ErrPrefix(), err) stderr.Color(tui.Info).Printfln("Run '%v --help' for usage.", command.CommandPath()) } diff --git a/cmd/validate/validate.go b/cmd/validate/validate.go index 878a0f3..1542030 100644 --- a/cmd/validate/validate.go +++ b/cmd/validate/validate.go @@ -52,7 +52,7 @@ func runE(cmd *cobra.Command, args []string) error { handlers = append(handlers, render.ExcludeKeyPrefix(filter)) } - stderr := tui.PrinterFromContext(cmd.Context(), tui.Stderr) + stderr := tui.WriterFromContext(cmd.Context(), tui.Stderr) // // Interpolate diff --git a/pkg/tui/color.go b/pkg/tui/color.go index 9de52d2..c8875cd 100644 --- a/pkg/tui/color.go +++ b/pkg/tui/color.go @@ -6,7 +6,7 @@ import ( "github.com/charmbracelet/lipgloss" ) -type Color struct { +type Style struct { Text lipgloss.AdaptiveColor TextEmphasis lipgloss.AdaptiveColor Background lipgloss.AdaptiveColor @@ -15,57 +15,58 @@ type Color struct { noColor bool } -func NewColor(config ColorConfig) Color { - color := Color{ +func NewColor(config ColorConfig) Style { + style := Style{ Text: config.Text.AdaptiveColor(), TextEmphasis: config.TextEmphasis.AdaptiveColor(), Background: config.Background.AdaptiveColor(), Border: config.Border.AdaptiveColor(), } - if len(color.Text.Dark) == 0 { - color.Text.Dark = color.TextEmphasis.Dark + if len(style.Text.Dark) == 0 { + style.Text.Dark = style.TextEmphasis.Dark } - return color + return style } -func NewNoColor() Color { - return Color{ +func NewStyle() Style { + return Style{ noColor: true, } } -func (c Color) Printer(renderer *lipgloss.Renderer, options ...PrinterOption) Print { - return NewPrinter(c, renderer, options...) +func (style Style) Printer(renderer *lipgloss.Renderer, options ...PrinterOption) Print { + return NewPrinter(style, renderer, options...) } -func (c Color) BufferPrinter(w io.Writer, options ...PrinterOption) Print { - return c.Printer(Renderer(w), options...) +func (style Style) BufferPrinter(w io.Writer, options ...PrinterOption) Print { + return style.Printer(Renderer(w), options...) } -func (c Color) TextStyle(style lipgloss.Style) lipgloss.Style { - if c.noColor { - return style +func (style Style) TextStyle() lipgloss.Style { + if style.noColor { + return lipgloss.NewStyle() } - return style. - Foreground(c.Text) + return lipgloss. + NewStyle(). + Foreground(style.Text) } -func (c Color) TextEmphasisStyle(style lipgloss.Style) lipgloss.Style { +func (c Style) TextEmphasisStyle() lipgloss.Style { if c.noColor { - return style + return lipgloss.NewStyle() } - return style. + return lipgloss.NewStyle(). Foreground(c.TextEmphasis). Background(c.Background). Bold(true). BorderForeground(c.Border) } -func (c Color) BoxStyles(header, body lipgloss.Style) Box { +func (c Style) BoxStyles(header, body lipgloss.Style) Box { return Box{ Header: header. Align(lipgloss.Center, lipgloss.Center). @@ -73,7 +74,7 @@ func (c Color) BoxStyles(header, body lipgloss.Style) Box { BorderForeground(c.Border). PaddingBottom(1). PaddingTop(1). - Inherit(c.TextEmphasisStyle(header)), + Inherit(c.TextEmphasisStyle()), Body: body. Align(lipgloss.Left). diff --git a/pkg/tui/context.go b/pkg/tui/context.go index 4a44346..d8be0b7 100644 --- a/pkg/tui/context.go +++ b/pkg/tui/context.go @@ -32,7 +32,7 @@ func ThemeFromContext(ctx context.Context) Theme { return ctx.Value(themeContextValue).(Theme) //nolint:forcetypeassert } -func PrinterFromContext(ctx context.Context, key printerContextKey) ThemeWriter { +func WriterFromContext(ctx context.Context, key printerContextKey) ThemeWriter { return ctx.Value(key).(ThemeWriter) //nolint:forcetypeassert } diff --git a/pkg/tui/printer.go b/pkg/tui/printer.go index 26c9313..3ed040e 100644 --- a/pkg/tui/printer.go +++ b/pkg/tui/printer.go @@ -113,19 +113,18 @@ type Printer interface { } type Print struct { - boxWidth int // Max width for strings when using WrapMode - writer io.Writer // Writer controls where implicit print output goes for [Print], [Printf], [Printfln] and [Println] - renderer *lipgloss.Renderer // The renderer responsible for providing the output and color management - color Color // Color config - + boxWidth int // Max width for strings when using WrapMode + writer io.Writer // Writer controls where implicit print output goes for [Print], [Printf], [Printfln] and [Println] + renderer *lipgloss.Renderer // The renderer responsible for providing the output and color management + color Style // Color config textStyle lipgloss.Style textEmphasis bool boxStyles Box } -func NewPrinter(color Color, renderer *lipgloss.Renderer, options ...PrinterOption) Print { +func NewPrinter(color Style, renderer *lipgloss.Renderer, options ...PrinterOption) Print { options = append([]PrinterOption{ - WitBoxWidth(100), + WitBoxWidth(80), WithColor(color), WithRenderer(renderer), WithEmphasis(false), @@ -332,7 +331,7 @@ func (p Print) printHelper(a ...any) string { // Printer options // ----------------------------------------------------- -func WithColor(color Color) PrinterOption { +func WithColor(color Style) PrinterOption { return func(p *Print) { p.color = color } @@ -362,12 +361,12 @@ func WithEmphasis(b bool) PrinterOption { printer.textEmphasis = b if b { - printer.textStyle = printer.color.TextEmphasisStyle(printer.renderer.NewStyle()) + printer.textStyle = printer.renderer.NewStyle().Inherit(printer.color.TextEmphasisStyle()) return } - printer.textStyle = printer.color.TextStyle(printer.renderer.NewStyle()) + printer.textStyle = printer.renderer.NewStyle().Inherit(printer.color.TextStyle()) } } diff --git a/pkg/tui/theme.go b/pkg/tui/theme.go index 4407e2c..9ca64a0 100644 --- a/pkg/tui/theme.go +++ b/pkg/tui/theme.go @@ -4,32 +4,16 @@ import ( "io" ) -type colorType int - -const ( - Danger colorType = iota - Dark - Info - Light - NoColor - Primary - Secondary - Success - Warning -) - type Theme struct { - DefaultWidth int - - Danger Color - Dark Color - Info Color - Light Color - NoColor Color - Primary Color - Secondary Color - Success Color - Warning Color + Danger Style + Dark Style + Info Style + Light Style + NoColor Style + Primary Style + Secondary Style + Success Style + Warning Style } func (theme Theme) Printer(w io.Writer) ThemeWriter { @@ -42,7 +26,6 @@ func (theme Theme) Printer(w io.Writer) ThemeWriter { func NewTheme() Theme { theme := Theme{} - theme.DefaultWidth = 80 theme.Danger = NewColor(NewColorComponentConfig(Red)) theme.Info = NewColor(NewColorComponentConfig(Cyan)) @@ -51,7 +34,7 @@ func NewTheme() Theme { theme.Secondary = NewColor(NewColorComponentConfig(Gray600)) theme.Success = NewColor(NewColorComponentConfig(Green)) theme.Warning = NewColor(NewColorComponentConfig(Yellow)) - theme.NoColor = NewNoColor() + theme.NoColor = NewStyle() dark := NewColorComponentConfig(Gray700) diff --git a/pkg/tui/theme_writer.go b/pkg/tui/theme_writer.go index 264d7d8..3d78052 100644 --- a/pkg/tui/theme_writer.go +++ b/pkg/tui/theme_writer.go @@ -2,6 +2,20 @@ package tui import "io" +type colorType int + +const ( + Danger colorType = iota + Dark + Info + Light + NoColor + Primary + Secondary + Success + Warning +) + type ThemeWriter struct { cache map[colorType]Printer theme Theme @@ -13,25 +27,33 @@ func (tp ThemeWriter) Color(colorType colorType) Printer { return printer } - var color Color + var color Style switch colorType { case Danger: color = tp.theme.Danger + case Dark: color = tp.theme.Dark + case Info: color = tp.theme.Info + case Light: color = tp.theme.Light + case Primary: color = tp.theme.Primary + case Secondary: color = tp.theme.Secondary + case Success: color = tp.theme.Success + case Warning: color = tp.theme.Warning + case NoColor: color = tp.theme.NoColor } diff --git a/pkg/validation/explain.go b/pkg/validation/explain.go index 515785e..0116531 100644 --- a/pkg/validation/explain.go +++ b/pkg/validation/explain.go @@ -29,7 +29,7 @@ func Explain(ctx context.Context, doc *ast.Document, inputError any, keyErr Vali light := printer.Color(tui.Light) primary := printer.Color(tui.Primary) - stderr := tui.PrinterFromContext(ctx, tui.Stderr) + stderr := tui.WriterFromContext(ctx, tui.Stderr) switch err := inputError.(type) { // Unwrap the ValidationError @@ -152,7 +152,7 @@ func Explain(ctx context.Context, doc *ast.Document, inputError any, keyErr Vali func AskToCreateDirectory(ctx context.Context, path string) { var ( confirm = true - stderr = tui.PrinterFromContext(ctx, tui.Stderr) + stderr = tui.WriterFromContext(ctx, tui.Stderr) ) err := huh.NewConfirm(). @@ -185,7 +185,7 @@ func AskToCreateDirectory(ctx context.Context, path string) { func AskToSetValue(ctx context.Context, doc *ast.Document, assignment *ast.Assignment) { var ( value string - stderr = tui.PrinterFromContext(ctx, tui.Stderr) + stderr = tui.WriterFromContext(ctx, tui.Stderr) ) err := huh.NewInput(). From b1f61e43d9bc30468bdb0d023ae642dfdea10c49 Mon Sep 17 00:00:00 2001 From: Christian Winther Date: Thu, 15 Feb 2024 18:34:34 +0100 Subject: [PATCH 15/33] shift things around --- pkg/tui/{color.go => style.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pkg/tui/{color.go => style.go} (100%) diff --git a/pkg/tui/color.go b/pkg/tui/style.go similarity index 100% rename from pkg/tui/color.go rename to pkg/tui/style.go From a70b126b7c30cfd7e43f89b5c6802c51b386d545 Mon Sep 17 00:00:00 2001 From: Christian Winther Date: Thu, 15 Feb 2024 18:35:16 +0100 Subject: [PATCH 16/33] shift things around --- pkg/render/output_colorized.go | 2 +- pkg/tui/style.go | 6 +++--- pkg/tui/theme_writer.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/render/output_colorized.go b/pkg/render/output_colorized.go index 64024f4..0233c1e 100644 --- a/pkg/render/output_colorized.go +++ b/pkg/render/output_colorized.go @@ -15,7 +15,7 @@ type ColorizedOutput struct{} func (ColorizedOutput) GroupBanner(ctx context.Context, group *ast.Group, settings Settings) *Lines { var buf bytes.Buffer - out := tui.ThemeFromContext(ctx).Info.Printer(tui.RendererWithTTY(&buf)) + out := tui.ThemeFromContext(ctx).Info.NewPrinter(tui.RendererWithTTY(&buf)) out.Println("################################################################################") out.ApplyStyle(tui.Bold).Println(group.Name) diff --git a/pkg/tui/style.go b/pkg/tui/style.go index c8875cd..348ef6f 100644 --- a/pkg/tui/style.go +++ b/pkg/tui/style.go @@ -36,12 +36,12 @@ func NewStyle() Style { } } -func (style Style) Printer(renderer *lipgloss.Renderer, options ...PrinterOption) Print { +func (style Style) NewPrinter(renderer *lipgloss.Renderer, options ...PrinterOption) Print { return NewPrinter(style, renderer, options...) } -func (style Style) BufferPrinter(w io.Writer, options ...PrinterOption) Print { - return style.Printer(Renderer(w), options...) +func (style Style) NewBufferPrinter(w io.Writer, options ...PrinterOption) Print { + return style.NewPrinter(Renderer(w), options...) } func (style Style) TextStyle() lipgloss.Style { diff --git a/pkg/tui/theme_writer.go b/pkg/tui/theme_writer.go index 3d78052..2ed55e3 100644 --- a/pkg/tui/theme_writer.go +++ b/pkg/tui/theme_writer.go @@ -58,7 +58,7 @@ func (tp ThemeWriter) Color(colorType colorType) Printer { color = tp.theme.NoColor } - tp.cache[colorType] = color.BufferPrinter(tp.w) + tp.cache[colorType] = color.NewBufferPrinter(tp.w) return tp.cache[colorType] } From 94d15027dafb9017b094f5e59eeac79ac525782b Mon Sep 17 00:00:00 2001 From: Christian Winther Date: Thu, 15 Feb 2024 19:09:12 +0100 Subject: [PATCH 17/33] fix styling? --- cmd/print/print.go | 19 +++++++++++++------ pkg/render/output_colorized.go | 6 +++--- pkg/tui/context.go | 16 ++++++++++++++-- pkg/tui/helpers.go | 13 ------------- pkg/tui/style.go | 6 ------ pkg/tui/theme.go | 22 ++++++++++++++++++---- pkg/tui/theme_writer.go | 32 +++++++++++++++++--------------- pkg/validation/explain.go | 2 +- 8 files changed, 66 insertions(+), 50 deletions(-) diff --git a/cmd/print/print.go b/cmd/print/print.go index b04bf00..6bf44a5 100644 --- a/cmd/print/print.go +++ b/cmd/print/print.go @@ -1,6 +1,8 @@ package print_cmd import ( + "fmt" + "github.com/jippi/dottie/pkg" "github.com/jippi/dottie/pkg/ast" "github.com/jippi/dottie/pkg/cli/shared" @@ -25,10 +27,17 @@ func NewCommand() *cobra.Command { return err } - // fmt.Fprintln(cmd.OutOrStdout(), render.NewRenderer(*settings).Statement(env).String()) - tui. - ColorPrinterFromContext(cmd.Context(), tui.Stdout, tui.NoColor). - Println(render.NewRenderer(*settings).Statement(cmd.Context(), env).String()) + fmt.Fprintln( + cmd.OutOrStdout(), + render. + NewRenderer(*settings). + Statement(cmd.Context(), env). + String(), + ) + + // tui. + // ColorPrinterFromContext(cmd.Context(), tui.Stdout, tui.NoColor). + // Println(render.NewRenderer(*settings).Statement(cmd.Context(), env).String()) return nil }, @@ -96,7 +105,5 @@ func setup(flags *pflag.FlagSet) (*ast.Document, *render.Settings, error, error) settings.Apply(render.WithFormattedOutput(true)) } - settings.Apply(render.WithColors(false)) - return doc, settings, allWarnings, allErrors } diff --git a/pkg/render/output_colorized.go b/pkg/render/output_colorized.go index 0233c1e..f015533 100644 --- a/pkg/render/output_colorized.go +++ b/pkg/render/output_colorized.go @@ -15,7 +15,7 @@ type ColorizedOutput struct{} func (ColorizedOutput) GroupBanner(ctx context.Context, group *ast.Group, settings Settings) *Lines { var buf bytes.Buffer - out := tui.ThemeFromContext(ctx).Info.NewPrinter(tui.RendererWithTTY(&buf)) + out := tui.ThemeFromContext(ctx).WriterPrinter(ctx, &buf).Color(tui.Success) out.Println("################################################################################") out.ApplyStyle(tui.Bold).Println(group.Name) @@ -27,7 +27,7 @@ func (ColorizedOutput) GroupBanner(ctx context.Context, group *ast.Group, settin func (ColorizedOutput) Assignment(ctx context.Context, assignment *ast.Assignment, settings Settings) *Lines { var buf bytes.Buffer - printer := tui.ThemeFromContext(ctx).Printer(&buf) + printer := tui.ThemeFromContext(ctx).WriterPrinter(ctx, &buf) if !assignment.Enabled { printer.Color(tui.Danger).Print("#") @@ -51,7 +51,7 @@ func (ColorizedOutput) Assignment(ctx context.Context, assignment *ast.Assignmen func (ColorizedOutput) Comment(ctx context.Context, comment *ast.Comment, settings Settings) *Lines { var buf bytes.Buffer - out := tui.ThemeFromContext(ctx).Printer(&buf).Color(tui.Success) + out := tui.ThemeFromContext(ctx).WriterPrinter(ctx, &buf).Color(tui.Success) if comment.Annotation == nil { out.Print(comment.Value) diff --git a/pkg/tui/context.go b/pkg/tui/context.go index d8be0b7..f764385 100644 --- a/pkg/tui/context.go +++ b/pkg/tui/context.go @@ -3,6 +3,9 @@ package tui import ( "context" "io" + + "github.com/charmbracelet/lipgloss" + "github.com/muesli/termenv" ) type printerContextKey int @@ -16,14 +19,19 @@ type themeContextKey int const ( themeContextValue themeContextKey = iota + colorProfileContextValue ) func CreateContext(ctx context.Context, stdout, stderr io.Writer) context.Context { theme := NewTheme() + stdoutOutput := lipgloss.NewRenderer(stdout, termenv.WithColorCache(true)) + stderrOutput := lipgloss.NewRenderer(stderr, termenv.WithColorCache(true)) + ctx = context.WithValue(ctx, themeContextValue, theme) - ctx = context.WithValue(ctx, Stdout, theme.Printer(stdout)) - ctx = context.WithValue(ctx, Stderr, theme.Printer(stderr)) + ctx = context.WithValue(ctx, colorProfileContextValue, stdoutOutput.ColorProfile()) + ctx = context.WithValue(ctx, Stdout, theme.Printer(stdoutOutput)) + ctx = context.WithValue(ctx, Stderr, theme.Printer(stderrOutput)) return ctx } @@ -32,6 +40,10 @@ func ThemeFromContext(ctx context.Context) Theme { return ctx.Value(themeContextValue).(Theme) //nolint:forcetypeassert } +func ColorProfile(ctx context.Context) termenv.Profile { + return ctx.Value(colorProfileContextValue).(termenv.Profile) //nolint:forcetypeassert +} + func WriterFromContext(ctx context.Context, key printerContextKey) ThemeWriter { return ctx.Value(key).(ThemeWriter) //nolint:forcetypeassert } diff --git a/pkg/tui/helpers.go b/pkg/tui/helpers.go index 0c69c83..3ee7bfb 100644 --- a/pkg/tui/helpers.go +++ b/pkg/tui/helpers.go @@ -1,23 +1,10 @@ package tui import ( - "io" - "github.com/charmbracelet/lipgloss" - "github.com/muesli/termenv" "github.com/teacat/noire" ) -func Renderer(w io.Writer, opts ...termenv.OutputOption) *lipgloss.Renderer { - return lipgloss.NewRenderer(w, opts...) -} - -func RendererWithTTY(w io.Writer, opts ...termenv.OutputOption) *lipgloss.Renderer { - opts = append(opts, termenv.WithTTY(true)) - - return lipgloss.NewRenderer(w, opts...) -} - func ShadeColor(in string, percent float64) lipgloss.Color { if percent < 0 || percent > 1 { panic("ShadeColor [percent] must be between 0.0 and 1.0 (0.5 == 50%)") diff --git a/pkg/tui/style.go b/pkg/tui/style.go index 348ef6f..38763c0 100644 --- a/pkg/tui/style.go +++ b/pkg/tui/style.go @@ -1,8 +1,6 @@ package tui import ( - "io" - "github.com/charmbracelet/lipgloss" ) @@ -40,10 +38,6 @@ func (style Style) NewPrinter(renderer *lipgloss.Renderer, options ...PrinterOpt return NewPrinter(style, renderer, options...) } -func (style Style) NewBufferPrinter(w io.Writer, options ...PrinterOption) Print { - return style.NewPrinter(Renderer(w), options...) -} - func (style Style) TextStyle() lipgloss.Style { if style.noColor { return lipgloss.NewStyle() diff --git a/pkg/tui/theme.go b/pkg/tui/theme.go index 9ca64a0..aa9aa10 100644 --- a/pkg/tui/theme.go +++ b/pkg/tui/theme.go @@ -1,7 +1,11 @@ package tui import ( + "context" "io" + + "github.com/charmbracelet/lipgloss" + "github.com/muesli/termenv" ) type Theme struct { @@ -16,12 +20,22 @@ type Theme struct { Warning Style } -func (theme Theme) Printer(w io.Writer) ThemeWriter { +func (theme Theme) Printer(writer *lipgloss.Renderer) ThemeWriter { return ThemeWriter{ - w: w, - theme: theme, - cache: make(map[colorType]Printer), + writer: writer, + theme: theme, + cache: make(map[colorType]Printer), + } +} + +func (theme Theme) WriterPrinter(ctx context.Context, writer io.Writer) ThemeWriter { + options := []termenv.OutputOption{} + + if ColorProfile(ctx) != termenv.Ascii { + options = append(options, termenv.WithTTY(true)) } + + return theme.Printer(lipgloss.NewRenderer(writer, options...)) } func NewTheme() Theme { diff --git a/pkg/tui/theme_writer.go b/pkg/tui/theme_writer.go index 2ed55e3..df82bf8 100644 --- a/pkg/tui/theme_writer.go +++ b/pkg/tui/theme_writer.go @@ -1,6 +1,8 @@ package tui -import "io" +import ( + "github.com/charmbracelet/lipgloss" +) type colorType int @@ -17,9 +19,9 @@ const ( ) type ThemeWriter struct { - cache map[colorType]Printer - theme Theme - w io.Writer + cache map[colorType]Printer + theme Theme + writer *lipgloss.Renderer } func (tp ThemeWriter) Color(colorType colorType) Printer { @@ -27,38 +29,38 @@ func (tp ThemeWriter) Color(colorType colorType) Printer { return printer } - var color Style + var style Style switch colorType { case Danger: - color = tp.theme.Danger + style = tp.theme.Danger case Dark: - color = tp.theme.Dark + style = tp.theme.Dark case Info: - color = tp.theme.Info + style = tp.theme.Info case Light: - color = tp.theme.Light + style = tp.theme.Light case Primary: - color = tp.theme.Primary + style = tp.theme.Primary case Secondary: - color = tp.theme.Secondary + style = tp.theme.Secondary case Success: - color = tp.theme.Success + style = tp.theme.Success case Warning: - color = tp.theme.Warning + style = tp.theme.Warning case NoColor: - color = tp.theme.NoColor + style = tp.theme.NoColor } - tp.cache[colorType] = color.NewBufferPrinter(tp.w) + tp.cache[colorType] = style.NewPrinter(tp.writer) return tp.cache[colorType] } diff --git a/pkg/validation/explain.go b/pkg/validation/explain.go index 0116531..f28dbe9 100644 --- a/pkg/validation/explain.go +++ b/pkg/validation/explain.go @@ -22,7 +22,7 @@ type multiError interface { func Explain(ctx context.Context, doc *ast.Document, inputError any, keyErr ValidationError, applyFixer, showField bool) string { var buff bytes.Buffer - printer := tui.ThemeFromContext(ctx).Printer(&buff) + printer := tui.ThemeFromContext(ctx).WriterPrinter(ctx, &buff) dark := printer.Color(tui.Dark) bold := printer.Color(tui.Warning).Copy(tui.WithEmphasis(true)) danger := printer.Color(tui.Danger) From 79e993bd58b20b101389ebd0491223f0937e8085 Mon Sep 17 00:00:00 2001 From: Christian Winther Date: Thu, 15 Feb 2024 19:10:03 +0100 Subject: [PATCH 18/33] update banners to 80 width instead of 100 --- cmd/groups/tests/multiple-groups/stdout.golden | 10 +++++----- cmd/groups/tests/single-group/stdout.golden | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/cmd/groups/tests/multiple-groups/stdout.golden b/cmd/groups/tests/multiple-groups/stdout.golden index fa41443..c54c0e7 100644 --- a/cmd/groups/tests/multiple-groups/stdout.golden +++ b/cmd/groups/tests/multiple-groups/stdout.golden @@ -1,9 +1,9 @@ ---- exec command line 0: [groups] -┌──────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ │ -│ Groups in tests/multiple-groups.env │ -│ │ -└──────────────────────────────────────────────────────────────────────────────────────────────────┘ +┌──────────────────────────────────────────────────────────────────────────────┐ +│ │ +│ Groups in tests/multiple-groups.env │ +│ │ +└──────────────────────────────────────────────────────────────────────────────┘ my-first-group (tests/multiple-groups.env:2) my-second-group (tests/multiple-groups.env:13) my-third-group (tests/multiple-groups.env:17) diff --git a/cmd/groups/tests/single-group/stdout.golden b/cmd/groups/tests/single-group/stdout.golden index 2a00e01..d449141 100644 --- a/cmd/groups/tests/single-group/stdout.golden +++ b/cmd/groups/tests/single-group/stdout.golden @@ -1,7 +1,7 @@ ---- exec command line 0: [groups] -┌──────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ │ -│ Groups in tests/single-group.env │ -│ │ -└──────────────────────────────────────────────────────────────────────────────────────────────────┘ +┌──────────────────────────────────────────────────────────────────────────────┐ +│ │ +│ Groups in tests/single-group.env │ +│ │ +└──────────────────────────────────────────────────────────────────────────────┘ my-group (tests/single-group.env:2) From 3642caa0dc33b006c79a20550dd3292af6af23f2 Mon Sep 17 00:00:00 2001 From: Christian Winther Date: Thu, 15 Feb 2024 19:10:58 +0100 Subject: [PATCH 19/33] rename colors --- pkg/tui/{colors_gen.go => colors.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pkg/tui/{colors_gen.go => colors.go} (100%) diff --git a/pkg/tui/colors_gen.go b/pkg/tui/colors.go similarity index 100% rename from pkg/tui/colors_gen.go rename to pkg/tui/colors.go From 13bb4a1cb0b2479f4d549fbe92c30634c8e148a8 Mon Sep 17 00:00:00 2001 From: Christian Winther Date: Thu, 15 Feb 2024 20:16:37 +0100 Subject: [PATCH 20/33] simplify tui --- pkg/tui/colors.go | 5 ++ pkg/tui/config.go | 114 ---------------------------------------- pkg/tui/conventions.go | 5 -- pkg/tui/helpers.go | 8 --- pkg/tui/style.go | 29 +++++++--- pkg/tui/style_config.go | 52 ++++++++++++++++++ pkg/tui/theme.go | 39 ++++++-------- 7 files changed, 95 insertions(+), 157 deletions(-) delete mode 100644 pkg/tui/config.go create mode 100644 pkg/tui/style_config.go diff --git a/pkg/tui/colors.go b/pkg/tui/colors.go index dbba22a..c8e2fdf 100644 --- a/pkg/tui/colors.go +++ b/pkg/tui/colors.go @@ -4,6 +4,11 @@ import ( "github.com/charmbracelet/lipgloss" ) +type ColorPair struct { + Name string + Value lipgloss.Color +} + const ( White = lipgloss.Color("#fff") Black = lipgloss.Color("#000") diff --git a/pkg/tui/config.go b/pkg/tui/config.go deleted file mode 100644 index e1a58a9..0000000 --- a/pkg/tui/config.go +++ /dev/null @@ -1,114 +0,0 @@ -package tui - -import ( - "github.com/charmbracelet/lipgloss" -) - -type ColorConfig struct { - Text ComponentColor - TextEmphasis ComponentColor - Background ComponentColor - Border ComponentColor -} - -func NewNeutralColorComponentConfig() ColorConfig { - config := ColorConfig{ - Text: ComponentColor{}, - TextEmphasis: ComponentColor{}, - Background: ComponentColor{}, - Border: ComponentColor{}, - } - - return config -} - -func NewColorComponentConfig(baseColor lipgloss.Color) ColorConfig { - base := ColorToHex(baseColor) - - config := ColorConfig{ - Text: ComponentColor{ - Light: ComponentColorConfig{ - Color: base, - }, - }, - TextEmphasis: ComponentColor{ - Light: ComponentColorConfig{ - Color: base, - Filter: "shade", - Percent: 0.6, - }, - Dark: ComponentColorConfig{ - Color: base, - Filter: "tint", - Percent: 0.4, - }, - }, - Background: ComponentColor{ - Light: ComponentColorConfig{ - Color: base, - Filter: "tint", - Percent: 0.8, - }, - Dark: ComponentColorConfig{ - Color: base, - Filter: "shade", - Percent: 0.8, - }, - }, - Border: ComponentColor{ - Light: ComponentColorConfig{ - Color: base, - Filter: "tint", - Percent: 0.6, - }, - Dark: ComponentColorConfig{ - Color: base, - Filter: "shade", - Percent: 0.4, - }, - }, - } - - return config -} - -type ComponentColor struct { - Light ComponentColorConfig - Dark ComponentColorConfig -} - -func (cc ComponentColor) AdaptiveColor() lipgloss.AdaptiveColor { - result := lipgloss.AdaptiveColor{} - result.Light = cc.Light.AsHex() - result.Dark = cc.Dark.AsHex() - - return result -} - -type ComponentColorConfig struct { - Color string - MixColor string - Filter string - Percent float64 -} - -func (ccc ComponentColorConfig) AsHex() string { - switch ccc.Filter { - case "shade": - return ColorToHex(ShadeColor(ccc.Color, ccc.Percent)) - - case "tint": - return ColorToHex(TintColor(ccc.Color, ccc.Percent)) - - case "mix": - percent := ccc.Percent - if percent == 0 { - percent = 0.5 - } - - return ColorToHex(MixColors(ccc.Color, ccc.MixColor, percent)) - - default: - return ccc.Color - } -} diff --git a/pkg/tui/conventions.go b/pkg/tui/conventions.go index 4ac440f..cc684db 100644 --- a/pkg/tui/conventions.go +++ b/pkg/tui/conventions.go @@ -4,11 +4,6 @@ import "github.com/charmbracelet/lipgloss" const borderWidth = 2 -type ColorPair struct { - Name string - Value lipgloss.Color -} - var ( headerBorder = lipgloss.Border{ Top: "─", diff --git a/pkg/tui/helpers.go b/pkg/tui/helpers.go index 3ee7bfb..7d1d2c4 100644 --- a/pkg/tui/helpers.go +++ b/pkg/tui/helpers.go @@ -21,14 +21,6 @@ func TintColor(in string, percent float64) lipgloss.Color { return lipgloss.Color("#" + noire.NewHex(in).Tint(percent).Hex()) } -func MixColors(a, b string, weight float64) lipgloss.Color { - if weight < 0 || weight > 1 { - panic("MixColors [weight] must be between 0.0 and 1.0 (0.5 == 50%)") - } - - return lipgloss.Color("#" + noire.NewHex(a).Mix(noire.NewHex(b), weight).Hex()) -} - func ColorToHex(in lipgloss.Color) string { return string(in) } diff --git a/pkg/tui/style.go b/pkg/tui/style.go index 38763c0..795bb40 100644 --- a/pkg/tui/style.go +++ b/pkg/tui/style.go @@ -13,12 +13,12 @@ type Style struct { noColor bool } -func NewColor(config ColorConfig) Style { +func NewStyle(config StyleConfig) Style { style := Style{ - Text: config.Text.AdaptiveColor(), - TextEmphasis: config.TextEmphasis.AdaptiveColor(), - Background: config.Background.AdaptiveColor(), - Border: config.Border.AdaptiveColor(), + Text: config.Text, + TextEmphasis: config.TextEmphasis, + Background: config.Background, + Border: config.Border, } if len(style.Text.Dark) == 0 { @@ -28,7 +28,24 @@ func NewColor(config ColorConfig) Style { return style } -func NewStyle() Style { +func NewStyleFromColor(color lipgloss.Color) Style { + config := NewStyleConfig(color) + + style := Style{ + Text: config.Text, + TextEmphasis: config.TextEmphasis, + Background: config.Background, + Border: config.Border, + } + + if len(style.Text.Dark) == 0 { + style.Text.Dark = style.TextEmphasis.Dark + } + + return style +} + +func NewStyleWithoutColor() Style { return Style{ noColor: true, } diff --git a/pkg/tui/style_config.go b/pkg/tui/style_config.go new file mode 100644 index 0000000..21e8a9d --- /dev/null +++ b/pkg/tui/style_config.go @@ -0,0 +1,52 @@ +package tui + +import ( + "github.com/charmbracelet/lipgloss" +) + +type StyleConfig struct { + Text lipgloss.AdaptiveColor + TextEmphasis lipgloss.AdaptiveColor + Background lipgloss.AdaptiveColor + Border lipgloss.AdaptiveColor +} + +func NewStyleConfig(baseColor lipgloss.Color) StyleConfig { + base := ColorToHex(baseColor) + + config := StyleConfig{ + Text: lipgloss.AdaptiveColor{ + Light: transformColor(base, "", 0), + }, + TextEmphasis: lipgloss.AdaptiveColor{ + Light: transformColor(base, "shade", 0.6), + Dark: transformColor(base, "tint", 0.4), + }, + Background: lipgloss.AdaptiveColor{ + Light: transformColor(base, "tint", 0.8), + Dark: transformColor(base, "shade", 0.8), + }, + Border: lipgloss.AdaptiveColor{ + Light: transformColor(base, "tint", 0.6), + Dark: transformColor(base, "shade", 0.4), + }, + } + + return config +} + +func transformColor(base, filter string, percent float64) string { + switch filter { + case "shade": + return ColorToHex(ShadeColor(base, percent)) + + case "tint": + return ColorToHex(TintColor(base, percent)) + + case "mix": + panic("unexpected mix filter") + + default: + return base + } +} diff --git a/pkg/tui/theme.go b/pkg/tui/theme.go index aa9aa10..c59fd39 100644 --- a/pkg/tui/theme.go +++ b/pkg/tui/theme.go @@ -41,30 +41,21 @@ func (theme Theme) WriterPrinter(ctx context.Context, writer io.Writer) ThemeWri func NewTheme() Theme { theme := Theme{} - theme.Danger = NewColor(NewColorComponentConfig(Red)) - theme.Info = NewColor(NewColorComponentConfig(Cyan)) - theme.Light = NewColor(NewColorComponentConfig(Gray300)) - theme.Primary = NewColor(NewColorComponentConfig(Blue)) - theme.Secondary = NewColor(NewColorComponentConfig(Gray600)) - theme.Success = NewColor(NewColorComponentConfig(Green)) - theme.Warning = NewColor(NewColorComponentConfig(Yellow)) - theme.NoColor = NewStyle() - - dark := NewColorComponentConfig(Gray700) - - dark.TextEmphasis.Dark = ComponentColorConfig{ - Color: ColorToHex(Gray300), - } - - dark.Background.Dark = ComponentColorConfig{ - Color: "#1a1d20", - } - - dark.Border.Dark = ComponentColorConfig{ - Color: ColorToHex(Gray800), - } - - theme.Dark = NewColor(dark) + theme.Danger = NewStyleFromColor(Red) + theme.Info = NewStyleFromColor(Cyan) + theme.Light = NewStyleFromColor(Gray300) + theme.Primary = NewStyleFromColor(Blue) + theme.Secondary = NewStyleFromColor(Gray600) + theme.Success = NewStyleFromColor(Green) + theme.Warning = NewStyleFromColor(Yellow) + theme.NoColor = NewStyleWithoutColor() + + dark := NewStyleConfig(Gray700) + dark.TextEmphasis.Dark = ColorToHex(Gray300) + dark.Background.Dark = "#1a1d20" + dark.Border.Dark = ColorToHex(Gray800) + + theme.Dark = NewStyle(dark) return theme } From 09feb2ec196d26726a2b71be6ef07feb1bf08356 Mon Sep 17 00:00:00 2001 From: Christian Winther Date: Thu, 15 Feb 2024 20:19:30 +0100 Subject: [PATCH 21/33] simplify tui --- pkg/tui/helpers.go | 16 +++++++++++++ pkg/tui/style.go | 38 ++++++++++++++---------------- pkg/tui/style_config.go | 52 ----------------------------------------- pkg/tui/theme.go | 24 +++++++++---------- 4 files changed, 44 insertions(+), 86 deletions(-) delete mode 100644 pkg/tui/style_config.go diff --git a/pkg/tui/helpers.go b/pkg/tui/helpers.go index 7d1d2c4..4c0753f 100644 --- a/pkg/tui/helpers.go +++ b/pkg/tui/helpers.go @@ -24,3 +24,19 @@ func TintColor(in string, percent float64) lipgloss.Color { func ColorToHex(in lipgloss.Color) string { return string(in) } + +func transformColor(base, filter string, percent float64) string { + switch filter { + case "shade": + return ColorToHex(ShadeColor(base, percent)) + + case "tint": + return ColorToHex(TintColor(base, percent)) + + case "mix": + panic("unexpected mix filter") + + default: + return base + } +} diff --git a/pkg/tui/style.go b/pkg/tui/style.go index 795bb40..80c2628 100644 --- a/pkg/tui/style.go +++ b/pkg/tui/style.go @@ -13,29 +13,25 @@ type Style struct { noColor bool } -func NewStyle(config StyleConfig) Style { - style := Style{ - Text: config.Text, - TextEmphasis: config.TextEmphasis, - Background: config.Background, - Border: config.Border, - } - - if len(style.Text.Dark) == 0 { - style.Text.Dark = style.TextEmphasis.Dark - } - - return style -} - -func NewStyleFromColor(color lipgloss.Color) Style { - config := NewStyleConfig(color) +func NewStyle(baseColor lipgloss.Color) Style { + base := ColorToHex(baseColor) style := Style{ - Text: config.Text, - TextEmphasis: config.TextEmphasis, - Background: config.Background, - Border: config.Border, + Text: lipgloss.AdaptiveColor{ + Light: transformColor(base, "", 0), + }, + TextEmphasis: lipgloss.AdaptiveColor{ + Light: transformColor(base, "shade", 0.6), + Dark: transformColor(base, "tint", 0.4), + }, + Background: lipgloss.AdaptiveColor{ + Light: transformColor(base, "tint", 0.8), + Dark: transformColor(base, "shade", 0.8), + }, + Border: lipgloss.AdaptiveColor{ + Light: transformColor(base, "tint", 0.6), + Dark: transformColor(base, "shade", 0.4), + }, } if len(style.Text.Dark) == 0 { diff --git a/pkg/tui/style_config.go b/pkg/tui/style_config.go deleted file mode 100644 index 21e8a9d..0000000 --- a/pkg/tui/style_config.go +++ /dev/null @@ -1,52 +0,0 @@ -package tui - -import ( - "github.com/charmbracelet/lipgloss" -) - -type StyleConfig struct { - Text lipgloss.AdaptiveColor - TextEmphasis lipgloss.AdaptiveColor - Background lipgloss.AdaptiveColor - Border lipgloss.AdaptiveColor -} - -func NewStyleConfig(baseColor lipgloss.Color) StyleConfig { - base := ColorToHex(baseColor) - - config := StyleConfig{ - Text: lipgloss.AdaptiveColor{ - Light: transformColor(base, "", 0), - }, - TextEmphasis: lipgloss.AdaptiveColor{ - Light: transformColor(base, "shade", 0.6), - Dark: transformColor(base, "tint", 0.4), - }, - Background: lipgloss.AdaptiveColor{ - Light: transformColor(base, "tint", 0.8), - Dark: transformColor(base, "shade", 0.8), - }, - Border: lipgloss.AdaptiveColor{ - Light: transformColor(base, "tint", 0.6), - Dark: transformColor(base, "shade", 0.4), - }, - } - - return config -} - -func transformColor(base, filter string, percent float64) string { - switch filter { - case "shade": - return ColorToHex(ShadeColor(base, percent)) - - case "tint": - return ColorToHex(TintColor(base, percent)) - - case "mix": - panic("unexpected mix filter") - - default: - return base - } -} diff --git a/pkg/tui/theme.go b/pkg/tui/theme.go index c59fd39..5e98aa2 100644 --- a/pkg/tui/theme.go +++ b/pkg/tui/theme.go @@ -41,21 +41,19 @@ func (theme Theme) WriterPrinter(ctx context.Context, writer io.Writer) ThemeWri func NewTheme() Theme { theme := Theme{} - theme.Danger = NewStyleFromColor(Red) - theme.Info = NewStyleFromColor(Cyan) - theme.Light = NewStyleFromColor(Gray300) - theme.Primary = NewStyleFromColor(Blue) - theme.Secondary = NewStyleFromColor(Gray600) - theme.Success = NewStyleFromColor(Green) - theme.Warning = NewStyleFromColor(Yellow) + theme.Danger = NewStyle(Red) + theme.Info = NewStyle(Cyan) + theme.Light = NewStyle(Gray300) + theme.Primary = NewStyle(Blue) + theme.Secondary = NewStyle(Gray600) + theme.Success = NewStyle(Green) + theme.Warning = NewStyle(Yellow) theme.NoColor = NewStyleWithoutColor() - dark := NewStyleConfig(Gray700) - dark.TextEmphasis.Dark = ColorToHex(Gray300) - dark.Background.Dark = "#1a1d20" - dark.Border.Dark = ColorToHex(Gray800) - - theme.Dark = NewStyle(dark) + theme.Dark = NewStyle(Gray700) + theme.Dark.TextEmphasis.Dark = ColorToHex(Gray300) + theme.Dark.Background.Dark = "#1a1d20" + theme.Dark.Border.Dark = ColorToHex(Gray800) return theme } From 9cc4c5083d725fd4e9d9efea3b33c1592f687ae7 Mon Sep 17 00:00:00 2001 From: Christian Winther Date: Thu, 15 Feb 2024 20:38:30 +0100 Subject: [PATCH 22/33] simplify tui --- pkg/tui/box.go | 15 ------- pkg/tui/helpers.go | 2 +- pkg/tui/printer.go | 54 ++++++++++-------------- pkg/tui/style.go | 100 ++++++++++++++++++++++----------------------- pkg/tui/theme.go | 8 ++-- 5 files changed, 76 insertions(+), 103 deletions(-) delete mode 100644 pkg/tui/box.go diff --git a/pkg/tui/box.go b/pkg/tui/box.go deleted file mode 100644 index a5eb4d7..0000000 --- a/pkg/tui/box.go +++ /dev/null @@ -1,15 +0,0 @@ -package tui - -import "github.com/charmbracelet/lipgloss" - -type Box struct { - Header lipgloss.Style - Body lipgloss.Style -} - -func (b Box) Copy() Box { - return Box{ - Header: b.Header.Copy(), - Body: b.Body.Copy(), - } -} diff --git a/pkg/tui/helpers.go b/pkg/tui/helpers.go index 4c0753f..5d96dea 100644 --- a/pkg/tui/helpers.go +++ b/pkg/tui/helpers.go @@ -25,7 +25,7 @@ func ColorToHex(in lipgloss.Color) string { return string(in) } -func transformColor(base, filter string, percent float64) string { +func TransformColor(base, filter string, percent float64) string { switch filter { case "shade": return ColorToHex(ShadeColor(base, percent)) diff --git a/pkg/tui/printer.go b/pkg/tui/printer.go index 3ed040e..7621e02 100644 --- a/pkg/tui/printer.go +++ b/pkg/tui/printer.go @@ -113,21 +113,20 @@ type Printer interface { } type Print struct { - boxWidth int // Max width for strings when using WrapMode - writer io.Writer // Writer controls where implicit print output goes for [Print], [Printf], [Printfln] and [Println] - renderer *lipgloss.Renderer // The renderer responsible for providing the output and color management - color Style // Color config - textStyle lipgloss.Style - textEmphasis bool - boxStyles Box + boxWidth int // Max width for strings when using WrapMode + writer io.Writer // Writer controls where implicit print output goes for [Print], [Printf], [Printfln] and [Println] + renderer *lipgloss.Renderer // The renderer responsible for providing the output and color management + style Style // Color config + textStyle lipgloss.Style + boxHeaderStyle lipgloss.Style + boxBodyStyle lipgloss.Style } -func NewPrinter(color Style, renderer *lipgloss.Renderer, options ...PrinterOption) Print { +func NewPrinter(style Style, renderer *lipgloss.Renderer, options ...PrinterOption) Print { options = append([]PrinterOption{ WitBoxWidth(80), - WithColor(color), + WithStyle(style), WithRenderer(renderer), - WithEmphasis(false), }, options...) printer := &Print{} @@ -135,10 +134,8 @@ func NewPrinter(color Style, renderer *lipgloss.Renderer, options ...PrinterOpti option(printer) } - printer.boxStyles = printer.color.BoxStyles( - printer.renderer.NewStyle(), - printer.renderer.NewStyle(), - ) + printer.boxHeaderStyle = printer.style.BoxHeader() + printer.boxBodyStyle = printer.style.BoxBody() return *printer } @@ -207,13 +204,13 @@ func (p Print) Box(header string, bodies ...string) { body := strings.Join(bodies, " ") // Copy the box styles to avoid leaking changes to the styles - styles := p.boxStyles.Copy() + headerStyle, bodyStyle := p.boxHeaderStyle.Copy(), p.boxBodyStyle.Copy() // If there are no body, just render the header box directly if len(body) == 0 { fmt.Fprintln( p.writer, - styles.Header. + headerStyle. Width(p.boxWidth-borderWidth). Border(headerOnlyBorder). Render(header), @@ -223,8 +220,8 @@ func (p Print) Box(header string, bodies ...string) { } // Render the header and body box - boxHeader := styles.Header.Width(p.boxWidth - borderWidth).Render(header) - boxBody := styles.Body.Width(p.boxWidth - borderWidth).Render(body) + boxHeader := headerStyle.Width(p.boxWidth - borderWidth).Render(header) + boxBody := bodyStyle.Width(p.boxWidth - borderWidth).Render(body) // If a maxWidth is set, the boxes will be aligned automatically to the max if p.boxWidth > 0 { @@ -247,10 +244,10 @@ func (p Print) Box(header string, bodies ...string) { // Find the shortest box and (re)render it to the length of the longest one switch { case headerWidth > bodyWidth: - boxBody = styles.Body.Width(headerWidth).Render(body) + boxBody = bodyStyle.Width(headerWidth).Render(body) case headerWidth < bodyWidth: - boxHeader = styles.Header.Width(bodyWidth).Render(header) + boxHeader = headerStyle.Width(bodyWidth).Render(header) } fmt.Fprintln( @@ -331,9 +328,10 @@ func (p Print) printHelper(a ...any) string { // Printer options // ----------------------------------------------------- -func WithColor(color Style) PrinterOption { +func WithStyle(style Style) PrinterOption { return func(p *Print) { - p.color = color + p.style = style + p.textStyle = p.renderer.NewStyle().Inherit(style.TextStyle()) } } @@ -350,23 +348,15 @@ func WithTextStyle(style lipgloss.Style) PrinterOption { } } -func WithBoxStyle(style Box) PrinterOption { - return func(p *Print) { - p.boxStyles = style - } -} - func WithEmphasis(b bool) PrinterOption { return func(printer *Print) { - printer.textEmphasis = b - if b { - printer.textStyle = printer.renderer.NewStyle().Inherit(printer.color.TextEmphasisStyle()) + printer.textStyle = printer.renderer.NewStyle().Inherit(printer.style.TextEmphasisStyle()) return } - printer.textStyle = printer.renderer.NewStyle().Inherit(printer.color.TextStyle()) + printer.textStyle = printer.renderer.NewStyle().Inherit(printer.style.TextStyle()) } } diff --git a/pkg/tui/style.go b/pkg/tui/style.go index 80c2628..14f813f 100644 --- a/pkg/tui/style.go +++ b/pkg/tui/style.go @@ -5,10 +5,12 @@ import ( ) type Style struct { - Text lipgloss.AdaptiveColor - TextEmphasis lipgloss.AdaptiveColor - Background lipgloss.AdaptiveColor - Border lipgloss.AdaptiveColor + textColor lipgloss.AdaptiveColor + textStyle lipgloss.Style + textEmphasisColor lipgloss.AdaptiveColor + textEmphasisStyle lipgloss.Style + backgroundColor lipgloss.AdaptiveColor + borderColor lipgloss.AdaptiveColor noColor bool } @@ -17,33 +19,43 @@ func NewStyle(baseColor lipgloss.Color) Style { base := ColorToHex(baseColor) style := Style{ - Text: lipgloss.AdaptiveColor{ - Light: transformColor(base, "", 0), + textColor: lipgloss.AdaptiveColor{ + Light: TransformColor(base, "", 0), + Dark: TransformColor(base, "tint", 0.4), }, - TextEmphasis: lipgloss.AdaptiveColor{ - Light: transformColor(base, "shade", 0.6), - Dark: transformColor(base, "tint", 0.4), + textEmphasisColor: lipgloss.AdaptiveColor{ + Light: TransformColor(base, "shade", 0.6), + Dark: TransformColor(base, "tint", 0.4), }, - Background: lipgloss.AdaptiveColor{ - Light: transformColor(base, "tint", 0.8), - Dark: transformColor(base, "shade", 0.8), + backgroundColor: lipgloss.AdaptiveColor{ + Light: TransformColor(base, "tint", 0.8), + Dark: TransformColor(base, "shade", 0.8), }, - Border: lipgloss.AdaptiveColor{ - Light: transformColor(base, "tint", 0.6), - Dark: transformColor(base, "shade", 0.4), + borderColor: lipgloss.AdaptiveColor{ + Light: TransformColor(base, "tint", 0.6), + Dark: TransformColor(base, "shade", 0.4), }, } - if len(style.Text.Dark) == 0 { - style.Text.Dark = style.TextEmphasis.Dark - } + style.textStyle = lipgloss. + NewStyle(). + Foreground(style.textColor) + + style.textEmphasisStyle = lipgloss. + NewStyle(). + Bold(true). + Foreground(style.textEmphasisColor). + Background(style.backgroundColor). + BorderForeground(style.borderColor) return style } func NewStyleWithoutColor() Style { return Style{ - noColor: true, + noColor: true, + textStyle: lipgloss.NewStyle(), + textEmphasisStyle: lipgloss.NewStyle(), } } @@ -52,42 +64,28 @@ func (style Style) NewPrinter(renderer *lipgloss.Renderer, options ...PrinterOpt } func (style Style) TextStyle() lipgloss.Style { - if style.noColor { - return lipgloss.NewStyle() - } - - return lipgloss. - NewStyle(). - Foreground(style.Text) + return style.textStyle } -func (c Style) TextEmphasisStyle() lipgloss.Style { - if c.noColor { - return lipgloss.NewStyle() - } +func (style Style) TextEmphasisStyle() lipgloss.Style { + return style.textEmphasisStyle +} +func (style Style) BoxHeader() lipgloss.Style { return lipgloss.NewStyle(). - Foreground(c.TextEmphasis). - Background(c.Background). - Bold(true). - BorderForeground(c.Border) + Align(lipgloss.Center, lipgloss.Center). + Border(headerBorder). + BorderForeground(style.borderColor). + PaddingBottom(1). + PaddingTop(1). + Inherit(style.TextEmphasisStyle()) } -func (c Style) BoxStyles(header, body lipgloss.Style) Box { - return Box{ - Header: header. - Align(lipgloss.Center, lipgloss.Center). - Border(headerBorder). - BorderForeground(c.Border). - PaddingBottom(1). - PaddingTop(1). - Inherit(c.TextEmphasisStyle()), - - Body: body. - Align(lipgloss.Left). - Border(bodyBorder). - BorderForeground(c.Border). - BorderTop(false). - Padding(1), - } +func (style Style) BoxBody() lipgloss.Style { + return lipgloss.NewStyle(). + Align(lipgloss.Left). + Border(bodyBorder). + BorderForeground(style.borderColor). + BorderTop(false). + Padding(1) } diff --git a/pkg/tui/theme.go b/pkg/tui/theme.go index 5e98aa2..ebc75b0 100644 --- a/pkg/tui/theme.go +++ b/pkg/tui/theme.go @@ -44,16 +44,16 @@ func NewTheme() Theme { theme.Danger = NewStyle(Red) theme.Info = NewStyle(Cyan) theme.Light = NewStyle(Gray300) + theme.NoColor = NewStyleWithoutColor() theme.Primary = NewStyle(Blue) theme.Secondary = NewStyle(Gray600) theme.Success = NewStyle(Green) theme.Warning = NewStyle(Yellow) - theme.NoColor = NewStyleWithoutColor() theme.Dark = NewStyle(Gray700) - theme.Dark.TextEmphasis.Dark = ColorToHex(Gray300) - theme.Dark.Background.Dark = "#1a1d20" - theme.Dark.Border.Dark = ColorToHex(Gray800) + theme.Dark.textEmphasisColor.Dark = ColorToHex(Gray300) + theme.Dark.backgroundColor.Dark = "#1a1d20" + theme.Dark.borderColor.Dark = ColorToHex(Gray800) return theme } From ac6b5e60d506c42eda30280cc80e38cadc6d4406 Mon Sep 17 00:00:00 2001 From: Christian Winther Date: Thu, 15 Feb 2024 20:58:45 +0100 Subject: [PATCH 23/33] simplify tui --- cmd/disable/disable.go | 4 +-- cmd/enable/enable.go | 4 +-- cmd/groups/groups.go | 6 ++-- cmd/root.go | 4 +-- cmd/set/set.go | 8 ++--- cmd/update/update.go | 14 ++++---- cmd/validate/validate.go | 12 +++---- pkg/render/output_colorized.go | 18 +++++----- pkg/tui/context.go | 6 ++-- pkg/tui/style.go | 9 ++--- pkg/tui/theme.go | 41 ++++++++++++---------- pkg/tui/theme_writer.go | 64 ++++++++++++++++++++++++++-------- pkg/validation/explain.go | 28 +++++++-------- 13 files changed, 127 insertions(+), 91 deletions(-) diff --git a/cmd/disable/disable.go b/cmd/disable/disable.go index 7125931..390e6af 100644 --- a/cmd/disable/disable.go +++ b/cmd/disable/disable.go @@ -39,7 +39,7 @@ func NewCommand() *cobra.Command { stdout, stderr := tui.PrintersFromContext(cmd.Context()) if !existing.Enabled { - stderr.Color(tui.Warning).Printfln("WARNING: The key [%s] is already disabled", key) + stderr.Warning().Printfln("WARNING: The key [%s] is already disabled", key) return nil } @@ -50,7 +50,7 @@ func NewCommand() *cobra.Command { return fmt.Errorf("could not save file: %w", err) } - stdout.Color(tui.Success).Printfln("Key [%s] was successfully disabled", key) + stdout.Success().Printfln("Key [%s] was successfully disabled", key) return nil }, diff --git a/cmd/enable/enable.go b/cmd/enable/enable.go index 257051a..26f22a4 100644 --- a/cmd/enable/enable.go +++ b/cmd/enable/enable.go @@ -39,7 +39,7 @@ func NewCommand() *cobra.Command { stdout, stderr := tui.PrintersFromContext(cmd.Context()) if existing.Enabled { - stderr.Color(tui.Warning).Printfln("WARNING: The key [%s] is already enabled", key) + stderr.Warning().Printfln("WARNING: The key [%s] is already enabled", key) } existing.Enable() @@ -48,7 +48,7 @@ func NewCommand() *cobra.Command { return fmt.Errorf("could not save file: %w", err) } - stdout.Color(tui.Success).Printfln("Key [%s] was successfully enabled", key) + stdout.Success().Printfln("Key [%s] was successfully enabled", key) return nil }, diff --git a/cmd/groups/groups.go b/cmd/groups/groups.go index 30d21a8..bcffa6e 100644 --- a/cmd/groups/groups.go +++ b/cmd/groups/groups.go @@ -32,10 +32,10 @@ func NewCommand() *cobra.Command { width := longesGroupName(groups) stdout := tui.WriterFromContext(cmd.Context(), tui.Stdout) - secondary := stdout.Color(tui.Secondary) - primary := stdout.Color(tui.Primary) + secondary := stdout.Secondary() + primary := stdout.Primary() - stdout.Color(tui.Info).Box("Groups in " + filename) + stdout.Info().Box("Groups in " + filename) for _, group := range groups { primary.Printf("%-"+strconv.Itoa(width)+"s", slug.Make(group.String())) diff --git a/cmd/root.go b/cmd/root.go index 156725d..86ed521 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -78,8 +78,8 @@ func RunCommand(ctx context.Context, args []string, stdout io.Writer, stderr io. command, err := root.ExecuteC() if err != nil { stderr := tui.WriterFromContext(ctx, tui.Stderr) - stderr.Color(tui.Danger).Copy(tui.WithEmphasis(true)).Printfln("%s %+v", command.ErrPrefix(), err) - stderr.Color(tui.Info).Printfln("Run '%v --help' for usage.", command.CommandPath()) + stderr.Danger().Copy(tui.WithEmphasis(true)).Printfln("%s %+v", command.ErrPrefix(), err) + stderr.Info().Printfln("Run '%v --help' for usage.", command.CommandPath()) } return command, err diff --git a/cmd/set/set.go b/cmd/set/set.go index 2d2c0e5..561df17 100644 --- a/cmd/set/set.go +++ b/cmd/set/set.go @@ -117,12 +117,12 @@ func runE(cmd *cobra.Command, args []string) error { assignment, warnings, err := upserter.Upsert(cmd.Context(), assignment) if warnings != nil { - stderr.Color(tui.Warning).Println("WARNING:", warnings) + stderr.Warning().Println("WARNING:", warnings) } if err != nil { z := validation.NewError(assignment, err) - stderr.Color(tui.NoColor).Println(validation.Explain(cmd.Context(), document, z, z, false, true)) + stderr.NoColor().Println(validation.Explain(cmd.Context(), document, z, z, false, true)) if shared.BoolWithInverseValue(cmd.Flags(), "validate") { allErrors = multierr.Append(allErrors, err) @@ -131,7 +131,7 @@ func runE(cmd *cobra.Command, args []string) error { } } - stdout.Color(tui.Success).Printfln("Key [%s] was successfully upserted", key) + stdout.Success().Printfln("Key [%s] was successfully upserted", key) } if allErrors != nil { @@ -146,7 +146,7 @@ func runE(cmd *cobra.Command, args []string) error { return fmt.Errorf("failed to save file: %w", err) } - stdout.Color(tui.Success).Println("File was successfully saved") + stdout.Success().Println("File was successfully saved") return nil } diff --git a/cmd/update/update.go b/cmd/update/update.go index d8f9aa4..3d464ec 100644 --- a/cmd/update/update.go +++ b/cmd/update/update.go @@ -42,13 +42,13 @@ func runE(cmd *cobra.Command, args []string) error { stdout, stderr := tui.PrintersFromContext(cmd.Context()) - dark := stdout.Color(tui.Dark) - info := stdout.Color(tui.Info) - danger := stdout.Color(tui.Danger) - dangerEmphasis := stdout.Color(tui.Danger).Copy(tui.WithEmphasis(true)) - success := stdout.Color(tui.Success) - primary := stdout.Color(tui.Primary) - warningStderr := stderr.Color(tui.Warning) + dark := stdout.Dark() + info := stdout.Info() + danger := stdout.Danger() + dangerEmphasis := stdout.Danger().Copy(tui.WithEmphasis(true)) + success := stdout.Success() + primary := stdout.Primary() + warningStderr := stderr.Warning() info.Box("Starting update of " + filename + " from upstream") info.Println() diff --git a/cmd/validate/validate.go b/cmd/validate/validate.go index 1542030..61f4d76 100644 --- a/cmd/validate/validate.go +++ b/cmd/validate/validate.go @@ -61,7 +61,7 @@ func runE(cmd *cobra.Command, args []string) error { warn, err := env.InterpolateAll() if warn != nil { - stderr.Color(tui.Warning).Printfln("%+v", warn) + stderr.Warning().Printfln("%+v", warn) } if err != nil { @@ -74,12 +74,12 @@ func runE(cmd *cobra.Command, args []string) error { res := validation.Validate(cmd.Context(), env, handlers, ignoreRules) if len(res) == 0 { - stderr.Color(tui.Success).Box("No validation errors found") + stderr.Success().Box("No validation errors found") return nil } - danger := stderr.Color(tui.Danger) + danger := stderr.Danger() danger.Box(fmt.Sprintf("%d validation errors found", len(res))) danger.Println() @@ -98,17 +98,17 @@ func runE(cmd *cobra.Command, args []string) error { newRes := validation.Validate(cmd.Context(), env, handlers, ignoreRules) if len(newRes) == 0 { - stderr.Color(tui.Success).Println("All validation errors fixed") + stderr.Success().Println("All validation errors fixed") return nil } diff := len(res) - len(newRes) if diff > 0 { - stderr.Color(tui.Warning). + stderr.Warning(). Box( fmt.Sprintf("%d validation errors left", len(newRes)), - stderr.Color(tui.Success).Sprintf("%d validation errors was fixed", diff), + stderr.Success().Sprintf("%d validation errors was fixed", diff), ) } diff --git a/pkg/render/output_colorized.go b/pkg/render/output_colorized.go index f015533..fefc3ff 100644 --- a/pkg/render/output_colorized.go +++ b/pkg/render/output_colorized.go @@ -15,7 +15,7 @@ type ColorizedOutput struct{} func (ColorizedOutput) GroupBanner(ctx context.Context, group *ast.Group, settings Settings) *Lines { var buf bytes.Buffer - out := tui.ThemeFromContext(ctx).WriterPrinter(ctx, &buf).Color(tui.Success) + out := tui.ThemeFromContext(ctx).NewWriterWriter(ctx, &buf).Success() out.Println("################################################################################") out.ApplyStyle(tui.Bold).Println(group.Name) @@ -27,10 +27,10 @@ func (ColorizedOutput) GroupBanner(ctx context.Context, group *ast.Group, settin func (ColorizedOutput) Assignment(ctx context.Context, assignment *ast.Assignment, settings Settings) *Lines { var buf bytes.Buffer - printer := tui.ThemeFromContext(ctx).WriterPrinter(ctx, &buf) + printer := tui.ThemeFromContext(ctx).NewWriterWriter(ctx, &buf) if !assignment.Enabled { - printer.Color(tui.Danger).Print("#") + printer.Danger().Print("#") } val := assignment.Literal @@ -39,11 +39,11 @@ func (ColorizedOutput) Assignment(ctx context.Context, assignment *ast.Assignmen val = assignment.Interpolated } - printer.Color(tui.Primary).Print(assignment.Name) - printer.Color(tui.Dark).Print("=") - printer.Color(tui.Success).Print(assignment.Quote) - printer.Color(tui.Warning).Print(val) - printer.Color(tui.Success).Print(assignment.Quote) + printer.Primary().Print(assignment.Name) + printer.Dark().Print("=") + printer.Success().Print(assignment.Quote) + printer.Warning().Print(val) + printer.Success().Print(assignment.Quote) return NewLinesCollection().Add(buf.String()) } @@ -51,7 +51,7 @@ func (ColorizedOutput) Assignment(ctx context.Context, assignment *ast.Assignmen func (ColorizedOutput) Comment(ctx context.Context, comment *ast.Comment, settings Settings) *Lines { var buf bytes.Buffer - out := tui.ThemeFromContext(ctx).WriterPrinter(ctx, &buf).Color(tui.Success) + out := tui.ThemeFromContext(ctx).NewWriterWriter(ctx, &buf).Success() if comment.Annotation == nil { out.Print(comment.Value) diff --git a/pkg/tui/context.go b/pkg/tui/context.go index f764385..0f5535a 100644 --- a/pkg/tui/context.go +++ b/pkg/tui/context.go @@ -30,8 +30,8 @@ func CreateContext(ctx context.Context, stdout, stderr io.Writer) context.Contex ctx = context.WithValue(ctx, themeContextValue, theme) ctx = context.WithValue(ctx, colorProfileContextValue, stdoutOutput.ColorProfile()) - ctx = context.WithValue(ctx, Stdout, theme.Printer(stdoutOutput)) - ctx = context.WithValue(ctx, Stderr, theme.Printer(stderrOutput)) + ctx = context.WithValue(ctx, Stdout, theme.NewWriter(stdoutOutput)) + ctx = context.WithValue(ctx, Stderr, theme.NewWriter(stderrOutput)) return ctx } @@ -49,7 +49,7 @@ func WriterFromContext(ctx context.Context, key printerContextKey) ThemeWriter { } func ColorPrinterFromContext(ctx context.Context, key printerContextKey, color colorType) Printer { - return ctx.Value(key).(ThemeWriter).Color(color) //nolint:forcetypeassert + return ctx.Value(key).(*ThemeWriter).Color(color) //nolint:forcetypeassert } func PrintersFromContext(ctx context.Context) (ThemeWriter, ThemeWriter) { diff --git a/pkg/tui/style.go b/pkg/tui/style.go index 14f813f..670db74 100644 --- a/pkg/tui/style.go +++ b/pkg/tui/style.go @@ -11,8 +11,6 @@ type Style struct { textEmphasisStyle lipgloss.Style backgroundColor lipgloss.AdaptiveColor borderColor lipgloss.AdaptiveColor - - noColor bool } func NewStyle(baseColor lipgloss.Color) Style { @@ -52,11 +50,8 @@ func NewStyle(baseColor lipgloss.Color) Style { } func NewStyleWithoutColor() Style { - return Style{ - noColor: true, - textStyle: lipgloss.NewStyle(), - textEmphasisStyle: lipgloss.NewStyle(), - } + // Since all lipgloss.Styles are non-pointers, they are by default an empty / unstyled version of themselves + return Style{} } func (style Style) NewPrinter(renderer *lipgloss.Renderer, options ...PrinterOption) Print { diff --git a/pkg/tui/theme.go b/pkg/tui/theme.go index ebc75b0..64be54e 100644 --- a/pkg/tui/theme.go +++ b/pkg/tui/theme.go @@ -20,24 +20,6 @@ type Theme struct { Warning Style } -func (theme Theme) Printer(writer *lipgloss.Renderer) ThemeWriter { - return ThemeWriter{ - writer: writer, - theme: theme, - cache: make(map[colorType]Printer), - } -} - -func (theme Theme) WriterPrinter(ctx context.Context, writer io.Writer) ThemeWriter { - options := []termenv.OutputOption{} - - if ColorProfile(ctx) != termenv.Ascii { - options = append(options, termenv.WithTTY(true)) - } - - return theme.Printer(lipgloss.NewRenderer(writer, options...)) -} - func NewTheme() Theme { theme := Theme{} @@ -57,3 +39,26 @@ func NewTheme() Theme { return theme } + +func (theme Theme) NewWriter(writer *lipgloss.Renderer) ThemeWriter { + return ThemeWriter{ + writer: writer, + theme: theme, + cache: make(map[colorType]Printer), + } +} + +func (theme Theme) NewWriterWriter(ctx context.Context, writer io.Writer) ThemeWriter { + var options []termenv.OutputOption + + // If the primary color profile is in color mode, enforce TTY to keep coloring on + if profile := ColorProfile(ctx); profile != termenv.Ascii { + options = append( + options, + termenv.WithTTY(true), + termenv.WithProfile(profile), + ) + } + + return theme.NewWriter(lipgloss.NewRenderer(writer, options...)) +} diff --git a/pkg/tui/theme_writer.go b/pkg/tui/theme_writer.go index df82bf8..060e61f 100644 --- a/pkg/tui/theme_writer.go +++ b/pkg/tui/theme_writer.go @@ -7,7 +7,7 @@ import ( type colorType int const ( - Danger colorType = iota + Danger colorType = 1 << iota Dark Info Light @@ -24,8 +24,44 @@ type ThemeWriter struct { writer *lipgloss.Renderer } -func (tp ThemeWriter) Color(colorType colorType) Printer { - if printer, ok := tp.cache[colorType]; ok { +func (tw ThemeWriter) Danger() Printer { + return tw.Color(Danger) +} + +func (tw ThemeWriter) Dark() Printer { + return tw.Color(Dark) +} + +func (tw ThemeWriter) Info() Printer { + return tw.Color(Info) +} + +func (tw ThemeWriter) Light() Printer { + return tw.Color(Light) +} + +func (tw ThemeWriter) NoColor() Printer { + return tw.Color(NoColor) +} + +func (tw ThemeWriter) Primary() Printer { + return tw.Color(Primary) +} + +func (tw ThemeWriter) Secondary() Printer { + return tw.Color(Secondary) +} + +func (tw ThemeWriter) Success() Printer { + return tw.Color(Success) +} + +func (tw ThemeWriter) Warning() Printer { + return tw.Color(Warning) +} + +func (tw ThemeWriter) Color(colorType colorType) Printer { + if printer, ok := tw.cache[colorType]; ok { return printer } @@ -33,34 +69,34 @@ func (tp ThemeWriter) Color(colorType colorType) Printer { switch colorType { case Danger: - style = tp.theme.Danger + style = tw.theme.Danger case Dark: - style = tp.theme.Dark + style = tw.theme.Dark case Info: - style = tp.theme.Info + style = tw.theme.Info case Light: - style = tp.theme.Light + style = tw.theme.Light case Primary: - style = tp.theme.Primary + style = tw.theme.Primary case Secondary: - style = tp.theme.Secondary + style = tw.theme.Secondary case Success: - style = tp.theme.Success + style = tw.theme.Success case Warning: - style = tp.theme.Warning + style = tw.theme.Warning case NoColor: - style = tp.theme.NoColor + style = tw.theme.NoColor } - tp.cache[colorType] = style.NewPrinter(tp.writer) + tw.cache[colorType] = style.NewPrinter(tw.writer) - return tp.cache[colorType] + return tw.cache[colorType] } diff --git a/pkg/validation/explain.go b/pkg/validation/explain.go index f28dbe9..3fe1018 100644 --- a/pkg/validation/explain.go +++ b/pkg/validation/explain.go @@ -22,12 +22,12 @@ type multiError interface { func Explain(ctx context.Context, doc *ast.Document, inputError any, keyErr ValidationError, applyFixer, showField bool) string { var buff bytes.Buffer - printer := tui.ThemeFromContext(ctx).WriterPrinter(ctx, &buff) - dark := printer.Color(tui.Dark) - bold := printer.Color(tui.Warning).Copy(tui.WithEmphasis(true)) - danger := printer.Color(tui.Danger) - light := printer.Color(tui.Light) - primary := printer.Color(tui.Primary) + printer := tui.ThemeFromContext(ctx).NewWriterWriter(ctx, &buff) + dark := printer.Dark() + bold := printer.Warning().Copy(tui.WithEmphasis(true)) + danger := printer.Danger() + light := printer.Light() + primary := printer.Primary() stderr := tui.WriterFromContext(ctx, tui.Stderr) @@ -135,7 +135,7 @@ func Explain(ctx context.Context, doc *ast.Document, inputError any, keyErr Vali } if askToFix { - stderr.Color(tui.NoColor).Println(buff.String()) + stderr.NoColor().Println(buff.String()) buff.Reset() AskToSetValue(ctx, doc, keyErr.Assignment) @@ -162,24 +162,24 @@ func AskToCreateDirectory(ctx context.Context, path string) { Value(&confirm). Run() if err != nil { - stderr.Color(tui.Warning).Println(" Prompt cancelled: " + err.Error()) + stderr.Warning().Println(" Prompt cancelled: " + err.Error()) return } if !confirm { - stderr.Color(tui.Warning).Println(" Prompt cancelled") + stderr.Warning().Println(" Prompt cancelled") return } if err := os.MkdirAll(path, os.ModePerm); err != nil { - stderr.Color(tui.Danger).Println(" Could not create directory: " + err.Error()) + stderr.Danger().Println(" Could not create directory: " + err.Error()) return } - stderr.Color(tui.Success).Println(" Directory was successfully created") + stderr.Success().Println(" Directory was successfully created") } func AskToSetValue(ctx context.Context, doc *ast.Document, assignment *ast.Assignment) { @@ -207,17 +207,17 @@ func AskToSetValue(ctx context.Context, doc *ast.Document, assignment *ast.Assig Value(&value). Run() if err != nil { - stderr.Color(tui.Warning).Println(" Prompt cancelled: " + err.Error()) + stderr.Warning().Println(" Prompt cancelled: " + err.Error()) return } assignment.Literal = value if err := pkg.Save(ctx, assignment.Position.File, doc); err != nil { - stderr.Color(tui.Danger).Println(" Could not update key with value [" + value + "]: " + err.Error()) + stderr.Danger().Println(" Could not update key with value [" + value + "]: " + err.Error()) return } - stderr.Color(tui.Success).Println(" Successfully updated key with value [" + value + "]") + stderr.Success().Println(" Successfully updated key with value [" + value + "]") } From 489f158c3a36bc9fed55b07d8f8e66ba6368b6e1 Mon Sep 17 00:00:00 2001 From: Christian Winther Date: Thu, 15 Feb 2024 21:04:46 +0100 Subject: [PATCH 24/33] simplify tui --- pkg/render/output_colorized.go | 6 +++--- pkg/tui/theme.go | 4 ++-- pkg/validation/explain.go | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/render/output_colorized.go b/pkg/render/output_colorized.go index fefc3ff..a9eac96 100644 --- a/pkg/render/output_colorized.go +++ b/pkg/render/output_colorized.go @@ -15,7 +15,7 @@ type ColorizedOutput struct{} func (ColorizedOutput) GroupBanner(ctx context.Context, group *ast.Group, settings Settings) *Lines { var buf bytes.Buffer - out := tui.ThemeFromContext(ctx).NewWriterWriter(ctx, &buf).Success() + out := tui.NewWriter(ctx, &buf).Success() out.Println("################################################################################") out.ApplyStyle(tui.Bold).Println(group.Name) @@ -27,7 +27,7 @@ func (ColorizedOutput) GroupBanner(ctx context.Context, group *ast.Group, settin func (ColorizedOutput) Assignment(ctx context.Context, assignment *ast.Assignment, settings Settings) *Lines { var buf bytes.Buffer - printer := tui.ThemeFromContext(ctx).NewWriterWriter(ctx, &buf) + printer := tui.NewWriter(ctx, &buf) if !assignment.Enabled { printer.Danger().Print("#") @@ -51,7 +51,7 @@ func (ColorizedOutput) Assignment(ctx context.Context, assignment *ast.Assignmen func (ColorizedOutput) Comment(ctx context.Context, comment *ast.Comment, settings Settings) *Lines { var buf bytes.Buffer - out := tui.ThemeFromContext(ctx).NewWriterWriter(ctx, &buf).Success() + out := tui.NewWriter(ctx, &buf).Success() if comment.Annotation == nil { out.Print(comment.Value) diff --git a/pkg/tui/theme.go b/pkg/tui/theme.go index 64be54e..fbbdb21 100644 --- a/pkg/tui/theme.go +++ b/pkg/tui/theme.go @@ -48,7 +48,7 @@ func (theme Theme) NewWriter(writer *lipgloss.Renderer) ThemeWriter { } } -func (theme Theme) NewWriterWriter(ctx context.Context, writer io.Writer) ThemeWriter { +func NewWriter(ctx context.Context, writer io.Writer) ThemeWriter { var options []termenv.OutputOption // If the primary color profile is in color mode, enforce TTY to keep coloring on @@ -60,5 +60,5 @@ func (theme Theme) NewWriterWriter(ctx context.Context, writer io.Writer) ThemeW ) } - return theme.NewWriter(lipgloss.NewRenderer(writer, options...)) + return ThemeFromContext(ctx).NewWriter(lipgloss.NewRenderer(writer, options...)) } diff --git a/pkg/validation/explain.go b/pkg/validation/explain.go index 3fe1018..fa11918 100644 --- a/pkg/validation/explain.go +++ b/pkg/validation/explain.go @@ -22,7 +22,7 @@ type multiError interface { func Explain(ctx context.Context, doc *ast.Document, inputError any, keyErr ValidationError, applyFixer, showField bool) string { var buff bytes.Buffer - printer := tui.ThemeFromContext(ctx).NewWriterWriter(ctx, &buff) + printer := tui.NewWriter(ctx, &buff) dark := printer.Dark() bold := printer.Warning().Copy(tui.WithEmphasis(true)) danger := printer.Danger() From ead081ff300162f7b0e4c2bc72cf504516dac416 Mon Sep 17 00:00:00 2001 From: Christian Winther Date: Thu, 15 Feb 2024 21:06:23 +0100 Subject: [PATCH 25/33] simplify tui --- pkg/validation/explain.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/pkg/validation/explain.go b/pkg/validation/explain.go index fa11918..5d85095 100644 --- a/pkg/validation/explain.go +++ b/pkg/validation/explain.go @@ -22,12 +22,13 @@ type multiError interface { func Explain(ctx context.Context, doc *ast.Document, inputError any, keyErr ValidationError, applyFixer, showField bool) string { var buff bytes.Buffer - printer := tui.NewWriter(ctx, &buff) - dark := printer.Dark() - bold := printer.Warning().Copy(tui.WithEmphasis(true)) - danger := printer.Danger() - light := printer.Light() - primary := printer.Primary() + writer := tui.NewWriter(ctx, &buff) + + dark := writer.Dark() + bold := writer.Warning().Copy(tui.WithEmphasis(true)) + danger := writer.Danger() + light := writer.Light() + primary := writer.Primary() stderr := tui.WriterFromContext(ctx, tui.Stderr) From 68daafb8de0842da634f8421d506f7b751f514f3 Mon Sep 17 00:00:00 2001 From: Christian Winther Date: Thu, 15 Feb 2024 21:26:15 +0100 Subject: [PATCH 26/33] renaming tui --- pkg/render/render_test.go | 2 +- pkg/tui/context.go | 10 +- pkg/tui/printer.go | 212 ++++++++++++++------------------------ pkg/tui/style.go | 2 +- pkg/tui/theme.go | 6 +- pkg/tui/theme_writer.go | 102 ------------------ pkg/tui/writer.go | 102 ++++++++++++++++++ 7 files changed, 191 insertions(+), 245 deletions(-) delete mode 100644 pkg/tui/theme_writer.go create mode 100644 pkg/tui/writer.go diff --git a/pkg/render/render_test.go b/pkg/render/render_test.go index bf49249..197c798 100644 --- a/pkg/render/render_test.go +++ b/pkg/render/render_test.go @@ -67,7 +67,7 @@ func TestFormatter(t *testing.T) { env, err := pkg.Load(tt.filename) require.NoError(t, err) - golden.Assert(t, tt.name, []byte(render.NewFormatter().Statement(context.TODO(), env).String())) + golden.Assert(t, tt.name, []byte(render.NewFormatter().Statement(context.Background(), env).String())) }) } } diff --git a/pkg/tui/context.go b/pkg/tui/context.go index 0f5535a..a4b467c 100644 --- a/pkg/tui/context.go +++ b/pkg/tui/context.go @@ -44,14 +44,14 @@ func ColorProfile(ctx context.Context) termenv.Profile { return ctx.Value(colorProfileContextValue).(termenv.Profile) //nolint:forcetypeassert } -func WriterFromContext(ctx context.Context, key printerContextKey) ThemeWriter { - return ctx.Value(key).(ThemeWriter) //nolint:forcetypeassert +func WriterFromContext(ctx context.Context, key printerContextKey) Writer { + return ctx.Value(key).(Writer) //nolint:forcetypeassert } func ColorPrinterFromContext(ctx context.Context, key printerContextKey, color colorType) Printer { - return ctx.Value(key).(*ThemeWriter).Color(color) //nolint:forcetypeassert + return ctx.Value(key).(*Writer).Color(color) //nolint:forcetypeassert } -func PrintersFromContext(ctx context.Context) (ThemeWriter, ThemeWriter) { - return ctx.Value(Stdout).(ThemeWriter), ctx.Value(Stderr).(ThemeWriter) //nolint:forcetypeassert +func PrintersFromContext(ctx context.Context) (Writer, Writer) { + return ctx.Value(Stdout).(Writer), ctx.Value(Stderr).(Writer) //nolint:forcetypeassert } diff --git a/pkg/tui/printer.go b/pkg/tui/printer.go index 7621e02..7416148 100644 --- a/pkg/tui/printer.go +++ b/pkg/tui/printer.go @@ -15,148 +15,65 @@ var Bold = func(s *lipgloss.Style) { s.Bold(true) } -type PrinterOption func(p *Print) +type PrinterOption func(p *Printer) // Printer mirrors the [fmt] package print/sprint functions, wraps them in a [lipgloss.Style] -// and an optional [WordWrap] configuration with a configured [MaxWidth]. +// and an optional [WordWrap] configuration with a configured [BoxWidth]. // -// Additionally, [Print*] methods writes to the configured [Writer] instead of [os.Stdout] -type Printer interface { - // ---------------------------------------- - // print to a specific io.Writer - // ---------------------------------------- - - // Fprint mirrors [fmt.Fprint] signature and behavior, with the configured style - // and (optional) word wrapping applied - Fprint(w io.Writer, a ...any) (n int, err error) - - // Fprintf mirrors [fmt.Fprintf] signature and behavior, with the configured style - // and (optional) word wrapping applied - Fprintf(w io.Writer, format string, a ...any) (n int, err error) - - // Fprintfln mirrors [fmt.Fprintfln] signature and behavior, with the configured style - // and (optional) word wrapping applied - Fprintfln(w io.Writer, format string, a ...any) (n int, err error) - - // Fprintln mirrors [fmt.Fprintln] signature and behavior, with the configured style - // and (optional) word wrapping applied - Fprintln(w io.Writer, a ...any) (n int, err error) - - // ---------------------------------------- - // print to the default io.Writer - // ---------------------------------------- - - // Print mirrors [fmt.Print] signature and behavior, with the configured style - // and (optional) word wrapping applied. - // - // Instead of writing to [os.Stdout] it will write to the configured [io.Writer]. - Print(a ...any) (n int, err error) - - // Printf mirrors [fmt.Printf] signature and behavior, with the configured style - // and (optional) word wrapping applied. - // - // Instead of writing to [os.Stdout] it will write to the configured [io.Writer]. - Printf(format string, a ...any) (n int, err error) - - // Printfln behaves like [fmt.Printf] but supports the [formatter] signature. - // - // This does *not* map to a Go native printer, but a mix for formatting + newline - Printfln(format string, a ...any) (n int, err error) - - // Println mirrors [fmt.Println] signature and behavior, with the configured style - // and (optional) word wrapping applied. - // - // Instead of writing to [os.Stdout] it will write to the configured [io.Writer]. - Println(a ...any) (n int, err error) - - // ---------------------------------------- - // return string - // ---------------------------------------- - - // Sprint mirrors [fmt.Sprint] signature and behavior, with the configured style - // and (optional) word wrapping applied. - Sprint(a ...any) string - - // Sprintf mirrors [fmt.Sprintf] signature and behavior, with the configured style - // and (optional) word wrapping applied. - Sprintf(format string, a ...any) string - - // Sprintfln behaves like [fmt.Sprintln] but supports the [formatter] signature. - // - // This does *not* map to a Go native printer, but a mix for formatting + newline - Sprintfln(format string, a ...any) string - - // Sprintln mirrors [fmt.Sprintln] signature and behavior, with the configured style - // and (optional) word wrapping applied. - Sprintln(a ...any) string - - // ---------------------------------------- - // helper methods - // ---------------------------------------- - - Copy(options ...PrinterOption) Print - - // GetMaxWidth returns the configured [MaxWidth] for word wrapping - MaxWidth() int - - // TextStyle returns a *copy* of the current [lipgloss.Style] - Style() lipgloss.Style - - // ApplyTextStyle returns a new copy of [StylePrint] instance with the [Style] based on the callback changes - ApplyStyle(changer StyleChanger) Print - - // Writer returns the configured [io.Writer] - Writer() io.Writer - - // Create a visual box with the printer style - Box(header string, body ...string) -} - -type Print struct { +// Additionally, [Printer*] methods writes to the configured [Writer] instead of [os.Stdout] +type Printer struct { boxWidth int // Max width for strings when using WrapMode writer io.Writer // Writer controls where implicit print output goes for [Print], [Printf], [Printfln] and [Println] renderer *lipgloss.Renderer // The renderer responsible for providing the output and color management - style Style // Color config + style Style // Style config textStyle lipgloss.Style boxHeaderStyle lipgloss.Style boxBodyStyle lipgloss.Style } -func NewPrinter(style Style, renderer *lipgloss.Renderer, options ...PrinterOption) Print { +func NewPrinter(style Style, renderer *lipgloss.Renderer, options ...PrinterOption) Printer { options = append([]PrinterOption{ WitBoxWidth(80), WithStyle(style), WithRenderer(renderer), }, options...) - printer := &Print{} + printer := &Printer{} for _, option := range options { option(printer) } - printer.boxHeaderStyle = printer.style.BoxHeader() - printer.boxBodyStyle = printer.style.BoxBody() + printer.boxHeaderStyle = style.BoxHeader() + printer.boxBodyStyle = style.BoxBody() return *printer } -// ----------------------------------------------------- -// Print to a user-provided io.Writer -// ----------------------------------------------------- +// ---------------------------------------- +// print to a specific io.Writer +// ---------------------------------------- -func (p Print) Fprint(w io.Writer, a ...any) (n int, err error) { +// Fprint mirrors [fmt.Fprint] signature and behavior, with the configured style +// and (optional) word wrapping applied +func (p Printer) Fprint(w io.Writer, a ...any) (n int, err error) { return fmt.Fprint(w, p.Sprint(a...)) } -func (p Print) Fprintf(w io.Writer, format string, a ...any) (n int, err error) { +// Fprintf mirrors [fmt.Fprintf] signature and behavior, with the configured style +// and (optional) word wrapping applied +func (p Printer) Fprintf(w io.Writer, format string, a ...any) (n int, err error) { return p.Fprint(w, p.Sprintf(format, a...)) } -func (p Print) Fprintfln(w io.Writer, format string, a ...any) (n int, err error) { +// Fprintfln mirrors [fmt.Fprintfln] signature and behavior, with the configured style +// and (optional) word wrapping applied +func (p Printer) Fprintfln(w io.Writer, format string, a ...any) (n int, err error) { return p.Fprintln(w, p.Sprintf(format, a...)) } -func (p Print) Fprintln(w io.Writer, a ...any) (n int, err error) { +// Fprintln mirrors [fmt.Fprintln] signature and behavior, with the configured style +// and (optional) word wrapping applied +func (p Printer) Fprintln(w io.Writer, a ...any) (n int, err error) { return fmt.Fprintln(w, p.printHelper(a...)) } @@ -164,19 +81,34 @@ func (p Print) Fprintln(w io.Writer, a ...any) (n int, err error) { // Print to the default [p.writer] over [os.Stdout] // ----------------------------------------------------- -func (p Print) Print(a ...any) (n int, err error) { +// Print mirrors [fmt.Print] signature and behavior, with the configured style +// and (optional) word wrapping applied. +// +// Instead of writing to [os.Stdout] it will write to the configured [io.Writer]. +func (p Printer) Print(a ...any) (n int, err error) { return p.Fprint(p.writer, a...) } -func (p Print) Printf(format string, a ...any) (n int, err error) { +// Printf mirrors [fmt.Printf] signature and behavior, with the configured style +// and (optional) word wrapping applied. +// +// Instead of writing to [os.Stdout] it will write to the configured [io.Writer]. +func (p Printer) Printf(format string, a ...any) (n int, err error) { return p.Fprintf(p.writer, format, a...) } -func (p Print) Printfln(format string, a ...any) (n int, err error) { +// Printfln behaves like [fmt.Printf] but supports the [formatter] signature. +// +// This does *not* map to a Go native printer, but a mix for formatting + newline +func (p Printer) Printfln(format string, a ...any) (n int, err error) { return p.Fprintfln(p.writer, format, a...) } -func (p Print) Println(a ...any) (n int, err error) { +// Println mirrors [fmt.Println] signature and behavior, with the configured style +// and (optional) word wrapping applied. +// +// Instead of writing to [os.Stdout] it will write to the configured [io.Writer] +func (p Printer) Println(a ...any) (n int, err error) { return p.Fprintln(p.writer, a...) } @@ -184,23 +116,33 @@ func (p Print) Println(a ...any) (n int, err error) { // Return string // ----------------------------------------------------- -func (p Print) Sprint(a ...any) string { +// Sprint mirrors [fmt.Sprint] signature and behavior, with the configured style +// and (optional) word wrapping applied. +func (p Printer) Sprint(a ...any) string { return p.render(fmt.Sprint(a...)) } -func (p Print) Sprintf(format string, a ...any) string { +// Sprintf mirrors [fmt.Sprintf] signature and behavior, with the configured style +// and (optional) word wrapping applied. +func (p Printer) Sprintf(format string, a ...any) string { return p.render(fmt.Sprintf(format, a...)) } -func (p Print) Sprintfln(format string, a ...any) string { +// Sprintfln behaves like [fmt.Sprintln] but supports the [formatter] signature. +// +// This does *not* map to a Go native printer, but a mix for formatting + newline +func (p Printer) Sprintfln(format string, a ...any) string { return fmt.Sprintln(p.Sprintf(format, a...)) } -func (p Print) Sprintln(a ...any) string { +// Sprintln mirrors [fmt.Sprintln] signature and behavior, with the configured style +// and (optional) word wrapping applied. +func (p Printer) Sprintln(a ...any) string { return fmt.Sprintln(p.printHelper(a...)) } -func (p Print) Box(header string, bodies ...string) { +// Create a visual box with the printer style +func (p Printer) Box(header string, bodies ...string) { body := strings.Join(bodies, " ") // Copy the box styles to avoid leaking changes to the styles @@ -223,7 +165,7 @@ func (p Print) Box(header string, bodies ...string) { boxHeader := headerStyle.Width(p.boxWidth - borderWidth).Render(header) boxBody := bodyStyle.Width(p.boxWidth - borderWidth).Render(body) - // If a maxWidth is set, the boxes will be aligned automatically to the max + // If a BoxWidth is set, the boxes will be aligned automatically to the max if p.boxWidth > 0 { fmt.Fprintln( p.writer, @@ -260,7 +202,7 @@ func (p Print) Box(header string, bodies ...string) { // io.Writer // ----------------------------------------------------- -func (p Print) Write(b []byte) (n int, err error) { +func (p Printer) Write(b []byte) (n int, err error) { return p.Print(string(b)) } @@ -268,15 +210,17 @@ func (p Print) Write(b []byte) (n int, err error) { // Helper methods // ----------------------------------------------------- -func (p Print) MaxWidth() int { +// GetBoxWidth returns the configured [BoxWidth] for word wrapping +func (p Printer) BoxWidth() int { return p.boxWidth } -func (p Print) Writer() io.Writer { +// Writer returns the configured [io.Writer] +func (p Printer) Writer() io.Writer { return p.writer } -func (p Print) Copy(options ...PrinterOption) Print { +func (p Printer) Copy(options ...PrinterOption) Printer { clone := &p for _, option := range options { @@ -286,18 +230,20 @@ func (p Print) Copy(options ...PrinterOption) Print { return *clone } -func (p Print) Style() lipgloss.Style { +// TextStyle returns a *copy* of the current [lipgloss.Style] +func (p Printer) Style() lipgloss.Style { return p.textStyle.Copy() } -func (p Print) ApplyStyle(callback StyleChanger) Print { +// ApplyTextStyle returns a new copy of [StylePrint] instance with the [Style] based on the callback changes +func (p Printer) ApplyStyle(callback StyleChanger) Printer { style := p.Style() callback(&style) return p.Copy(WithTextStyle(style)) } -func (p Print) GetWriter() io.Writer { +func (p Printer) GetWriter() io.Writer { return p.writer } @@ -305,15 +251,15 @@ func (p Print) GetWriter() io.Writer { // internal helpers // ----------------------------------------------------- -func (p Print) render(input string) string { +func (p Printer) render(input string) string { return p.wrap(p.textStyle.Render(input)) } -func (p Print) wrap(input string) string { +func (p Printer) wrap(input string) string { return input } -func (p Print) printHelper(a ...any) string { +func (p Printer) printHelper(a ...any) string { var buff bytes.Buffer fmt.Fprintln(&buff, a...) @@ -329,27 +275,27 @@ func (p Print) printHelper(a ...any) string { // ----------------------------------------------------- func WithStyle(style Style) PrinterOption { - return func(p *Print) { + return func(p *Printer) { p.style = style p.textStyle = p.renderer.NewStyle().Inherit(style.TextStyle()) } } func WithRenderer(renderer *lipgloss.Renderer) PrinterOption { - return func(p *Print) { + return func(p *Printer) { p.renderer = renderer p.writer = renderer.Output() } } func WithTextStyle(style lipgloss.Style) PrinterOption { - return func(p *Print) { + return func(p *Printer) { p.textStyle = style } } func WithEmphasis(b bool) PrinterOption { - return func(printer *Print) { + return func(printer *Printer) { if b { printer.textStyle = printer.renderer.NewStyle().Inherit(printer.style.TextEmphasisStyle()) @@ -361,13 +307,13 @@ func WithEmphasis(b bool) PrinterOption { } func WithWriter(w io.Writer) PrinterOption { - return func(p *Print) { + return func(p *Printer) { p.writer = w } } func WitBoxWidth(i int) PrinterOption { - return func(p *Print) { + return func(p *Printer) { p.boxWidth = i } } diff --git a/pkg/tui/style.go b/pkg/tui/style.go index 670db74..0a123c7 100644 --- a/pkg/tui/style.go +++ b/pkg/tui/style.go @@ -54,7 +54,7 @@ func NewStyleWithoutColor() Style { return Style{} } -func (style Style) NewPrinter(renderer *lipgloss.Renderer, options ...PrinterOption) Print { +func (style Style) NewPrinter(renderer *lipgloss.Renderer, options ...PrinterOption) Printer { return NewPrinter(style, renderer, options...) } diff --git a/pkg/tui/theme.go b/pkg/tui/theme.go index fbbdb21..1fe3913 100644 --- a/pkg/tui/theme.go +++ b/pkg/tui/theme.go @@ -40,15 +40,15 @@ func NewTheme() Theme { return theme } -func (theme Theme) NewWriter(writer *lipgloss.Renderer) ThemeWriter { - return ThemeWriter{ +func (theme Theme) NewWriter(writer *lipgloss.Renderer) Writer { + return Writer{ writer: writer, theme: theme, cache: make(map[colorType]Printer), } } -func NewWriter(ctx context.Context, writer io.Writer) ThemeWriter { +func NewWriter(ctx context.Context, writer io.Writer) Writer { var options []termenv.OutputOption // If the primary color profile is in color mode, enforce TTY to keep coloring on diff --git a/pkg/tui/theme_writer.go b/pkg/tui/theme_writer.go deleted file mode 100644 index 060e61f..0000000 --- a/pkg/tui/theme_writer.go +++ /dev/null @@ -1,102 +0,0 @@ -package tui - -import ( - "github.com/charmbracelet/lipgloss" -) - -type colorType int - -const ( - Danger colorType = 1 << iota - Dark - Info - Light - NoColor - Primary - Secondary - Success - Warning -) - -type ThemeWriter struct { - cache map[colorType]Printer - theme Theme - writer *lipgloss.Renderer -} - -func (tw ThemeWriter) Danger() Printer { - return tw.Color(Danger) -} - -func (tw ThemeWriter) Dark() Printer { - return tw.Color(Dark) -} - -func (tw ThemeWriter) Info() Printer { - return tw.Color(Info) -} - -func (tw ThemeWriter) Light() Printer { - return tw.Color(Light) -} - -func (tw ThemeWriter) NoColor() Printer { - return tw.Color(NoColor) -} - -func (tw ThemeWriter) Primary() Printer { - return tw.Color(Primary) -} - -func (tw ThemeWriter) Secondary() Printer { - return tw.Color(Secondary) -} - -func (tw ThemeWriter) Success() Printer { - return tw.Color(Success) -} - -func (tw ThemeWriter) Warning() Printer { - return tw.Color(Warning) -} - -func (tw ThemeWriter) Color(colorType colorType) Printer { - if printer, ok := tw.cache[colorType]; ok { - return printer - } - - var style Style - - switch colorType { - case Danger: - style = tw.theme.Danger - - case Dark: - style = tw.theme.Dark - - case Info: - style = tw.theme.Info - - case Light: - style = tw.theme.Light - - case Primary: - style = tw.theme.Primary - - case Secondary: - style = tw.theme.Secondary - - case Success: - style = tw.theme.Success - - case Warning: - style = tw.theme.Warning - - case NoColor: - style = tw.theme.NoColor - } - - tw.cache[colorType] = style.NewPrinter(tw.writer) - - return tw.cache[colorType] -} diff --git a/pkg/tui/writer.go b/pkg/tui/writer.go new file mode 100644 index 0000000..5d256d0 --- /dev/null +++ b/pkg/tui/writer.go @@ -0,0 +1,102 @@ +package tui + +import ( + "github.com/charmbracelet/lipgloss" +) + +type colorType int + +const ( + Danger colorType = 1 << iota + Dark + Info + Light + NoColor + Primary + Secondary + Success + Warning +) + +type Writer struct { + cache map[colorType]Printer + theme Theme + writer *lipgloss.Renderer +} + +func (w Writer) Danger() Printer { + return w.Color(Danger) +} + +func (w Writer) Dark() Printer { + return w.Color(Dark) +} + +func (w Writer) Info() Printer { + return w.Color(Info) +} + +func (w Writer) Light() Printer { + return w.Color(Light) +} + +func (w Writer) NoColor() Printer { + return w.Color(NoColor) +} + +func (w Writer) Primary() Printer { + return w.Color(Primary) +} + +func (w Writer) Secondary() Printer { + return w.Color(Secondary) +} + +func (w Writer) Success() Printer { + return w.Color(Success) +} + +func (w Writer) Warning() Printer { + return w.Color(Warning) +} + +func (w Writer) Color(colorType colorType) Printer { + if printer, ok := w.cache[colorType]; ok { + return printer + } + + var style Style + + switch colorType { + case Danger: + style = w.theme.Danger + + case Dark: + style = w.theme.Dark + + case Info: + style = w.theme.Info + + case Light: + style = w.theme.Light + + case Primary: + style = w.theme.Primary + + case Secondary: + style = w.theme.Secondary + + case Success: + style = w.theme.Success + + case Warning: + style = w.theme.Warning + + case NoColor: + style = w.theme.NoColor + } + + w.cache[colorType] = style.NewPrinter(w.writer) + + return w.cache[colorType] +} From ba2c3d7dc2205833e21e0212ae5e57030825d826 Mon Sep 17 00:00:00 2001 From: Christian Winther Date: Thu, 15 Feb 2024 21:39:08 +0100 Subject: [PATCH 27/33] renaming tui --- cmd/disable/disable.go | 2 +- cmd/enable/enable.go | 2 +- cmd/fmt/fmt.go | 2 +- cmd/print/print.go | 2 +- cmd/root.go | 2 +- cmd/set/set.go | 2 +- cmd/update/update.go | 2 +- cmd/value/value.go | 6 ++++-- pkg/tui/context.go | 22 +++++++++------------- pkg/tui/theme.go | 4 ++-- pkg/tui/writer.go | 26 +++++++++++++------------- 11 files changed, 35 insertions(+), 37 deletions(-) diff --git a/cmd/disable/disable.go b/cmd/disable/disable.go index 390e6af..07a5f0c 100644 --- a/cmd/disable/disable.go +++ b/cmd/disable/disable.go @@ -36,7 +36,7 @@ func NewCommand() *cobra.Command { return fmt.Errorf("Could not find KEY [%s]", key) } - stdout, stderr := tui.PrintersFromContext(cmd.Context()) + stdout, stderr := tui.WritersFromContext(cmd.Context()) if !existing.Enabled { stderr.Warning().Printfln("WARNING: The key [%s] is already disabled", key) diff --git a/cmd/enable/enable.go b/cmd/enable/enable.go index 26f22a4..08802dd 100644 --- a/cmd/enable/enable.go +++ b/cmd/enable/enable.go @@ -36,7 +36,7 @@ func NewCommand() *cobra.Command { return fmt.Errorf("Could not find KEY [%s]", key) } - stdout, stderr := tui.PrintersFromContext(cmd.Context()) + stdout, stderr := tui.WritersFromContext(cmd.Context()) if existing.Enabled { stderr.Warning().Printfln("WARNING: The key [%s] is already enabled", key) diff --git a/cmd/fmt/fmt.go b/cmd/fmt/fmt.go index 74f18f8..ce120b3 100644 --- a/cmd/fmt/fmt.go +++ b/cmd/fmt/fmt.go @@ -23,7 +23,7 @@ func NewCommand() *cobra.Command { return err } - tui.ColorPrinterFromContext(cmd.Context(), tui.Stdout, tui.Success).Printfln("File [%s] was successfully formatted", filename) + tui.WriterFromContext(cmd.Context(), tui.Stdout).Success().Printfln("File [%s] was successfully formatted", filename) return nil }, diff --git a/cmd/print/print.go b/cmd/print/print.go index 6bf44a5..fc60251 100644 --- a/cmd/print/print.go +++ b/cmd/print/print.go @@ -21,7 +21,7 @@ func NewCommand() *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { env, settings, warnings, err := setup(cmd.Flags()) if warnings != nil { - tui.ColorPrinterFromContext(cmd.Context(), tui.Stderr, tui.Warning).Printfln("%+v", warnings) + tui.WriterFromContext(cmd.Context(), tui.Stderr).Warning().Printfln("%+v", warnings) } if err != nil { return err diff --git a/cmd/root.go b/cmd/root.go index 86ed521..22a1f91 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -51,7 +51,7 @@ func RunCommand(ctx context.Context, args []string, stdout io.Writer, stderr io. Version: buildVersion().String(), } - ctx = tui.CreateContext(ctx, stdout, stderr) + ctx = tui.NewContext(ctx, stdout, stderr) root.SetArgs(args) root.SetContext(ctx) diff --git a/cmd/set/set.go b/cmd/set/set.go index 561df17..f4b7e5c 100644 --- a/cmd/set/set.go +++ b/cmd/set/set.go @@ -88,7 +88,7 @@ func runE(cmd *cobra.Command, args []string) error { var ( allErrors error - stdout, stderr = tui.PrintersFromContext(cmd.Context()) + stdout, stderr = tui.WritersFromContext(cmd.Context()) ) for _, stringPair := range args { diff --git a/cmd/update/update.go b/cmd/update/update.go index 3d464ec..a2d88f2 100644 --- a/cmd/update/update.go +++ b/cmd/update/update.go @@ -40,7 +40,7 @@ func runE(cmd *cobra.Command, args []string) error { return err } - stdout, stderr := tui.PrintersFromContext(cmd.Context()) + stdout, stderr := tui.WritersFromContext(cmd.Context()) dark := stdout.Dark() info := stdout.Info() diff --git a/cmd/value/value.go b/cmd/value/value.go index 3ab6596..6e06c5e 100644 --- a/cmd/value/value.go +++ b/cmd/value/value.go @@ -43,7 +43,8 @@ func NewCommand() *cobra.Command { warn, err := env.InterpolateStatement(existing) if warn != nil { tui. - ColorPrinterFromContext(cmd.Context(), tui.Stderr, tui.Warning). + WriterFromContext(cmd.Context(), tui.Stderr). + Warning(). Printfln("%+v", warn) } if err != nil { @@ -51,7 +52,8 @@ func NewCommand() *cobra.Command { } tui. - ColorPrinterFromContext(cmd.Context(), tui.Stdout, tui.NoColor). + WriterFromContext(cmd.Context(), tui.Stdout). + NoColor(). Println(existing.Interpolated) return nil diff --git a/pkg/tui/context.go b/pkg/tui/context.go index a4b467c..1e9ac68 100644 --- a/pkg/tui/context.go +++ b/pkg/tui/context.go @@ -8,21 +8,21 @@ import ( "github.com/muesli/termenv" ) -type printerContextKey int +type fileDescriptorKey int const ( - Stdout printerContextKey = iota + Stdout fileDescriptorKey = iota Stderr ) -type themeContextKey int +type contextKey int const ( - themeContextValue themeContextKey = iota + themeContextValue contextKey = iota colorProfileContextValue ) -func CreateContext(ctx context.Context, stdout, stderr io.Writer) context.Context { +func NewContext(ctx context.Context, stdout, stderr io.Writer) context.Context { theme := NewTheme() stdoutOutput := lipgloss.NewRenderer(stdout, termenv.WithColorCache(true)) @@ -40,18 +40,14 @@ func ThemeFromContext(ctx context.Context) Theme { return ctx.Value(themeContextValue).(Theme) //nolint:forcetypeassert } -func ColorProfile(ctx context.Context) termenv.Profile { +func ColorProfileFromContext(ctx context.Context) termenv.Profile { return ctx.Value(colorProfileContextValue).(termenv.Profile) //nolint:forcetypeassert } -func WriterFromContext(ctx context.Context, key printerContextKey) Writer { - return ctx.Value(key).(Writer) //nolint:forcetypeassert +func WriterFromContext(ctx context.Context, descriptor fileDescriptorKey) Writer { + return ctx.Value(descriptor).(Writer) //nolint:forcetypeassert } -func ColorPrinterFromContext(ctx context.Context, key printerContextKey, color colorType) Printer { - return ctx.Value(key).(*Writer).Color(color) //nolint:forcetypeassert -} - -func PrintersFromContext(ctx context.Context) (Writer, Writer) { +func WritersFromContext(ctx context.Context) (Writer, Writer) { return ctx.Value(Stdout).(Writer), ctx.Value(Stderr).(Writer) //nolint:forcetypeassert } diff --git a/pkg/tui/theme.go b/pkg/tui/theme.go index 1fe3913..5d20953 100644 --- a/pkg/tui/theme.go +++ b/pkg/tui/theme.go @@ -44,7 +44,7 @@ func (theme Theme) NewWriter(writer *lipgloss.Renderer) Writer { return Writer{ writer: writer, theme: theme, - cache: make(map[colorType]Printer), + cache: make(map[style]Printer), } } @@ -52,7 +52,7 @@ func NewWriter(ctx context.Context, writer io.Writer) Writer { var options []termenv.OutputOption // If the primary color profile is in color mode, enforce TTY to keep coloring on - if profile := ColorProfile(ctx); profile != termenv.Ascii { + if profile := ColorProfileFromContext(ctx); profile != termenv.Ascii { options = append( options, termenv.WithTTY(true), diff --git a/pkg/tui/writer.go b/pkg/tui/writer.go index 5d256d0..59d01d6 100644 --- a/pkg/tui/writer.go +++ b/pkg/tui/writer.go @@ -4,10 +4,10 @@ import ( "github.com/charmbracelet/lipgloss" ) -type colorType int +type style int const ( - Danger colorType = 1 << iota + Danger style = 1 << iota Dark Info Light @@ -19,48 +19,48 @@ const ( ) type Writer struct { - cache map[colorType]Printer + cache map[style]Printer theme Theme writer *lipgloss.Renderer } func (w Writer) Danger() Printer { - return w.Color(Danger) + return w.Style(Danger) } func (w Writer) Dark() Printer { - return w.Color(Dark) + return w.Style(Dark) } func (w Writer) Info() Printer { - return w.Color(Info) + return w.Style(Info) } func (w Writer) Light() Printer { - return w.Color(Light) + return w.Style(Light) } func (w Writer) NoColor() Printer { - return w.Color(NoColor) + return w.Style(NoColor) } func (w Writer) Primary() Printer { - return w.Color(Primary) + return w.Style(Primary) } func (w Writer) Secondary() Printer { - return w.Color(Secondary) + return w.Style(Secondary) } func (w Writer) Success() Printer { - return w.Color(Success) + return w.Style(Success) } func (w Writer) Warning() Printer { - return w.Color(Warning) + return w.Style(Warning) } -func (w Writer) Color(colorType colorType) Printer { +func (w Writer) Style(colorType style) Printer { if printer, ok := w.cache[colorType]; ok { return printer } From 7809a8fc9ff1b629b993b91926db2c535001079e Mon Sep 17 00:00:00 2001 From: Christian Winther Date: Thu, 15 Feb 2024 22:15:50 +0100 Subject: [PATCH 28/33] cleanup --- .vscode/settings.json | 1 + cmd/disable/disable.go | 16 +++--- cmd/disable/tests/missing-key/stderr.golden | 2 +- cmd/enable/enable.go | 26 +++++----- cmd/enable/tests/missing-key/stderr.golden | 2 +- cmd/fmt/fmt.go | 5 +- cmd/groups/groups.go | 7 +-- cmd/json/json.go | 1 + cmd/print/print.go | 56 ++++++++++---------- cmd/set/set.go | 9 +--- cmd/update/update.go | 1 + cmd/validate/validate.go | 1 + cmd/value/value.go | 30 ++++------- pkg/tui/context.go | 14 +++-- pkg/tui/helpers.go | 12 +++++ pkg/tui/style.go | 14 +++++ pkg/tui/theme.go | 57 ++++++++++----------- pkg/tui/writer.go | 55 ++------------------ 18 files changed, 141 insertions(+), 168 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 13bb05a..1ae5a11 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -16,6 +16,7 @@ }, "cSpell.words": [ "bitmask", + "Printfln", "Upsert", "upserter" ] diff --git a/cmd/disable/disable.go b/cmd/disable/disable.go index 07a5f0c..c80f459 100644 --- a/cmd/disable/disable.go +++ b/cmd/disable/disable.go @@ -1,7 +1,6 @@ package disable import ( - "errors" "fmt" "github.com/jippi/dottie/pkg" @@ -16,13 +15,10 @@ func NewCommand() *cobra.Command { Use: "disable KEY", Short: "Disable (comment out) a KEY if it exists", GroupID: "manipulate", + Args: cobra.ExactArgs(1), ValidArgsFunction: shared.NewCompleter().WithHandlers(render.ExcludeDisabledAssignments).Get(), RunE: func(cmd *cobra.Command, args []string) error { - if len(args) == 0 { - return errors.New("Missing required argument: KEY") - } - - key := args[0] + key := cmd.Flags().Arg(0) filename := cmd.Flag("file").Value.String() @@ -31,20 +27,20 @@ func NewCommand() *cobra.Command { return err } - existing := env.Get(key) - if existing == nil { + assignment := env.Get(key) + if assignment == nil { return fmt.Errorf("Could not find KEY [%s]", key) } stdout, stderr := tui.WritersFromContext(cmd.Context()) - if !existing.Enabled { + if !assignment.Enabled { stderr.Warning().Printfln("WARNING: The key [%s] is already disabled", key) return nil } - existing.Disable() + assignment.Disable() if err := pkg.Save(cmd.Context(), filename, env); err != nil { return fmt.Errorf("could not save file: %w", err) diff --git a/cmd/disable/tests/missing-key/stderr.golden b/cmd/disable/tests/missing-key/stderr.golden index 8af21b1..0c56eca 100644 --- a/cmd/disable/tests/missing-key/stderr.golden +++ b/cmd/disable/tests/missing-key/stderr.golden @@ -1,3 +1,3 @@ ---- exec command line 0: [disable] -Error: Missing required argument: KEY +Error: accepts 1 arg(s), received 0 Run 'dottie disable --help' for usage. diff --git a/cmd/enable/enable.go b/cmd/enable/enable.go index 08802dd..9a74f2e 100644 --- a/cmd/enable/enable.go +++ b/cmd/enable/enable.go @@ -1,7 +1,6 @@ package enable import ( - "errors" "fmt" "github.com/jippi/dottie/pkg" @@ -15,13 +14,10 @@ func NewCommand() *cobra.Command { return &cobra.Command{ Use: "enable KEY", Short: "Enable (uncomment) a KEY if it exists", + Args: cobra.ExactArgs(1), GroupID: "manipulate", ValidArgsFunction: shared.NewCompleter().WithHandlers(render.ExcludeActiveAssignments).Get(), RunE: func(cmd *cobra.Command, args []string) error { - if len(args) == 0 { - return errors.New("Missing required argument: KEY") - } - filename := cmd.Flag("file").Value.String() env, err := pkg.Load(filename) @@ -29,26 +25,28 @@ func NewCommand() *cobra.Command { return err } - key := args[0] + key := cmd.Flags().Arg(0) - existing := env.Get(key) - if existing == nil { + assignment := env.Get(key) + if assignment == nil { return fmt.Errorf("Could not find KEY [%s]", key) } - stdout, stderr := tui.WritersFromContext(cmd.Context()) - - if existing.Enabled { - stderr.Warning().Printfln("WARNING: The key [%s] is already enabled", key) + if assignment.Enabled { + tui.StderrFromContext(cmd.Context()). + Warning(). + Printfln("WARNING: The key [%s] is already enabled", key) } - existing.Enable() + assignment.Enable() if err := pkg.Save(cmd.Context(), filename, env); err != nil { return fmt.Errorf("could not save file: %w", err) } - stdout.Success().Printfln("Key [%s] was successfully enabled", key) + tui.StdoutFromContext(cmd.Context()). + Success(). + Printfln("Key [%s] was successfully enabled", key) return nil }, diff --git a/cmd/enable/tests/missing-key/stderr.golden b/cmd/enable/tests/missing-key/stderr.golden index 3f26e84..a3c97c0 100644 --- a/cmd/enable/tests/missing-key/stderr.golden +++ b/cmd/enable/tests/missing-key/stderr.golden @@ -1,3 +1,3 @@ ---- exec command line 0: [enable] -Error: Missing required argument: KEY +Error: accepts 1 arg(s), received 0 Run 'dottie enable --help' for usage. diff --git a/cmd/fmt/fmt.go b/cmd/fmt/fmt.go index ce120b3..36755a5 100644 --- a/cmd/fmt/fmt.go +++ b/cmd/fmt/fmt.go @@ -10,6 +10,7 @@ func NewCommand() *cobra.Command { return &cobra.Command{ Use: "fmt", Short: "Format a .env file", + Args: cobra.NoArgs, GroupID: "manipulate", RunE: func(cmd *cobra.Command, args []string) error { filename := cmd.Flag("file").Value.String() @@ -23,7 +24,9 @@ func NewCommand() *cobra.Command { return err } - tui.WriterFromContext(cmd.Context(), tui.Stdout).Success().Printfln("File [%s] was successfully formatted", filename) + tui.StdoutFromContext(cmd.Context()). + Success(). + Printfln("File [%s] was successfully formatted", filename) return nil }, diff --git a/cmd/groups/groups.go b/cmd/groups/groups.go index bcffa6e..2bf7e25 100644 --- a/cmd/groups/groups.go +++ b/cmd/groups/groups.go @@ -15,6 +15,7 @@ func NewCommand() *cobra.Command { return &cobra.Command{ Use: "groups", Short: "Print groups found in the .env file", + Args: cobra.NoArgs, GroupID: "output", RunE: func(cmd *cobra.Command, args []string) error { filename := cmd.Flag("file").Value.String() @@ -29,16 +30,16 @@ func NewCommand() *cobra.Command { return errors.New("No groups found") } - width := longesGroupName(groups) + maxWidth := longesGroupName(groups) - stdout := tui.WriterFromContext(cmd.Context(), tui.Stdout) + stdout := tui.StdoutFromContext(cmd.Context()) secondary := stdout.Secondary() primary := stdout.Primary() stdout.Info().Box("Groups in " + filename) for _, group := range groups { - primary.Printf("%-"+strconv.Itoa(width)+"s", slug.Make(group.String())) + primary.Printf("%-"+strconv.Itoa(maxWidth)+"s", slug.Make(group.String())) primary.Print(" ") secondary.Printfln("(%s:%d)", filename, group.Position.FirstLine) } diff --git a/cmd/json/json.go b/cmd/json/json.go index e285b2d..68b9d99 100644 --- a/cmd/json/json.go +++ b/cmd/json/json.go @@ -12,6 +12,7 @@ func NewCommand() *cobra.Command { return &cobra.Command{ Use: "json", Short: "Print as JSON", + Args: cobra.NoArgs, GroupID: "output", RunE: func(cmd *cobra.Command, args []string) error { filename := cmd.Flag("file").Value.String() diff --git a/cmd/print/print.go b/cmd/print/print.go index fc60251..258aa4b 100644 --- a/cmd/print/print.go +++ b/cmd/print/print.go @@ -1,15 +1,12 @@ package print_cmd import ( - "fmt" - "github.com/jippi/dottie/pkg" "github.com/jippi/dottie/pkg/ast" "github.com/jippi/dottie/pkg/cli/shared" "github.com/jippi/dottie/pkg/render" "github.com/jippi/dottie/pkg/tui" "github.com/spf13/cobra" - "github.com/spf13/pflag" "go.uber.org/multierr" ) @@ -17,30 +14,9 @@ func NewCommand() *cobra.Command { cmd := &cobra.Command{ Use: "print", Short: "Print environment variables", + Args: cobra.NoArgs, GroupID: "output", - RunE: func(cmd *cobra.Command, args []string) error { - env, settings, warnings, err := setup(cmd.Flags()) - if warnings != nil { - tui.WriterFromContext(cmd.Context(), tui.Stderr).Warning().Printfln("%+v", warnings) - } - if err != nil { - return err - } - - fmt.Fprintln( - cmd.OutOrStdout(), - render. - NewRenderer(*settings). - Statement(cmd.Context(), env). - String(), - ) - - // tui. - // ColorPrinterFromContext(cmd.Context(), tui.Stdout, tui.NoColor). - // Println(render.NewRenderer(*settings).Statement(cmd.Context(), env).String()) - - return nil - }, + RunE: runE, } cmd.Flags().Bool("pretty", false, "implies --color --comments --blank-lines --group-banners") @@ -58,7 +34,27 @@ func NewCommand() *cobra.Command { return cmd } -func setup(flags *pflag.FlagSet) (*ast.Document, *render.Settings, error, error) { +func runE(cmd *cobra.Command, args []string) error { + env, settings, err := setup(cmd) + if err != nil { + return err + } + + tui.StdoutFromContext(cmd.Context()). + NoColor(). + Println( + render. + NewRenderer(*settings). + Statement(cmd.Context(), env). + String(), + ) + + return nil +} + +func setup(cmd *cobra.Command) (*ast.Document, *render.Settings, error) { + flags := cmd.Flags() + boolFlag := func(name string) bool { return shared.BoolFlag(flags, name) } @@ -69,7 +65,7 @@ func setup(flags *pflag.FlagSet) (*ast.Document, *render.Settings, error, error) doc, err := pkg.Load(stringFlag("file")) if err != nil { - return nil, nil, nil, err + return nil, nil, err } settings := render.NewSettings( @@ -105,5 +101,7 @@ func setup(flags *pflag.FlagSet) (*ast.Document, *render.Settings, error, error) settings.Apply(render.WithFormattedOutput(true)) } - return doc, settings, allWarnings, allErrors + tui.MaybePrintWarnings(cmd.Context(), allWarnings) + + return doc, settings, allErrors } diff --git a/cmd/set/set.go b/cmd/set/set.go index f4b7e5c..0351ef1 100644 --- a/cmd/set/set.go +++ b/cmd/set/set.go @@ -22,6 +22,7 @@ func NewCommand() *cobra.Command { Use: "set KEY=VALUE [KEY=VALUE ...]", Short: "Set/update one or multiple key=value pairs", GroupID: "manipulate", + Args: cobra.MinimumNArgs(1), ValidArgsFunction: shared.NewCompleter(). WithSuffixIsLiteral(true). WithHandlers(render.ExcludeDisabledAssignments). @@ -52,10 +53,6 @@ func runE(cmd *cobra.Command, args []string) error { return err } - if len(args) == 0 { - return errors.New("Missing required argument: KEY=VALUE") - } - // // Initialize Upserter // @@ -116,9 +113,7 @@ func runE(cmd *cobra.Command, args []string) error { // assignment, warnings, err := upserter.Upsert(cmd.Context(), assignment) - if warnings != nil { - stderr.Warning().Println("WARNING:", warnings) - } + tui.MaybePrintWarnings(cmd.Context(), warnings) if err != nil { z := validation.NewError(assignment, err) diff --git a/cmd/update/update.go b/cmd/update/update.go index a2d88f2..c47332c 100644 --- a/cmd/update/update.go +++ b/cmd/update/update.go @@ -22,6 +22,7 @@ func NewCommand() *cobra.Command { Use: "update", Short: "Update the .env file from a source", GroupID: "manipulate", + Args: cobra.NoArgs, RunE: runE, } diff --git a/cmd/validate/validate.go b/cmd/validate/validate.go index 61f4d76..d4e961c 100644 --- a/cmd/validate/validate.go +++ b/cmd/validate/validate.go @@ -18,6 +18,7 @@ func NewCommand() *cobra.Command { Use: "validate", Short: "Validate an .env file", GroupID: "output", + Args: cobra.NoArgs, RunE: runE, } diff --git a/cmd/value/value.go b/cmd/value/value.go index 6e06c5e..3096d2a 100644 --- a/cmd/value/value.go +++ b/cmd/value/value.go @@ -1,7 +1,6 @@ package value import ( - "errors" "fmt" "github.com/jippi/dottie/pkg" @@ -16,45 +15,36 @@ func NewCommand() *cobra.Command { Use: "value KEY", Short: "Print value of a env key if it exists", GroupID: "output", + Args: cobra.ExactArgs(1), ValidArgsFunction: shared.NewCompleter().WithHandlers(render.ExcludeDisabledAssignments).Get(), RunE: func(cmd *cobra.Command, args []string) error { - if len(args) == 0 { - return errors.New("Missing required argument: KEY") - } - filename := cmd.Flag("file").Value.String() - env, err := pkg.Load(filename) + document, err := pkg.Load(filename) if err != nil { return err } - key := args[0] + key := cmd.Flags().Arg(0) - existing := env.Get(key) - if existing == nil { + assignment := document.Get(key) + if assignment == nil { return fmt.Errorf("Key [%s] does not exists", key) } - if !existing.Enabled && !shared.BoolFlag(cmd.Flags(), "include-commented") { + if !assignment.Enabled && !shared.BoolFlag(cmd.Flags(), "include-commented") { return fmt.Errorf("Key [%s] exists, but is commented out - use [--include-commented] to include it", key) } - warn, err := env.InterpolateStatement(existing) - if warn != nil { - tui. - WriterFromContext(cmd.Context(), tui.Stderr). - Warning(). - Printfln("%+v", warn) - } + warnings, err := document.InterpolateStatement(assignment) + tui.MaybePrintWarnings(cmd.Context(), warnings) if err != nil { return err } - tui. - WriterFromContext(cmd.Context(), tui.Stdout). + tui.StdoutFromContext(cmd.Context()). NoColor(). - Println(existing.Interpolated) + Println(assignment.Interpolated) return nil }, diff --git a/pkg/tui/context.go b/pkg/tui/context.go index 1e9ac68..d045845 100644 --- a/pkg/tui/context.go +++ b/pkg/tui/context.go @@ -30,8 +30,8 @@ func NewContext(ctx context.Context, stdout, stderr io.Writer) context.Context { ctx = context.WithValue(ctx, themeContextValue, theme) ctx = context.WithValue(ctx, colorProfileContextValue, stdoutOutput.ColorProfile()) - ctx = context.WithValue(ctx, Stdout, theme.NewWriter(stdoutOutput)) - ctx = context.WithValue(ctx, Stderr, theme.NewWriter(stderrOutput)) + ctx = context.WithValue(ctx, Stdout, theme.Writer(stdoutOutput)) + ctx = context.WithValue(ctx, Stderr, theme.Writer(stderrOutput)) return ctx } @@ -48,6 +48,14 @@ func WriterFromContext(ctx context.Context, descriptor fileDescriptorKey) Writer return ctx.Value(descriptor).(Writer) //nolint:forcetypeassert } +func StdoutFromContext(ctx context.Context) Writer { + return WriterFromContext(ctx, Stdout) +} + +func StderrFromContext(ctx context.Context) Writer { + return WriterFromContext(ctx, Stderr) +} + func WritersFromContext(ctx context.Context) (Writer, Writer) { - return ctx.Value(Stdout).(Writer), ctx.Value(Stderr).(Writer) //nolint:forcetypeassert + return StdoutFromContext(ctx), StderrFromContext(ctx) } diff --git a/pkg/tui/helpers.go b/pkg/tui/helpers.go index 5d96dea..f7225ac 100644 --- a/pkg/tui/helpers.go +++ b/pkg/tui/helpers.go @@ -1,6 +1,8 @@ package tui import ( + "context" + "github.com/charmbracelet/lipgloss" "github.com/teacat/noire" ) @@ -40,3 +42,13 @@ func TransformColor(base, filter string, percent float64) string { return base } } + +func MaybePrintWarnings(ctx context.Context, warnings error) { + if warnings == nil { + return + } + + StderrFromContext(ctx). + Warning(). + Printfln("%+v", warnings) +} diff --git a/pkg/tui/style.go b/pkg/tui/style.go index 0a123c7..f9105e8 100644 --- a/pkg/tui/style.go +++ b/pkg/tui/style.go @@ -4,6 +4,20 @@ import ( "github.com/charmbracelet/lipgloss" ) +type styleIdentifier int + +const ( + Danger styleIdentifier = 1 << iota + Dark + Info + Light + NoColor + Primary + Secondary + Success + Warning +) + type Style struct { textColor lipgloss.AdaptiveColor textStyle lipgloss.Style diff --git a/pkg/tui/theme.go b/pkg/tui/theme.go index 5d20953..edb2f70 100644 --- a/pkg/tui/theme.go +++ b/pkg/tui/theme.go @@ -9,49 +9,48 @@ import ( ) type Theme struct { - Danger Style - Dark Style - Info Style - Light Style - NoColor Style - Primary Style - Secondary Style - Success Style - Warning Style + styles map[styleIdentifier]Style } func NewTheme() Theme { theme := Theme{} - - theme.Danger = NewStyle(Red) - theme.Info = NewStyle(Cyan) - theme.Light = NewStyle(Gray300) - theme.NoColor = NewStyleWithoutColor() - theme.Primary = NewStyle(Blue) - theme.Secondary = NewStyle(Gray600) - theme.Success = NewStyle(Green) - theme.Warning = NewStyle(Yellow) - - theme.Dark = NewStyle(Gray700) - theme.Dark.textEmphasisColor.Dark = ColorToHex(Gray300) - theme.Dark.backgroundColor.Dark = "#1a1d20" - theme.Dark.borderColor.Dark = ColorToHex(Gray800) + theme.styles = make(map[styleIdentifier]Style) + theme.styles[Danger] = NewStyle(Red) + theme.styles[Info] = NewStyle(Cyan) + theme.styles[Light] = NewStyle(Gray300) + theme.styles[NoColor] = NewStyleWithoutColor() + theme.styles[Primary] = NewStyle(Blue) + theme.styles[Secondary] = NewStyle(Gray600) + theme.styles[Success] = NewStyle(Green) + theme.styles[Warning] = NewStyle(Yellow) + + dark := NewStyle(Gray700) + dark.textEmphasisColor.Dark = ColorToHex(Gray300) + dark.backgroundColor.Dark = "#1a1d20" + dark.borderColor.Dark = ColorToHex(Gray800) + + theme.styles[Dark] = dark return theme } -func (theme Theme) NewWriter(writer *lipgloss.Renderer) Writer { +func (theme Theme) Style(id styleIdentifier) Style { + return theme.styles[id] +} + +func (theme Theme) Writer(renderer *lipgloss.Renderer) Writer { return Writer{ - writer: writer, - theme: theme, - cache: make(map[style]Printer), + renderer: renderer, + theme: theme, + cache: make(map[styleIdentifier]Printer), } } func NewWriter(ctx context.Context, writer io.Writer) Writer { var options []termenv.OutputOption - // If the primary color profile is in color mode, enforce TTY to keep coloring on + // If the primary (stdout) color profile is in color mode (aka not ASCII), + // force TTY and color profile for the new renderer and writer if profile := ColorProfileFromContext(ctx); profile != termenv.Ascii { options = append( options, @@ -60,5 +59,5 @@ func NewWriter(ctx context.Context, writer io.Writer) Writer { ) } - return ThemeFromContext(ctx).NewWriter(lipgloss.NewRenderer(writer, options...)) + return ThemeFromContext(ctx).Writer(lipgloss.NewRenderer(writer, options...)) } diff --git a/pkg/tui/writer.go b/pkg/tui/writer.go index 59d01d6..e44a801 100644 --- a/pkg/tui/writer.go +++ b/pkg/tui/writer.go @@ -4,24 +4,10 @@ import ( "github.com/charmbracelet/lipgloss" ) -type style int - -const ( - Danger style = 1 << iota - Dark - Info - Light - NoColor - Primary - Secondary - Success - Warning -) - type Writer struct { - cache map[style]Printer - theme Theme - writer *lipgloss.Renderer + cache map[styleIdentifier]Printer + theme Theme + renderer *lipgloss.Renderer } func (w Writer) Danger() Printer { @@ -60,43 +46,12 @@ func (w Writer) Warning() Printer { return w.Style(Warning) } -func (w Writer) Style(colorType style) Printer { +func (w Writer) Style(colorType styleIdentifier) Printer { if printer, ok := w.cache[colorType]; ok { return printer } - var style Style - - switch colorType { - case Danger: - style = w.theme.Danger - - case Dark: - style = w.theme.Dark - - case Info: - style = w.theme.Info - - case Light: - style = w.theme.Light - - case Primary: - style = w.theme.Primary - - case Secondary: - style = w.theme.Secondary - - case Success: - style = w.theme.Success - - case Warning: - style = w.theme.Warning - - case NoColor: - style = w.theme.NoColor - } - - w.cache[colorType] = style.NewPrinter(w.writer) + w.cache[colorType] = w.theme.Style(colorType).NewPrinter(w.renderer) return w.cache[colorType] } From 6bb1f4ca7d96599a855d468417224982f318ebfe Mon Sep 17 00:00:00 2001 From: Christian Winther Date: Thu, 15 Feb 2024 22:23:54 +0100 Subject: [PATCH 29/33] adjust --- cmd/disable/disable.go | 10 +++++----- cmd/enable/enable.go | 8 +++----- cmd/print/print.go | 3 +-- cmd/set/set.go | 4 ++-- pkg/tui/helpers.go | 2 +- 5 files changed, 12 insertions(+), 15 deletions(-) diff --git a/cmd/disable/disable.go b/cmd/disable/disable.go index c80f459..923f6f3 100644 --- a/cmd/disable/disable.go +++ b/cmd/disable/disable.go @@ -29,13 +29,11 @@ func NewCommand() *cobra.Command { assignment := env.Get(key) if assignment == nil { - return fmt.Errorf("Could not find KEY [%s]", key) + return fmt.Errorf("Could not find KEY [ %s ]", key) } - stdout, stderr := tui.WritersFromContext(cmd.Context()) - if !assignment.Enabled { - stderr.Warning().Printfln("WARNING: The key [%s] is already disabled", key) + tui.MaybePrintWarnings(cmd.Context(), fmt.Errorf("The key [ %s ] is already disabled", key)) return nil } @@ -46,7 +44,9 @@ func NewCommand() *cobra.Command { return fmt.Errorf("could not save file: %w", err) } - stdout.Success().Printfln("Key [%s] was successfully disabled", key) + tui.StdoutFromContext(cmd.Context()). + Success(). + Printfln("Key [%s] was successfully disabled", key) return nil }, diff --git a/cmd/enable/enable.go b/cmd/enable/enable.go index 9a74f2e..5eab1c7 100644 --- a/cmd/enable/enable.go +++ b/cmd/enable/enable.go @@ -29,13 +29,11 @@ func NewCommand() *cobra.Command { assignment := env.Get(key) if assignment == nil { - return fmt.Errorf("Could not find KEY [%s]", key) + return fmt.Errorf("Could not find KEY [ %s ]", key) } if assignment.Enabled { - tui.StderrFromContext(cmd.Context()). - Warning(). - Printfln("WARNING: The key [%s] is already enabled", key) + tui.MaybePrintWarnings(cmd.Context(), fmt.Errorf("The key [ %s ] is already enabled", key)) } assignment.Enable() @@ -46,7 +44,7 @@ func NewCommand() *cobra.Command { tui.StdoutFromContext(cmd.Context()). Success(). - Printfln("Key [%s] was successfully enabled", key) + Printfln("Key [ %s ] was successfully enabled", key) return nil }, diff --git a/cmd/print/print.go b/cmd/print/print.go index 258aa4b..817fe86 100644 --- a/cmd/print/print.go +++ b/cmd/print/print.go @@ -43,8 +43,7 @@ func runE(cmd *cobra.Command, args []string) error { tui.StdoutFromContext(cmd.Context()). NoColor(). Println( - render. - NewRenderer(*settings). + render.NewRenderer(*settings). Statement(cmd.Context(), env). String(), ) diff --git a/cmd/set/set.go b/cmd/set/set.go index 0351ef1..f73ea23 100644 --- a/cmd/set/set.go +++ b/cmd/set/set.go @@ -91,7 +91,7 @@ func runE(cmd *cobra.Command, args []string) error { for _, stringPair := range args { pairSlice := strings.SplitN(stringPair, "=", 2) if len(pairSlice) != 2 { - allErrors = multierr.Append(allErrors, fmt.Errorf("Key: '%s' Error: expected KEY=VALUE pair, missing '='", stringPair)) + allErrors = multierr.Append(allErrors, fmt.Errorf("Key [ %s ] Error: expected KEY=VALUE pair, missing '='", stringPair)) continue } @@ -126,7 +126,7 @@ func runE(cmd *cobra.Command, args []string) error { } } - stdout.Success().Printfln("Key [%s] was successfully upserted", key) + stdout.Success().Printfln("Key [ %s ] was successfully upserted", key) } if allErrors != nil { diff --git a/pkg/tui/helpers.go b/pkg/tui/helpers.go index f7225ac..3adb296 100644 --- a/pkg/tui/helpers.go +++ b/pkg/tui/helpers.go @@ -50,5 +50,5 @@ func MaybePrintWarnings(ctx context.Context, warnings error) { StderrFromContext(ctx). Warning(). - Printfln("%+v", warnings) + Printfln("WARNING: %+v", warnings) } From a3ae076cfe6ccb92b87e2c13348d9f8280d57fc1 Mon Sep 17 00:00:00 2001 From: Christian Winther Date: Thu, 15 Feb 2024 22:24:24 +0100 Subject: [PATCH 30/33] update tests --- .../disable-a-key-already-disabled/stderr.golden | 2 +- cmd/disable/tests/invalid-key/stderr.golden | 2 +- .../enable-a-key-already-enabled/stderr.golden | 2 +- .../enable-a-key-already-enabled/stdout.golden | 2 +- cmd/enable/tests/enable-a-key/stdout.golden | 2 +- cmd/enable/tests/invalid-key/stderr.golden | 2 +- cmd/set/tests/manipulate-empty/stdout.golden | 16 ++++++++-------- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/cmd/disable/tests/disable-a-key-already-disabled/stderr.golden b/cmd/disable/tests/disable-a-key-already-disabled/stderr.golden index 8188166..5f374f5 100644 --- a/cmd/disable/tests/disable-a-key-already-disabled/stderr.golden +++ b/cmd/disable/tests/disable-a-key-already-disabled/stderr.golden @@ -1,2 +1,2 @@ ---- exec command line 0: [disable KEY_B] -WARNING: The key [KEY_B] is already disabled +WARNING: The key [ KEY_B ] is already disabled diff --git a/cmd/disable/tests/invalid-key/stderr.golden b/cmd/disable/tests/invalid-key/stderr.golden index e91443c..39a71a7 100644 --- a/cmd/disable/tests/invalid-key/stderr.golden +++ b/cmd/disable/tests/invalid-key/stderr.golden @@ -1,3 +1,3 @@ ---- exec command line 0: [disable NONEXISTING_KEY] -Error: Could not find KEY [NONEXISTING_KEY] +Error: Could not find KEY [ NONEXISTING_KEY ] Run 'dottie disable --help' for usage. diff --git a/cmd/enable/tests/enable-a-key-already-enabled/stderr.golden b/cmd/enable/tests/enable-a-key-already-enabled/stderr.golden index 7fb3b5b..e1955d6 100644 --- a/cmd/enable/tests/enable-a-key-already-enabled/stderr.golden +++ b/cmd/enable/tests/enable-a-key-already-enabled/stderr.golden @@ -1,2 +1,2 @@ ---- exec command line 0: [enable KEY_B] -WARNING: The key [KEY_B] is already enabled +WARNING: The key [ KEY_B ] is already enabled diff --git a/cmd/enable/tests/enable-a-key-already-enabled/stdout.golden b/cmd/enable/tests/enable-a-key-already-enabled/stdout.golden index eb68006..cf24473 100644 --- a/cmd/enable/tests/enable-a-key-already-enabled/stdout.golden +++ b/cmd/enable/tests/enable-a-key-already-enabled/stdout.golden @@ -1,2 +1,2 @@ ---- exec command line 0: [enable KEY_B] -Key [KEY_B] was successfully enabled +Key [ KEY_B ] was successfully enabled diff --git a/cmd/enable/tests/enable-a-key/stdout.golden b/cmd/enable/tests/enable-a-key/stdout.golden index eb68006..cf24473 100644 --- a/cmd/enable/tests/enable-a-key/stdout.golden +++ b/cmd/enable/tests/enable-a-key/stdout.golden @@ -1,2 +1,2 @@ ---- exec command line 0: [enable KEY_B] -Key [KEY_B] was successfully enabled +Key [ KEY_B ] was successfully enabled diff --git a/cmd/enable/tests/invalid-key/stderr.golden b/cmd/enable/tests/invalid-key/stderr.golden index fd12d5f..75ec7f6 100644 --- a/cmd/enable/tests/invalid-key/stderr.golden +++ b/cmd/enable/tests/invalid-key/stderr.golden @@ -1,3 +1,3 @@ ---- exec command line 0: [enable NONEXISTING_KEY] -Error: Could not find KEY [NONEXISTING_KEY] +Error: Could not find KEY [ NONEXISTING_KEY ] Run 'dottie enable --help' for usage. diff --git a/cmd/set/tests/manipulate-empty/stdout.golden b/cmd/set/tests/manipulate-empty/stdout.golden index e84d99d..a4b6ec0 100644 --- a/cmd/set/tests/manipulate-empty/stdout.golden +++ b/cmd/set/tests/manipulate-empty/stdout.golden @@ -1,25 +1,25 @@ ---- exec command line 0: [set SOME_KEY=SOME_VALUE] -Key [SOME_KEY] was successfully upserted +Key [ SOME_KEY ] was successfully upserted File was successfully saved ---- exec command line 1: [set ANOTHER_KEY=ANOTHER_VALUE --quote-style single] -Key [ANOTHER_KEY] was successfully upserted +Key [ ANOTHER_KEY ] was successfully upserted File was successfully saved ---- exec command line 2: [set SECOND_KEY=should be before ANOTHER_KEY --before ANOTHER_KEY] -Key [SECOND_KEY] was successfully upserted +Key [ SECOND_KEY ] was successfully upserted File was successfully saved ---- exec command line 3: [set TRUE_SECOND_KEY=HA, I'm after SOME_KEY, so I'm before ANOTHER_KEY now --after SOME_KEY] -Key [TRUE_SECOND_KEY] was successfully upserted +Key [ TRUE_SECOND_KEY ] was successfully upserted File was successfully saved ---- exec command line 4: [set SECOND_KEY=damn, I'm the third key now] -Key [SECOND_KEY] was successfully upserted +Key [ SECOND_KEY ] was successfully upserted File was successfully saved ---- exec command line 5: [set SOME_KEY=ANOTHER_VALUE --comment I'm a comment --comment I'm another comment] -Key [SOME_KEY] was successfully upserted +Key [ SOME_KEY ] was successfully upserted File was successfully saved ---- exec command line 6: [set A_NUMBER=1 --comment @dottie/validate number] -Key [A_NUMBER] was successfully upserted +Key [ A_NUMBER ] was successfully upserted File was successfully saved ---- exec command line 7: [set NOT_A_NUMBER=abc --comment @dottie/validate number] ---- exec command line 8: [set A_NUMBER=2] -Key [A_NUMBER] was successfully upserted +Key [ A_NUMBER ] was successfully upserted File was successfully saved From bd71815fc600aacdcf5ae5a6b03d53ca1c28f551 Mon Sep 17 00:00:00 2001 From: Christian Winther Date: Thu, 15 Feb 2024 22:28:36 +0100 Subject: [PATCH 31/33] sync tests --- cmd/disable/disable.go | 2 +- cmd/disable/tests/disable-a-key/stdout.golden | 2 +- cmd/disable/tests/{disable-a-key.env => disable-key-b.env} | 0 cmd/disable/tests/{disable-a-key.run => disable-key-b.run} | 0 cmd/disable/tests/disable-key-b/env.golden | 6 ++++++ cmd/disable/tests/disable-key-b/stderr.golden | 1 + cmd/disable/tests/disable-key-b/stdout.golden | 2 ++ 7 files changed, 11 insertions(+), 2 deletions(-) rename cmd/disable/tests/{disable-a-key.env => disable-key-b.env} (100%) rename cmd/disable/tests/{disable-a-key.run => disable-key-b.run} (100%) create mode 100644 cmd/disable/tests/disable-key-b/env.golden create mode 100644 cmd/disable/tests/disable-key-b/stderr.golden create mode 100644 cmd/disable/tests/disable-key-b/stdout.golden diff --git a/cmd/disable/disable.go b/cmd/disable/disable.go index 923f6f3..7c395e0 100644 --- a/cmd/disable/disable.go +++ b/cmd/disable/disable.go @@ -46,7 +46,7 @@ func NewCommand() *cobra.Command { tui.StdoutFromContext(cmd.Context()). Success(). - Printfln("Key [%s] was successfully disabled", key) + Printfln("Key [ %s ] was successfully disabled", key) return nil }, diff --git a/cmd/disable/tests/disable-a-key/stdout.golden b/cmd/disable/tests/disable-a-key/stdout.golden index e258b95..69e127d 100644 --- a/cmd/disable/tests/disable-a-key/stdout.golden +++ b/cmd/disable/tests/disable-a-key/stdout.golden @@ -1,2 +1,2 @@ ---- exec command line 0: [disable KEY_B] -Key [KEY_B] was successfully disabled +Key [ KEY_B ] was successfully disabled diff --git a/cmd/disable/tests/disable-a-key.env b/cmd/disable/tests/disable-key-b.env similarity index 100% rename from cmd/disable/tests/disable-a-key.env rename to cmd/disable/tests/disable-key-b.env diff --git a/cmd/disable/tests/disable-a-key.run b/cmd/disable/tests/disable-key-b.run similarity index 100% rename from cmd/disable/tests/disable-a-key.run rename to cmd/disable/tests/disable-key-b.run diff --git a/cmd/disable/tests/disable-key-b/env.golden b/cmd/disable/tests/disable-key-b/env.golden new file mode 100644 index 0000000..1c0767f --- /dev/null +++ b/cmd/disable/tests/disable-key-b/env.golden @@ -0,0 +1,6 @@ +KEY_A="I'm key A" + +# Comment for KEY_B +#KEY_B="I'm key B" + +KEY_C="I'm key C" diff --git a/cmd/disable/tests/disable-key-b/stderr.golden b/cmd/disable/tests/disable-key-b/stderr.golden new file mode 100644 index 0000000..b9de686 --- /dev/null +++ b/cmd/disable/tests/disable-key-b/stderr.golden @@ -0,0 +1 @@ +---- exec command line 0: [disable KEY_B] diff --git a/cmd/disable/tests/disable-key-b/stdout.golden b/cmd/disable/tests/disable-key-b/stdout.golden new file mode 100644 index 0000000..69e127d --- /dev/null +++ b/cmd/disable/tests/disable-key-b/stdout.golden @@ -0,0 +1,2 @@ +---- exec command line 0: [disable KEY_B] +Key [ KEY_B ] was successfully disabled From 6951a78b11482b03ea800d8a764efc17a8217a83 Mon Sep 17 00:00:00 2001 From: Christian Winther Date: Thu, 15 Feb 2024 22:36:02 +0100 Subject: [PATCH 32/33] refactoring --- cmd/enable/enable.go | 6 +++--- cmd/fmt/fmt.go | 6 +++--- cmd/groups/groups.go | 6 +++--- cmd/json/json.go | 6 +++--- cmd/print/print.go | 4 ++-- cmd/update/update.go | 26 +++++++++++++------------- cmd/validate/validate.go | 31 ++++++++++++++++--------------- cmd/value/value.go | 4 ++-- 8 files changed, 45 insertions(+), 44 deletions(-) diff --git a/cmd/enable/enable.go b/cmd/enable/enable.go index 5eab1c7..e7cec0b 100644 --- a/cmd/enable/enable.go +++ b/cmd/enable/enable.go @@ -20,14 +20,14 @@ func NewCommand() *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { filename := cmd.Flag("file").Value.String() - env, err := pkg.Load(filename) + document, err := pkg.Load(filename) if err != nil { return err } key := cmd.Flags().Arg(0) - assignment := env.Get(key) + assignment := document.Get(key) if assignment == nil { return fmt.Errorf("Could not find KEY [ %s ]", key) } @@ -38,7 +38,7 @@ func NewCommand() *cobra.Command { assignment.Enable() - if err := pkg.Save(cmd.Context(), filename, env); err != nil { + if err := pkg.Save(cmd.Context(), filename, document); err != nil { return fmt.Errorf("could not save file: %w", err) } diff --git a/cmd/fmt/fmt.go b/cmd/fmt/fmt.go index 36755a5..0dddd04 100644 --- a/cmd/fmt/fmt.go +++ b/cmd/fmt/fmt.go @@ -15,18 +15,18 @@ func NewCommand() *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { filename := cmd.Flag("file").Value.String() - env, err := pkg.Load(filename) + document, err := pkg.Load(filename) if err != nil { return err } - if err := pkg.Save(cmd.Context(), filename, env); err != nil { + if err := pkg.Save(cmd.Context(), filename, document); err != nil { return err } tui.StdoutFromContext(cmd.Context()). Success(). - Printfln("File [%s] was successfully formatted", filename) + Printfln("File [ %s ] was successfully formatted", filename) return nil }, diff --git a/cmd/groups/groups.go b/cmd/groups/groups.go index 2bf7e25..f961b32 100644 --- a/cmd/groups/groups.go +++ b/cmd/groups/groups.go @@ -20,12 +20,12 @@ func NewCommand() *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { filename := cmd.Flag("file").Value.String() - env, err := pkg.Load(filename) + document, err := pkg.Load(filename) if err != nil { return err } - groups := env.Groups + groups := document.Groups if len(groups) == 0 { return errors.New("No groups found") } @@ -33,8 +33,8 @@ func NewCommand() *cobra.Command { maxWidth := longesGroupName(groups) stdout := tui.StdoutFromContext(cmd.Context()) - secondary := stdout.Secondary() primary := stdout.Primary() + secondary := stdout.Secondary() stdout.Info().Box("Groups in " + filename) diff --git a/cmd/json/json.go b/cmd/json/json.go index 68b9d99..4baa465 100644 --- a/cmd/json/json.go +++ b/cmd/json/json.go @@ -17,17 +17,17 @@ func NewCommand() *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { filename := cmd.Flag("file").Value.String() - env, err := pkg.Load(filename) + document, err := pkg.Load(filename) if err != nil { return err } - b, err := json.MarshalIndent(env, "", " ") + output, err := json.MarshalIndent(document, "", " ") if err != nil { return err } - fmt.Println(string(b)) + fmt.Println(string(output)) return nil }, diff --git a/cmd/print/print.go b/cmd/print/print.go index 817fe86..15f4d41 100644 --- a/cmd/print/print.go +++ b/cmd/print/print.go @@ -35,7 +35,7 @@ func NewCommand() *cobra.Command { } func runE(cmd *cobra.Command, args []string) error { - env, settings, err := setup(cmd) + document, settings, err := setup(cmd) if err != nil { return err } @@ -44,7 +44,7 @@ func runE(cmd *cobra.Command, args []string) error { NoColor(). Println( render.NewRenderer(*settings). - Statement(cmd.Context(), env). + Statement(cmd.Context(), document). String(), ) diff --git a/cmd/update/update.go b/cmd/update/update.go index c47332c..c35b974 100644 --- a/cmd/update/update.go +++ b/cmd/update/update.go @@ -36,7 +36,7 @@ func NewCommand() *cobra.Command { func runE(cmd *cobra.Command, args []string) error { filename := cmd.Flag("file").Value.String() - originalEnv, err := pkg.Load(filename) + originalDocument, err := pkg.Load(filename) if err != nil { return err } @@ -58,7 +58,7 @@ func runE(cmd *cobra.Command, args []string) error { source, _ := cmd.Flags().GetString("source") if len(source) == 0 { - source, err = originalEnv.GetConfig("dottie/source") + source, err = originalDocument.GetConfig("dottie/source") if err != nil { return err } @@ -103,7 +103,7 @@ func runE(cmd *cobra.Command, args []string) error { // Load the soon-to-be-merged file dark.Println("Loading and parsing source") - sourceDoc, err := pkg.Load(tmp.Name()) + sourceDocument, err := pkg.Load(tmp.Name()) if err != nil { return err } @@ -119,13 +119,13 @@ func runE(cmd *cobra.Command, args []string) error { lastWasError := false counter := 0 - for _, originalStatement := range originalEnv.AllAssignments() { + for _, originalStatement := range originalDocument.AllAssignments() { if !originalStatement.Enabled { continue } upserter, err := upsert.New( - sourceDoc, + sourceDocument, upsert.WithSetting(upsert.SkipIfSame), upsert.WithSettingIf(upsert.ErrorIfMissing, shared.BoolWithInverseValue(cmd.Flags(), "error-on-missing-key")), ) @@ -134,9 +134,9 @@ func runE(cmd *cobra.Command, args []string) error { } // If the KEY does *NOT* exists in the SOURCE doc - if sourceDoc.Get(originalStatement.Name) == nil { + if sourceDocument.Get(originalStatement.Name) == nil { // Try to find positioning in the statement list for the new KEY pair - var parent ast.StatementCollection = originalEnv + var parent ast.StatementCollection = originalDocument if originalStatement.Group != nil { parent = originalStatement.Group @@ -151,7 +151,7 @@ func runE(cmd *cobra.Command, args []string) error { upserter.ApplyOptions(upsert.WithPlacement(upsert.AddLast)) // Retain the group name if its still present in the SOURCE doc - if originalStatement.Group != nil && sourceDoc.HasGroup(originalStatement.Group.String()) { + if originalStatement.Group != nil && sourceDocument.HasGroup(originalStatement.Group.String()) { upserter.ApplyOptions(upsert.WithGroup(originalStatement.Group.String())) } @@ -160,7 +160,7 @@ func runE(cmd *cobra.Command, args []string) error { upserter.ApplyOptions(upsert.WithPlacement(upsert.AddFirst)) // Retain the group name if its still present in the SOURCE doc - if originalStatement.Group != nil && sourceDoc.HasGroup(originalStatement.Group.String()) { + if originalStatement.Group != nil && sourceDocument.HasGroup(originalStatement.Group.String()) { upserter.ApplyOptions(upsert.WithGroup(originalStatement.Group.String())) } @@ -173,7 +173,7 @@ func runE(cmd *cobra.Command, args []string) error { return err } - if before.Group != nil && sourceDoc.HasGroup(before.Group.String()) { + if before.Group != nil && sourceDocument.HasGroup(before.Group.String()) { upserter.ApplyOptions(upsert.WithGroup(before.Group.String())) } } @@ -205,7 +205,7 @@ func runE(cmd *cobra.Command, args []string) error { continue } - if errors := validation.ValidateSingleAssignment(cmd.Context(), originalEnv, originalStatement, nil, []string{"file", "dir"}); len(errors) > 0 { + if errors := validation.ValidateSingleAssignment(cmd.Context(), originalDocument, originalStatement, nil, []string{"file", "dir"}); len(errors) > 0 { sawError = true lastWasError = true @@ -220,7 +220,7 @@ func runE(cmd *cobra.Command, args []string) error { dark.Println(" due to validation error:") for _, errIsh := range errors { - danger.Println(" ", strings.Repeat(" ", len(originalStatement.Name)), strings.TrimSpace(validation.Explain(cmd.Context(), originalEnv, errIsh, errIsh, false, false))) + danger.Println(" ", strings.Repeat(" ", len(originalStatement.Name)), strings.TrimSpace(validation.Explain(cmd.Context(), originalDocument, errIsh, errIsh, false, false))) } counter++ @@ -266,7 +266,7 @@ func runE(cmd *cobra.Command, args []string) error { dark.Println("Saving the new", primary.Sprint(filename)) - if err := pkg.Save(cmd.Context(), filename, sourceDoc); err != nil { + if err := pkg.Save(cmd.Context(), filename, sourceDocument); err != nil { danger.Println(" ERROR", err.Error()) return err diff --git a/cmd/validate/validate.go b/cmd/validate/validate.go index d4e961c..9faad53 100644 --- a/cmd/validate/validate.go +++ b/cmd/validate/validate.go @@ -33,7 +33,7 @@ func NewCommand() *cobra.Command { func runE(cmd *cobra.Command, args []string) error { filename := cmd.Flag("file").Value.String() - env, err := pkg.Load(filename) + document, err := pkg.Load(filename) if err != nil { return err } @@ -42,14 +42,11 @@ func runE(cmd *cobra.Command, args []string) error { // Build filters // - fix := shared.BoolWithInverseValue(cmd.Flags(), "fix") - ignoreRules, _ := cmd.Flags().GetStringSlice("ignore-rule") - handlers := []render.Handler{} handlers = append(handlers, render.ExcludeDisabledAssignments) - slice, _ := cmd.Flags().GetStringSlice("exclude-prefix") - for _, filter := range slice { + excludedPrefixes := shared.StringSliceFlag(cmd.Flags(), "exclude-prefix") + for _, filter := range excludedPrefixes { handlers = append(handlers, render.ExcludeKeyPrefix(filter)) } @@ -59,7 +56,7 @@ func runE(cmd *cobra.Command, args []string) error { // Interpolate // - warn, err := env.InterpolateAll() + warn, err := document.InterpolateAll() if warn != nil { stderr.Warning().Printfln("%+v", warn) @@ -73,38 +70,42 @@ func runE(cmd *cobra.Command, args []string) error { // Validate // - res := validation.Validate(cmd.Context(), env, handlers, ignoreRules) - if len(res) == 0 { + ignoreRules := shared.StringSliceFlag(cmd.Flags(), "ignore-rule") + + validationErrors := validation.Validate(cmd.Context(), document, handlers, ignoreRules) + if len(validationErrors) == 0 { stderr.Success().Box("No validation errors found") return nil } + attemptFixOfValidationError := shared.BoolWithInverseValue(cmd.Flags(), "fix") + danger := stderr.Danger() - danger.Box(fmt.Sprintf("%d validation errors found", len(res))) + danger.Box(fmt.Sprintf("%d validation errors found", len(validationErrors))) danger.Println() - for _, errIsh := range res { - fmt.Fprintln(os.Stderr, validation.Explain(cmd.Context(), env, errIsh, errIsh, fix, true)) + for _, errIsh := range validationErrors { + fmt.Fprintln(os.Stderr, validation.Explain(cmd.Context(), document, errIsh, errIsh, attemptFixOfValidationError, true)) } // // Validate file again, in case some of the fixers from before fixed them // - env, err = pkg.Load(cmd.Flag("file").Value.String()) + document, err = pkg.Load(cmd.Flag("file").Value.String()) if err != nil { return fmt.Errorf("failed to reload .env file: %w", err) } - newRes := validation.Validate(cmd.Context(), env, handlers, ignoreRules) + newRes := validation.Validate(cmd.Context(), document, handlers, ignoreRules) if len(newRes) == 0 { stderr.Success().Println("All validation errors fixed") return nil } - diff := len(res) - len(newRes) + diff := len(validationErrors) - len(newRes) if diff > 0 { stderr.Warning(). Box( diff --git a/cmd/value/value.go b/cmd/value/value.go index 3096d2a..c38e56b 100644 --- a/cmd/value/value.go +++ b/cmd/value/value.go @@ -29,11 +29,11 @@ func NewCommand() *cobra.Command { assignment := document.Get(key) if assignment == nil { - return fmt.Errorf("Key [%s] does not exists", key) + return fmt.Errorf("Key [ %s ] does not exists", key) } if !assignment.Enabled && !shared.BoolFlag(cmd.Flags(), "include-commented") { - return fmt.Errorf("Key [%s] exists, but is commented out - use [--include-commented] to include it", key) + return fmt.Errorf("Key [ %s ] exists, but is commented out - use [--include-commented] to include it", key) } warnings, err := document.InterpolateStatement(assignment) From 25a20799c6ce6ebc0bec5048a2d4e8cd90fc602d Mon Sep 17 00:00:00 2001 From: Christian Winther Date: Thu, 15 Feb 2024 23:57:57 +0100 Subject: [PATCH 33/33] more testing --- cmd/disable/disable_test.go | 2 +- .../stdout.golden | 1 + cmd/disable/tests/disable-key-b/stderr.golden | 1 + cmd/disable/tests/invalid-key/stdout.golden | 1 + cmd/disable/tests/missing-key/stdout.golden | 1 + cmd/enable/enable_test.go | 2 +- cmd/enable/tests/enable-a-key/stderr.golden | 1 + cmd/enable/tests/invalid-key/stdout.golden | 1 + cmd/enable/tests/missing-key/stdout.golden | 1 + cmd/groups/groups_test.go | 4 +- .../tests/multiple-groups/stderr.golden | 1 + cmd/groups/tests/no-groups/stdout.golden | 1 + cmd/groups/tests/single-group/stderr.golden | 1 + cmd/print/print_test.go | 4 +- cmd/print/tests/empty/stderr.golden | 1 + cmd/print/tests/full/stderr.golden | 1 + cmd/print/tests/simple-pretty/stderr.golden | 1 + cmd/print/tests/simple/stderr.golden | 1 + cmd/print/tests/specific-group/stderr.golden | 1 + cmd/set/set.go | 8 ++- cmd/set/set_test.go | 2 +- cmd/set/tests/error-if-missing.env | 1 + cmd/set/tests/error-if-missing.run | 2 + cmd/set/tests/error-if-missing/env.golden | 1 + cmd/set/tests/error-if-missing/stderr.golden | 7 +++ cmd/set/tests/error-if-missing/stdout.golden | 5 ++ cmd/set/tests/manipulate-empty.env | 1 - cmd/set/tests/manipulate-empty/stderr.golden | 10 +++- cmd/set/tests/manipulate-empty/stdout.golden | 1 + cmd/set/tests/skip-if-exists.run | 2 + cmd/set/tests/skip-if-exists/env.golden | 1 + cmd/set/tests/skip-if-exists/stderr.golden | 4 ++ cmd/set/tests/skip-if-exists/stdout.golden | 6 +++ cmd/set/tests/skip-if-same.run | 2 + cmd/set/tests/skip-if-same/env.golden | 1 + cmd/set/tests/skip-if-same/stderr.golden | 4 ++ cmd/set/tests/skip-if-same/stdout.golden | 6 +++ pkg/ast/upsert/upsert.go | 18 +++---- pkg/test_helpers/filebased_command_tests.go | 49 +++++++++++++------ 39 files changed, 123 insertions(+), 35 deletions(-) create mode 100644 cmd/set/tests/error-if-missing.env create mode 100644 cmd/set/tests/error-if-missing.run create mode 100644 cmd/set/tests/error-if-missing/env.golden create mode 100644 cmd/set/tests/error-if-missing/stderr.golden create mode 100644 cmd/set/tests/error-if-missing/stdout.golden delete mode 100644 cmd/set/tests/manipulate-empty.env create mode 100644 cmd/set/tests/skip-if-exists.run create mode 100644 cmd/set/tests/skip-if-exists/env.golden create mode 100644 cmd/set/tests/skip-if-exists/stderr.golden create mode 100644 cmd/set/tests/skip-if-exists/stdout.golden create mode 100644 cmd/set/tests/skip-if-same.run create mode 100644 cmd/set/tests/skip-if-same/env.golden create mode 100644 cmd/set/tests/skip-if-same/stderr.golden create mode 100644 cmd/set/tests/skip-if-same/stdout.golden diff --git a/cmd/disable/disable_test.go b/cmd/disable/disable_test.go index 03865bc..088db2d 100644 --- a/cmd/disable/disable_test.go +++ b/cmd/disable/disable_test.go @@ -6,7 +6,7 @@ import ( "github.com/jippi/dottie/pkg/test_helpers" ) -func TestCommand(t *testing.T) { +func TestDisableCommand(t *testing.T) { t.Parallel() test_helpers.RunFileBasedCommandTests(t, 0, "disable") diff --git a/cmd/disable/tests/disable-a-key-already-disabled/stdout.golden b/cmd/disable/tests/disable-a-key-already-disabled/stdout.golden index b9de686..b8fe0b5 100644 --- a/cmd/disable/tests/disable-a-key-already-disabled/stdout.golden +++ b/cmd/disable/tests/disable-a-key-already-disabled/stdout.golden @@ -1 +1,2 @@ ---- exec command line 0: [disable KEY_B] +(no output to stdout) diff --git a/cmd/disable/tests/disable-key-b/stderr.golden b/cmd/disable/tests/disable-key-b/stderr.golden index b9de686..7a47971 100644 --- a/cmd/disable/tests/disable-key-b/stderr.golden +++ b/cmd/disable/tests/disable-key-b/stderr.golden @@ -1 +1,2 @@ ---- exec command line 0: [disable KEY_B] +(no output to stderr) diff --git a/cmd/disable/tests/invalid-key/stdout.golden b/cmd/disable/tests/invalid-key/stdout.golden index cc62e8b..f791f4f 100644 --- a/cmd/disable/tests/invalid-key/stdout.golden +++ b/cmd/disable/tests/invalid-key/stdout.golden @@ -1 +1,2 @@ ---- exec command line 0: [disable NONEXISTING_KEY] +(no output to stdout) diff --git a/cmd/disable/tests/missing-key/stdout.golden b/cmd/disable/tests/missing-key/stdout.golden index faefdb7..ade1f17 100644 --- a/cmd/disable/tests/missing-key/stdout.golden +++ b/cmd/disable/tests/missing-key/stdout.golden @@ -1 +1,2 @@ ---- exec command line 0: [disable] +(no output to stdout) diff --git a/cmd/enable/enable_test.go b/cmd/enable/enable_test.go index 2394256..42eed73 100644 --- a/cmd/enable/enable_test.go +++ b/cmd/enable/enable_test.go @@ -6,7 +6,7 @@ import ( "github.com/jippi/dottie/pkg/test_helpers" ) -func TestCommand(t *testing.T) { +func TestEnableCommand(t *testing.T) { t.Parallel() test_helpers.RunFileBasedCommandTests(t, 0, "enable") diff --git a/cmd/enable/tests/enable-a-key/stderr.golden b/cmd/enable/tests/enable-a-key/stderr.golden index fec5fa2..6915875 100644 --- a/cmd/enable/tests/enable-a-key/stderr.golden +++ b/cmd/enable/tests/enable-a-key/stderr.golden @@ -1 +1,2 @@ ---- exec command line 0: [enable KEY_B] +(no output to stderr) diff --git a/cmd/enable/tests/invalid-key/stdout.golden b/cmd/enable/tests/invalid-key/stdout.golden index 5dc11c0..f53d6f0 100644 --- a/cmd/enable/tests/invalid-key/stdout.golden +++ b/cmd/enable/tests/invalid-key/stdout.golden @@ -1 +1,2 @@ ---- exec command line 0: [enable NONEXISTING_KEY] +(no output to stdout) diff --git a/cmd/enable/tests/missing-key/stdout.golden b/cmd/enable/tests/missing-key/stdout.golden index e66e7b4..0a0fea8 100644 --- a/cmd/enable/tests/missing-key/stdout.golden +++ b/cmd/enable/tests/missing-key/stdout.golden @@ -1 +1,2 @@ ---- exec command line 0: [enable] +(no output to stdout) diff --git a/cmd/groups/groups_test.go b/cmd/groups/groups_test.go index 8f8288b..8291f96 100644 --- a/cmd/groups/groups_test.go +++ b/cmd/groups/groups_test.go @@ -6,8 +6,8 @@ import ( "github.com/jippi/dottie/pkg/test_helpers" ) -func TestCommand(t *testing.T) { +func TestGroupsCommand(t *testing.T) { t.Parallel() - test_helpers.RunFileBasedCommandTests(t, test_helpers.SkipEnvCopy, "groups") + test_helpers.RunFileBasedCommandTests(t, test_helpers.ReadOnly, "groups") } diff --git a/cmd/groups/tests/multiple-groups/stderr.golden b/cmd/groups/tests/multiple-groups/stderr.golden index 94cc795..a4fd48d 100644 --- a/cmd/groups/tests/multiple-groups/stderr.golden +++ b/cmd/groups/tests/multiple-groups/stderr.golden @@ -1 +1,2 @@ ---- exec command line 0: [groups] +(no output to stderr) diff --git a/cmd/groups/tests/no-groups/stdout.golden b/cmd/groups/tests/no-groups/stdout.golden index 94cc795..ec6a993 100644 --- a/cmd/groups/tests/no-groups/stdout.golden +++ b/cmd/groups/tests/no-groups/stdout.golden @@ -1 +1,2 @@ ---- exec command line 0: [groups] +(no output to stdout) diff --git a/cmd/groups/tests/single-group/stderr.golden b/cmd/groups/tests/single-group/stderr.golden index 94cc795..a4fd48d 100644 --- a/cmd/groups/tests/single-group/stderr.golden +++ b/cmd/groups/tests/single-group/stderr.golden @@ -1 +1,2 @@ ---- exec command line 0: [groups] +(no output to stderr) diff --git a/cmd/print/print_test.go b/cmd/print/print_test.go index f02b771..6ccc9af 100644 --- a/cmd/print/print_test.go +++ b/cmd/print/print_test.go @@ -6,8 +6,8 @@ import ( "github.com/jippi/dottie/pkg/test_helpers" ) -func TestCommand(t *testing.T) { +func TestPrintCommand(t *testing.T) { t.Parallel() - test_helpers.RunFileBasedCommandTests(t, test_helpers.SkipEnvCopy, "print") + test_helpers.RunFileBasedCommandTests(t, test_helpers.ReadOnly, "print") } diff --git a/cmd/print/tests/empty/stderr.golden b/cmd/print/tests/empty/stderr.golden index a1e5768..069fc9c 100644 --- a/cmd/print/tests/empty/stderr.golden +++ b/cmd/print/tests/empty/stderr.golden @@ -1 +1,2 @@ ---- exec command line 0: [print --no-color] +(no output to stderr) diff --git a/cmd/print/tests/full/stderr.golden b/cmd/print/tests/full/stderr.golden index cb65407..22e39bb 100644 --- a/cmd/print/tests/full/stderr.golden +++ b/cmd/print/tests/full/stderr.golden @@ -1 +1,2 @@ ---- exec command line 0: [print --no-color --pretty] +(no output to stderr) diff --git a/cmd/print/tests/simple-pretty/stderr.golden b/cmd/print/tests/simple-pretty/stderr.golden index cb65407..22e39bb 100644 --- a/cmd/print/tests/simple-pretty/stderr.golden +++ b/cmd/print/tests/simple-pretty/stderr.golden @@ -1 +1,2 @@ ---- exec command line 0: [print --no-color --pretty] +(no output to stderr) diff --git a/cmd/print/tests/simple/stderr.golden b/cmd/print/tests/simple/stderr.golden index a1e5768..069fc9c 100644 --- a/cmd/print/tests/simple/stderr.golden +++ b/cmd/print/tests/simple/stderr.golden @@ -1 +1,2 @@ ---- exec command line 0: [print --no-color] +(no output to stderr) diff --git a/cmd/print/tests/specific-group/stderr.golden b/cmd/print/tests/specific-group/stderr.golden index 8e61f95..a034bd6 100644 --- a/cmd/print/tests/specific-group/stderr.golden +++ b/cmd/print/tests/specific-group/stderr.golden @@ -1 +1,2 @@ ---- exec command line 0: [print --no-color --group my-first-group] +(no output to stderr) diff --git a/cmd/set/set.go b/cmd/set/set.go index f73ea23..ccca723 100644 --- a/cmd/set/set.go +++ b/cmd/set/set.go @@ -1,7 +1,6 @@ package set import ( - "errors" "fmt" "strings" @@ -34,6 +33,9 @@ func NewCommand() *cobra.Command { cmd.Flags().Bool("disabled", false, "Set/change the flag to be disabled (commented out)") cmd.Flags().Bool("error-if-missing", false, "Exit with an error if the KEY does not exists in the .env file already") + cmd.Flags().Bool("skip-if-exists", false, "If the already KEY exists, do not set or change any settings") + cmd.Flags().Bool("skip-if-same", false, "If the already KEY exists, and it the value is identical, do not set or change any settings") + cmd.Flags().String("group", "", "The (optional) group name to add the KEY=VALUE pair under") cmd.Flags().String("before", "", "If the key doesn't exist, add it to the file *before* this KEY") cmd.Flags().String("after", "", "If the key doesn't exist, add it to the file *after* this KEY") @@ -61,6 +63,8 @@ func runE(cmd *cobra.Command, args []string) error { document, upsert.WithGroup(shared.StringFlag(cmd.Flags(), "group")), upsert.WithSettingIf(upsert.ErrorIfMissing, shared.BoolFlag(cmd.Flags(), "error-if-missing")), + upsert.WithSettingIf(upsert.SkipIfExists, shared.BoolFlag(cmd.Flags(), "skip-if-exists")), + upsert.WithSettingIf(upsert.SkipIfSame, shared.BoolFlag(cmd.Flags(), "skip-if-same")), upsert.WithSettingIf(upsert.UpdateComments, cmd.Flag("comment").Changed), ) if err != nil { @@ -130,7 +134,7 @@ func runE(cmd *cobra.Command, args []string) error { } if allErrors != nil { - return errors.New("validation error") + return fmt.Errorf("validation error: %+w", allErrors) } // diff --git a/cmd/set/set_test.go b/cmd/set/set_test.go index 3a6811a..e86616e 100644 --- a/cmd/set/set_test.go +++ b/cmd/set/set_test.go @@ -6,7 +6,7 @@ import ( "github.com/jippi/dottie/pkg/test_helpers" ) -func TestCommand(t *testing.T) { +func TestSetCommand(t *testing.T) { t.Parallel() test_helpers.RunFileBasedCommandTests(t, 0, "set") diff --git a/cmd/set/tests/error-if-missing.env b/cmd/set/tests/error-if-missing.env new file mode 100644 index 0000000..8afe00b --- /dev/null +++ b/cmd/set/tests/error-if-missing.env @@ -0,0 +1 @@ +SOME_KEY="ERROR THE VALUE WAS NOT CHANGED" diff --git a/cmd/set/tests/error-if-missing.run b/cmd/set/tests/error-if-missing.run new file mode 100644 index 0000000..de23a8b --- /dev/null +++ b/cmd/set/tests/error-if-missing.run @@ -0,0 +1,2 @@ +--error-if-missing MISSING_KEY="THIS MUST FAIL" +--error-if-missing SOME_KEY="SUCCESS" diff --git a/cmd/set/tests/error-if-missing/env.golden b/cmd/set/tests/error-if-missing/env.golden new file mode 100644 index 0000000..1a7fe8f --- /dev/null +++ b/cmd/set/tests/error-if-missing/env.golden @@ -0,0 +1 @@ +SOME_KEY="SUCCESS" diff --git a/cmd/set/tests/error-if-missing/stderr.golden b/cmd/set/tests/error-if-missing/stderr.golden new file mode 100644 index 0000000..ac6afd4 --- /dev/null +++ b/cmd/set/tests/error-if-missing/stderr.golden @@ -0,0 +1,7 @@ +---- exec command line 0: [set --error-if-missing MISSING_KEY=THIS MUST FAIL] +(error *errors.errorString) key [MISSING_KEY] does not exists in the document + +Error: validation error: key [MISSING_KEY] does not exists in the document +Run 'dottie set --help' for usage. +---- exec command line 1: [set --error-if-missing SOME_KEY=SUCCESS] +(no output to stderr) diff --git a/cmd/set/tests/error-if-missing/stdout.golden b/cmd/set/tests/error-if-missing/stdout.golden new file mode 100644 index 0000000..1216cda --- /dev/null +++ b/cmd/set/tests/error-if-missing/stdout.golden @@ -0,0 +1,5 @@ +---- exec command line 0: [set --error-if-missing MISSING_KEY=THIS MUST FAIL] +(no output to stdout) +---- exec command line 1: [set --error-if-missing SOME_KEY=SUCCESS] +Key [ SOME_KEY ] was successfully upserted +File was successfully saved diff --git a/cmd/set/tests/manipulate-empty.env b/cmd/set/tests/manipulate-empty.env deleted file mode 100644 index dfd1584..0000000 --- a/cmd/set/tests/manipulate-empty.env +++ /dev/null @@ -1 +0,0 @@ -SOME_KEY="SOME_VALUE" diff --git a/cmd/set/tests/manipulate-empty/stderr.golden b/cmd/set/tests/manipulate-empty/stderr.golden index c3a6913..9ddd180 100644 --- a/cmd/set/tests/manipulate-empty/stderr.golden +++ b/cmd/set/tests/manipulate-empty/stderr.golden @@ -1,14 +1,22 @@ ---- exec command line 0: [set SOME_KEY=SOME_VALUE] +(no output to stderr) ---- exec command line 1: [set ANOTHER_KEY=ANOTHER_VALUE --quote-style single] +(no output to stderr) ---- exec command line 2: [set SECOND_KEY=should be before ANOTHER_KEY --before ANOTHER_KEY] +(no output to stderr) ---- exec command line 3: [set TRUE_SECOND_KEY=HA, I'm after SOME_KEY, so I'm before ANOTHER_KEY now --after SOME_KEY] +(no output to stderr) ---- exec command line 4: [set SECOND_KEY=damn, I'm the third key now] +(no output to stderr) ---- exec command line 5: [set SOME_KEY=ANOTHER_VALUE --comment I'm a comment --comment I'm another comment] +(no output to stderr) ---- exec command line 6: [set A_NUMBER=1 --comment @dottie/validate number] +(no output to stderr) ---- exec command line 7: [set NOT_A_NUMBER=abc --comment @dottie/validate number] NOT_A_NUMBER (-:2) * (number) The value [abc] is not a valid number. -Error: validation error +Error: validation error: Key: 'NOT_A_NUMBER' Error:Field validation for 'NOT_A_NUMBER' failed on the 'number' tag Run 'dottie set --help' for usage. ---- exec command line 8: [set A_NUMBER=2] +(no output to stderr) diff --git a/cmd/set/tests/manipulate-empty/stdout.golden b/cmd/set/tests/manipulate-empty/stdout.golden index a4b6ec0..b9c0e55 100644 --- a/cmd/set/tests/manipulate-empty/stdout.golden +++ b/cmd/set/tests/manipulate-empty/stdout.golden @@ -20,6 +20,7 @@ File was successfully saved Key [ A_NUMBER ] was successfully upserted File was successfully saved ---- exec command line 7: [set NOT_A_NUMBER=abc --comment @dottie/validate number] +(no output to stdout) ---- exec command line 8: [set A_NUMBER=2] Key [ A_NUMBER ] was successfully upserted File was successfully saved diff --git a/cmd/set/tests/skip-if-exists.run b/cmd/set/tests/skip-if-exists.run new file mode 100644 index 0000000..0eb9aff --- /dev/null +++ b/cmd/set/tests/skip-if-exists.run @@ -0,0 +1,2 @@ +--skip-if-exists SOME_KEY="SUCCESS" +--skip-if-exists SOME_KEY="ERROR; THE VALUE MUST NOT CHANGE" diff --git a/cmd/set/tests/skip-if-exists/env.golden b/cmd/set/tests/skip-if-exists/env.golden new file mode 100644 index 0000000..1a7fe8f --- /dev/null +++ b/cmd/set/tests/skip-if-exists/env.golden @@ -0,0 +1 @@ +SOME_KEY="SUCCESS" diff --git a/cmd/set/tests/skip-if-exists/stderr.golden b/cmd/set/tests/skip-if-exists/stderr.golden new file mode 100644 index 0000000..a9afcd8 --- /dev/null +++ b/cmd/set/tests/skip-if-exists/stderr.golden @@ -0,0 +1,4 @@ +---- exec command line 0: [set --skip-if-exists SOME_KEY=SUCCESS] +(no output to stderr) +---- exec command line 1: [set --skip-if-exists SOME_KEY=ERROR; THE VALUE MUST NOT CHANGE] +(no output to stderr) diff --git a/cmd/set/tests/skip-if-exists/stdout.golden b/cmd/set/tests/skip-if-exists/stdout.golden new file mode 100644 index 0000000..afa258a --- /dev/null +++ b/cmd/set/tests/skip-if-exists/stdout.golden @@ -0,0 +1,6 @@ +---- exec command line 0: [set --skip-if-exists SOME_KEY=SUCCESS] +Key [ SOME_KEY ] was successfully upserted +File was successfully saved +---- exec command line 1: [set --skip-if-exists SOME_KEY=ERROR; THE VALUE MUST NOT CHANGE] +Key [ SOME_KEY ] was successfully upserted +File was successfully saved diff --git a/cmd/set/tests/skip-if-same.run b/cmd/set/tests/skip-if-same.run new file mode 100644 index 0000000..c22d1b0 --- /dev/null +++ b/cmd/set/tests/skip-if-same.run @@ -0,0 +1,2 @@ +--skip-if-same SOME_KEY="ERROR THIS SHOULD BE CHANGED BY NEXT LINE" +--skip-if-same SOME_KEY="SUCCESS" diff --git a/cmd/set/tests/skip-if-same/env.golden b/cmd/set/tests/skip-if-same/env.golden new file mode 100644 index 0000000..1a7fe8f --- /dev/null +++ b/cmd/set/tests/skip-if-same/env.golden @@ -0,0 +1 @@ +SOME_KEY="SUCCESS" diff --git a/cmd/set/tests/skip-if-same/stderr.golden b/cmd/set/tests/skip-if-same/stderr.golden new file mode 100644 index 0000000..486b3a8 --- /dev/null +++ b/cmd/set/tests/skip-if-same/stderr.golden @@ -0,0 +1,4 @@ +---- exec command line 0: [set --skip-if-same SOME_KEY=ERROR THIS SHOULD BE CHANGED BY NEXT LINE] +(no output to stderr) +---- exec command line 1: [set --skip-if-same SOME_KEY=SUCCESS] +(no output to stderr) diff --git a/cmd/set/tests/skip-if-same/stdout.golden b/cmd/set/tests/skip-if-same/stdout.golden new file mode 100644 index 0000000..a1b27e3 --- /dev/null +++ b/cmd/set/tests/skip-if-same/stdout.golden @@ -0,0 +1,6 @@ +---- exec command line 0: [set --skip-if-same SOME_KEY=ERROR THIS SHOULD BE CHANGED BY NEXT LINE] +Key [ SOME_KEY ] was successfully upserted +File was successfully saved +---- exec command line 1: [set --skip-if-same SOME_KEY=SUCCESS] +Key [ SOME_KEY ] was successfully upserted +File was successfully saved diff --git a/pkg/ast/upsert/upsert.go b/pkg/ast/upsert/upsert.go index 27ea1cc..66bd621 100644 --- a/pkg/ast/upsert/upsert.go +++ b/pkg/ast/upsert/upsert.go @@ -53,29 +53,29 @@ func (u *Upserter) ApplyOptions(options ...Option) error { // Upsert will, depending on its options, either Update or Insert (thus, "[Up]date + In[sert]"). func (u *Upserter) Upsert(ctx context.Context, input *ast.Assignment) (*ast.Assignment, error, error) { assignment := u.document.Get(input.Name) - found := assignment != nil + exists := assignment != nil // Short circuit with some quick settings checks switch { // The assignment exists, so return early - case found && u.settings.Has(SkipIfExists): + case exists && u.settings.Has(SkipIfExists): return nil, nil, nil + // The assignment does *NOT* exists, and we require it to + case !exists && u.settings.Has(ErrorIfMissing): + return nil, nil, fmt.Errorf("key [%s] does not exists in the document", input.Name) + // The assignment exists, has a literal value, and the literal value isn't what we should consider empty - case found && u.settings.Has(SkipIfSet) && len(assignment.Literal) > 0 && !slices.Contains(u.valuesConsideredEmpty, assignment.Literal): + case exists && u.settings.Has(SkipIfSet) && len(assignment.Literal) > 0 && !slices.Contains(u.valuesConsideredEmpty, assignment.Literal): return nil, nil, nil // The assignment exists, the literal values are the same, and they have same 'Enabled' level - case found && u.settings.Has(SkipIfSame) && assignment.Literal == input.Literal && assignment.Enabled == input.Enabled: + case exists && u.settings.Has(SkipIfSame) && assignment.Literal == input.Literal && assignment.Enabled == input.Enabled: return nil, nil, nil - // The assignment does *NOT* exists, and we require it to - case !found && u.settings.Has(ErrorIfMissing): - return nil, nil, fmt.Errorf("key [%s] does not exists in the document", input.Name) - // The KEY was *NOT* found, and all other preconditions are not triggering - case !found: + case !exists: var err error // Create and insert the (*ast.Assignment) into the Statement list diff --git a/pkg/test_helpers/filebased_command_tests.go b/pkg/test_helpers/filebased_command_tests.go index cac3dd6..4470c5a 100644 --- a/pkg/test_helpers/filebased_command_tests.go +++ b/pkg/test_helpers/filebased_command_tests.go @@ -3,6 +3,7 @@ package test_helpers import ( "bytes" "context" + "errors" "fmt" "io" "os" @@ -21,7 +22,7 @@ import ( type Setting int const ( - SkipEnvCopy Setting = 1 << iota + ReadOnly Setting = 1 << iota ) // Has checks if [check] exists in the [settings] bitmask or not. @@ -118,18 +119,23 @@ func RunFileBasedCommandTests(t *testing.T, settings Setting, globalArgs ...stri dotEnvFile := "tests/" + tt.envFile - if !settings.Has(SkipEnvCopy) { - tmpDir := t.TempDir() - dotEnvFile = tmpDir + "/tmp.env" - - // Copy the input.env to temporary place - err := copyFile(t, "tests/"+tt.envFile, tmpDir+"/tmp.env") - require.NoErrorf(t, err, "failed to copy [%s] to TempDir", tt.envFile) + if !settings.Has(ReadOnly) { + dotEnvFile = t.TempDir() + "/tmp.env" + + if _, err := os.Stat("tests/" + tt.envFile); errors.Is(err, os.ErrNotExist) { + // Create a temporary empty .env file + _, err := os.Create(dotEnvFile) + require.NoErrorf(t, err, "failed to create empty .env file [ %s ] in TempDir", tt.envFile) + } else { + // Copy the input.env to temporary place + err := copyFile(t, "tests/"+tt.envFile, dotEnvFile) + require.NoErrorf(t, err, "failed to copy [ %s ] to TempDir", tt.envFile) + } } // Prepare output buffers - stdout := bytes.Buffer{} - stderr := bytes.Buffer{} + combinedStdout := bytes.Buffer{} + combinedStderr := bytes.Buffer{} ctx := context.Background() @@ -139,23 +145,36 @@ func RunFileBasedCommandTests(t *testing.T, settings Setting, globalArgs ...stri args = append(args, globalArgs...) args = append(args, command...) - stdout.WriteString(fmt.Sprintf("---- exec command line %d: %+v\n", idx, args)) - stderr.WriteString(fmt.Sprintf("---- exec command line %d: %+v\n", idx, args)) + combinedStdout.WriteString(fmt.Sprintf("---- exec command line %d: %+v\n", idx, args)) + combinedStderr.WriteString(fmt.Sprintf("---- exec command line %d: %+v\n", idx, args)) commandArgs := append(args, "--file", dotEnvFile) // Run command + stdout := bytes.Buffer{} + stderr := bytes.Buffer{} out, _ := cmd.RunCommand(ctx, commandArgs, &stdout, &stderr) + if stdout.Len() == 0 { + stdout.WriteString("(no output to stdout)\n") + } + + if stderr.Len() == 0 { + stderr.WriteString("(no output to stderr)\n") + } + + stdout.WriteTo(&combinedStdout) + stderr.WriteTo(&combinedStderr) + // Assert we got a Cobra command back require.NotNil(t, out, "expected a return value") } // Assert stdout + stderr + modified env file is as expected - golden.Assert(t, tt.goldenStdout, stdout.Bytes()) - golden.Assert(t, tt.goldenStderr, stderr.Bytes()) + golden.Assert(t, tt.goldenStdout, combinedStdout.Bytes()) + golden.Assert(t, tt.goldenStderr, combinedStderr.Bytes()) - if !settings.Has(SkipEnvCopy) { + if !settings.Has(ReadOnly) { // Read the modified .env file back modifiedEnv, err := os.ReadFile(dotEnvFile)