Tool Description
Zero Effort Configuration
I've always loved the elegance of Go's flag package - how clean and straightforward it is to define and use configuration options. While working on various Go projects, I found myself wanting that same simplicity but with support for YAML configs. I couldn't find anything that preserved this paradigm, so I built zerocfg.
Table of Contents
Installation
go get -u github.com/chaindead/zerocfg
Quick Start
package main
import (
"fmt"
zfg "github.com/chaindead/zerocfg"
"github.com/chaindead/zerocfg/env"
"github.com/chaindead/zerocfg/yaml"
)
var (
// Configuration variables
path = zfg.Str("config.path", "", "path to yaml conf file", zfg.Alias("c"))
ip = zfg.IP("db.ip", "127.0.0.1", "database location")
port = zfg.Uint("db.port", 5678, "database port")
username = zfg.Str("db.user", "guest", "user of database")
password = zfg.Str("db.password", "qwerty", "password for user", zfg.Secret())
)
func main() {
// Initialize configuration with multiple sources
err := zfg.Parse(
env.New(),
yaml.New(path),
)
if err != nil {
panic(err)
}
fmt.Printf("Connect to %s:%d creds=%s:%s\n", *ip, *port, *username, *password)
// OUTPUT: Connect to 127.0.0.1:5678 creds=guest:qwerty
fmt.Println(zfg.Show())
// CMD: go run ./... -c test.yaml
// OUTPUT:
// config.path = test.yaml (path to yaml conf file)
// db.ip = 127.0.0.1 (database location)
// db.password = <secret> (password for user)
// db.port = 5678 (database port)
// db.user = guest (user of database)
}
Usage
Options naming
Example:
zfg.Str("groupOptions.thisOption", "", "camelCase usage")
zfg.Str("group_options.this_option", "", "underscore usage")
zfg.Str("group-options.this-option", "", "dash usage")
Restrictions
Unknown values
If zfg.Parse
encounters an unknown value (e.g. variable not registered as an option), it returns an error.
This helps avoid boilerplate and ensures only declared options are used.
But you can ignore unknown values if desired.
err := zfg.Parse(
env.New(),
yaml.New(path),
)
if u, ok := zfg.IsUnknown(err); !ok {
panic(err)
} else {
// u is map <source_name> to slice of unknown keys
fmt.Println(u)
}
Complex Types as string
var (
_ = zfg.Dur("timeout", 5*time.Second, "duration via fmt.Stringer interface")
_ = zfg.Floats64("floats", nil, "list via json")
)
func main() {
_ = zfg.Parse()
fmt.Printf(zfg.Show())
// CMD: go run ./... --timeout 10s --floats '[1.1, 2.2, 3.3]'
// OUTPUT:
// floats = [1.1,2.2,3.3] (list via json)
// timeout = 10s (duration via fmt.Stringer interface)
}
Configuration Sources
The configuration system follows a strict priority hierarchy:
For example, if you initialize configuration like this:
zfg.Parse(
env.New(), // Second highest priority (after cli flags)
yaml.New(path), // Third highest priority
)
The final value resolution order will be:
Important notes:
Command-line Arguments
Example:
path := zfg.Str("config.path", "", "path to yaml conf file", zfg.Alias("c"))
You can run your application with:
go run ./... -c test.yaml
# or
go run ./... --config.path test.yaml
In both cases, the value test.yaml
will be assigned to config.path
.
Environment Variables
Environment variables are automatically transformed from the configuration key format:
The transformation rules:
Example:
import (
"fmt"
zfg "github.com/chaindead/zerocfg"
"github.com/chaindead/zerocfg/env"
)
var dbUser = zfg.Str("db.user", "", "database's username")
func main() {
_ = zfg.Parse(
env.New(),
)
fmt.Printf("DB user: %s", *dbUser)
}
When you run, dbUser
will be set to admin
.
DB_USER=admin go run main.go
# OUTPUT: DB user: admin
YAML Source
Example YAML file:
group:
option: "foo"
numbers:
- 1
- 2
- 3
limits:
max: 10
min: 1
Example Go config:
zfg.Str("group.option", "", "hierarchical usage")
zfg.Ints("numbers", nil, "slice of server configs")
zfg.Map("limits", nil, "map of limits")
Advanced Usage
Value Representation
Custom Options
You can define your own option types by implementing the Value
interface and registering them via Any
function.
Methods Set
and String
should be compatible.
// Custom type
type MyType struct{ V string }
func newValue(val MyType, p *MyType) zfg.Value {
*p = val
return p
}
func (m *MyType) Set(s string) error { m.V = s; return nil }
func (m *MyType) Type() string { return "custom" }
func (m *MyType) String() string { return m.V }
func Custom(name string, defVal MyType, desc string, opts ...zfg.OptNode) *MyType {
return zfg.Any(name, defVal, desc, newValue, opts...)
}
// Register custom option
var myOpt = Custom("custom.opt", MyType{"default"}, "custom option")
Custom Providers
You can add your own configuration sources by implementing the Provider
interface.
type MyProvider struct{}
func (p *MyProvider) Type() string { return "my" }
func (p *MyProvider) Provide(awaited map[string]bool, conv func(any) string) (map[string]string, map[string]string, error) {
found := map[string]string{}
unknown := map[string]string{}
// ... fill found/unknown based on awaited ...
return found, unknown, nil
}
// Usage
zfg.Parse(&MyProvider{})
Documentation
For detailed documentation and advanced usage examples, visit our Godoc page.
Star History
License
This project is licensed under the MIT License - see the LICENSE file for details.