mjl blog
March 19th 2020

Sconf: simple configuration files

I used to write config files for my admittedly small/simple services in JSON. Mostly because they are reasonably easy to read/write as human, and everyone and their programming language can handle it.

It isn’t great though. Not new information, we know this. Still, a quick summary:

  1. Trailing commas are annoying.
  2. No comments.

Other formats exist. YAML is the rage. But simple it isn’t. Syntax-wise I always get confused with the indenting of nested lists and objects. I’m pretty sure it is more complex than JSON. I suppose someone, somewhere needs it, but I don’t. Also, I’m with Richard.

So what do you do? You write a new thing. It’s quicker than research!

The ideas of sconf:

  • Whitespace-only lines are ignored.
  • Comments are lines with first non-whitespace character is the #.
  • Nested structures require indenting, like YAML. Indenting is done with tab, the indent character.
  • No needless syntax. So no quotes around strings. Types should be known by the parser for proper interpretation.
  • Key/value “objects” are separated by a colon, like in JSON.
  • Arrays have their elements indented, starting with a dash, and one line per element.

Limitations:

  • No multiline strings. It would probably complicate the syntax too much. For now, I’m just configuring multiline strings (eg OpenSSH private keys) as multiple lines, combining them after parsing.

I have an implementation in Go, https://github.com/mjl-/sconf. It’s very easy to use. Just create a config struct with all the fields you need configured. You can make it a nested struct. Now call sconf.ParseFile on a filename, with a pointer to the config struct, and it’ll be filled.

More interesting bits about the implementation:

  • If fields are missing, you’ll get an error message. You explicitly have to mark fields optional. This regularly turns up configuration mistakes. You can mark a field optional with a tag on the struct field.
  • You can add documentation for a field in a different struct tag.
  • The Go sconf implementation provides a function Describe that generates an example config file based on your config struct. It uses the struct tags described above to add comments to your config file. Now you never have to write an example config file again! They often go out of date, and regularly have subtle typo’s that aren’t caught because of lax parsing.

Here’s an example of sconf in action, sconfex.go:

package main

import (
        "log"
        "os"

        "github.com/mjl-/sconf"
)

var config struct {
        Database struct {
                Host   string
                DBName string
                User   string
        } `sconf-doc:"Database configuration."`
        Mail struct {
                SMTP struct {
                        TLS  bool
                        Host string
                }
        } `sconf:"optional"`
}

func main() {
        switch {
        case len(os.Args) == 3 && os.Args[1] == "parse":
                err := sconf.ParseFile(os.Args[2], &config)
                if err != nil {
                        log.Fatal(err)
                }
        case len(os.Args) == 2:
                sconf.Describe(os.Stdout, &config)
        default:
                log.Fatal("bad usage")
        }
}

And running this code:

$ go run sconfex.go describe >app.conf
$ cat app.conf

# Database configuration.
Database:
    Host: 
    DBName: 
    User: 

# (optional)
Mail:
    SMTP:
        TLS: false
        Host: 
$ go run sconfex.go parse app.conf

Again, get it at https://github.com/mjl-/sconf, or skip straight to the documentation.

Comments