No description
  • Go 97.2%
  • Shell 2.8%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Merith-TK bd5a208c98 license: add MPL-2.0 to config library
- Add LICENSE file with full MPL-2.0 text
- Add MPL-2.0 header to all 18 .go files
- Add pre-commit hook (hooks/pre-commit) to enforce header checks
- Add setup script (hooks/setup.sh) to install the hook
- Fix .gitignore to not exclude cmd/ source directory
2026-08-10 08:38:05 -07:00
.opencode chore: update agent harness configs 2026-08-09 19:37:55 -07:00
cmd/res-validate-config license: add MPL-2.0 to config library 2026-08-10 08:38:05 -07:00
core license: add MPL-2.0 to config library 2026-08-10 08:38:05 -07:00
docs restructure: move docs to docs/, consolidate source files 2026-08-09 17:21:39 -07:00
hooks license: add MPL-2.0 to config library 2026-08-10 08:38:05 -07:00
.gitignore license: add MPL-2.0 to config library 2026-08-10 08:38:05 -07:00
app.go license: add MPL-2.0 to config library 2026-08-10 08:38:05 -07:00
config.go license: add MPL-2.0 to config library 2026-08-10 08:38:05 -07:00
go.mod feat: scaffold config library with path helpers and template rendering 2026-08-09 14:13:55 -07:00
go.sum feat: scaffold config library with path helpers and template rendering 2026-08-09 14:13:55 -07:00
LICENSE license: add MPL-2.0 to config library 2026-08-10 08:38:05 -07:00
load_test.go license: add MPL-2.0 to config library 2026-08-10 08:38:05 -07:00
merge_test.go license: add MPL-2.0 to config library 2026-08-10 08:38:05 -07:00
README.md restructure: split into core/ sub-package with stub passthrough 2026-08-09 17:31:46 -07:00
stubs.go license: add MPL-2.0 to config library 2026-08-10 08:38:05 -07:00
write_test.go license: add MPL-2.0 to config library 2026-08-10 08:38:05 -07:00

config

A lightweight, dependency-light configuration library for Go applications.

Structure

config/
├── config.go      package doc
├── app.go         App struct (re-exports from core)
├── stubs.go       passthrough functions to core/
├── core/          implementation (load, merge, write, env, codec, tag, wildcard)
├── cmd/           CLI tools
├── docs/          documentation
└── README.md

Import git.merith.xyz/residual/config for the easy API. The core/ package has the full implementation.

Features

  • Instance-based app configuration — no global state, no hardcoded paths
  • Cascading merge — defaults → system → user → env vars
  • Per-field merge rules — cascade or overwrite, with wildcard support
  • Env var overrides — double-underscore separation (PREFIX__KEY__SUBKEY)
  • Struct tagsconf, default, optional, envsep, raw
  • TOML support — YAML/JSON not yet implemented

Quick Start

package main

import (
    "fmt"
    "git.merith.xyz/residual/config"
)

type AppConfig struct {
    Port  int    `conf:"port" default:"8080"`
    Host  string `conf:"host" default:"localhost"`
    Debug bool   `conf:"debug"`
}

func main() {
    // Create app instance
    app := config.New("myapp", "residual")

    // Load each layer
    cfg := config.Defaults[AppConfig]()
    sysCfg, _ := config.System[AppConfig](app)
    usrCfg, _ := config.User[AppConfig](app)
    cfg = config.UseConfig(cfg, sysCfg)
    cfg = config.UseConfig(cfg, usrCfg)

    fmt.Printf("Port: %d, Host: %s\n", cfg.Port, cfg.Host)
}

App Configuration

// Default directories
app := config.New("myapp", "residual")
// SystemPath: /etc/residual/myapp.toml
// UserPath:   ~/.config/residual/myapp.toml

// Custom directories
app := config.NewWithOptions("myapp", "residual", "/custom/etc", "/custom/config")
// SystemPath: /custom/etc/residual/myapp.toml
// UserPath:   /custom/config/residual/myapp.toml

// Without group
app := config.New("myapp", "")
// SystemPath: /etc/myapp.toml
// UserPath:   ~/.config/myapp.toml

Load Functions

// Tag defaults only (no files, no env)
cfg := app.Defaults[AppConfig]()

// From system config directory
cfg, err := app.System[AppConfig]()

// From user config directory
cfg, err := app.User[AppConfig]()

// From explicit file path
cfg, err := config.ReadFile[AppConfig]("/path/to/config.toml")

// From environment variables (double-underscore separation)
cfg := config.Env[AppConfig]("MYAPP")
// MYAPP__PORT=3000
// MYAPP__HOST=new.host

Merge Functions

// Cascade: zero fields in overlay are transparent — base values win
result := config.UseConfig(base, overlay)

// Overwrite: all fields in overlay win, even if zero
result := config.Overwrite(base, overlay)

// With per-field rules from ResidualConfig
result := config.MergeWithRules(base, overlay, rules)

Struct Tags

Tag Purpose Example
conf:"name" Key name in config file / env path segment conf:"port"
default:"value" Static default applied before any file or env default:"8080"
optional:"true" Field may be absent everywhere without error optional:"true"
envsep:"X" Override env slice separator (default \n) envsep:","
raw:"-" Exclude from raw parsing (internal use) raw:"-"

Env Var Format

Double underscores separate path segments:

MYAPP__PORT=3000
MYAPP__HOST=new.host
MYAPP__DATABASE__HOST=db.example.com
MYAPP__DATABASE__PORT=5432

Per-Field Merge Rules

Users can define merge rules in the config file's reserved residualconfig section:

[residualconfig.loadoverrides]
cascade = ["port"]        # cascade: zero in higher-priority → this wins
overwrite = ["host"]      # overwrite: always wins, even if zero

port = 9090
host = "example.com"

Wildcard Support

[residualconfig.loadoverrides]
cascade = ["*"]           # blanket: everything uses cascade
overwrite = ["host"]      # except host always overwrites

# OR per-section:
cascade = ["database.*"]  # all database.* fields use cascade
Pattern Matches Doesn't Match
* everything
host host database.host
database.* database.host, database.port database.host.timeout
database.*.timeout database.pool.timeout database.host

Write Functions

// Write to user config path
err := config.WriteUser(app, cfg)

// Write to system config path (requires root)
err := config.WriteSystem(app, cfg)

// Write to explicit path
err := config.WriteFile(cfg, "/path/to/config.toml")

Load Cascade

struct zero value
    ↓ tag defaults (default:"value")
    ↓ system config file (System function)
    ↓ user config file (User function)
    ↓ env vars (Env function — always overwrite)

Validation

GOWORK=off go build ./...
GOWORK=off go test -count=1 ./...
GOWORK=off go vet ./...
GOWORK=off go doc .

Documentation

Additional docs in docs/:

  • docs/AUDIT.md — audit findings and resolution status
  • docs/handoff.md — original design handoff
  • docs/residual-overview.md — residual conventions reference