✨ Parse config overrides on cli
This commit is contained in:
parent
40f9518611
commit
25f5c37243
8 changed files with 169 additions and 32 deletions
|
|
@ -23,8 +23,6 @@ import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cfgFile string
|
|
||||||
|
|
||||||
// rootCmd represents the base command when called without any subcommands
|
// rootCmd represents the base command when called without any subcommands
|
||||||
var rootCmd = &cobra.Command{
|
var rootCmd = &cobra.Command{
|
||||||
Use: "my-log",
|
Use: "my-log",
|
||||||
|
|
@ -52,6 +50,7 @@ func init() {
|
||||||
// will be global for your application.
|
// will be global for your application.
|
||||||
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&config.ConfigPath, "config", "c", config.ConfigPath, "config file")
|
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
|
// Cobra also supports local flags, which will only run
|
||||||
// when this action is called directly.
|
// when this action is called directly.
|
||||||
|
|
@ -61,4 +60,5 @@ func init() {
|
||||||
// initConfig reads in config file and ENV variables if set.
|
// initConfig reads in config file and ENV variables if set.
|
||||||
func initConfig() {
|
func initConfig() {
|
||||||
// @todo
|
// @todo
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,18 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
fp "path/filepath"
|
fp "path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"codeberg.org/danjones000/my-log/tools"
|
||||||
"github.com/BurntSushi/toml"
|
"github.com/BurntSushi/toml"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ConfigPath string
|
var ConfigPath string
|
||||||
|
var Overrides map[string]string
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
conf, _ := os.UserConfigDir()
|
conf, _ := os.UserConfigDir()
|
||||||
|
|
@ -17,10 +22,32 @@ func init() {
|
||||||
func Load() (Config, error) {
|
func Load() (Config, error) {
|
||||||
c, _ := DefaultConfig()
|
c, _ := DefaultConfig()
|
||||||
_, err := os.Stat(ConfigPath)
|
_, err := os.Stat(ConfigPath)
|
||||||
if os.IsNotExist(err) {
|
if !os.IsNotExist(err) {
|
||||||
return c, nil
|
_, err = toml.DecodeFile(ConfigPath, &c)
|
||||||
|
if err != nil {
|
||||||
|
return c, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_, err = toml.DecodeFile(ConfigPath, &c)
|
|
||||||
// @todo get environ
|
// @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
|
return c, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
|
import mapst "github.com/mitchellh/mapstructure"
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Input Input
|
Input Input
|
||||||
Outputs map[string]Output `toml:"output"`
|
Outputs Outputs `toml:"output"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Input struct {
|
type Input struct {
|
||||||
|
|
@ -11,7 +13,23 @@ type Input struct {
|
||||||
Ext string
|
Ext string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Outputs map[string]Output
|
||||||
|
|
||||||
type Output struct {
|
type Output struct {
|
||||||
Enabled bool
|
Enabled bool
|
||||||
Config map[string]any
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
1
go.mod
1
go.mod
|
|
@ -4,6 +4,7 @@ go 1.21.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/BurntSushi/toml v1.3.2
|
github.com/BurntSushi/toml v1.3.2
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0
|
||||||
github.com/spf13/cobra v1.8.0
|
github.com/spf13/cobra v1.8.0
|
||||||
github.com/stretchr/testify v1.8.4
|
github.com/stretchr/testify v1.8.4
|
||||||
)
|
)
|
||||||
|
|
|
||||||
2
go.sum
2
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/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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
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/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 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"codeberg.org/danjones000/my-log/tools"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Meta struct {
|
type Meta struct {
|
||||||
|
|
@ -73,33 +74,11 @@ func (m *Meta) processMeta(in []byte) error {
|
||||||
if len(in) == 0 {
|
if len(in) == 0 {
|
||||||
return newParsingError(errors.New("No value found"))
|
return newParsingError(errors.New("No value found"))
|
||||||
}
|
}
|
||||||
s := strings.TrimSpace(string(in))
|
v := tools.ParseBytes(in)
|
||||||
if len(s) == 0 {
|
if v == "" {
|
||||||
return newParsingError(errors.New("No value found"))
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
44
tools/parse.go
Normal file
44
tools/parse.go
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
66
tools/parse_test.go
Normal file
66
tools/parse_test.go
Normal file
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue