• Stars
    star
    6
  • Rank 2,539,965 (Top 51 %)
  • Language
    Go
  • License
    MIT License
  • Created about 3 years ago
  • Updated about 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Golang struct-tag based configfile and flag parsing

nfigure - per-library configuration

GoDoc unit tests report card codecov

Install:

go get github.com/muir/nfigure

Nfigure is a reflective configuration library. It supports:

  • Describing what to configure using struct tags
  • Configuration from: multiple configuration file formats, and multiple files
  • Configuration from: environment variables
  • Configuration from: the command line
  • Posix-style and Go-style command line parsing
  • Support for subcommands when parsing command lines
  • Multi-stage binding to allow independently-developed libraries to express their configruration needs ahead of program startup
  • Custom type support using reflectutils.RegisterStringSetter().
  • Filling all normal Go types, array, slices, maps, and time.Duration
  • Ability to export flag-based configuration requests to the "flag" module (useful for libraries)

While nfigure has lots of flexibility and many features, using it should be simple.

Example: In a library, in-house or published for others to use

It can pre-register configuration at the library level, before program startup. This allows library-specific configuration to be handled at the library-level rather than pushed to a central main.

type myLibraryConfig struct {
	Field1	string	  `env="FIELD1" flag:"field1" default:"f1" help:"Field1 controls the first field"`
	Field2	int	  `config:"mylibrary.field2"` 
	Field3	[]string  `flag:"field3"`
}

type MyLibrary struct {
	config	myLibraryConfig
}

func createMyLibrary(nreg *nfigure.Registry) *MyLibrary {
	lib := MyLibrary{}
	nreg.Request(&lib.config,
		nfigure.Prefix("myLibrary"),
		nfigure.ConfigFileFrom(`env="MYLIBRARY_CONFIG_FILE" flag:"mylibrary-config"`),
	)
	_ = nreg.Configure()
	return &lib
}

Example: At the program level

This is an example using nserve. Where this gets interesting is if multiple binaries are built from the same source, the set of libraires can exist in a list and only the ones that are needed for particular executables will have their configuration evaluated.

type programLevelConfig struct {
	Field1	string `env="field1" default:"F1"`
	Field4	float64	`flag:"field4" default:"3.9"`
}

func createMyApp(myLibrary *mylibrary.MyLibrary, nreg *nfigure.Registery) error {
	// start servers, do not return until time to shut down
	var config programLevelConfig
	nreg.Request(&config, "main")
	_ = nreg.Configure()
}

func main() {
	app, _ := nserve.CreateApp("myApp", 
		nfigure.NewRegistryFactory(),
		createMyLibrary, 
		createMyApp)
	_ = app.Do(nserve.Start)
	_ = app.Do(nserve.Stop)
}

Supported tags

Assuming a command line parser was bound, the follwing tags are supported:

  • nfigure: the meta tag, used to control filler interactions
  • default: a filler that provides a literal value
  • env: fill values from environment variables
  • config: fill values from configuration files
  • flag: fill values from the command line (Go style or Posix style)
  • help: per-item help text for command line Usage

Environment variables

Usage:

  • env:"VARNAME" specifies that a value can or should be loaded from an environment variable

Command line parsing

Both Go-style and Posix-style command line parsing is supported. In addition to the base features, counters are supported. Filling maps, arrays, and slices is supported.

  • flag:"name" specifies the name of the command line flag to fill a value.
  • flag:"name n" specifies a single letter alternative
  • flag:"name,split=comma for array values, specifies that strings will be split on comma, flag can only be given once
  • flag:"name,explode=true for array values, specifies that the flag can be given multiple times and is not split
  • flag:"name,counter for numberic values, counts the number of times the flag is used, flag cannot take argument
  • flag:"name,map=explode,split=equal for maps, support -name a=b -name b=c
  • flag:"name,map=prefix for maps, support --namex=a --nameb=c

Posix-style

When using Posix-style flags (PosixFlagHandler()), flags whose names are only a single rune can be combined on the command line:

--debug 
-d
--verbose
-v
-dv (debug and verbose)

For boolean values, negation is "--no-":

--no-verbose

Best Practices

Best Practices for existing libraries

Libraries that are already published and using the standard "flag" package can be refactored to use nfigure. If they register themselves with flag during init, then that behavior should be retained:

package mylibrary 

import (
	"flag"
	"github.com/muir/nfigure"
)

type MyConfig struct {
	MyField string `flag:"myfield"`
}
var myConfig MyConfig

sub init() {
	err := nfigure.ExportToFlagSet(flag.CommandLine, "flag", &myConfig)
	if err != nil {
		panic(err.Error())
	}
}

In a program that is using nfigure, MyConfig can be explicitly imported:

registery.Request(&mylibrary.MyConfig)

However, if there are other libraries that only support "flag" and they're being imported:

GoFlagHandler(nfigure.ImportFlagSet(flag.CommandLine))

Then MyConfig should not also be explicity imported since that would end up with the flags being defined twice.

Best practices for new libraries

New libraries should use nfigure to handle their configruation needs. The suggested way to do this is to have a New function that takes a registry as arguments.

Separate New() and Start() so that configuation can happen after New() but before Start().

Users of your library can use NewWithRegistry() if they're using nfigure. For other users, they can fill MyConfig by hand or use "flag" to populate the configuration

Library writer boilerplate

import "github.com/muir/nfigure"

func NewWithRegistry(registry *nfigure.Registry) MySelf {
	var config Config
	registry.Request(&config)
	...
}

func NewWithConfig(config *Config) MySelf {
	...
}

func (m MySelf) Start() {
	...
}

Library user not using nfigure boilerplate:

import (
	"flag"
	"library"
	"github.com/muir/nfigure"
)

func main() {
	var libConfig library.Config
	nfigure.MustExportToFlagSet(flag.CommandLine, "flag", &libConfig)
	lib := library.NewWithConfig(libConfig) // config is not used yet
	flag.Parse()
	lib.Start() // call to Start must be after config is filled in
}

Library user using nfigure boilerplate:

import (
	"flag"
	"library"
	"github.com/muir/nfigure"
)

func main() {
	registry := nfigure.NewRegistry(WithFiller("flag", GoFlagHandler()))
	lib := registry.NewWithRegistry(registry)
	registry.Configure()
	lib.Start()
}

Best practices for program writers

Use nfigure everywhere! Be careful not to combine ImportFlagSet with registry.Request() of the same models that are ExportToFlagSet()ed in library inits.

Separate library creation from library starting. Allow configuration to be deferred until until library start.

More Repositories

1

nject

Golang type-safe dependency injection
Go
28
star
2

libschema

database schema migrations on a per-library basis [Go]
Go
15
star
3

nchi

golang http router with elegance, speed, and flexibility
Go
13
star
4

Net-Netmask

Net::Netmask perl module
Perl
6
star
5

reflectutils

Golang utility functions for working with reflection
Go
6
star
6

Time-modules

CPAN modules: TIme::ParseDate, Time::CTime, Time::JulianDay etc
Perl
5
star
7

sqltoken

High performance SQL tokenizer - Golang
Go
4
star
8

Cisco-Reconfig

The Cisco::Reconfig perl module
Perl
4
star
9

drbd-lxd

Instructions/tools for shared nothing DRBD LXC HA setup
Shell
4
star
10

nvelope

injection chains for building endpoints (golang)
Go
3
star
11

xop-go

Golang structured logging/tracing framework
Go
3
star
12

nserve

injection chains for for starting and stopping servers
Go
3
star
13

Daemon-Generic

The Daemon::Generic CPAN perl module
Perl
3
star
14

IO-Event

the CPAN IO::Event module
Perl
2
star
15

Stream-Aggregate

The Stream::Aggregate perl module
Perl
2
star
16

npoint

dependency injection wrappers for binding http endpoint handlers
Go
2
star
17

OOPS

OOPS cpan module --- object persistant store
Perl
2
star
18

nape

dependency injection of endpoint handlers using gorilla/mux
Go
2
star
19

Eval-LineNumbers

Eval::LineNumbers perl module on CPAN
Perl
1
star
20

Mail-SendVarious

Mail::SendVarious perl module
Perl
1
star
21

Proc-JobQueue

Proc::JobQueue perl modules
Perl
1
star
22

HTML-Transmorgify

HTML::Transmorgify perl module
Perl
1
star
23

Net-SMTP-Receive

Net::SMTP::Receive perl module
Perl
1
star
24

Text-Tabs-Wrap

The Text::Tabs & Text::Wrap CPAN Modules
1
star
25

rinetd.pl

rinetd.pl - tcp redirection server
Perl
1
star
26

genpxelinux.pl

genpxelinux.pl command
Perl
1
star
27

Proc-Parallel

The Proc::Parallel and RCP::ToWorker CPAN modules
Perl
1
star
28

Config-Checker

Config::Checker perl module
Perl
1
star
29

Object-Dependency

Object::Dependency perl module
Perl
1
star
30

File-Slurp-Remote

The File::Slurp::Remote cpan module
Perl
1
star
31

gwrap

Golang generic wrappers for core library functions
Go
1
star