Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 17 additions & 9 deletions dotconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,17 @@ import (
type DecodeOption int

const (
ReturnFileIOErrors DecodeOption = iota // Return file IO errors
EnforceStructTags // Make sure all fields in config struct have `env` struct tags
AllowWhitespace // Allow leading/trailing whitespace in string values
ReturnFileIOErrors DecodeOption = iota // Return file IO errors
EnforceStructTags // Make sure all fields in config struct have `env` struct tags
AllowWhitespace // Allow leading/trailing whitespace in string values
SkipNewlineDecoding // Don't turn "\n" into newlines
)

type options struct {
ReturnFileIOErrors bool
EnforceStructTags bool
AllowWhitespace bool
ReturnFileIOErrors bool
EnforceStructTags bool
AllowWhitespace bool
SkipNewlineDecoding bool
}

func optsFromVariadic(opts []DecodeOption) options {
Expand All @@ -37,6 +39,8 @@ func optsFromVariadic(opts []DecodeOption) options {
v.EnforceStructTags = true
case AllowWhitespace:
v.AllowWhitespace = true
case SkipNewlineDecoding:
v.SkipNewlineDecoding = true
}
}
return v
Expand Down Expand Up @@ -91,6 +95,8 @@ func FromFileName[T any](name string, opts ...DecodeOption) (T, error) {
// In the future might look in to more advanced escaping, etc.
// but this suits our needs for the time being.
func FromReader[T any](r io.Reader, opts ...DecodeOption) (T, error) {
// Get options struct from variadic input
decodedOpts := optsFromVariadic(opts)
// First, parse all values in our reader and os.Setenv them.
scanner := bufio.NewScanner(r)
for scanner.Scan() {
Expand Down Expand Up @@ -124,13 +130,15 @@ func FromReader[T any](r io.Reader, opts ...DecodeOption) (T, error) {
// And trim starting double quote
value = strings.TrimPrefix(value, `"`)
}
// Turn \n into newlines
value = strings.ReplaceAll(value, `\n`, "\n")
// Optionally turn \n into newlines.
if !decodedOpts.SkipNewlineDecoding {
value = strings.ReplaceAll(value, `\n`, "\n")
}
// Finally, set our env variable.
os.Setenv(key, value)
}
// Next, populate config file based on struct tags and return populated config
return fromEnv[T](optsFromVariadic(opts))
return fromEnv[T](decodedOpts)
}

var (
Expand Down
15 changes: 15 additions & 0 deletions dotconfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,21 @@ NUM_RETRIES=13`)
}
}

func TestFromReaderSkipDecodingNewlines(t *testing.T) {
type noNewlines struct {
StrVal string `env:"NO_NEWLINES"`
}
reader := strings.NewReader(`NO_NEWLINES=\n single \n line! \n`)
config, err := dotconfig.FromReader[noNewlines](reader, dotconfig.SkipNewlineDecoding)
if err != nil {
t.Fatalf("Didn't expect error. Got %v.", err)
}
expected := `\n single \n line! \n`
if config.StrVal != expected {
t.Fatalf("Expected:\n%v\nGot:\n%v", expected, config.StrVal)
}
}

type moreAdvancedConfig struct {
MaxBytesPerRequest int `env:"MAX_BYTES_PER_REQUEST"`
APIVersion float64 `env:"API_VERSION"`
Expand Down
Loading