Skip to content

Commit

Permalink
Update README
Browse files Browse the repository at this point in the history
It's been a while since it was last updated, and I felt weird adding a
section explaining slice parsing without fixing the complete staleness
of the readme's examples.

 - Removed Fill() calls, which are deprecated
 - Updated ez package references to use Params
 - Removed type-asserts on View() calls
 - Removed Events() calls, and updated to the less convoluted
   callback-based change-notification mechanism.
  • Loading branch information
dfinkel committed Jun 7, 2024
1 parent bf0b477 commit 217eeb1
Showing 1 changed file with 43 additions and 45 deletions.
88 changes: 43 additions & 45 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@ Dials is an extensible configuration package for Go.
## Installation

```
go get github.com/vimeo/dials
go get github.com/vimeo/dials@latest
```

## Prerequisites

Dials requires Go 1.13 or later.
Dials requires Go 1.18 or later.

## What is Dials?

Dials is a configuration package for Go applications. It supports several different configuration sources including:
* JSON, YAML, and TOML config files
* [Cue](https://cuelang.org), JSON, YAML, and TOML config files
* environment variables
* command line flags (for both Go's [flag](https://golang.org/pkg/flag) package and [pflag](https://pkg.go.dev/github.com/spf13/pflag) package)
* watched config files and re-reading when there are changes to the watched files
Expand All @@ -29,7 +29,7 @@ Dials is a configuration package for Go applications. It supports several differ
## Why choose Dials?
Dials is a configuration solution that supports several configuration sources so you only have to focus on the business logic.
Define the configuration struct and select the configuration sources and Dials will do the rest. Dials is designed to be extensible so if the built-in sources don't meet your needs, you can write your own and still get all the other benefits. Moreover, setting defaults doesn't require additional function calls.
Just populate the config struct with the default values and pass the struct to Dials.
Just populate the config struct with the default values and pass the struct to Dials.
Dials also allows the flexibility to choose the precedence order to determine which sources can overwrite the configuration values. Additionally, Dials has special handling of structs that implement [`encoding.TextUnmarshaler`](https://golang.org/pkg/encoding/#TextUnmarshaler) so structs (like [`IP`](https://pkg.go.dev/net?tab=doc#IP) and [`time`](https://pkg.go.dev/time?tab=doc#Time)) can be properly parsed.

## Using Dials
Expand Down Expand Up @@ -89,7 +89,7 @@ func (c *Config) ConfigPath() (string, bool) {
}

func main() {
c := &Config{}
defCfg := Config{}

ctx, cancel := context.WithCancel(context.Background())
defer cancel()
Expand All @@ -102,17 +102,16 @@ func main() {
// passed in as well to indicate whether the file will be watched and updates
// to the file should update the config struct and if the flags name component
// separation should use different encoding than lower-kebab-case
d, dialsErr := ez.YAMLConfigEnvFlag(ctx, c, ez.WithWatchingConfigFile(true))
d, dialsErr := ez.YAMLConfigEnvFlag(ctx, &defCfg, ez.Params[Config]{WatchConfigFile: true})
if dialsErr != nil {
// error handling
}

// Fill will deep copy the fully-stacked configuration into its argument.
// View returns a pointer to the fully stacked configuration file
// The stacked configuration is populated from the config file, environment
// variables and commandline flags. Can alternatively use
// c = d.View().(*Config) for a cheaper operation
d.Fill(c)
fmt.Printf("Config: %+v\n", c)
// variables and commandline flags.
cfg := d.View()
fmt.Printf("Config: %+v\n", cfg)
}
```

Expand All @@ -135,16 +134,16 @@ export VAL_2=5
go run main.go --some-val
```

the output will be
the output will be
`Config: &{Val1:valueb Val2:5 Val3:true}`

Note that even though val_2 has a value of 2 in the yaml file, the config value
output for Val2 is 5 because environment variables take precedence.
Note that even though `val_2` has a value of 2 in the yaml file, the config value
output for `Val2` is 5 because environment variables take precedence.

### Configure your configuration settings
If the predefined functions in the ez package don't meet your needs, you can specify the
sources you would like in the order of precedence you prefer. Not much setup is needed
to configure this. Choose the predefined sources and add the appropriate `dials` tags to the config struct.
If the predefined functions in the ez package don't meet your needs, you can specify the
sources you would like in the order of precedence you prefer. Not much setup is needed
to configure this. Choose the predefined sources and add the appropriate `dials` tags to the config struct.

``` go
package main
Expand All @@ -157,7 +156,7 @@ import (
"github.com/vimeo/dials/sources/env"
"github.com/vimeo/dials/sources/file"
"github.com/vimeo/dials/sources/flag"
"github.com/vimeo/dials/sources/yaml"
"github.com/vimeo/dials/decoders/yaml"
)

type Config struct {
Expand All @@ -172,23 +171,23 @@ type Config struct {
}

func main() {
config := &Config{
defaultConfig := &Config{
// Val1 has a default value of "hello" and will be overwritten by the
// sources if there is a corresponding field for Val1
Val1: "hello",
}

// Define a file source if you want to read from a config file. To read
// from other source files such as JSON, and TOML, use "&json.Decoder{}"
// or "&toml.Decoder{}"
// from other source files such as Cue, JSON, and TOML, use "&cue.Decoder{}",
// "&json.Decoder{}" or "&toml.Decoder{}"
fileSrc, fileErr := file.NewSource("path/to/config", &yaml.Decoder{})
if fileErr != nil {
// error handling
}

// Define a `dials.Source` for command line flags. Consider using the dials
// pflag library if the application uses the github.com/spf13/pflag package
flagSet, flagErr := flag.NewCmdLineSet(flag.DefaultFlagNameConfig(), config)
flagSet, flagErr := flag.NewCmdLineSet(flag.DefaultFlagNameConfig(), defaultConfig)
if flagErr != nil {
// error handling
}
Expand All @@ -200,17 +199,16 @@ func main() {
// passed in the Config function with increasing precedence. So the fileSrc
// value will overwrite the flagSet value if they both were to set the
// same field
d, err := dials.Config(context.Background(), config, envSrc, flagSet, fileSrc)
d, err := dials.Config(context.Background(), defaultConfig, envSrc, flagSet, fileSrc)
if err != nil {
// error handling
}

// Fill will deep copy the fully-stacked configuration into its argument.
// View returns a pointer to the fully stacked configuration.
// The stacked configuration is populated from the config file, environment
// variables and commandline flags. Can alternatively use
// c = d.View().(*Config) for a cheaper operation
d.Fill(config)
fmt.Printf("Config: %+v\n", config)
cfg := d.View()
fmt.Printf("Config: %+v\n", cfg)
}
```

Expand All @@ -221,13 +219,13 @@ b: valueb
val-3: false
```
and the following commands
and the following commands
```
export VAL_2=5
go run main.go --val-3
```

the output will be `Config: &{Val1:valueb Val2:5 Val3:true}`.

Note that even when val-3 is defined in the yaml file and the file source takes precedence,
Expand Down Expand Up @@ -256,26 +254,26 @@ If you wish to watch the config file and make updates to your configuration, use
// additional sources can be passed along with the watching file source and the
// precedence order will still be dictated by the order in which the sources are
// defined in the Config function.
d, err := dials.Config(ctx, config, watchingFileSource)
d, err := dials.Config(ctx, defCfg, watchingFileSource)
if err != nil {
// error handling
}

conf := d.View().(*Config)

// you can get notified whenever the config changes through the channel
// returned by the Events method. Wait on that channel if you need to take
// any steps when the config changes
go func(ctx context.Context){
for{
select{
case <-ctx.Done():
return
case c := <-d.Events():
// increment metrics and log
}
}
}(ctx)
conf, serial := d.ViewVersion()

// You can get notified whenever the config changes by registering a callback.
// If a new version has become available since the serial argument was
// returned by ViewVersion(), it will be called immediately to bring
// the callback up to date.
// You can call the returned unregister function to unregister at a later point.
unreg := d.RegisterCallback(ctx, serial, func(ctx context.Context, oldCfg, newCfg *Config) {
// log, increment metrics, re-index a map, etc.
})
// If the passed context expires before registration succeeds, a nil
// unregister callback will be returned.
if unreg != nil {
defer unreg()
}
```

### Source
Expand Down

0 comments on commit 217eeb1

Please sign in to comment.