wonsz

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Jul 19, 2026 License: MIT Imports: 13 Imported by: 0

README

img


Wrapper Of Naughty SnakeZ


The best of Viper & Cobra combined.
Bind your config struct to CLI flags, environment variables, and configuration files.

example workflow Go Report Card PkgGoDev

What does it do?

It creates a configuration struct that fields are automatically bound to:

  1. configuration file
  2. environment variables
  3. command line flags

Why?

  • Let's say you want to write a configurable app.

So, I use viper to load configuration from a file. I fetch configuration fields by viper.Get(key).

  • But it sucks to not have autocompletion from the IDE.

So, I marshall your config to a struct.

  • But let's say, you dockerized your app, and when you run containers, you want also to manage config by environment variables.

So, I use AutomaticEnv to get env variables.

  • But it marshalls to struct only when you bind specific environment variables by name.

I would bind them by viper.BindEnv().

  • But you have a config struct field named like: ThisIsMyConfigField, so you must set THISISMYCONFIGFIELD env variable, which is not really readable and nice.

:/

  • And let's say, you also want to run your app like a CLI app.

I would use cobra.

  • But you may also want configuration fields to be overwritten with values from the command line flags.

So, I use viper.BindPFlag to bind some flags to your config structs.

  • And you end up with 3 different names of the same config field and pretty complicated initialization logic. Also, you must remember to add proper code when adding a new field to the configuration, so every way of loading the config field is properly handled.

So I use this library, and then you just create 1 config struct without any tags, initialize it, and you have all 3 ways of configuring your app (by the configuration file, by environment variables, and by command flags) out of the box and in one place.
And I have all the above problems resolved!

  • Awessssome!

How to install?

Requires Go 1.25 or newer. Import dependency into your project.

go get github.com/Mrucznik/wonsz

How to use?

Simplest application

// main.go file
package main

import (
	"fmt"
	"github.com/Mrucznik/wonsz"
	"github.com/spf13/cobra"
)

var config Configuration

type Configuration struct {
	// Here we declare configuration fields. No need to add any tags.
	SnakeName string
}

var rootCmd = &cobra.Command{Run: execute}

func main() {
	err := wonsz.BindConfig(&config, // pointer to the configuration struct
		rootCmd,            // root cobra command
		wonsz.ConfigOpts{}) // Wonsz configuration options
	if err != nil {
		panic(err)
	}

	if err := rootCmd.Execute(); err != nil {
		panic(err)
	}
}

func execute(_ *cobra.Command, _ []string) {
	fmt.Printf("Application config: %+v\n", config)
}

This is the simplest example, more production-ready and detailed you will find here. You can also look at tests.

Integrate with an existing application

  1. Create file config/config.go
    // config.go file
    package config
    
    var Config Configuration
    
    type Configuration struct {
    // Here we declare configuration fields. No need to add any tags.
        SnakeName string
    }
    
  2. Bind created config structure to cobra & viper using wonsz.BindConfig()
// cmd/root.go
package cmd

import (
    "github.com/Mrucznik/wonsz"
	"github.com/You/your-project/config"
    "github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{
	// your root cobra command
}

func init() {
	if err := wonsz.BindConfig(&config.Config, rootCmd, wonsz.ConfigOpts{}); err != nil {
		panic(err)
	}

	// other code
}

  1. Done!

Configure and run your application with

  • default struct values
    config := &Config{
        SnakeName: "nope-rope",
    }
    
  • configuration files e.g.
    • config.json
      {
        "snake_name": "hazard spaghetti" 
      }
      
    • config.yaml
      snake_name: "judgemental shoelace"
      
    • config.toml
      snake_name = "slippery tube dude"
      
  • environment variables
    SNAKE_NAME="caution ramen" go run main.go
    
  • command-line flags
    go run main.go --snake-name="danger noodle"
    

Key concepts

  • keys in configuration files should be in snake_case
  • environment variables are in SCREAMING_SNAKE_CASE
  • command-line flags names should be dash-separated

Supported field types

Strings, booleans, all int/uint/float variants, time.Duration, time.Time (RFC 3339), net.IP, net.IPNet, slices of strings/bools/ints/floats/time.Duration/net.IP, string arrays, and map[string]string/map[string]int/map[string]int64.

Nested structs (also behind pointers) are fully supported — their fields get prefixed names, e.g. Server.Port becomes server.port in the file, SERVER_PORT in env, and --server-port on the command line.

Field tags

All tags are optional:

Tag Effect
mapstructure:"custom_name" Overrides the derived name for all bindings (flag becomes --custom-name).
default:"value" Default value used when no source provides one.
usage:"help text" Usage string shown in --help for the flag.
shortcut:"p" Single-character flag shorthand, e.g. -p.
wonsz:"flag-ignore" Skips binding the field to a command-line flag (env and file still work).
wonsz:"-" Excludes the field from all bindings entirely.

Configuration options

ConfigOpts fields:

  • EnvPrefix — prefix for environment variables ("WONSZ"WONSZ_SNAKE_NAME).
  • ConfigPaths, ConfigType, ConfigName — where and how to look for the config file.
  • Viper — pass your own viper instance (defaults to the global one).
  • IgnoreFlagBindErrors — skip fields that cannot be bound to flags instead of returning an error.
  • WatchConfig — watch the config file and re-unmarshal the struct when it changes.

Typed instances

wonsz.New returns a typed *wonsz.Wonsz[T] instance with Get() *T (no type assertions) and Viper(). Instances are independent, so several configs can coexist:

w, err := wonsz.New(&cfg, rootCmd, wonsz.ConfigOpts{Viper: viper.New()})
name := w.Get().SnakeName

wonsz.BindConfig is a convenience wrapper for when you keep your own pointer to the struct and only need the error.

Cobra integration notes

Wonsz chains config initialization into the root command's PersistentPreRunE; your own hook (if any) runs right after it. If a subcommand defines its own PersistentPreRun(E), cobra skips the root hook — set cobra.EnableTraverseRunHooks = true to run both.

Stability

Wonsz follows semantic versioning. Starting with v1.0.0 the public API is stable: breaking changes only happen in a new major version.

More examples

You can find more information by checking out example app.

Documentation

Overview

Package wonsz binds a plain Go configuration struct to configuration files, environment variables and cobra command-line flags at once — the best of viper and cobra combined, without writing any binding boilerplate.

Define a struct, call New (or the convenience wrapper BindConfig) and every field is readable from a config file (snake_case keys), environment variables (SCREAMING_SNAKE_CASE) and command-line flags (kebab-case), with the usual precedence: flags > env > config file > defaults.

type Config struct {
	SnakeName string `default:"nope-rope" usage:"the snake's name"`
}

var cfg Config
w, err := wonsz.New(&cfg, rootCmd, wonsz.ConfigOpts{EnvPrefix: "APP"})

Field behavior can be tuned with optional struct tags: mapstructure (custom key), default, usage, shortcut, wonsz:"flag-ignore" (no flag) and wonsz:"-" (excluded entirely). See ConfigOpts for file lookup, custom viper instances and config hot-reload via WatchConfig.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func BindConfig

func BindConfig[T any](config *T, rootCmd *cobra.Command, options ConfigOpts) error

BindConfig binds the configuration structure to a config file, environment variables and cobra command flags. It is a convenience wrapper around New for callers that do not need the returned instance. You can pass nil to rootCmd if you don't want to bind cobra command flags with config.

Example
_ = os.Setenv("EXAMPLE_FIELD", "this is my example config field")

var myConfig struct {
	ExampleField string
}

err := BindConfig(&myConfig, nil, ConfigOpts{})
if err != nil {
	panic(err)
}

fmt.Println(myConfig.ExampleField)
Output:
this is my example config field

Types

type ConfigOpts

type ConfigOpts struct {
	// Environment variables prefix.
	// E.g., if your prefix is "wonsz", the env registry will look for env variables that start with "WONSZ_".
	EnvPrefix string

	// Paths to search for the config file in.
	ConfigPaths []string

	// Type of the configuration file, e.g. "json".
	// Wonsz uses Viper for loading the configuration file,
	// so you can use any type of configuration file that Viper supports.
	ConfigType string

	// Name for the config file. Does not include extension.
	// If no configuration name is specified, Wonsz will not throw an error if the config file is not found.
	ConfigName string

	// Pass own viper instance. Default is a global viper instance.
	Viper *globalViper.Viper

	// If true, Wonsz will not return an error if a config field cannot be bound to a flag
	// or the resulting flag cannot be bound to viper. Such fields are silently skipped.
	IgnoreFlagBindErrors bool

	// If true, Wonsz watches the config file and re-unmarshals the config struct
	// when the file changes. Note that the struct is updated from a background
	// goroutine, so guard access to it if your application reads it concurrently.
	WatchConfig bool

	// Called after each WatchConfig reload with the re-unmarshal result.
	// Runs on the watcher goroutine, right after the config struct is updated,
	// so it is a safe synchronization point for reacting to config changes.
	OnConfigChange func(err error)
}

ConfigOpts provide additional options to configure Wonsz.

type Tag

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

Tag contains the name and value of a structure field tag.

func GetTagsForField

func GetTagsForField(field reflect.StructField) []Tag

GetTagsForField extracts field tag names and values from a specified structure.

type Wonsz added in v0.3.0

type Wonsz[T any] struct {
	// contains filtered or unexported fields
}

Wonsz binds a single configuration struct of type T to a config file, environment variables and cobra command flags. Create instances with New; each instance keeps its own options and viper, so multiple configs can coexist.

func New added in v0.3.0

func New[T any](config *T, rootCmd *cobra.Command, options ConfigOpts) (*Wonsz[T], error)

New binds the configuration structure to a config file, environment variables and cobra command flags, and returns an independent Wonsz instance. The config parameter must be a non-nil pointer to a struct. You can pass nil to rootCmd if you don't want to bind cobra command flags with config.

Example
package main

import (
	"fmt"
	"os"

	"github.com/Mrucznik/wonsz"
	"github.com/spf13/viper"
)

func main() {
	_ = os.Setenv("SNAKE_NAME", "danger noodle")
	defer func() { _ = os.Unsetenv("SNAKE_NAME") }()

	type Config struct {
		SnakeName   string
		SnakeLength int `default:"5"`
	}

	var cfg Config
	w, err := wonsz.New(&cfg, nil, wonsz.ConfigOpts{Viper: viper.New()})
	if err != nil {
		panic(err)
	}

	// Get returns the typed pointer, no type assertion needed.
	fmt.Println(w.Get().SnakeName, w.Get().SnakeLength)
}
Output:
danger noodle 5
Example (Tags)
package main

import (
	"fmt"
	"os"

	"github.com/Mrucznik/wonsz"
	"github.com/spf13/viper"
)

func main() {
	_ = os.Setenv("CUSTOM_NAME", "hazard spaghetti")
	defer func() { _ = os.Unsetenv("CUSTOM_NAME") }()

	type Config struct {
		Renamed  string `mapstructure:"custom_name"`
		Excluded string `wonsz:"-"`
	}

	var cfg Config
	if err := wonsz.BindConfig(&cfg, nil, wonsz.ConfigOpts{Viper: viper.New()}); err != nil {
		panic(err)
	}

	fmt.Printf("%q %q\n", cfg.Renamed, cfg.Excluded)
}
Output:
"hazard spaghetti" ""

func (*Wonsz[T]) Get added in v0.3.0

func (w *Wonsz[T]) Get() *T

Get returns the config struct instance passed to New.

func (*Wonsz[T]) Viper added in v0.3.0

func (w *Wonsz[T]) Viper() *globalViper.Viper

Viper returns the viper instance used by this Wonsz instance.

Directories

Path Synopsis
internal
retag
Package retag provides an ability to change tags of structures' fields in runtime without copying of the data.
Package retag provides an ability to change tags of structures' fields in runtime without copying of the data.

Jump to

Keyboard shortcuts

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