From 587c89a50ded9c94b7274fa32afa62286bdb2f13 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Thu, 23 May 2024 10:10:16 -0300 Subject: [PATCH] docs: improving docs and examples Signed-off-by: Carlos Alexandro Becker --- README.md | 112 +++++-------------------------------- env.go | 27 +++++++++ env_test.go | 90 ----------------------------- examples_test.go | 143 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 183 insertions(+), 189 deletions(-) create mode 100644 examples_test.go diff --git a/README.md b/README.md index 9a693a9..1bf11d1 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,20 @@ [![Build Status](https://img.shields.io/github/actions/workflow/status/caarlos0/env/build.yml?branch=main&style=for-the-badge)](https://github.com/caarlos0/env/actions?workflow=build) [![Coverage Status](https://img.shields.io/codecov/c/gh/caarlos0/env.svg?logo=codecov&style=for-the-badge)](https://codecov.io/gh/caarlos0/env) -[![](http://img.shields.io/badge/godoc-reference-5272B4.svg?style=for-the-badge)](https://pkg.go.dev/github.com/caarlos0/env/v11) +[![Go Docs](http://img.shields.io/badge/godoc-reference-5272B4.svg?style=for-the-badge)](https://pkg.go.dev/github.com/caarlos0/env/v11) -A simple and zero-dependencies library to parse environment variables into -`struct`s. +A simple and zero-dependencies library to parse environment variables into `struct`s. + +```go +type config struct { + Home string `env:"HOME"` +} + +cfg, err := env.ParseAs[config]() +``` + +You can see the full list of examples +[here](https://pkg.go.dev/github.com/caarlos0/env/v11#pkg-examples). ## Used and supported by @@ -19,60 +29,6 @@ A simple and zero-dependencies library to parse environment variables into

-## Example - -Get the module with: - -```sh -go get github.com/caarlos0/env/v11 -``` - -The usage looks like this: - -```go -package main - -import ( - "fmt" - "time" - - "github.com/caarlos0/env/v11" -) - -type config struct { - Home string `env:"HOME"` - Port int `env:"PORT" envDefault:"3000"` - Password string `env:"PASSWORD,unset"` - IsProduction bool `env:"PRODUCTION"` - Duration time.Duration `env:"DURATION"` - Hosts []string `env:"HOSTS" envSeparator:":"` - TempFolder string `env:"TEMP_FOLDER,expand" envDefault:"${HOME}/tmp"` - StringInts map[string]int `env:"MAP_STRING_INT"` -} - -func main() { - cfg := config{} - if err := env.Parse(&cfg); err != nil { - fmt.Printf("%+v\n", err) - } - - // or you can use generics - cfg, err := env.ParseAs[config]() - if err != nil { - fmt.Printf("%+v\n", err) - } - - fmt.Printf("%+v\n", cfg) -} -``` - -You can run it like this: - -```sh -$ PRODUCTION=true HOSTS="host1:host2:host3" DURATION=1s MAP_STRING_INT=k1:1,k2:2 go run main.go -{Home:/your/home Port:3000 IsProduction:true Hosts:[host1 host2 host3] Duration:1s StringInts:map[k1:1 k2:2]} -``` - ## Caveats > [!CAUTION] @@ -185,48 +141,6 @@ type config struct { > value. > This also means that custom parser funcs will not be invoked. -## Expand vars - -If you set the `expand` option, environment variables (either in `${var}` or -`$var` format) in the string will be replaced according with the actual value -of the variable. For example: - -```go -type config struct { - SecretKey string `env:"SECRET_KEY,expand"` -} -``` - -This also works with `envDefault`: - -```go -import ( - "fmt" - "github.com/caarlos0/env/v11" -) - -type config struct { - Host string `env:"HOST" envDefault:"localhost"` - Port int `env:"PORT" envDefault:"3000"` - Address string `env:"ADDRESS,expand" envDefault:"$HOST:${PORT}"` -} - -func main() { - cfg := config{} - if err := env.Parse(&cfg); err != nil { - fmt.Printf("%+v\n", err) - } - fmt.Printf("%+v\n", cfg) -} -``` - -results in this: - -```sh -$ PORT=8080 go run main.go -{Host:localhost Port:8080 Address:localhost:8080} -``` - ## Not Empty fields While `required` demands the environment variable to be set, it doesn't check diff --git a/env.go b/env.go index d4bdf55..ffeb493 100644 --- a/env.go +++ b/env.go @@ -1,3 +1,30 @@ +// Package env parses environment variables into structs. +// +// type config struct { +// Home string `env:"HOME"` +// } +// cfg, err := env.ParseAs[config]() +// +// The following types are supported by default: +// - string +// - bool +// - int +// - int8 +// - int16 +// - int32 +// - int64 +// - uint +// - uint8 +// - uint16 +// - uint32 +// - uint64 +// - float32 +// - float64 +// - time.Duration +// - encoding.TextUnmarshaler +// - url.URL +// +// You can, however, implement custom parsers by setting the FuncMap option. package env import ( diff --git a/env_test.go b/env_test.go index f322e10..ca3cda3 100644 --- a/env_test.go +++ b/env_test.go @@ -1353,70 +1353,6 @@ func TestParseInvalidURL(t *testing.T) { isTrue(t, errors.Is(err, ParseError{})) } -func ExampleParse() { - type inner struct { - Foo string `env:"FOO" envDefault:"foobar"` - } - type config struct { - Home string `env:"HOME,required"` - Port int `env:"PORT" envDefault:"3000"` - IsProduction bool `env:"PRODUCTION"` - TempFolder string `env:"TEMP_FOLDER,expand" envDefault:"${HOME}/.tmp"` - StringInts map[string]int `env:"MAP_STRING_INT" envDefault:"k1:1,k2:2"` - Inner inner - } - os.Setenv("HOME", "/tmp/fakehome") - var cfg config - if err := Parse(&cfg); err != nil { - fmt.Println("failed:", err) - } - fmt.Printf("%+v", cfg) - // Output: {Home:/tmp/fakehome Port:3000 IsProduction:false TempFolder:/tmp/fakehome/.tmp StringInts:map[k1:1 k2:2] Inner:{Foo:foobar}} -} - -func ExampleParse_onSet() { - type config struct { - Home string `env:"HOME,required"` - Port int `env:"PORT" envDefault:"3000"` - IsProduction bool `env:"PRODUCTION"` - NoEnvTag bool - Inner struct{} `envPrefix:"INNER_"` - } - os.Setenv("HOME", "/tmp/fakehome") - var cfg config - if err := ParseWithOptions(&cfg, Options{ - OnSet: func(tag string, value interface{}, isDefault bool) { - fmt.Printf("Set %s to %v (default? %v)\n", tag, value, isDefault) - }, - }); err != nil { - fmt.Println("failed:", err) - } - fmt.Printf("%+v", cfg) - // Output: Set HOME to /tmp/fakehome (default? false) - // Set PORT to 3000 (default? true) - // Set PRODUCTION to (default? false) - // {Home:/tmp/fakehome Port:3000 IsProduction:false NoEnvTag:false Inner:{}} -} - -func ExampleParse_defaults() { - type config struct { - A string `env:"FOO" envDefault:"foo"` - B string `env:"FOO"` - } - - // env FOO is not set - - cfg := config{ - A: "A", - B: "B", - } - if err := Parse(&cfg); err != nil { - fmt.Println("failed:", err) - } - fmt.Printf("%+v", cfg) - // Output: {A:foo B:B} -} - func TestIgnoresUnexported(t *testing.T) { type unexportedConfig struct { home string `env:"HOME"` @@ -1466,32 +1402,6 @@ func TestPrecedenceUnmarshalText(t *testing.T) { isEqual(t, []LogLevel{DebugLevel, InfoLevel}, cfg.LogLevels) } -func ExampleParseWithOptions() { - type thing struct { - desc string - } - - type conf struct { - Thing thing `env:"THING"` - } - - os.Setenv("THING", "my thing") - - c := conf{} - - err := ParseWithOptions(&c, Options{FuncMap: map[reflect.Type]ParserFunc{ - reflect.TypeOf(thing{}): func(v string) (interface{}, error) { - return thing{desc: v}, nil - }, - }}) - if err != nil { - fmt.Println(err) - } - fmt.Println(c.Thing.desc) - // Output: - // my thing -} - func TestFile(t *testing.T) { type config struct { SecretKey string `env:"SECRET_KEY,file"` diff --git a/examples_test.go b/examples_test.go new file mode 100644 index 0000000..62c5e4a --- /dev/null +++ b/examples_test.go @@ -0,0 +1,143 @@ +package env + +import ( + "fmt" + "os" + "reflect" +) + +func ExampleParseAs() { + type config struct { + Home string `env:"HOME,required"` + Port int `env:"PORT" envDefault:"3000"` + IsProduction bool `env:"PRODUCTION"` + TempFolder string `env:"TEMP_FOLDER,expand" envDefault:"${HOME}/.tmp"` + StringInts map[string]int `env:"MAP_STRING_INT" envDefault:"k1:1,k2:2"` + } + os.Setenv("HOME", "/tmp/fakehome") + cfg, err := ParseAs[config]() + if err != nil { + fmt.Println("failed:", err) + } + fmt.Printf("%+v", cfg) + // Output: {Home:/tmp/fakehome Port:3000 IsProduction:false TempFolder:/tmp/fakehome/.tmp StringInts:map[k1:1 k2:2]} +} + +func ExampleParse() { + type inner struct { + Foo string `env:"FOO" envDefault:"foobar"` + } + type config struct { + Home string `env:"HOME,required"` + Port int `env:"PORT" envDefault:"3000"` + IsProduction bool `env:"PRODUCTION"` + TempFolder string `env:"TEMP_FOLDER,expand" envDefault:"${HOME}/.tmp"` + StringInts map[string]int `env:"MAP_STRING_INT" envDefault:"k1:1,k2:2"` + Inner inner + } + os.Setenv("HOME", "/tmp/fakehome") + var cfg config + if err := Parse(&cfg); err != nil { + fmt.Println("failed:", err) + } + fmt.Printf("%+v", cfg) + // Output: {Home:/tmp/fakehome Port:3000 IsProduction:false TempFolder:/tmp/fakehome/.tmp StringInts:map[k1:1 k2:2] Inner:{Foo:foobar}} +} + +func ExampleParse_onSet() { + type config struct { + Home string `env:"HOME,required"` + Port int `env:"PORT" envDefault:"3000"` + IsProduction bool `env:"PRODUCTION"` + NoEnvTag bool + Inner struct{} `envPrefix:"INNER_"` + } + os.Setenv("HOME", "/tmp/fakehome") + var cfg config + if err := ParseWithOptions(&cfg, Options{ + OnSet: func(tag string, value interface{}, isDefault bool) { + fmt.Printf("Set %s to %v (default? %v)\n", tag, value, isDefault) + }, + }); err != nil { + fmt.Println("failed:", err) + } + fmt.Printf("%+v", cfg) + // Output: Set HOME to /tmp/fakehome (default? false) + // Set PORT to 3000 (default? true) + // Set PRODUCTION to (default? false) + // {Home:/tmp/fakehome Port:3000 IsProduction:false NoEnvTag:false Inner:{}} +} + +func ExampleParse_defaults() { + type config struct { + A string `env:"FOO" envDefault:"foo"` + B string `env:"FOO"` + } + + // env FOO is not set + + cfg := config{ + A: "A", + B: "B", + } + if err := Parse(&cfg); err != nil { + fmt.Println("failed:", err) + } + fmt.Printf("%+v", cfg) + // Output: {A:foo B:B} +} + +func ExampleParseWithOptions() { + type thing struct { + desc string + } + + type conf struct { + Thing thing `env:"THING"` + } + + os.Setenv("THING", "my thing") + + c := conf{} + + err := ParseWithOptions(&c, Options{FuncMap: map[reflect.Type]ParserFunc{ + reflect.TypeOf(thing{}): func(v string) (interface{}, error) { + return thing{desc: v}, nil + }, + }}) + if err != nil { + fmt.Println(err) + } + fmt.Println(c.Thing.desc) + // Output: + // my thing +} + +func ExampleParse_expandVars() { + type config struct { + Host string `env:"HOST" envDefault:"localhost"` + Port int `env:"PORT" envDefault:"3000"` + Address string `env:"ADDRESS,expand" envDefault:"$HOST:${PORT}"` + } + + cfg := config{} + if err := Parse(&cfg); err != nil { + fmt.Printf("%+v\n", err) + } + fmt.Printf("%+v\n", cfg) + // Output: {Host:localhost Port:3000 Address:localhost:3000} +} + +func ExampleParse_unset() { + type config struct { + SecretKey string `env:"SECRET_KEY,unset"` + } + os.Setenv("SECRET_KEY", "asd") + cfg := config{} + if err := Parse(&cfg); err != nil { + fmt.Printf("%+v\n", err) + } + fmt.Printf("%+v\n", cfg) + fmt.Println(os.Getenv("SECRET_KEY")) + // Output: {SecretKey:asd} +}