A friendlier viper.
go get snai.pe/boa
Boa aims to be a simple, human-focused, no-dependency configuration management library.
It supports JSON5 and TOML.
Configurations are expressed as Go types, using struct field tags to map configuration files to type-safe data types.
For instance, the following TOML configuration file:
[person]
first-name = "John"
last-name = "Doe"
dob = 1953-02-25T00:00:42-08:00
Can be loaded into this Go type:
type Config struct {
Person struct {
FirstName string
LastName string
Birth time.Time `name:"dob"`
} `naming:"kebab-case"`
}
At the time of writing, none of the other configuration parsers are actually designed
for configuration. Most of the standard parsers under the encoding
package are
designed for robots. Non-standard parsers either do not follow the same semantics
as the standard packages, or suffer the same flaws as standard parsers. Comments
and formatting are usually not parsed nor preserved. Encoders interact poorly
with other encoders, usually necessitating multiple struct tags per configuration
language, or do not provide a good interface for accessing and discovering
configuration values.
Boa aims to be an overall better configuration management library. It has no dependencies outside of the standard Go library, and provides a unified way to load, manipulate, and save configurations.
The following languages are supported:
- JSON5
- TOML
In addition, all configuration parsers have the following properties:
- Error messages contain the filename when available as well as the line and column number where the error occured.
- Parsers support the same set of base struct tags for consistency and conciseness.
- Comments and whitespace are preserved by the parsers in the configuration AST, which makes it possible to edit configuration while still preserving the style of the original file.
Tag | Description |
---|---|
name:"<name>" |
Set key name. |
help:"<help>" |
Set documentation; appears as comment in the config. |
naming:"<name>" |
Set naming convention for key and subkeys. |
env:"<var>" |
Populate field with specified environment variable. |
inline |
Inline field. All sub-fields will be treated as if they were in the containing struct itself. Does the same as embedding the field. |
- |
Ignore field. |
Any type that implements encoding.TextMarshaler
can be saved as a string.
Any type that implements encoding.TextUnmarshaler
can be loaded from a string.
In addition, the following standard library types are marshaled and unmarshaled as the appropriate type:
Type | Treated as |
---|---|
[]byte |
String |
*big.Int |
Number |
*big.Float |
Number |
*big.Rat |
Number |
time.Time |
String |
*url.URL |
String |
*regexp.Regexp |
String |
Some packages also define or support some specialized types for specific configuration objects:
Type | Treated as | Packages (under snai.pe/boa/encoding ) |
---|---|---|
time.Time |
DateTime | toml |
toml.LocalDateTime |
DateTime | toml |
toml.LocalDate |
DateTime | toml |
toml.LocalTime |
DateTime | toml |
package main
import (
"fmt"
"log"
"snai.pe/boa"
)
func main() {
var config struct {
Answer int `help:"This is an important field that needs to be 42"`
Primes []int `help:"Some prime numbers"`
Contacts map[string]string `help:"Some people in my contact list"`
}
// Will load any matching "appname.toml" config file from the system config path,
// then the user config path. The TOML decoder is inferred from the .toml extension.
//
// For instance, on Linux, this will load in order:
// - /etc/<appname>.toml
// - /etc/xdg/<appname>.toml
// - ~/.config/<appname>.toml
//
if err := boa.Load("appname", &config); err != nil {
log.Fatalln(err)
}
}
Configuration defaults are not, by design, set via struct tags or other field-specific mechanisms.
Instead, write a default configuration file in your package, and embed it. Multiple configs defaults can be embedded into the same embed.FS declaration -- see the documentation of the embed package.
package main
import (
"embed"
"fmt"
"log"
"snai.pe/boa"
)
//go:embed appname.toml
var defaults embed.FS
func main() {
// Register defaults
boa.SetDefaults(defaults)
var config struct {
Answer int `help:"This is an important field that needs to be 42"`
Primes []int `help:"Some prime numbers"`
Contacts map[string]string `help:"Some people in my contact list"`
}
if err := boa.Load("appname", &config); err != nil {
log.Fatalln(err)
}
}
Good configuration defaults should be consistent and self-explanatory. Consider making the default for fields their respective type's zero value.
Configuration fields can be explicitly bound to environment variables via the env
struct tag:
type Config struct {
Shell string `env:"SHELL"`
Path []string `env:"PATH"`
}
Environment values are generally parsed according to the strconv Parse functions, or using UnmarshalText if the field's type implements encoding.TextUnmarshaler.
Slices and arrays are parsed as a path-list-separated list of strings. The delimiter
is os.PathListSeparator: with the above example, on Unix derivatives, PATH=a:b:c
would
get unmarshaled as [a, b, c], while on Windows the value would need to be PATH=a;b;c
.
Fields with no env
tag are not populated from the environment, unless the AutomaticEnv
option is provided:
type Config struct {
ImplicitVariable string
}
boa.SetOptions(
boa.AutomaticEnv("PREFIX"),
)
In this example, PREFIX_IMPLICIT_VARIABLE=value
would set Config.ImplicitVariable
.
Logo made by Irina Mir