antconfig

package module
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Sep 17, 2025 License: MIT Imports: 9 Imported by: 0

README

AntConfig

AntConfig

Go Reference Go Report Card

AntConfig is a small, zero-dependency Go configuration library focused on simplicity, clarity, and predictable precedence. Version v0.1.0 marks the first tagged release and introduces refreshed documentation. Configuration is defined through tagged structs, which can be overridden by environment variables, a .env file, or command-line flags. Optional configuration files are supported in JSON or JSONC format only. Unlike many other configuration libraries that include support for TOML, YAML, and extensive feature sets, AntConfig is opinionated: it keeps things minimal, simple, and free of external dependencies.

Why Choose AntConfig for Go Configuration

  • Opinionated, zero-dependency design keeps binaries small and secure.
  • Works out of the box with tagged structs, env vars, JSON/JSONC files, and CLI flags.
  • Automatic config discovery and .env loading streamline deployment workflows.
  • Type-safe parsing for core Go types avoids reflection surprises at runtime.

Features

  • Zero dependencies: uses only the Go standard library.
  • JSON and JSONC: helpers to strip comments and trailing commas for JSONC.
  • Tag-based configuration: default:"…" and env:"ENV_NAME" on struct fields.
  • Nested structs supported: including pointer fields (auto-initialized when needed).
  • Type-safe env parsing: string, int/uint, bool, float64, and []int from JSON.
  • Supports .env files
  • Discovery helpers: locate config file by walking upward from CWD or executable.

Use Cases

  • Bootstrapping new Go microservices with minimal config wiring.
  • Shipping CLIs where predictable flag and env precedence is critical.
  • Migrating from heavier configuration stacks (e.g., Viper) to a lighter runtime.
  • Embedding configuration into serverless functions or containers with strict size budgets.

Status and Precedence

Current precedence when applying configuration values:

  1. Defaults from struct tags (default:"…")
  2. Configuration file (.json or .jsonc). If no path is set via SetConfigPath, AntConfig auto-discovers config.jsonc or config.json starting from the current working directory and walking upward.
  3. .env file (when SetEnvPath is used)
  4. Environment variables (env:"NAME") — override .env
  5. Command line flags (flag:"name") — highest priority

Quick Start

Install via go get (Go modules):

go get github.com/robfordww/antconfig@latest
package main

import (
    "flag"
    "fmt"
    "os"

    "github.com/robfordww/antconfig"
)

type SecretKey struct {
    Key string `default:"secretkey123" env:"SEC" flag:"sec" desc:"Secret key for encryption"`
}

type Config struct {
    Host string    `default:"localhost" env:"CONFIG_HOST"`
    Port int       `default:"8080" env:"CONFIG_PORT"`
    SC   SecretKey
}

func main() {
    fs := flag.NewFlagSet(os.Args[0], flag.ExitOnError)

    var cfg Config
    ac := antconfig.New().MustSetConfig(&cfg)
    ac.SetFlagPrefix("config-")            // optional flag prefix
    ac.MustBindConfigFlags(fs)             // register flags from struct tags

    // Optional: add your own app flags
    fs.Bool("verbose", false, "Enable verbose output")

    // Show env help after flag defaults
    fs.Usage = func() {
        fs.PrintDefaults()
        fmt.Print("\n" + ac.EnvHelpString())
    }

    _ = fs.Parse(os.Args[1:])             // parse CLI args

    // Apply: defaults -> config file -> .env -> env -> flags
    if err := ac.WriteConfigValues(); err != nil {
        fmt.Fprintln(os.Stderr, "error:", err)
        os.Exit(1)
    }

    fmt.Printf("Config: %#v\n", cfg)
}

Behavior shown above is verified in tests: defaults are set first, then env vars (if non-empty) override them. Empty env values do not override defaults.

JSONC Support

The jsonc.go helper lets you read JSONC (comments + trailing commas) and turn it into strict JSON before unmarshaling.

data, err := os.ReadFile("config_test.jsonc")
if err != nil { /* handle */ }

jsonBytes := antconfig.ToJSON(data) // or ToJSONInPlace(data)
if err := json.Unmarshal(jsonBytes, &cfg); err != nil { /* handle */ }

An example JSONC file is included at config_test.jsonc.

Config Discovery Helpers

Two helpers return a config file path by walking parent directories up to a limit (10 levels):

  • antconfig.LocateFromWorkingDir(filename)
  • antconfig.LocateFromExe(filename)

Both return the first match found or ErrConfigNotFound if not found.

API Overview (package antconfig)

  • type AntConfig (fields unexported)

    • SetEnvPath(path string) error: set .EnvPath and validate the file exists. When set, .env is loaded and variables are added to the process environment only if they are not already set. If EnvPath is not set, AntConfig auto-discovers a .env in the current working directory.
    • SetConfigPath(path string) error: set .ConfigPath and validate it exists.
    • WriteConfigValues() error: apply defaults, config file (JSON/JSONC), .env, env, then flag overrides to the config passed via SetConfig.
    • SetFlagArgs(args []string): provide explicit CLI args (defaults to os.Args[1:]).
    • SetFlagPrefix(prefix string): set optional prefix used for generated CLI flags.
    • ListFlags(cfg any) ([]FlagSpec, error): return available flags with names and types.
    • SetConfig(&cfg) error: provide the config pointer for reflection when binding flags.
    • MustSetConfig(&cfg) *AntConfig: like SetConfig but panics on error and returns the receiver for chaining.
    • BindConfigFlags(fs *flag.FlagSet) error: register flags derived from your config onto a provided FlagSet (and bind it for later reads).
  • Struct tags on cfg fields

    • default:"…": default value used when field is zero-value.
    • env:"ENV_NAME": if present and non-empty, overrides the field with a parsed value.
    • flag:"name": if present, allows --name value (or --name=value) to override the field. When SetFlagPrefix("config-") is set, use --config-name instead.
    • desc:"…": optional description used as usage text when registering flags via BindConfigFlags and shown in env help.

Dynamic Flag Usage

You can build CLI usage dynamically from your config struct. For example:

var cfg AppConfig
ant := antconfig.New().MustSetConfig(&cfg)
ant.SetFlagPrefix("config-") // optional
flags, _ := ant.ListFlags(&cfg)
fmt.Println("Config flags:")
for _, f := range flags {
    fmt.Printf("  --%s  (%s)\n", f.CLI, f.Kind)
}

// Populate a FlagSet and parse
fs := flag.NewFlagSet("myapp", flag.ExitOnError)
if err := ant.BindConfigFlags(fs); err != nil { panic(err) }
_ = fs.Parse(os.Args[1:])
// Apply: defaults -> config file -> .env -> env -> flags (from FlagSet)
if err := ant.WriteConfigValues(); err != nil { panic(err) }

Notes

  • Nested structs and pointers to structs are traversed and initialized as needed.
  • Empty env values do not override defaults.
  • Not supported yet: maps, slices of non-int types (e.g., []string), TOML/YAML input.

Playground

A small playground command included under cmd/. Use it as a experimental testingground.

Build and run:

go build -o antapp ./playground
./antapp -config-host=localhost

Testing

Run all tests:

go test ./...

The tests exercise defaults, env overrides, pointer initialization for nested structs, discovery helpers, and JSONC parsing.


Feel free to open an issue or PR if you have any suggestions.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrConfigNotFound = errors.New("config file not found")

Errors

View Source
var ErrEnvFileNotFound = errors.New("environment file not found")

Functions

func LocateFromExeUp

func LocateFromExeUp(filename string) (string, error)

LocateFromExeUp searches for filename starting from the directory of the current executable and then walking upward up to 10 levels. Returns the first match or ErrConfigNotFound.

func LocateFromWorkingDirUp

func LocateFromWorkingDirUp(filename string) (string, error)

LocateFromWorkingDirUp searches for filename starting from the current working directory and then walking upward up to 10 levels. Returns the first match or ErrConfigNotFound.

func ToJSON

func ToJSON(src []byte) []byte

ToJSON converts JSONC (JSON with // and /* */ comments and trailing commas) into strict JSON suitable for json.Unmarshal. It allocates a new buffer.

func ToJSONInPlace

func ToJSONInPlace(src []byte) []byte

ToJSONInPlace is the same as ToJSON, but this method reuses the input json buffer to avoid allocations. Do not use the original bytes slice upon return.

Types

type AntConfig

type AntConfig struct {
	// contains filtered or unexported fields
}

AntConfig is a small, zero-dependency configuration helper that applies values to a tagged struct from (in order): defaults, config file (JSON/JSONC), .env file, OS environment variables, and command-line flags.

Use New() to construct, MustSetConfig/SetConfig to register your struct pointer, optionally BindConfigFlags to register flags on a flag.FlagSet, then call WriteConfigValues() to apply.

func New

func New() *AntConfig

New constructs a new AntConfig with default settings.

func (*AntConfig) BindConfigFlags

func (a *AntConfig) BindConfigFlags(fs *flag.FlagSet) error

BindConfigFlags registers flags for all fields tagged with `flag:"name"` onto the provided FlagSet. It respects the configured prefix (via SetFlagPrefix) for the CLI names. This method does not parse or apply flags; call fs.Parse(...) yourself, then WriteConfigValues to apply. It also binds the FlagSet to AntConfig so WriteConfigValues reads values from it. Requires SetConfig to be called first.

func (*AntConfig) ConfigPath

func (a *AntConfig) ConfigPath() string

ConfigPath returns the configured config file path, if any.

func (*AntConfig) EnvHelpString

func (a *AntConfig) EnvHelpString() string

EnvHelpString builds a help section for environment variables that can configure fields of the registered config struct. It returns a string formatted to append after flag usage output, using the same two-space indentation convention as flag.PrintDefaults. Requires SetConfig to have been called; otherwise returns an empty string.

func (*AntConfig) EnvPath

func (a *AntConfig) EnvPath() string

EnvPath returns the configured .env path, if any.

func (*AntConfig) FlagArgs

func (a *AntConfig) FlagArgs() []string

FlagArgs returns a copy of the configured flag args slice.

func (*AntConfig) FlagPrefix

func (a *AntConfig) FlagPrefix() string

FlagPrefix returns the CLI flag prefix, if any.

func (*AntConfig) ListFlags

func (a *AntConfig) ListFlags(c any) ([]FlagSpec, error)

ListFlags returns the set of CLI flags for fields tagged with `flag:"name"`. If a flag prefix is set, the returned CLI names include the prefix.

func (*AntConfig) MustBindConfigFlags

func (a *AntConfig) MustBindConfigFlags(fs *flag.FlagSet) *AntConfig

MustBindConfigFlags is like BindConfigFlags but panics on error. It returns the receiver to allow simple chaining with New()/MustSetConfig.

func (*AntConfig) MustSetConfig

func (a *AntConfig) MustSetConfig(cfg any) *AntConfig

MustSetConfig is like SetConfig but panics on error. It returns the receiver to allow simple chaining: antconfig.New().MustSetConfig(&cfg).

func (*AntConfig) SetConfig

func (a *AntConfig) SetConfig(cfg any) error

SetConfig stores a reference to the config pointer for later operations like BindConfigFlags. cfg must be a non-nil pointer to a struct.

func (*AntConfig) SetConfigPath

func (c *AntConfig) SetConfigPath(path string) error

SetConfigPath sets the path to a JSON/JSONC config file and validates it exists. When not set, WriteConfigValues will auto-discover config.jsonc or config.json by walking upward from the current working directory.

func (*AntConfig) SetEnvPath

func (c *AntConfig) SetEnvPath(path string) error

SetEnvPath sets the path to a .env file and validates it exists. When not set, WriteConfigValues will auto-discover a .env in the current working directory.

func (*AntConfig) SetFlagArgs

func (c *AntConfig) SetFlagArgs(args []string)

SetFlagArgs sets the CLI arguments that should be used for flag overrides. If not provided, WriteConfigValues falls back to os.Args[1:].

func (*AntConfig) SetFlagPrefix

func (c *AntConfig) SetFlagPrefix(prefix string)

SetFlagPrefix sets an optional CLI flag prefix (e.g., "config-").

func (*AntConfig) WriteConfigValues

func (a *AntConfig) WriteConfigValues() error

WriteConfigValues applies configuration values to the struct registered via SetConfig/MustSetConfig, in this precedence order:

  1. default values from `default:"…"` tags
  2. config file (JSON/JSONC) from SetConfigPath or auto-discovery
  3. .env file from SetEnvPath or auto-discovery (does not override existing OS env)
  4. OS environment variables from `env:"NAME"` tags (non-empty values override)
  5. command-line flags from a bound FlagSet (BindConfigFlags) or from SetFlagArgs/os.Args

Returns an error on invalid inputs, I/O, or parsing failures.

type FlagSpec

type FlagSpec struct {
	// Name is the logical flag name from the tag (without prefix), e.g., "secret".
	Name string
	// CLI is the concrete CLI flag including any configured prefix, e.g., "config-secret".
	CLI string
	// Kind is the Go kind for the target field (string, int, bool, float64, slice).
	Kind string
}

FlagSpec describes a single CLI flag derived from a struct field with a `flag:"name"` tag.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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