From 25f5c37243ebd108f446267f47d3dae94150f25c Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Fri, 9 Feb 2024 09:44:35 -0600 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Parse=20config=20overrides=20on=20c?= =?UTF-8?q?li?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/root.go | 4 +-- config/load.go | 33 ++++++++++++++++++++--- config/types.go | 20 +++++++++++++- go.mod | 1 + go.sum | 2 ++ models/meta.go | 31 ++++----------------- tools/parse.go | 44 ++++++++++++++++++++++++++++++ tools/parse_test.go | 66 +++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 169 insertions(+), 32 deletions(-) create mode 100644 tools/parse.go create mode 100644 tools/parse_test.go diff --git a/cmd/root.go b/cmd/root.go index dc1273f..a07455d 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -23,8 +23,6 @@ import ( "github.com/spf13/cobra" ) -var cfgFile string - // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "my-log", @@ -52,6 +50,7 @@ func init() { // will be global for your application. 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.json=true") // Cobra also supports local flags, which will only run // when this action is called directly. @@ -61,4 +60,5 @@ func init() { // initConfig reads in config file and ENV variables if set. func initConfig() { // @todo + } diff --git a/config/load.go b/config/load.go index fad626c..44a1b75 100644 --- a/config/load.go +++ b/config/load.go @@ -1,13 +1,18 @@ package config import ( + "encoding/json" + "fmt" "os" fp "path/filepath" + "time" + "codeberg.org/danjones000/my-log/tools" "github.com/BurntSushi/toml" ) var ConfigPath string +var Overrides map[string]string func init() { conf, _ := os.UserConfigDir() @@ -17,10 +22,32 @@ func init() { func Load() (Config, error) { c, _ := DefaultConfig() _, err := os.Stat(ConfigPath) - if os.IsNotExist(err) { - return c, nil + if !os.IsNotExist(err) { + _, err = toml.DecodeFile(ConfigPath, &c) + if err != nil { + return c, err + } } - _, err = toml.DecodeFile(ConfigPath, &c) // @todo get environ + + 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 } diff --git a/config/types.go b/config/types.go index b8d56bb..bc07f82 100644 --- a/config/types.go +++ b/config/types.go @@ -1,8 +1,10 @@ package config +import mapst "github.com/mitchellh/mapstructure" + type Config struct { Input Input - Outputs map[string]Output `toml:"output"` + Outputs Outputs `toml:"output"` } type Input struct { @@ -11,7 +13,23 @@ type Input struct { Ext string } +type Outputs map[string]Output + type Output struct { Enabled bool Config map[string]any } + +func (oo Outputs) Stdout() (s Stdout, enabled bool) { + o, ok := oo["stdout"] + if !ok { + return s, false + } + enabled = o.Enabled + mapst.Decode(o.Config, &s) + return +} + +type Stdout struct { + Json bool +} diff --git a/go.mod b/go.mod index b162b9b..a1de78f 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.21.5 require ( github.com/BurntSushi/toml v1.3.2 + github.com/mitchellh/mapstructure v1.5.0 github.com/spf13/cobra v1.8.0 github.com/stretchr/testify v1.8.4 ) diff --git a/go.sum b/go.sum index 3c50e65..ea20855 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,8 @@ 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/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= diff --git a/models/meta.go b/models/meta.go index 2d6522c..c737f95 100644 --- a/models/meta.go +++ b/models/meta.go @@ -7,8 +7,9 @@ import ( "fmt" "regexp" "strconv" - "strings" "time" + + "codeberg.org/danjones000/my-log/tools" ) type Meta struct { @@ -73,33 +74,11 @@ func (m *Meta) processMeta(in []byte) error { if len(in) == 0 { return newParsingError(errors.New("No value found")) } - s := strings.TrimSpace(string(in)) - if len(s) == 0 { + v := tools.ParseBytes(in) + if v == "" { return newParsingError(errors.New("No value found")) } - yesno := regexp.MustCompile("^(y|Y|yes|Yes|YES|n|N|no|No|NO|true|True|TRUE|false|False|FALSE|on|On|ON|off|Off|OFF)$") - yes := regexp.MustCompile("^(y|Y|yes|Yes|YES|true|True|TRUE|on|On|ON)$") - null := regexp.MustCompile("^(~|null|Null|NULL|none|None|NONE|nil|Nil|NIL)$") - var j json.RawMessage - if null.MatchString(s) { - m.Value = nil - } else if yesno.MatchString(s) { - if yes.MatchString(s) { - m.Value = true - } else { - m.Value = false - } - } else if i, err := strconv.Atoi(s); err == nil { - m.Value = i - } else if f, err := strconv.ParseFloat(s, 64); err == nil { - m.Value = f - } else if t, err := time.Parse(time.RFC3339, s); err == nil { - m.Value = t - } else if err := json.Unmarshal(in, &j); err == nil { - m.Value = j - } else { - m.Value = s - } + m.Value = v return nil } diff --git a/tools/parse.go b/tools/parse.go new file mode 100644 index 0000000..38d7b5e --- /dev/null +++ b/tools/parse.go @@ -0,0 +1,44 @@ +package tools + +import ( + "encoding/json" + "regexp" + "strconv" + "strings" + "time" +) + +func ParseBytes(in []byte) any { + return ParseString(string(in)) +} + +func ParseString(in string) any { + s := strings.TrimSpace(in) + if s == "" { + return s + } + + yesno := regexp.MustCompile("^(y|Y|yes|Yes|YES|n|N|no|No|NO|true|True|TRUE|false|False|FALSE|on|On|ON|off|Off|OFF)$") + yes := regexp.MustCompile("^(y|Y|yes|Yes|YES|true|True|TRUE|on|On|ON)$") + null := regexp.MustCompile("^(~|null|Null|NULL|none|None|NONE|nil|Nil|NIL)$") + var j json.RawMessage + if null.MatchString(s) { + return nil + } else if yesno.MatchString(s) { + if yes.MatchString(s) { + return true + } else { + return false + } + } else if i, err := strconv.Atoi(s); err == nil { + return i + } else if f, err := strconv.ParseFloat(s, 64); err == nil { + return f + } else if t, err := time.Parse(time.RFC3339, s); err == nil { + return t + } else if err := json.Unmarshal([]byte(s), &j); err == nil { + return j + } + + return s +} diff --git a/tools/parse_test.go b/tools/parse_test.go new file mode 100644 index 0000000..2e8e49f --- /dev/null +++ b/tools/parse_test.go @@ -0,0 +1,66 @@ +package tools + +import ( + "encoding/json" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestParse(t *testing.T) { + when := time.Now() + tests := []struct { + name string + in string + out any + }{ + {"int", "42", 42}, + {"float", "42.13", 42.13}, + {"string", "hello", "hello"}, + {"true", "true", true}, + {"false", "false", false}, + {"nil", "nil", nil}, + {"time", when.Format(time.RFC3339), when}, + {"json-obj", `{"foo":"bar","baz":"quux"}`, json.RawMessage(`{"foo":"bar","baz":"quux"}`)}, + {"json-arr", `["foo",42,"bar", null,"quux", true]`, json.RawMessage(`["foo",42,"bar", null,"quux", true]`)}, + {"empty", "", ""}, + {"space-value", " ", ""}, + {"space-nl-value", " \n ", ""}, + {"null-value", "null", nil}, + {"tilda-value", "~", nil}, + {"none-value", "none", nil}, + {"nil-value", "nil", nil}, + {"yes-value", "yes", true}, + {"on-value", "on", true}, + {"no-value", "no", false}, + {"off-value", "off", false}, + } + + for _, tt := range tests { + t.Run(tt.name, getParseTestRunner(tt.in, tt.out)) + } +} + +func getParseTestRunner(in string, exp any) func(*testing.T) { + return func(t *testing.T) { + out := ParseString(in) + if expT, ok := exp.(time.Time); ok { + ti, gotTime := out.(time.Time) + if assert.True(t, gotTime, "Should have gotten a time.Time, but didn't") { + assert.WithinRange(t, expT, ti.Add(-time.Second), ti.Add(time.Second)) + } + } else { + assert.Equal(t, exp, out) + } + out = ParseBytes([]byte(in)) + if expT, ok := exp.(time.Time); ok { + ti, gotTime := out.(time.Time) + if assert.True(t, gotTime, "Should have gotten a time.Time, but didn't") { + assert.WithinRange(t, expT, ti.Add(-time.Second), ti.Add(time.Second)) + } + } else { + assert.Equal(t, exp, out) + } + } +}