- Odin 100%
| lobster | ||
| tests | ||
| readme.md | ||
| spec.md | ||
Lobster
A configuration format and parser for Odin. Hierarchical, INI-inspired, boring on purpose.
[server]
host = "localhost"
port = 8080
[tls]
enabled = true
cert = "/etc/ssl/cert.pem"
[logging]
level = "info"
Lobster exists because flat INI breaks down as configs grow, YAML is a footgun factory, JSON hates comments, and TOML's dotted keys hide structure inside strings. Lobster makes the hierarchy visible.
Format
Sections nest via indentation. Tabs are canonical; spaces (consistently sized) are accepted.
[database]
name = "myapp"
[primary]
host = "db1.internal"
port = 5432
[replica]
host = "db2.internal"
port = 5432
Values are typed: strings ("hello"), integers (42), floats (3.14), booleans (true / false), and null.
Comments use // for line comments and /* */ for blocks.
Repeated sections with the same name become arrays:
[route]
path = "/api"
handler = "api_handler"
[route]
path = "/health"
handler = "health_check"
Multiline strings are supported. The closing quote's indentation sets the baseline:
[motd]
text = "
Welcome to the server.
Please be nice.
"
Includes pull in other files, with later values overriding earlier ones:
include "defaults.lob"
[server]
port = 9000
See spec.md for the full specification.
Usage
import lob "lobster"
main :: proc() {
doc, err := lob.parse_file("config.lob")
if err.kind != .None {
fmt.eprintln(lob.format_error(err))
lob.destroy_error(err)
return
}
defer lob.destroy(doc)
host, _ := lob.get_string(doc, "server.host")
port, _ := lob.get_int(doc, "server.port")
tls, _ := lob.get_bool(doc, "server.tls.enabled")
fmt.printf("Listening on %s:%d (tls: %v)\n", host, port, tls)
}
Querying
Dotted paths navigate the section hierarchy:
// Typed getters
name, ok := lob.get_string(doc, "database.name")
port, ok := lob.get_int(doc, "database.primary.port")
flag, ok := lob.get_bool(doc, "server.tls.enabled")
val, ok := lob.get_float(doc, "tuning.ratio")
// Existence check
if lob.has(doc, "server.tls.enabled") { ... }
// Generic value
v, ok := lob.get(doc, "some.key")
// Repeated sections as arrays
replicas := lob.get_array(doc, "database.replica")
defer delete(replicas)
Formatting
Re-emit a document in canonical form (tabs, normalized spacing, comments stripped):
output := lob.format(doc)
defer delete(output)
Parsing from a string
doc, err := lob.parse(source, "config.lob", file_reader)
The file_reader callback is only needed if your config uses include directives.
Errors
Errors carry source location and produce readable messages:
config.lob:5:2: mixed tabs and spaces are not allowed
config.lob:12:3: duplicate key "port" in section "server"
Errors with allocated = true must be cleaned up with lob.destroy_error(err).
License
See LICENSE.