Skip to content

Commit

Permalink
Add support for slog.Level fields
Browse files Browse the repository at this point in the history
  • Loading branch information
rowanseymour committed Jan 16, 2024
1 parent 5145095 commit c82bd33
Show file tree
Hide file tree
Showing 6 changed files with 48 additions and 25 deletions.
54 changes: 31 additions & 23 deletions ezconf.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package ezconf
import (
"flag"
"fmt"
"log/slog"

Check failure on line 6 in ezconf.go

View workflow job for this annotation

GitHub Actions / Test (1.19.x)

package log/slog is not in GOROOT (/opt/hostedtoolcache/go/1.19.13/x64/src/log/slog)

Check failure on line 6 in ezconf.go

View workflow job for this annotation

GitHub Actions / Test (1.20.x)

package log/slog is not in GOROOT (/opt/hostedtoolcache/go/1.20.12/x64/src/log/slog)

Check failure on line 6 in ezconf.go

View workflow job for this annotation

GitHub Actions / Test (1.19.x)

package log/slog is not in GOROOT (/opt/hostedtoolcache/go/1.19.13/x64/src/log/slog)

Check failure on line 6 in ezconf.go

View workflow job for this annotation

GitHub Actions / Test (1.20.x)

package log/slog is not in GOROOT (/opt/hostedtoolcache/go/1.20.12/x64/src/log/slog)
"os"
"sort"
"strconv"
Expand All @@ -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{}
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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 {
Expand All @@ -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)
Expand Down Expand Up @@ -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() {
Expand All @@ -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 {
Expand Down
10 changes: 9 additions & 1 deletion ezconf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package ezconf

import (
"fmt"
"log/slog"
"os"
"testing"
"time"
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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, ""},
}

Expand All @@ -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) {
Expand Down
4 changes: 4 additions & 0 deletions flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package ezconf
import (
"flag"
"fmt"
"log/slog"
"strings"
"time"
)
Expand Down Expand Up @@ -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)
}
}

Expand Down
1 change: 1 addition & 0 deletions testdata/simple.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion toml.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions toml_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package ezconf

import (
"log/slog"
"testing"
"time"

Expand All @@ -11,6 +12,7 @@ type simpleStruct struct {
MyInt int
MyBool bool
MyDatetime time.Time
MyLogLevel slog.Level

MyInts []int

Expand Down

0 comments on commit c82bd33

Please sign in to comment.