From 9f05f933dd89cc55ea53490615b73b6c1d28e82b Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Sun, 8 Mar 2026 22:59:33 -0500 Subject: [PATCH] =?UTF-8?q?=E2=99=B2=20Refactor=20configuration=20to=20use?= =?UTF-8?q?=20viper=20with=20context=20propagation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace global ConfigPath and Overrides with viper-based configuration - Add viper.New() to create configurable viper instances - Store viper and unmarshaled Config struct in context for testability - Add RetrieveFromContext and AddToContext helper functions - Update files.Append to accept context and retrieve config from it - Update formatters.Preferred and formatters.New to accept context - Add PersistentPreRunE in CLI to create and configure viper instance - Support -c flag for custom config file path - Support -v flag for config value overrides - Update all test files to create viper and add to context - Remove unused config types and load functions - Add viper as dependency with automatic env var support (MYLOG_*) --- cli/config.go | 11 ++-- cli/drop.go | 7 ++- cli/root.go | 36 ++++++++++-- cmd/my-log/main.go | 10 +++- config/default.go | 14 ++--- config/default_test.go | 17 ------ config/load.go | 119 ++++++++++++--------------------------- config/load_test.go | 112 ++++++++++++------------------------ config/types.go | 22 ++------ files/append.go | 10 ++-- files/append_test.go | 107 ++++++++++++++++------------------- formatters/json.go | 11 +++- formatters/json_test.go | 29 ++++++++-- formatters/new.go | 28 ++++----- formatters/new_test.go | 38 +++++-------- formatters/null.go | 3 +- formatters/null_test.go | 25 ++++++-- formatters/plain.go | 3 +- formatters/plain_test.go | 31 +++++++--- go.mod | 18 ++++-- go.sum | 47 +++++++++++++--- 21 files changed, 338 insertions(+), 360 deletions(-) delete mode 100644 config/default_test.go diff --git a/cli/config.go b/cli/config.go index 59b2e12..0512bb4 100644 --- a/cli/config.go +++ b/cli/config.go @@ -32,23 +32,24 @@ var ConfigCmd = &cobra.Command{ RunE: func(cmd *cobra.Command, args []string) (err error) { print, _ := cmd.Flags().GetBool("print") if print { - fmt.Fprintln(cmd.OutOrStdout(), config.ConfigPath) + fmt.Fprintln(cmd.OutOrStdout(), config.DefaultPath()) return nil } force, _ := cmd.Flags().GetBool("force") + configPath := config.DefaultPath() if !force { - _, err = os.Stat(config.ConfigPath) + _, err = os.Stat(configPath) if !os.IsNotExist(err) { - return fmt.Errorf("%s already exists. Use -f to overwrite", config.ConfigPath) + return fmt.Errorf("%s already exists. Use -f to overwrite", configPath) } } - dir := fp.Dir(config.ConfigPath) + dir := fp.Dir(configPath) err = os.MkdirAll(dir, 0755) if err != nil { return } - f, err := os.Create(config.ConfigPath) + f, err := os.Create(configPath) if err != nil { return } diff --git a/cli/drop.go b/cli/drop.go index c7d1d5f..e50a1ca 100644 --- a/cli/drop.go +++ b/cli/drop.go @@ -42,8 +42,9 @@ var DropCmd = &cobra.Command{ Args: cobra.ExactArgs(2), SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { + v, _ := config.RetrieveFromContext(cmd.Context()) if outJson { - config.Overrides["output.stdout.config.format"] = "json" + v.Set("output.stdout.config.format", "json") } log := args[0] @@ -60,12 +61,12 @@ var DropCmd = &cobra.Command{ } e := models.Entry{Title: title, Date: d.Time(), Fields: *ms} l := models.Log{Name: log, Entries: []models.Entry{e}} - err := files.Append(l) + err := files.Append(cmd.Context(), l) if err != nil { return err } - form, err := formatters.Preferred() + form, err := formatters.Preferred(cmd.Context()) if err != nil { return err } diff --git a/cli/root.go b/cli/root.go index ca24e61..def2fe8 100644 --- a/cli/root.go +++ b/cli/root.go @@ -1,5 +1,5 @@ /* -Copyright © 2024 Dan Jones +Copyright © 2026 Dan Jones This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by @@ -17,6 +17,7 @@ along with this program. If not, see . package cli import ( + "context" "os" "codeberg.org/danjones000/my-log/config" @@ -26,16 +27,41 @@ import ( var RootCmd = &cobra.Command{ Use: "my-log", Short: "A brief description of your application", + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + ctx, v, err := config.New(cmd.Context()) + if err != nil { + return err + } + + if configPath != "" { + v.SetConfigFile(configPath) + err := v.ReadInConfig() + if err != nil { + return err + } + } + + for k, val := range configValues { + v.Set(k, val) + } + + cmd.SetContext(ctx) + + return nil + }, } -func Execute() { - err := RootCmd.Execute() +func Execute(ctx context.Context) { + err := RootCmd.ExecuteContext(ctx) if err != nil { os.Exit(1) } } +var configPath string +var configValues map[string]string + func init() { - RootCmd.PersistentFlags().StringVarP(&config.ConfigPath, "config", "c", config.ConfigPath, "config file") - RootCmd.PersistentFlags().StringToStringVarP(&config.Overrides, "config-value", "v", config.Overrides, "Override config values. Use dot syntax to specify key. E.g. -v output.stdout.config.format=json") + RootCmd.PersistentFlags().StringVarP(&configPath, "config", "c", config.DefaultPath(), "config file") + RootCmd.PersistentFlags().StringToStringVarP(&configValues, "config-value", "v", nil, "Override config values. Use dot syntax to specify key. E.g. -v output.stdout.config.format=json") } diff --git a/cmd/my-log/main.go b/cmd/my-log/main.go index d09cd58..f93b0b0 100644 --- a/cmd/my-log/main.go +++ b/cmd/my-log/main.go @@ -1,5 +1,5 @@ /* -Copyright © 2024 Dan Jones +Copyright © 2026 Dan Jones This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by @@ -16,8 +16,12 @@ along with this program. If not, see . */ package main -import "codeberg.org/danjones000/my-log/cli" +import ( + "context" + + "codeberg.org/danjones000/my-log/cli" +) func main() { - cli.Execute() + cli.Execute(context.Background()) } diff --git a/config/default.go b/config/default.go index 8f93873..805c954 100644 --- a/config/default.go +++ b/config/default.go @@ -4,8 +4,6 @@ import ( "fmt" "os" fp "path/filepath" - - "github.com/BurntSushi/toml" ) const ConfigStr = `# Configuration for my-log @@ -38,15 +36,13 @@ pretty_print = false ` +func DefaultPath() string { + conf, _ := os.UserConfigDir() + return fp.Join(conf, "my-log", "config.toml") +} + func DefaultStr() string { home, _ := os.UserHomeDir() inDir := fp.Join(home, "my-log") return fmt.Sprintf(ConfigStr, inDir) } - -func DefaultConfig() (Config, error) { - s := DefaultStr() - c := Config{} - _, err := toml.Decode(s, &c) - return c, err -} diff --git a/config/default_test.go b/config/default_test.go deleted file mode 100644 index 58e0fee..0000000 --- a/config/default_test.go +++ /dev/null @@ -1,17 +0,0 @@ -package config - -import ( - "os" - fp "path/filepath" - "testing" - - "github.com/nalgeon/be" -) - -func TestDefaultConfig(t *testing.T) { - home, _ := os.UserHomeDir() - inDir := fp.Join(home, "my-log") - c, err := DefaultConfig() - be.Err(t, err, nil) - be.Equal(t, c.Input.Path, inDir) -} diff --git a/config/load.go b/config/load.go index 9b0341f..fd20cae 100644 --- a/config/load.go +++ b/config/load.go @@ -1,98 +1,53 @@ package config import ( - "encoding/json" + "bytes" + "context" "fmt" - "os" - fp "path/filepath" - "time" + "strings" - "codeberg.org/danjones000/my-log/tools" - "github.com/BurntSushi/toml" - "github.com/caarlos0/env/v10" - mapst "github.com/go-viper/mapstructure/v2" + "github.com/spf13/viper" ) -var ConfigPath string -var Overrides = map[string]string{} +type confKeyType uint8 -func init() { - conf, _ := os.UserConfigDir() - ConfigPath = fp.Join(conf, "my-log", "config.toml") -} +const ( + _ confKeyType = iota + viperKey +) -func Load() (Config, error) { - c, _ := DefaultConfig() - _, err := os.Stat(ConfigPath) - if !os.IsNotExist(err) { - _, err = toml.DecodeFile(ConfigPath, &c) - if err != nil { - return c, err - } - } - env.Parse(&c) - c.Outputs["stdout"] = loadStdout(c.Outputs["stdout"]) - c.Formatters["json"] = loadJsonFormat(c.Formatters["json"]) - - l := "" - for k, v := range Overrides { - val := tools.ParseString(v) - if val == nil { - continue - } - if _, isJson := val.(json.RawMessage); isJson { - continue - } - valout := fmt.Sprintf("%v", val) - if vals, isString := val.(string); isString { - valout = fmt.Sprintf(`"%s"`, vals) - } - if valt, isTime := val.(time.Time); isTime { - valout = valt.Format(time.RFC3339) - } - l = l + "\n" + fmt.Sprintf("%s = %s", k, valout) - } - _, err = toml.Decode(l, &c) - return c, err -} - -func loadStdout(stdout Output) Output { - st := stdoutEnabled{stdout.Enabled} - env.Parse(&st) - stdout.Enabled = st.Enabled - var std Stdout - mapst.Decode(stdout.Config, &std) - env.Parse(&std) - mapst.Decode(std, &stdout.Config) - return stdout -} - -func (oo Outputs) Stdout() (s Stdout, enabled bool) { - o, ok := oo["stdout"] +func RetrieveFromContext(ctx context.Context) (*viper.Viper, Config) { + v, ok := ctx.Value(viperKey).(*viper.Viper) if !ok { - return s, false + panic("config not found in context") + } + var c Config + if err := v.Unmarshal(&c); err != nil { + panic(fmt.Errorf("failed to unmarshal config: %w", err)) + } + return v, c +} + +func AddToContext(ctx context.Context, v *viper.Viper) context.Context { + return context.WithValue(ctx, viperKey, v) +} + +func New(ctx context.Context) (context.Context, *viper.Viper, error) { + v := viper.New() + v.SetConfigType("toml") + + if err := v.ReadConfig(bytes.NewBufferString(DefaultStr())); err != nil { + return ctx, nil, err } - enabled = o.Enabled - mapst.Decode(o.Config, &s) + v.SetConfigFile(DefaultPath()) + v.SetEnvPrefix("MYLOG") + v.AutomaticEnv() + v.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_")) - return -} - -func loadJsonFormat(c map[string]any) map[string]any { - jf := JsonFormat{} - mapst.Decode(c, &jf) - env.Parse(&jf) - mapst.Decode(jf, &c) - return c -} - -func (ff Formatters) Json() (jf JsonFormat) { - o, ok := ff["json"] - if !ok { - return + if err := v.ReadInConfig(); err != nil { + return ctx, nil, err } - mapst.Decode(o, &jf) - return + return AddToContext(ctx, v), v, nil } diff --git a/config/load_test.go b/config/load_test.go index 0306db5..71dbb0c 100644 --- a/config/load_test.go +++ b/config/load_test.go @@ -1,93 +1,51 @@ package config import ( - "fmt" "os" "testing" "github.com/nalgeon/be" + "github.com/spf13/viper" ) -func TestLoad(t *testing.T) { - f, _ := os.CreateTemp("", "test") - ConfigPath = f.Name() - defer f.Close() - fmt.Fprint(f, `[input] +func TestNew(t *testing.T) { + _, v, err := New(t.Context()) + be.Err(t, err, nil) + be.True(t, v != nil) +} + +func TestNewWithEnvOverrides(t *testing.T) { + os.Setenv("MYLOG_INPUT_PATH", "/test/path") + defer os.Unsetenv("MYLOG_INPUT_PATH") + + _, v, err := New(t.Context()) + be.Err(t, err, nil) + be.Equal(t, v.GetString("input.path"), "/test/path") +} + +func TestNewWithConfigFile(t *testing.T) { + dir := t.ArtifactDir() + f, _ := os.CreateTemp(dir, "test*.toml") + defer os.Remove(f.Name()) + f.WriteString(`[input] +path = "/file/path" ext = "log"`) - c, err := Load() + f.Close() + + _, v, err := New(t.Context()) be.Err(t, err, nil) - be.Equal(t, c.Input.Ext, "log") -} -func TestLoadBadFile(t *testing.T) { - f, _ := os.CreateTemp("", "test") - ConfigPath = f.Name() - defer f.Close() - fmt.Fprint(f, `{"not":"toml"}`) - _, err := Load() - be.Err(t, err) -} - -func TestLoadIgnoreMissingFile(t *testing.T) { - def, _ := DefaultConfig() - ConfigPath = "/not/a/real/file" - c, err := Load() + v.SetConfigFile(f.Name()) + v.SetConfigType("toml") + err = v.ReadInConfig() be.Err(t, err, nil) - be.Equal(t, c, def) + be.Equal(t, v.GetString("input.path"), "/file/path") + be.Equal(t, v.GetString("input.ext"), "log") } -func TestOverride(t *testing.T) { - Overrides = map[string]string{ - "input.path": "/path/to/it", - "input.ext": "~", - } - c, err := Load() - be.Err(t, err, nil) - be.Equal(t, c.Input.Path, Overrides["input.path"]) - be.Equal(t, c.Input.Ext, "txt") -} - -func TestOverrideJson(t *testing.T) { - Overrides = map[string]string{"input.ext": `{"a":"b"}`} - c, err := Load() - be.Err(t, err, nil) - be.Equal(t, c.Input.Ext, "txt") -} - -func TestTimeParse(t *testing.T) { - Overrides = map[string]string{"input.ext": "now"} - c, err := Load() - be.Err(t, err, "incompatible types: TOML value has type time.Time; destination has type string") - be.Equal(t, c.Input.Ext, "txt") -} - -func TestStdoutMissing(t *testing.T) { - var oo Outputs = map[string]Output{} - std, en := oo.Stdout() - be.True(t, !en) - be.Equal(t, std, Stdout{}) -} - -func TestStdoutLoad(t *testing.T) { - os.Setenv("LOG_STDOUT_FORMAT", "json") - defer os.Unsetenv("LOG_STDOUT_FORMAT") - os.Setenv("LOG_STDOUT_ENABLED", "true") - defer os.Unsetenv("LOG_STDOUT_ENABLED") - c, _ := Load() - std, en := c.Outputs.Stdout() - be.True(t, en) - be.Equal(t, std.Format, "json") -} - -func TestFormatJson(t *testing.T) { - ff := Formatters{ - "json": map[string]any{"pretty_print": true}, - } - - js := ff.Json() - be.True(t, js.PrettyPrint) - - ff = Formatters{} - js = ff.Json() - be.True(t, !js.PrettyPrint) +func TestRetrieveFromContext(t *testing.T) { + v := viper.New() + ctx := AddToContext(t.Context(), v) + result, _ := RetrieveFromContext(ctx) + be.True(t, v == result) } diff --git a/config/types.go b/config/types.go index 58fb2c4..4f9a32b 100644 --- a/config/types.go +++ b/config/types.go @@ -2,15 +2,15 @@ package config type Config struct { Input Input - Outputs Outputs `toml:"output"` + Outputs Outputs `mapstructure:"output"` Formatters Formatters } type Input struct { - Path string `env:"LOG_PATH"` - Recurse bool `env:"LOG_RECURSE"` - Ext string `env:"LOG_EXT"` - DotFolder bool `env:"LOG_DOT_FOLDER"` + Path string + Recurse bool + Ext string + DotFolder bool `mapstructure:"dotFolder"` } type Outputs map[string]Output @@ -20,16 +20,4 @@ type Output struct { Config map[string]any } -type Stdout struct { - Format string `env:"LOG_STDOUT_FORMAT" mapstructure:"format"` -} - -type stdoutEnabled struct { - Enabled bool `env:"LOG_STDOUT_ENABLED"` -} - type Formatters map[string]map[string]any - -type JsonFormat struct { - PrettyPrint bool `env:"LOG_JSON_PRETTY_PRINT" mapstructure:"pretty_print"` -} diff --git a/files/append.go b/files/append.go index cb62d9b..8114380 100644 --- a/files/append.go +++ b/files/append.go @@ -1,6 +1,7 @@ package files import ( + "context" "fmt" "io" "os" @@ -11,11 +12,8 @@ import ( "codeberg.org/danjones000/my-log/models" ) -func Append(l models.Log) error { - conf, err := config.Load() - if err != nil { - return err - } +func Append(ctx context.Context, l models.Log) error { + _, conf := config.RetrieveFromContext(ctx) filename := l.Name if conf.Input.DotFolder { @@ -27,7 +25,7 @@ func Append(l models.Log) error { } path := fp.Join(conf.Input.Path, filename) dir := fp.Dir(path) - err = os.MkdirAll(dir, 0750) + err := os.MkdirAll(dir, 0750) if err != nil { return err } diff --git a/files/append_test.go b/files/append_test.go index 890207a..5715feb 100644 --- a/files/append_test.go +++ b/files/append_test.go @@ -1,6 +1,7 @@ package files import ( + "context" "fmt" "os" "strings" @@ -10,6 +11,7 @@ import ( "codeberg.org/danjones000/my-log/config" "codeberg.org/danjones000/my-log/models" "github.com/nalgeon/be" + "github.com/spf13/viper" ) func TestAppend(tt *testing.T) { @@ -24,26 +26,25 @@ func TestAppend(tt *testing.T) { }) tt.Run("failure", func(t *testing.T) { t.Run("badEntry", appendTestBadEntry) - t.Run("load-err", appendTestConfLoadErr) t.Run("mkdir-err", appendTestMkdirErr) t.Run("append-log-err", appendTestOpenErr) }) } -func setupAppendTest(t *testing.T) string { +func setupAppendTest(t *testing.T) (string, context.Context) { t.Helper() dir := t.ArtifactDir() - config.Overrides["input.path"] = dir - config.Overrides["input.ext"] = "log" - t.Cleanup(func() { - delete(config.Overrides, "input.path") - delete(config.Overrides, "input.ext") - }) - return dir + v := viper.New() + v.SetConfigType("toml") + v.Set("input.path", dir) + v.Set("input.ext", "log") + v.Set("input.dotFolder", false) + ctx := config.AddToContext(t.Context(), v) + return dir, ctx } func appendTestSingle(t *testing.T) { - dir := setupAppendTest(t) + dir, ctx := setupAppendTest(t) when := time.Now().Local() e := models.Entry{ Title: "Jimmy", @@ -57,7 +58,7 @@ func appendTestSingle(t *testing.T) { Name: "test", Entries: []models.Entry{e}, } - err := Append(l) + err := Append(ctx, l) be.Err(t, err, nil) by, err := os.ReadFile(dir + "/test.log") be.Err(t, err, nil) @@ -68,7 +69,7 @@ func appendTestSingle(t *testing.T) { } func appendTestTwoEntries(t *testing.T) { - dir := setupAppendTest(t) + dir, ctx := setupAppendTest(t) when := time.Now().Local() whens := when.Format(models.DateFormat) e := []models.Entry{ @@ -79,7 +80,7 @@ func appendTestTwoEntries(t *testing.T) { Name: "test", Entries: e, } - err := Append(l) + err := Append(ctx, l) be.Err(t, err, nil) by, _ := os.ReadFile(dir + "/test.log") st := string(by) @@ -88,7 +89,7 @@ func appendTestTwoEntries(t *testing.T) { } func appendTestAddNewLine(t *testing.T) { - dir := setupAppendTest(t) + dir, ctx := setupAppendTest(t) os.WriteFile(dir+"/test.log", []byte("foo"), 0644) when := time.Now().Local() whens := when.Format(models.DateFormat) @@ -99,7 +100,7 @@ func appendTestAddNewLine(t *testing.T) { Name: "test", Entries: e, } - err := Append(l) + err := Append(ctx, l) be.Err(t, err, nil) by, _ := os.ReadFile(dir + "/test.log") exp := fmt.Sprintf("foo\n@begin %s - one\n@id jimmy @end\n", whens) @@ -107,7 +108,7 @@ func appendTestAddNewLine(t *testing.T) { } func appendTestDontAddNewLine(t *testing.T) { - dir := setupAppendTest(t) + dir, ctx := setupAppendTest(t) os.WriteFile(dir+"/test.log", []byte("foo\n"), 0644) when := time.Now().Local() whens := when.Format(models.DateFormat) @@ -118,7 +119,7 @@ func appendTestDontAddNewLine(t *testing.T) { Name: "test", Entries: e, } - err := Append(l) + err := Append(ctx, l) be.Err(t, err, nil) by, _ := os.ReadFile(dir + "/test.log") exp := fmt.Sprintf("foo\n@begin %s - one\n@id jimmy @end\n", whens) @@ -126,7 +127,7 @@ func appendTestDontAddNewLine(t *testing.T) { } func appendTestBadEntry(t *testing.T) { - dir := setupAppendTest(t) + dir, ctx := setupAppendTest(t) e := models.Entry{ Title: "Jimmy", } @@ -134,19 +135,21 @@ func appendTestBadEntry(t *testing.T) { Name: "test", Entries: []models.Entry{e}, } - err := Append(l) + err := Append(ctx, l) be.Err(t, err, nil) by, _ := os.ReadFile(dir + "/test.log") be.Equal(t, by, []byte{}) } func appendTestDotFolder(t *testing.T) { - config.Overrides["input.dotFolder"] = "true" - t.Cleanup(func() { - delete(config.Overrides, "input.dotFolder") - }) + dir := t.ArtifactDir() + v := viper.New() + v.SetConfigType("toml") + v.Set("input.path", dir) + v.Set("input.ext", "log") + v.Set("input.dotFolder", true) + ctx := config.AddToContext(t.Context(), v) - dir := setupAppendTest(t) e := models.Entry{ Title: "something", Date: time.Now(), @@ -155,7 +158,7 @@ func appendTestDotFolder(t *testing.T) { Name: "sub.test", Entries: []models.Entry{e}, } - err := Append(l) + err := Append(ctx, l) be.Err(t, err, nil) by, err := os.ReadFile(dir + "/sub/test.log") be.Err(t, err, nil) @@ -164,12 +167,7 @@ func appendTestDotFolder(t *testing.T) { } func appendTestDotFolderNo(t *testing.T) { - config.Overrides["input.dotFolder"] = "false" - t.Cleanup(func() { - delete(config.Overrides, "input.dotFolder") - }) - - dir := setupAppendTest(t) + dir, ctx := setupAppendTest(t) e := models.Entry{ Title: "another", Date: time.Now(), @@ -178,7 +176,7 @@ func appendTestDotFolderNo(t *testing.T) { Name: "sub.test", Entries: []models.Entry{e}, } - err := Append(l) + err := Append(ctx, l) be.Err(t, err, nil) by, err := os.ReadFile(dir + "/sub.test.log") be.Err(t, err, nil) @@ -187,11 +185,13 @@ func appendTestDotFolderNo(t *testing.T) { } func appendTestNoExt(t *testing.T) { - dir := setupAppendTest(t) - config.Overrides["input.ext"] = "" - t.Cleanup(func() { - config.Overrides["input.ext"] = "log" - }) + dir := t.ArtifactDir() + v := viper.New() + v.SetConfigType("toml") + v.Set("input.path", dir) + v.Set("input.ext", "") + v.Set("input.dotFolder", false) + ctx := config.AddToContext(t.Context(), v) e := models.Entry{ Title: "baz", @@ -201,7 +201,7 @@ func appendTestNoExt(t *testing.T) { Name: "foobar", Entries: []models.Entry{e}, } - err := Append(l) + err := Append(ctx, l) be.Err(t, err, nil) by, err := os.ReadFile(dir + "/foobar") be.Err(t, err, nil) @@ -209,33 +209,20 @@ func appendTestNoExt(t *testing.T) { be.True(t, strings.Contains(st, fmt.Sprintf("@begin %s - %s", e.Date.Format(models.DateFormat), e.Title))) } -func appendTestConfLoadErr(t *testing.T) { - dir := t.ArtifactDir() - - currConf := config.ConfigPath - tmp, _ := os.CreateTemp(dir, "app-conf-*.toml") - fname := tmp.Name() - t.Cleanup(func() { tmp.Close() }) - t.Cleanup(func() { os.Remove(fname) }) - fmt.Fprintln(tmp, `{"not":"toml"}`) - config.ConfigPath = fname - t.Cleanup(func() { config.ConfigPath = currConf }) - - err := Append(models.Log{}) - be.Err(t, err, "toml") -} - func appendTestMkdirErr(t *testing.T) { - // Don't run this test as root - config.Overrides["input.path"] = "/var/my-logs-test" - t.Cleanup(func() { delete(config.Overrides, "input.path") }) + v := viper.New() + v.SetConfigType("toml") + v.Set("input.path", "/var/my-logs-test") + v.Set("input.ext", "log") + v.Set("input.dotFolder", false) + ctx := config.AddToContext(t.Context(), v) - err := Append(models.Log{}) + err := Append(ctx, models.Log{}) be.Err(t, err, "permission denied") } func appendTestOpenErr(t *testing.T) { - dir := setupAppendTest(t) + dir, ctx := setupAppendTest(t) l := models.Log{ Name: "test-open-err", } @@ -244,6 +231,6 @@ func appendTestOpenErr(t *testing.T) { f, _ := os.Create(fname) f.Close() os.Chmod(fname, 0400) - err := Append(l) + err := Append(ctx, l) be.Err(t, err, "permission denied") } diff --git a/formatters/json.go b/formatters/json.go index d56854d..f75f9cb 100644 --- a/formatters/json.go +++ b/formatters/json.go @@ -5,12 +5,17 @@ import ( "encoding/json" "time" - "codeberg.org/danjones000/my-log/config" "codeberg.org/danjones000/my-log/models" ) -func newJson(ff config.Formatters) (Formatter, error) { - return &Json{ff.Json().PrettyPrint}, nil +func newJson(ff map[string]any) (Formatter, error) { + prettyPrint := false + if jf, ok := ff["json"].(map[string]any); ok { + if pp, ok := jf["pretty_print"].(bool); ok { + prettyPrint = pp + } + } + return &Json{prettyPrint}, nil } type Json struct { diff --git a/formatters/json_test.go b/formatters/json_test.go index b6a5668..2750f39 100644 --- a/formatters/json_test.go +++ b/formatters/json_test.go @@ -1,22 +1,35 @@ package formatters import ( + "context" "fmt" "testing" "time" + "codeberg.org/danjones000/my-log/config" "codeberg.org/danjones000/my-log/internal/testutil/bep" "codeberg.org/danjones000/my-log/models" "github.com/nalgeon/be" + "github.com/spf13/viper" ) +func setupJsonTestContext(t *testing.T) context.Context { + t.Helper() + v := viper.New() + v.SetConfigType("toml") + v.Set("formatters.json.pretty_print", false) + return config.AddToContext(t.Context(), v) +} + func TestJsonName(t *testing.T) { - f, _ := New("json") + ctx := setupJsonTestContext(t) + f, _ := New(ctx, "json") be.Equal(t, f.Name(), "json") } func TestJsonMeta(t *testing.T) { - f, _ := New("json") + ctx := setupJsonTestContext(t) + f, _ := New(ctx, "json") m := models.Meta{Key: "foo", Value: 42} exp := `{"foo":42}` o, err := f.Meta(m) @@ -25,8 +38,9 @@ func TestJsonMeta(t *testing.T) { } func TestJsonEntry(t *testing.T) { + ctx := setupJsonTestContext(t) when := time.Now() - f, _ := New("json") + f, _ := New(ctx, "json") m := models.Meta{Key: "foo", Value: 42} e := models.Entry{ Title: "Homer", @@ -40,8 +54,9 @@ func TestJsonEntry(t *testing.T) { } func TestJsonLog(t *testing.T) { + ctx := setupJsonTestContext(t) when := time.Now() - f, _ := New("json") + f, _ := New(ctx, "json") m := models.Meta{Key: "foo", Value: 42} e := models.Entry{ Title: "Homer", @@ -56,7 +71,8 @@ func TestJsonLog(t *testing.T) { } func TestJsonNoLogs(t *testing.T) { - f, _ := New("json") + ctx := setupJsonTestContext(t) + f, _ := New(ctx, "json") o, err := f.Logs([]models.Log{}) var exp []byte be.Err(t, err, nil) @@ -64,7 +80,8 @@ func TestJsonNoLogs(t *testing.T) { } func TestJsonErr(t *testing.T) { - f, _ := New("json") + ctx := setupJsonTestContext(t) + f, _ := New(ctx, "json") o, err := f.Meta(models.Meta{Key: "foo", Value: make(chan bool)}) var exp []byte be.Err(t, err) diff --git a/formatters/new.go b/formatters/new.go index bade49b..0aa41f1 100644 --- a/formatters/new.go +++ b/formatters/new.go @@ -1,12 +1,13 @@ package formatters import ( + "context" "errors" "codeberg.org/danjones000/my-log/config" ) -type formatMaker func(config.Formatters) (Formatter, error) +type formatMaker func(config map[string]any) (Formatter, error) var formatterMap = map[string]formatMaker{ "plain": newPlain, @@ -14,23 +15,22 @@ var formatterMap = map[string]formatMaker{ "zero": newNull, } -func Preferred() (f Formatter, err error) { - conf, err := config.Load() - if err != nil { - return - } - std, _ := conf.Outputs.Stdout() - return New(std.Format) +func Preferred(ctx context.Context) (f Formatter, err error) { + v, _ := config.RetrieveFromContext(ctx) + format := v.GetString("output.stdout.config.format") + return New(ctx, format) } -func New(kind string) (f Formatter, err error) { - conf, err := config.Load() - if err != nil { - return - } +func New(ctx context.Context, kind string) (f Formatter, err error) { + _, c := config.RetrieveFromContext(ctx) + conf := c.Formatters if make, ok := formatterMap[kind]; ok { - return make(conf.Formatters) + var formatterConf map[string]any + if cf, ok := conf[kind]; ok { + formatterConf = cf + } + return make(formatterConf) } return nil, errors.New("unimplemented") diff --git a/formatters/new_test.go b/formatters/new_test.go index 00f4727..18968e8 100644 --- a/formatters/new_test.go +++ b/formatters/new_test.go @@ -1,13 +1,13 @@ package formatters import ( - "fmt" - "os" + "context" "slices" "testing" "codeberg.org/danjones000/my-log/config" "github.com/nalgeon/be" + "github.com/spf13/viper" ) func TestKinds(t *testing.T) { @@ -17,33 +17,25 @@ func TestKinds(t *testing.T) { } } +func setupNewTest(t *testing.T) context.Context { + t.Helper() + v := viper.New() + v.SetConfigType("toml") + v.Set("output.stdout.config.format", "plain") + v.Set("formatters.json.pretty_print", false) + return config.AddToContext(t.Context(), v) +} + func TestNewUnsupported(t *testing.T) { - f, err := New("nope") + ctx := setupNewTest(t) + f, err := New(ctx, "nope") be.Equal(t, f, nil) be.Err(t, err) } -func TestNewCantGetConfig(t *testing.T) { - f, _ := os.CreateTemp("", "test") - oldConf := config.ConfigPath - config.ConfigPath = f.Name() - defer f.Close() - defer func() { - config.ConfigPath = oldConf - }() - - fmt.Fprint(f, `{"not":"toml"}`) - form, err := New("plain") - be.Equal(t, form, nil) - be.Err(t, err) - - form, err = Preferred() - be.Equal(t, form, nil) - be.Err(t, err) -} - func TestPreferred(t *testing.T) { - form, err := Preferred() + ctx := setupNewTest(t) + form, err := Preferred(ctx) be.Err(t, err, nil) be.True(t, form != nil) } diff --git a/formatters/null.go b/formatters/null.go index 4dd1775..5b0f3aa 100644 --- a/formatters/null.go +++ b/formatters/null.go @@ -1,11 +1,10 @@ package formatters import ( - "codeberg.org/danjones000/my-log/config" "codeberg.org/danjones000/my-log/models" ) -func newNull(ff config.Formatters) (Formatter, error) { +func newNull(ff map[string]any) (Formatter, error) { return &Null{}, nil } diff --git a/formatters/null_test.go b/formatters/null_test.go index ba34618..fa5aeca 100644 --- a/formatters/null_test.go +++ b/formatters/null_test.go @@ -1,44 +1,59 @@ package formatters import ( + "context" "testing" "time" + "codeberg.org/danjones000/my-log/config" "codeberg.org/danjones000/my-log/models" "github.com/nalgeon/be" + "github.com/spf13/viper" ) var empty []byte +func setupNullTestContext(t *testing.T) context.Context { + t.Helper() + v := viper.New() + v.SetConfigType("toml") + return config.AddToContext(t.Context(), v) +} + func TestNullName(t *testing.T) { - f, err := New("zero") + ctx := setupNullTestContext(t) + f, err := New(ctx, "zero") be.Err(t, err, nil) be.Equal(t, f.Name(), "zero") } func TestNullMeta(t *testing.T) { - f, _ := New("zero") + ctx := setupNullTestContext(t) + f, _ := New(ctx, "zero") o, err := f.Meta(models.Meta{Key: "foo", Value: 42}) be.Err(t, err, nil) be.Equal(t, o, empty) } func TestNullEntry(t *testing.T) { - f, _ := New("zero") + ctx := setupNullTestContext(t) + f, _ := New(ctx, "zero") o, err := f.Entry(models.Entry{Title: "title", Date: time.Now()}) be.Err(t, err, nil) be.Equal(t, o, empty) } func TestNullLog(t *testing.T) { - f, _ := New("zero") + ctx := setupNullTestContext(t) + f, _ := New(ctx, "zero") o, err := f.Log(models.Log{Name: "jim", Entries: []models.Entry{{Title: "title", Date: time.Now()}}}) be.Err(t, err, nil) be.Equal(t, o, empty) } func TestNullLogs(t *testing.T) { - f, _ := New("zero") + ctx := setupNullTestContext(t) + f, _ := New(ctx, "zero") o, err := f.Logs([]models.Log{{Name: "jim", Entries: []models.Entry{{Title: "title", Date: time.Now()}}}}) be.Err(t, err, nil) be.Equal(t, o, empty) diff --git a/formatters/plain.go b/formatters/plain.go index 2b79f00..6211b77 100644 --- a/formatters/plain.go +++ b/formatters/plain.go @@ -3,12 +3,11 @@ package formatters import ( "bytes" - "codeberg.org/danjones000/my-log/config" "codeberg.org/danjones000/my-log/models" "codeberg.org/danjones000/my-log/tools" ) -func newPlain(ff config.Formatters) (Formatter, error) { +func newPlain(ff map[string]any) (Formatter, error) { return &PlainText{}, nil } diff --git a/formatters/plain_test.go b/formatters/plain_test.go index bacc0a4..fee04f7 100644 --- a/formatters/plain_test.go +++ b/formatters/plain_test.go @@ -3,16 +3,27 @@ package formatters import ( "bufio" "bytes" + "context" "fmt" "testing" "time" + "codeberg.org/danjones000/my-log/config" "codeberg.org/danjones000/my-log/models" "codeberg.org/danjones000/my-log/tools" "github.com/nalgeon/be" + "github.com/spf13/viper" ) +func setupPlainTestContext(t *testing.T) context.Context { + t.Helper() + v := viper.New() + v.SetConfigType("toml") + return config.AddToContext(t.Context(), v) +} + func TestPlainLogs(t *testing.T) { + ctx := setupPlainTestContext(t) m := []models.Meta{ {Key: "foo", Value: "bar"}, {Key: "baz", Value: 42}, @@ -29,7 +40,7 @@ func TestPlainLogs(t *testing.T) { l2 := models.Log{Name: "more-stuff", Entries: []models.Entry{e2}} logs := []models.Log{l, l2} - f, err := New("plain") + f, err := New(ctx, "plain") be.Err(t, err, nil) out, err := f.Logs(logs) @@ -95,40 +106,46 @@ func TestPlainLogs(t *testing.T) { } func TestPlainName(t *testing.T) { - f, _ := New("plain") + ctx := setupPlainTestContext(t) + f, _ := New(ctx, "plain") be.Equal(t, f.Name(), "plain") } func TestPlainLogNone(t *testing.T) { - f, _ := New("plain") + ctx := setupPlainTestContext(t) + f, _ := New(ctx, "plain") out, err := f.Logs([]models.Log{}) be.Err(t, err, nil) be.Equal(t, len(out), 0) } func TestPlainLogNoEntries(t *testing.T) { - f, _ := New("plain") + ctx := setupPlainTestContext(t) + f, _ := New(ctx, "plain") out, err := f.Log(models.Log{Name: "foo"}) be.Err(t, err, nil) be.Equal(t, len(out), 0) } func TestPlainMetaEmpty(t *testing.T) { - f, _ := New("plain") + ctx := setupPlainTestContext(t) + f, _ := New(ctx, "plain") out, err := f.Meta(models.Meta{Key: "foo", Value: ""}) be.Err(t, err, nil) be.Equal(t, len(out), 0) } func TestPlainMetaError(t *testing.T) { - f, _ := New("plain") + ctx := setupPlainTestContext(t) + f, _ := New(ctx, "plain") out, err := f.Meta(models.Meta{Key: "foo", Value: make(chan bool)}) be.Err(t, err) be.Equal(t, len(out), 0) } func TestPlainEntry(t *testing.T) { - f, _ := New("plain") + ctx := setupPlainTestContext(t) + f, _ := New(ctx, "plain") now := time.Now() out, err := f.Entry(models.Entry{ Title: "foo", diff --git a/go.mod b/go.mod index b856302..bc420a9 100644 --- a/go.mod +++ b/go.mod @@ -3,25 +3,33 @@ module codeberg.org/danjones000/my-log go 1.26.0 require ( - github.com/BurntSushi/toml v1.3.2 - github.com/caarlos0/env/v10 v10.0.0 - github.com/go-viper/mapstructure/v2 v2.5.0 github.com/google/uuid v1.6.0 github.com/markusmobius/go-dateparser v1.2.3 github.com/nalgeon/be v0.3.0 github.com/spf13/cobra v1.8.0 + github.com/spf13/viper v1.21.0 ) require ( github.com/elliotchance/pie/v2 v2.7.0 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/hablullah/go-hijri v1.0.2 // indirect github.com/hablullah/go-juliandays v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jalaali/go-jalaali v0.0.0-20210801064154-80525e88d958 // indirect github.com/magefile/mage v1.14.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/sagikazarmark/locafero v0.11.0 // indirect + github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect + github.com/spf13/afero v1.15.0 // indirect + github.com/spf13/cast v1.10.0 // indirect + github.com/spf13/pflag v1.0.10 // indirect + github.com/subosito/gotenv v1.6.0 // indirect github.com/tetratelabs/wazero v1.2.1 // indirect github.com/wasilibs/go-re2 v1.3.0 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/exp v0.0.0-20220321173239-a90fa8a75705 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/text v0.28.0 // indirect ) diff --git a/go.sum b/go.sum index c9ae79b..dac7ff6 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,16 @@ -github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/caarlos0/env/v10 v10.0.0 h1:yIHUBZGsyqCnpTkbjk8asUlx6RFhhEs+h7TOBdgdzXA= -github.com/caarlos0/env/v10 v10.0.0/go.mod h1:ZfulV76NvVPw3tm591U4SwL3Xx9ldzBP9aGxzeN7G18= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/elliotchance/pie/v2 v2.7.0 h1:FqoIKg4uj0G/CrLGuMS9ejnFKa92lxE1dEgBD3pShXg= github.com/elliotchance/pie/v2 v2.7.0/go.mod h1:18t0dgGFH006g4eVdDtWfgFZPQEgl10IoEO8YWEq3Og= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hablullah/go-hijri v1.0.2 h1:drT/MZpSZJQXo7jftf5fthArShcaMtsal0Zf/dnmp6k= @@ -19,31 +21,58 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jalaali/go-jalaali v0.0.0-20210801064154-80525e88d958 h1:qxLoi6CAcXVzjfvu+KXIXJOAsQB62LXjsfbOaErsVzE= github.com/jalaali/go-jalaali v0.0.0-20210801064154-80525e88d958/go.mod h1:Wqfu7mjUHj9WDzSSPI5KfBclTTEnLveRUFr/ujWnTgE= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo= github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/markusmobius/go-dateparser v1.2.3 h1:TvrsIvr5uk+3v6poDjaicnAFJ5IgtFHgLiuMY2Eb7Nw= github.com/markusmobius/go-dateparser v1.2.3/go.mod h1:cMwQRrBUQlK1UI5TIFHEcvpsMbkWrQLXuaPNMFzuYLk= github.com/nalgeon/be v0.3.0 h1:QsPANqEtcOD5qT2S3KAtIkDBBn8SXUf/Lb5Bi/z4UqM= github.com/nalgeon/be v0.3.0/go.mod h1:PMwMuBLopwKJkSHnr2qHyLcZYUTqNejN7A8RAqNWO3E= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= +github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= +github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= +github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= +github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tetratelabs/wazero v1.2.1 h1:J4X2hrGzJvt+wqltuvcSjHQ7ujQxA9gb6PeMs4qlUWs= github.com/tetratelabs/wazero v1.2.1/go.mod h1:wYx2gNRg8/WihJfSDxA1TIL8H+GkfLYm+bIfbblu9VQ= github.com/wasilibs/go-re2 v1.3.0 h1:LFhBNzoStM3wMie6rN2slD1cuYH2CGiHpvNL3UtcsMw= github.com/wasilibs/go-re2 v1.3.0/go.mod h1:AafrCXVvGRJJOImMajgJ2M7rVmWyisVK7sFshbxnVrg= github.com/wasilibs/nottinygc v0.4.0 h1:h1TJMihMC4neN6Zq+WKpLxgd9xCFMw7O9ETLwY2exJQ= github.com/wasilibs/nottinygc v0.4.0/go.mod h1:oDcIotskuYNMpqMF23l7Z8uzD4TC0WXHK8jetlB3HIo= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/exp v0.0.0-20220321173239-a90fa8a75705 h1:ba9YlqfDGTTQ5aZ2fwOoQ1hf32QySyQkR6ODGDzHlnE= golang.org/x/exp v0.0.0-20220321173239-a90fa8a75705/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=