sconfig

package module
v1.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 13, 2020 License: MIT Imports: 11 Imported by: 2

README

This project is considered stable GoDoc Build Status codecov

sconfig is a simple and functional configuration file parser for Go.

Import as arp242.net/sconfig.

Go 1.5 and newer should work, but the test suite only runs with 1.7 and newer.

What does it look like?

A file like this:

# This is a comment

port 8080 # This is also a comment

# Look ma, no quotes!
base-url http://example.com

# We'll parse these in a []*regexp.Regexp
match ^foo.+
match ^b[ao]r

# Two values
order allow deny

host  # Idented lines are collapsed
    arp242.net         # My website
    goatcounter.com    # My other website

address arp242.net

Can be parsed with:

package main

import (
    "fmt"
    "os"

    "arp242.net/sconfig"

    // Types that need imports are in handlers/pkgname
    _ "arp242.net/sconfig/handlers/regexp"
)

type Config struct {
    Port    int64
    BaseURL string
    Match   []*regexp.Regexp
    Order   []string
    Hosts   []string
    Address string
}

func main() {
    config := Config{}
    err := sconfig.Parse(&config, "config", sconfig.Handlers{
        // Custom handler
        "address": func(line []string) error {
            addr, err := net.LookupHost(line[0])
            if err != nil {
                return err
            }

            config.Address = addr[0]
            return nil
        },
    })
    if err != nil {
        fmt.Fprintf(os.Stderr, "Error parsing config: %v", err)
        os.Exit(1)
    }

    fmt.Printf("%#v\n", config)
}

Will result in:

example.Config{
    Port:    8080,
    BaseURL: "http://example.com",
    Match:   []*regexp.Regexp{[..], [..]},
    Order:   []string{"allow", "deny"},
    Hosts:   []string{"arp242.net", "goatcounter.com"},
    Address: "arp242.net",
}

But why not...

Isn't "rolling your own" a bad idea? I don't think so. It's not that hard, and the syntax is simple/intuitive enough to be grokable by most people.

How do I...

Validate fields?

Handlers can be chained. For example the default handler for int64 is:

RegisterType("int64", ValidateSingleValue(), handleInt64)

ValidateSingleValue() returns a type handler that will give an error if there isn't a single value for this key; for example this is an error:

foo 42 42

There are several others as well. See Validate*() in godoc. You can add more complex validation handlers if you want, but in general I would recommend just using plain ol' if statements.

Adding things such as tag-based validation isn't a goal at this point. I'm not at all that sure this is a common enough problem that needs solving, and there are already many other packages which do this (no need to reinvent the wheel).

My personal recommendation would be zvalidate, mostly because I wrote it ;-)

Set default values?

Set them before parsing:

c := MyConfig{Value: "The default"}
sconfig.Parse(&c, "a-file", nil)

### Override from the environment/flags/etc.?

There is no direct built-in support for that, but there is `Fields()` to list
all the field names. For example:

c := MyConfig{Foo string}
sconfig.Parse(&c, "a-file", nil)

for name, val := range sconfig.Fields(&c) {
    if flag[name] != "" {
        val.SetString(flag[name])
    }
}

Use int types? I get an error?

Only int64 and uint64 are handled by default; this should be fine for almost all use cases of this package. If you want to add any of the other (u)int types you can do easily with your own type handler.

Note that the size of int and uint are platform-dependent, so adding those may not be a good idea.

Use my own types as config fields?

You have three options:

  • Add a type handler with sconfig.RegisterType().
  • Make your type satisfy the encoding.TextUnmarshaler interface.
  • Add a Handler in sconfig.Parse().

I get a "don’t know how to set fields of the type ..." error if I try to add a new type handler

Include the package name; even if the type handler is in the same package. Do:

sconfig.RegisterType("[]main.RecordT", func(v []string) (interface{}, error) { .. }

and not:

sconfig.RegisterType("[]RecordT", func(v []string) (interface{}, error) { .. }

Replace main with the appropriate package name.

Syntax

The syntax of the file is very simple.

Definitions

  • Whitespace: any Unicode whitespace (Zs or "Separator, Space" category).
  • Hash: # (U+0023), Backslash: \ (U+005C), Space: a space (U+0020), NULL: U+0000
  • Newline: LF (U+000A) or CR+LF (U+000D, U+000A).
  • Line: Any set of characters ending with a Newline

Reading the file

  • A file must be encoded in UTF-8.

  • Everything after the first Hash is considered to be a comment and will be ignored unless a Hash is immediately preceded by a Backslash.

  • All Whitespace is collapsed to a single Space unless a Whitespace character is preceded by a Backslash.

  • Any Backslash immediately preceded by a Backslash will be treated as a single Backslash.

  • Any Backslash immediately followed by anything other than a Hash, Whitespace, or Backslash is treated as a single Backslash.

  • Anything before the first Whitespace is considered the Key.

    • Any character except Whitespace and NULL bytes are allowed in the Key.
    • The special Key source can be used to include other config files. The Value for this must be a path.
  • Anything after the first Whitespace is considered the Value.

    • Any character except NULL bytes are allowed in the Value.
    • The Value is optional.
  • All Lines that start with one or more Whitespace characters will be appended to the last Value, even if there are blank lines or comments in between. The leading whitespace will be removed.

Alternatives

Aside from those mentioned in the "But why not..." section above:

Probably others? Open an issue/PR and I'll add it.

Documentation

Overview

Package sconfig is a simple yet functional configuration file parser.

See the README.markdown for an introduction.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Fields

func Fields(config interface{}) map[string]reflect.Value

Fields gets a list of all fields in a struct. The map key is the name of the field (as it appears in the struct) and the key is the field's reflect.Value (which can be used to set a value).

This is useful if you want to batch operate on a config struct, for example to override from the environment or flags.

func FindConfig

func FindConfig(file string) string

FindConfig tries to find a configuration file at the usual locations.

The following paths are checked (in this order):

$XDG_CONFIG/<file>
$HOME/.<file>
/etc/<file>
/usr/local/etc/<file>
/usr/pkg/etc/<file>
./<file>

The default for $XDG_CONFIG is $HOME/.config if it's not set.

func MustParse

func MustParse(c interface{}, file string, handlers Handlers)

MustParse behaves like Parse(), but panics if there is an error.

func Parse

func Parse(config interface{}, file string, handlers Handlers) (returnErr error)

Parse reads the file from disk and populates the given config struct.

A line is matched with a struct field by "camelizing" the first word. For example "key-name" becomes "KeyName". You can also use the plural ("KeyNames") as the field name.

sconfig will attempt to set the field from the passed Handlers map (see below), a configured type handler, or the encoding.TextUnmarshaler interface, in that order.

The Handlers map, which may be nil, can be given to customize the behaviour for individual configuration keys. This will override the type handler (if any). The function is expected to set any settings on the struct; for example:

Parse(&config, "config", Handlers{
    "SpecialBool": func(line []string) error {
        if line[0] == "yup!" {
            config.Bool = true
        }
        return nil
     },
})

Will allow you to do:

special-bool yup!

func RegisterType

func RegisterType(typ string, fun ...TypeHandler)

RegisterType sets the type handler functions for a type. Existing handlers are always overridden (it doesn't add to the list!)

The handlers are chained; the return value is passed to the next one. The chain is stopped if one handler returns a non-nil error. This is particularly useful for validation (see ValidateSingleValue() and ValidateValueLimit() for examples).

Types

type Handler

type Handler func([]string) error

Handler functions can be used to run special code for a field. The function takes the unprocessed line split by whitespace and with the option name removed.

type Handlers

type Handlers map[string]Handler

Handlers can be used to run special code for a field. The map key is the name of the field in the struct.

type TypeHandler

type TypeHandler func([]string) (interface{}, error)

TypeHandler takes the field to set and the value to set it to. It is expected to return the value to set it to.

func ValidateNoValue

func ValidateNoValue() TypeHandler

ValidateNoValue returns a type handler that will return an error if there are any values.

func ValidateSingleValue

func ValidateSingleValue() TypeHandler

ValidateSingleValue returns a type handler that will return an error if there is more than one value or if there are no values.

func ValidateValueLimit

func ValidateValueLimit(min, max int) TypeHandler

ValidateValueLimit returns a type handler that will return an error if there either more values than max, or fewer values than min.

Directories

Path Synopsis
Package handlers contains handlers that require extra imports (either from the standard library, or third-party packages).
Package handlers contains handlers that require extra imports (either from the standard library, or third-party packages).
big
Package big contains handlers for parsing values with the math/big package.
Package big contains handlers for parsing values with the math/big package.
html/template
Package template contains handlers for parsing values with the html/template package.
Package template contains handlers for parsing values with the html/template package.
net
Package net contains handlers for parsing values with the net package.
Package net contains handlers for parsing values with the net package.
net/url
Package url contains handlers for parsing values with the net/url package.
Package url contains handlers for parsing values with the net/url package.
regexp
Package regexp contains handlers for parsing values with the regexp package.
Package regexp contains handlers for parsing values with the regexp package.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL