diff --git a/ezconf.go b/ezconf.go index e38c456..191e731 100644 --- a/ezconf.go +++ b/ezconf.go @@ -3,6 +3,7 @@ package ezconf import ( "flag" "fmt" + "log/slog" "os" "sort" "strconv" @@ -14,19 +15,21 @@ import ( ) // CamelToSnake converts a CamelCase strings to a snake_case using the following algorithm: -// 1) for every transition from upper->lowercase insert an underscore before the uppercase character -// 2) for every transition fro lowercase->uppercase insert an underscore before the uppercase -// 3) lowercase resulting string // -// Examples: -// CamelCase -> camel_case -// AWSConfig -> aws_config -// IPAddress -> ip_address -// S3MediaPrefix -> s3_media_prefix -// Route53Region -> route53_region -// CamelCaseA -> camel_case_a -// CamelABCCaseDEF -> camel_abc_case_def +// 1. for every transition from upper->lowercase insert an underscore before the uppercase character // +// 2. for every transition fro lowercase->uppercase insert an underscore before the uppercase +// +// 3. lowercase resulting string +// +// Examples: +// CamelCase -> camel_case +// AWSConfig -> aws_config +// IPAddress -> ip_address +// S3MediaPrefix -> s3_media_prefix +// Route53Region -> route53_region +// CamelCaseA -> camel_case_a +// CamelABCCaseDEF -> camel_abc_case_def func CamelToSnake(camel string) string { snakes := make([]string, 0, 4) snake := strings.Builder{} @@ -76,7 +79,6 @@ func CamelToSnake(camel string) string { // 2. TOML files you specify (optional) // 3. Set environment variables // 4. Command line parameters -// type EZLoader struct { name string description string @@ -94,7 +96,6 @@ type EZLoader struct { // `name` and `description` are used to build environment variables and help parameters. The list of files // can be nil, or can contain optional files to read TOML configuration from in priority order. The first file // found and parsed will end parsing of others, but there is no requirement that any file is found. -// func NewLoader(config interface{}, name string, description string, files []string) *EZLoader { return &EZLoader{ name: name, @@ -106,12 +107,11 @@ func NewLoader(config interface{}, name string, description string, files []stri } // MustLoad loads our configuration from our sources in the order of: -// 1. TOML files -// 2. Environment variables -// 3. Command line parameters +// 1. TOML files +// 2. Environment variables +// 3. Command line parameters // // If any error is encountered, the program will exit reporting the error and showing usage. -// func (ez *EZLoader) MustLoad() { err := ez.Load() if err != nil { @@ -122,12 +122,11 @@ func (ez *EZLoader) MustLoad() { } // Load loads our configuration from our sources in the order of: -// 1. TOML files -// 2. Environment variables -// 3. Command line parameters +// 1. TOML files +// 2. Environment variables +// 3. Command line parameters // // If any error is encountered it is returned for the caller to process. -// func (ez *EZLoader) Load() error { // first build our mapping of name snake_case -> structs.Field fields, err := buildFields(ez.config) @@ -340,12 +339,20 @@ func setValues(fields *ezFields, values map[string]ezValue) error { } f.Set(t) + + case slog.Level: + var level slog.Level + err := level.UnmarshalText([]byte(value)) + if err != nil { + return err + } + f.Set(level) } } return nil } -func buildFields(config interface{}) (*ezFields, error) { +func buildFields(config any) (*ezFields, error) { fields := make(map[string]*structs.Field) s := structs.New(config) for _, f := range s.Fields() { @@ -356,7 +363,8 @@ func buildFields(config interface{}) (*ezFields, error) { float32, float64, bool, string, - time.Time: + time.Time, + slog.Level: name := CamelToSnake(f.Name()) dupe, found := fields[name] if found { diff --git a/ezconf_test.go b/ezconf_test.go index 239c691..e6a0af1 100644 --- a/ezconf_test.go +++ b/ezconf_test.go @@ -2,6 +2,7 @@ package ezconf import ( "fmt" + "log/slog" "os" "testing" "time" @@ -34,6 +35,7 @@ type allTypes struct { MyBool bool MyString string MyDatetime time.Time + MyLogLevel slog.Level } func toFields(t *testing.T, s interface{}) *ezFields { @@ -116,6 +118,10 @@ func TestSetValue(t *testing.T) { {"my_datetime", "2018-04-03T05:30:00.123+07:00", false, "2018-04-03 05:30:00.123 +0700 +0700"}, {"my_datetime", "notdate", true, ""}, + {"my_log_level", "info", false, "INFO"}, + {"my_log_level", "ERROR", false, "ERROR"}, + {"my_log_level", "crazy", true, ""}, + {"unknown", "", true, ""}, } @@ -139,9 +145,11 @@ func TestSetValue(t *testing.T) { func TestEndToEnd(t *testing.T) { at := &allTypes{} conf := NewLoader(at, "foo", "description", []string{"testdata/missing.toml", "testdata/fields.toml", "testdata/simple.toml"}) - conf.args = []string{"-my-int=48", "-debug-conf"} + conf.args = []string{"-my-int=48", "-my-log-level=error", "-debug-conf"} err := conf.Load() assert.NoError(t, err) + assert.Equal(t, 48, at.MyInt) + assert.Equal(t, slog.LevelError, at.MyLogLevel) } func TestPriority(t *testing.T) { diff --git a/flags.go b/flags.go index 3a8bebb..79a64c8 100644 --- a/flags.go +++ b/flags.go @@ -3,6 +3,7 @@ package ezconf import ( "flag" "fmt" + "log/slog" "strings" "time" ) @@ -91,6 +92,9 @@ func buildFlags(name string, description string, fields *ezFields, errorHandling case time.Time: flags.String(flagName, formatDatetime(f.Value().(time.Time)), help) + + case slog.Level: + flags.String(flagName, v.String(), help) } } diff --git a/testdata/simple.toml b/testdata/simple.toml index 3cfa123..1713bbc 100644 --- a/testdata/simple.toml +++ b/testdata/simple.toml @@ -2,6 +2,7 @@ my_int = 32 my_bool = true my_datetime = 2018-04-03T05:30:00Z +my_log_level = "info" # arrays cannot my_ints = [10, 20, 30] diff --git a/toml.go b/toml.go index ab30c46..7fca331 100644 --- a/toml.go +++ b/toml.go @@ -13,7 +13,7 @@ import ( // Iterates the list of files, parsing the first that is found and loading the // result into the passed in struct pointer. If no files are passed in or // no files are found, this is a noop. -func parseTOMLFiles(config interface{}, files []string, debug bool) error { +func parseTOMLFiles(config any, files []string, debug bool) error { // search through our list of files, stopping when we find one for i, file := range files { toml, err := os.ReadFile(file) diff --git a/toml_test.go b/toml_test.go index 78b5cd7..c158285 100644 --- a/toml_test.go +++ b/toml_test.go @@ -1,6 +1,7 @@ package ezconf import ( + "log/slog" "testing" "time" @@ -11,6 +12,7 @@ type simpleStruct struct { MyInt int MyBool bool MyDatetime time.Time + MyLogLevel slog.Level MyInts []int