Documentation ¶
Overview ¶
Example ¶
package main import ( "embed" "fmt" "log" "snai.pe/boa" ) // This embed declaration embeds the example_defaults.toml file (relative to // the package directory) into the filesystem at `defaults`. //go:embed example_defaults.* var defaults embed.FS var config struct { Greeting string `help:"A nice hello."` } func main() { // Register the default files boa.SetDefaults(defaults) // Opens and loads, in order, the example_defaults.toml files from the // following paths: // // - <defaults>/example_defaults.toml // - /etc/example_defaults.toml // - ~/.config/example_defaults.toml // cfg := boa.Open("example_defaults.toml") defer cfg.Close() // Load the defaults into the config variable if err := boa.NewDecoder(cfg).Decode(&config); err != nil { log.Fatalln(err) } fmt.Println(config.Greeting) }
Output: Hello from TOML!
Index ¶
- Variables
- func ConfigHome() (string, error)
- func ConfigHomeFS() fs.FS
- func ConfigPaths() []fs.FS
- func Load(name string, v interface{}) error
- func NewSingleFileFS(name, path string) fs.FS
- func Save(name string, v interface{}) error
- func SetConfigHomeFS(f fs.FS)
- func SetDefaults(defaults fs.FS)
- func SetOptions(options ...interface{})
- func SystemConfigDirs() []string
- type CommonOption
- type Decoder
- type DecoderOption
- type Encoder
- type EncoderOption
- type FileSet
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var Decoders = map[string]func(io.Reader) encoding.Decoder{ ".toml": toml.NewDecoder, ".json5": json5.NewDecoder, ".json": json5.NewDecoder, }
Decoders map filename extensions to decoders. By default, the following coniguration languages are associated with the extensions:
- JSON5: .json and .json5
- TOML: .toml
The contents of this map controls how the Decoder type deduces which decoder to use based on the file extension of the input.
var Encoders = map[string]func(io.Writer) encoding.Encoder{ ".toml": toml.NewEncoder, ".json5": json5.NewEncoder, ".json": json5.NewEncoder, }
Encoders map filename extensions to encoders. By default, the following coniguration languages are associated with the extensions:
- JSON5: .json and .json5
- TOML: .toml
The contents of this map controls how the Encoder type deduces which encoder to use based on the file extension of the output.
Functions ¶
func ConfigHome ¶
ConfigHome returns the filesystem path to the current user's configuration home, or an error if there is none.
This function should be used to determine where to save configuration. If it returns an error, no configuration should be saved.
The configuration home of a user is a os-dependent directory that is writeable by that user, and contains configuration files for the programs used by that user.
The returned path is generally OS-specific. Typical values per OS are:
- Linux & UNIX derivatives: ~/.config ($XDG_CONFIG_HOME)
- macOS: ~/Library/Preferences
- Windows: C:\Users\<user>\AppData\Roaming (%APPDATA%)
func ConfigHomeFS ¶
ConfigHomeFS returns the fs.FS for the user's configuration home.
By default, it returns os.DirFS(ConfigHome()) (or nil if unsuccessful), unless SetConfigHomeFS has been called, in which case the FS that was set by the function is returned.
func ConfigPaths ¶
ConfigPaths returns, in order of least important to most important, the paths that may hold configuration files for the current user.
This function should be used to determine from where to load configuration. Every matching configuration file in the paths should be loaded into the same configuration object, from least important to most important, in order to construct the full configuration.
User directories are always more important than system directories. Typical values per OS are:
- Linux & UNIX derivatives: /etc, /etc/xdg, ~/.config ($XDG_CONFIG_DIRS & $XDG_CONFIG_HOME)
- macOS: /Library/Preferences, ~/Library/Preferences
- Windows: C:\ProgramData, C:\Users\<user>\AppData\Roaming
func Load ¶
Load loads the configuration files for the specified name into the specified value pointed to by v.
The configuration language is deduced based on the file extension of the specified path:
- JSON5: .json and .json5
- TOML: .toml
This is a convenience function that is functionally equivalent to:
Custom file extensions are not supported, and one of the decoders in snai.pe/boa/encoding must be used instead.
func NewSingleFileFS ¶
NewSingleFileFS returns an io/fs.FS containing the file at path, under the specified name.
For instance, NewSingleFileFS("program.toml", os.Discard) will return an FS object such that opening the program.toml file opens the os.Discard file instead.
This is particularly useful to pass single file config overrides to Open and OpenMultiple.
If path is the empty string, NewSingleFileFS returns nil.
func Save ¶
Save saves the specified value in v into a named configuration file.
The name is interpreted relative to the return value of ConfigHome(). To save to arbitrary file paths, use os.Create and NewEncoder instead.
The configuration language is deduced based on the file extension of the specified path:
- JSON5: .json and .json5
- TOML: .toml
Custom file extensions are not supported, and one of the decoders in snai.pe/boa/encoding must be used instead.
func SetConfigHomeFS ¶
SetConfigHomeFS overrides the user configuration home, which gets added as the most important path in the slice returned by ConfigPaths.
Example ¶
var config struct { Greeting string `help:"A nice hello."` } // Use NewSingleFileFS to expose example_defaults.toml as program.toml. // // This could be used to override the default config paths via the // environment or a flag SetConfigHomeFS( NewSingleFileFS("program.toml", "example_defaults.toml"), ) cfg := Open("program.toml") defer cfg.Close() // Load the defaults into the config variable if err := NewDecoder(cfg).Decode(&config); err != nil { log.Fatalln(err) } fmt.Println(config.Greeting)
Output: Hello from TOML!
func SetDefaults ¶
SetDefaults sets the FS object containing configuration file defaults.
It is added as the least important path in the slice returned by ConfigPaths.
func SetOptions ¶
func SetOptions(options ...interface{})
SetOptions sets the default set of common, encoder-specific, and decoder-specific options for the functions in the boa package.
Note that it does not change the defaults of specific encoding packages.
Example ¶
package main import ( "io/ioutil" "log" "os" "snai.pe/boa" ) func main() { type Config struct { FirstName string LastName string Nickname string } config := Config{ FirstName: "Franklin", LastName: "Mathieu", Nickname: "Snaipe", } boa.SetOptions( boa.Indent("\t"), boa.NamingConvention("kebab-case"), ) f, err := os.Create("testdata/example_opts.json5") if err != nil { log.Fatalln(err) } defer f.Close() if err := boa.NewEncoder(f).Encode(config); err != nil { log.Fatalln(err) } out, err := ioutil.ReadFile("testdata/example_opts.json5") if err != nil { log.Fatalln(err) } os.Stdout.Write(out) }
Output: { "first-name": "Franklin", "last-name": "Mathieu", nickname: "Snaipe", }
func SystemConfigDirs ¶
func SystemConfigDirs() []string
SystemConfigDirs returns the filesystem paths to the system configuration directories.
The returned paths are OS-specific. Typical valies per OS are:
- Linux & UNIX derivatives: /etc, /etc/xdg ($XDG_CONFIG_DIRS)
- macOS: /Library/Preferences
- Windows: C:\ProgramData
Types ¶
type CommonOption ¶
type CommonOption = encoding.CommonOption
CommonOption represents an option common to all encoders and decoders in boa.
func NamingConvention ¶
func NamingConvention(name interface{}) CommonOption
NamingConvention returns an option that sets the default naming convention of an encoder or decoder to the specified convention.
This option takes either a known convention name, or a naming convention value (as defined in the snai.pe/boa/encoding package)
Supported naming conventions names are:
- "camelCase"
- "PascalCase"
- "snake_case"
- "SCREAMING_SNAKE_CASE"
- "kebab-case"
- "SCREAMING-KEBAB-CASE"
- "camel_Snake_Case"
- "Pascal_Snake_Case"
- "Train-Case"
- "flatcase"
- "UPPERFLATCASE"
type Decoder ¶
type Decoder struct {
// contains filtered or unexported fields
}
A Decoder reads and decodes a configuration from an input file.
Example ¶
package main import ( "fmt" "log" "os" "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"` } f, err := os.Open("testdata/example.json5") if err != nil { log.Fatalln(err) } defer f.Close() if err := boa.NewDecoder(f).Decode(&config); err != nil { log.Fatalln(err) } fmt.Println("answer:", config.Answer) fmt.Println("primes:", config.Primes) fmt.Println("contacts:", config.Contacts) }
Output: answer: 42 primes: [2 3 5 7 11] contacts: map[alice:alice@example.com bob:bob@example.com snaipe:me@snai.pe]
func NewDecoder ¶
func NewDecoder(in encoding.StatableReader) *Decoder
NewDecoder returns a new Decoder that reads from `in`.
The configuration language of the input file is deduced based on the file extension of its file path. The file-extension-to-decoder mapping is defined by the contents of the Decoders map in this package.
To only use one specific configuration language, do not use this decoder: Use instead the decoder for the chosen language in snai.pe/boa/encoding.
type DecoderOption ¶
type DecoderOption = encoding.DecoderOption
DecoderOption represents an option common to all decoders in boa.
func AutomaticEnv ¶
func AutomaticEnv(prefix string) DecoderOption
AutomaticEnv enables the automatic population of config values from the environment. An optional prefix may be specified for the environment variable names.
Example ¶
package main import ( "fmt" "log" "os" "strings" "snai.pe/boa" ) func main() { type Config struct { // Explicitly set by PATH variable. Does not need AutomaticEnv. Path []string `env:"PATH"` // Implicitly defined by BOA_SHELL due to AutomaticEnv("BOA") option. Shell string // Maps do not get implicitly populated from the environment -- however // existing values can still be overriden by environment variables if // the AutomaticEnv option is used. // // For instance, setting BOA_CONTACTS_NOT_MENTIONED will not create // an entry named "NOT_MENTIONED" in the Contacts field (nor will it // create a map named "NOT" under it with a "MENTIONED" entry, or // try to divine the user's intentions on the naming convention to // use with any of these). However, setting BOA_CONTACTS_ALICE to // alice@acme.org will successfully replace the existing contact in // the example config for alice to alice@acme.org. Contacts map[string]string } environ := []string{ "PATH=" + strings.Join([]string{"/bin", "/usr/bin", "/sbin", "/usr/sbin"}, string(os.PathListSeparator)), "BOA_SHELL=/bin/sh", "BOA_CONTACTS_ALICE=alice@acme.org", "BOA_CONTACTS_BOB=bob@acme.org", } boa.SetOptions( boa.AutomaticEnv("BOA"), boa.Environ(environ), ) var config Config f, err := os.Open("testdata/example.json5") if err != nil { log.Fatalln(err) } defer f.Close() if err := boa.NewDecoder(f).Decode(&config); err != nil { log.Fatalln(err) } fmt.Println("Path:", config.Path) fmt.Println("Shell:", config.Shell) fmt.Println("Contacts:", config.Contacts) }
Output: Path: [/bin /usr/bin /sbin /usr/sbin] Shell: /bin/sh Contacts: map[alice:alice@acme.org bob:bob@acme.org snaipe:me@snai.pe]
func Environ ¶
func Environ(env []string) DecoderOption
Environ sets the environment variables that will be used for any substitution, either by fields marked with an `env` tag, or fields implicitly matching variables via AutomaticEnv.
Incompatible with EnvironFunc. Setting Environ after EnvironFunc overrides the lookup function that EnvironFunc previously set.
func EnvironFunc ¶
func EnvironFunc(fn func(string) (string, bool)) DecoderOption
EnvironFunc sets the lookup function for environment variables. By default, os.LookupEnv is used.
Incompatible with EnvironFunc. Setting EnvironFunc after Environ overrides the variables that Environ previously set.
type Encoder ¶
type Encoder struct {
// contains filtered or unexported fields
}
An Encoder encodes and writes a configuration into an output file.
Example (Json5) ¶
package main import ( "io/ioutil" "log" "os" "snai.pe/boa" ) func main() { type Person struct { Name string Email string } type Database struct { Server string `help:"Database endpoint; can be one of: * IPv4 * IPv6 * DNS host name."` Ports []uint16 `help:"Database ports, in the range [1, 65535)."` ConnectionMax int `help:"Maximum number of connections."` Enabled bool } type Server struct { IP string DC string } type Config struct { Title string Owner Person Database Database Servers map[string]Server `help:"Set of servers. Each server has a name, an IP, and a datacenter name."` // This field is ignored Ignored string `-` ForeignKeys struct { SomeInt int } `naming:"kebab-case" help:"This map has a different naming convention"` } config := Config{ Title: "JSON5 Example", Owner: Person{ Name: "Snaipe", Email: "me@snai.pe", }, Database: Database{ Server: "192.168.1.1", Ports: []uint16{8001, 8001, 8002}, ConnectionMax: 5000, Enabled: true, }, Servers: map[string]Server{ "alpha": Server{ IP: "10.0.0.1", DC: "eqdc10", }, "beta": Server{ IP: "10.0.0.2", DC: "eqdc10", }, }, Ignored: "this field is ignored", } f, err := os.Create("testdata/example_save.json5") if err != nil { log.Fatalln(err) } defer f.Close() if err := boa.NewEncoder(f).Encode(config); err != nil { log.Fatalln(err) } out, err := ioutil.ReadFile("testdata/example_save.json5") if err != nil { log.Fatalln(err) } os.Stdout.Write(out) }
Output: { title: "JSON5 Example", owner: { name: "Snaipe", email: "me@snai.pe", }, database: { // Database endpoint; can be one of: // * IPv4 // * IPv6 // * DNS host name. server: "192.168.1.1", // Database ports, in the range [1, 65535). ports: [ 8001, 8001, 8002, ], // Maximum number of connections. connectionMax: 5000, enabled: true, }, // Set of servers. Each server has a name, an IP, and a datacenter name. servers: { alpha: { ip: "10.0.0.1", dc: "eqdc10", }, beta: { ip: "10.0.0.2", dc: "eqdc10", }, }, // This map has a different naming convention "foreign-keys": { "some-int": 0, }, }
Example (Toml) ¶
package main import ( "io/ioutil" "log" "os" "time" "snai.pe/boa" ) func main() { type Person struct { Name string DOB time.Time } type Database struct { Server string `help:"Database endpoint; can be one of: * IPv4 * IPv6 * DNS host name."` Ports []uint16 `help:"Database ports, in the range [1, 65535)."` ConnectionMax int `help:"Maximum number of connections."` Enabled bool } type Server struct { IP string DC string } type Config struct { Title string Owner Person Database Database Servers map[string]Server `help:"Set of servers. Each server has a name, an IP, and a datacenter name."` // This field is ignored Ignored string `-` ForeignKeys struct { SomeInt int } `naming:"kebab-case" help:"This table has a different naming convention"` } config := Config{ Title: "TOML Example", Owner: Person{ Name: "Snaipe", DOB: time.Date(1979, 05, 27, 07, 32, 00, 0, time.FixedZone("", -8*60*60)), }, Database: Database{ Server: "192.168.1.1", Ports: []uint16{8001, 8001, 8002}, ConnectionMax: 5000, Enabled: true, }, Servers: map[string]Server{ "alpha": Server{ IP: "10.0.0.1", DC: "eqdc10", }, "beta": Server{ IP: "10.0.0.2", DC: "eqdc10", }, }, } f, err := os.Create("testdata/example_save.toml") if err != nil { log.Fatalln(err) } defer f.Close() if err := boa.NewEncoder(f).Encode(config); err != nil { log.Fatalln(err) } out, err := ioutil.ReadFile("testdata/example_save.toml") if err != nil { log.Fatalln(err) } os.Stdout.Write(out) }
Output: title = "TOML Example" [owner] name = "Snaipe" dob = 1979-05-27T07:32:00-08:00 [database] # Database endpoint; can be one of: # * IPv4 # * IPv6 # * DNS host name. server = "192.168.1.1" # Database ports, in the range [1, 65535). ports = [ 8001, 8001, 8002, ] # Maximum number of connections. connection_max = 5000 enabled = true # Set of servers. Each server has a name, an IP, and a datacenter name. [servers] [servers.alpha] ip = "10.0.0.1" dc = "eqdc10" [servers.beta] ip = "10.0.0.2" dc = "eqdc10" # This table has a different naming convention [foreign-keys] some-int = 0
func NewEncoder ¶
func NewEncoder(out encoding.StatableWriter) *Encoder
NewEncoder returns a new encoder that writes into `out`.
The configuration language of the input file is deduced based on the file extension of its file path. The file-extension-to-decoder mapping is defined by the contents of the Decoders map in this package.
To only use one specific configuration language, do not use this encoder: Use instead the encoder for the chosen language in snai.pe/boa/encoding.
type EncoderOption ¶
type EncoderOption = encoding.EncoderOption
EncoderOption represents an option common to all encoders in boa.
func Indent ¶
func Indent(indent string) EncoderOption
Indent returns an encoder option that sets the indentation to the specified whitespace string.
func LineBreak ¶
func LineBreak(lb string) EncoderOption
LineBreak sets the line break sequence that must be used when encoding documents.
Can either be "\n" or "\r\n".
type FileSet ¶
type FileSet struct {
// contains filtered or unexported fields
}
FileSet represents the combined set of configuration files with a specific name, that are contained within any of the directories in a search path.
func Open ¶
Open opens a set of configuration files by name.
The files are searched by matching the stem (i.e. the filename without extension) of the files in the provided search paths to the specified name. If no path is provided, the result of ConfigPaths() is used.
The provided name can be a filename with an extension, or without an extension. It can also include one or more directory components, but must not be an absolute filesystem path; use os.Open instead to load configuration from specific filesystem paths.
Names with an extension will restrict the search to the files matching the specified extension. Conversely, names without an extension will match all files whose stem is the last path component of the filename.
func OpenMultiple ¶
OpenMultiple opens a set of configuration files with one or more names.
The order of the `names` slice defines a precedence of files across all search paths. With a single `name`, the behaviour is identical to `Open`.
The iteration order is described in detail in `Next()`.
func (*FileSet) Next ¶
Next closes the current configuration file if any is opened, and opens the first file in the next path that matches the set of allowed file extensions.
All subsequent calls to Read, Stat, and Close will then apply on the newly opened configuration file.
If all of the search paths have been exhausted, os.ErrNotExist is returned.
When there are multiple names (see OpenMultiple), they will be returned in order first by name and then by path. For instance, if the names slice is "base", "override" and the paths slice is "/etc", "/root/.config" we'll use the ordering:
- /etc/base.<ext>
- /root/.config/base.<ext>
- /etc/override.<ext>
- /root/.config/override.<ext>
This ordering of the names slice ensures that later entries take precedence over earlier ones, regardless of which directory they're in.
The files are matched in the order of the specified exts slice rather than directory (or lexical) order. For instance, if the extension slice is ".json5", ".json" on a path containing <name>.json5 and <name>.json will open <name>.json5 if it exists, or then <name>.json.
func (*FileSet) Read ¶
Read reads the contents of the currently opened configuration file into the data slice.
func (*FileSet) Skip ¶
func (cfg *FileSet) Skip()
Skip skips the current search path. This function is particularly useful when all of the matching configurations cannot be opened for any reason, and the application would rather warn but continue parsing configurations in the rest of the paths.