goconfigure

package module
v0.5.9 Latest Latest
Warning

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

Go to latest
Published: May 28, 2021 License: MIT Imports: 17 Imported by: 5

README

Configuration Package for Go

Build Issues Pull Requests Go Doc License

goconfigure allows configuration of an application via flags, environment variables, or a configuration file.

Installation

go get bitbucket.org/idomdavis/goconfigure

Usage

The following is derived from goconfigure_test.go which can be used to see how the various methods of setting options works:

package main

import (
	"fmt"
	"os"

	"bitbucket.org/idomdavis/goconfigure"
)

type Settings struct {
	Count   int    `json:"Message Count"`
	Message string `json:"Display Message"`
	Unset   string `json:"Unset Option"`
}

func (s *Settings) Description() string {
	return "Example Options Block"
}

func (s *Settings) Data() interface{} {
	return s
}

func (s *Settings) Register(opts goconfigure.OptionSet) {
	opts.Add(&goconfigure.Option{
		Pointer:     &s.Message,
		Description: "The message to display",
		ShortFlag:   'm',
		ConfigKey:   "message",
		Default:     "This space intentionally left blank",
	})

	opts.Add(goconfigure.NewOption(&s.Count, 1, "count",
		"The number of times to display the message"))

	opts.Add(&goconfigure.Option{
		Pointer: &s.Unset,
		Default: "This can't get set",
	})
}

func main() {
	settings := &Settings{}

	s := goconfigure.Settings{}
	s.AddHelp()
	s.AddConfigFile()
	s.Add(settings)

	args := []string{"-c", "testdata/options.json"}
	reporter := goconfigure.ConsoleReporter{}

	// Ordinarily we'd just use opts.Parse() to grab the command line arguments.
	if err := s.ParseUsing(args, reporter); err != nil {
		fmt.Println(err)
		s.Options.Usage()
		os.Exit(-1)
	}

	for i := 0; i < settings.Count; i++ {
		fmt.Println(settings.Message)
	}

	fmt.Println(settings.Unset)

	// Output:
	// Example Options Block: [Display Message:Hello, world!, Message Count:3, Unset Option:This can't get set]
	// Hello, world!
	// Hello, world!
	// Hello, world!
	// This can't get set
}

If this were in a file called example.go then you would get the following:

$ go run ./example.go 
This space intentionally left blank

Using an options file gives:

$ cat options.json
{
  "message": "Hello, world!",
  "count": 3
}
$ go run ./example.go -o ./options.json
Hello, world!
Hello, world!
Hello, world!

Options can be overridden, with command line arguments always taking precedence:

$ GOCONFIGURE_COUNT=2 go run example.go -o ./options.json 
Hello, world!
Hello, world!
$ GOCONFIGURE_COUNT=2 go run example.go -o ./options.json --count 4
Hello, world!
Hello, world!
Hello, world!
Hello, world!

Documentation

Overview

Package goconfigure provides configuration options for an application, allowing configuration to come from a variety of sources.

Example
package main

import (
	"fmt"
	"os"

	"bitbucket.org/idomdavis/goconfigure"
)

type Settings struct {
	Count   int    `json:"Message Count"`
	Message string `json:"Display Message"`
	Unset   string `json:"Unset Option"`
}

func (s *Settings) Description() string {
	return "Example Options Block"
}

// Data can override the values in the backing type to hide or enhance what is
// reported about the settings.
func (s *Settings) Data() interface{} {
	return Settings{
		Count:   s.Count,
		Message: s.Message,
		// Here we will display the contents of s.Unset if it is set, otherwise
		// a generic unset message will be shown.
		Unset: goconfigure.Sanitise(s.Unset, s.Unset, goconfigure.UNSET),
	}
}

func (s *Settings) Register(opts goconfigure.OptionSet) {
	opts.Add(&goconfigure.Option{
		Pointer:     &s.Message,
		Description: "The message to display",
		ShortFlag:   'm',
		ConfigKey:   "message",
		Default:     "This space intentionally left blank",
	})

	opts.Add(opts.Option(&s.Count, 1, "count",
		"The number of times to display the message"))

	opts.Add(&goconfigure.Option{
		Pointer: &s.Unset,
		Default: "This can't get set",
	})
}

func main() {
	settings := &Settings{}

	// When using environment variables they will all be of the form
	//    EXAMPLE_<name>
	s := goconfigure.NewSettings("EXAMPLE")
	s.AddHelp()
	s.AddConfigFile()
	s.Add(settings)

	args := []string{"-c", "testdata/options.json", "--count", "2"}
	reporter := goconfigure.ConsoleReporter{}

	// You can use opts.Parse() to grab the command line arguments, however,
	// passing in the arguments makes testing easier.
	if err := s.ParseUsing(args, reporter); err != nil {
		fmt.Println(err)
		s.Options.Usage(os.Stdout)
		os.Exit(-1)
	}

	for i := 0; i < settings.Count; i++ {
		fmt.Println(settings.Message)
	}

	fmt.Println(settings.Unset)
	fmt.Println(s.UsageString())

}
Output:

Example Options Block: [Display Message:Hello, world!, Message Count:2, Unset Option:This can't get set]
Hello, world!
Hello, world!
This can't get set

  -h, --help
        Display this help
  -c, --config
        Configure via the given configuration file.
        Use $EXAMPLE_CONFIG_FILE to set this using environment variables.
  -m
        The message to display (default "This space intentionally left blank")
        Use 'message' to set this in the config file.
  --count
        The number of times to display the message (default 1)
        Use $EXAMPLE_COUNT to set this using environment variables.
        Use 'count' to set this in the config file.
  No CLI option
         (default "This can't get set")
Example (No_config_file)
// Use Settings from goconfigure_test
settings := &Settings{}

s := goconfigure.NewSettings("TEST")
s.AddConfigFile()
s.Add(settings)

reporter := goconfigure.ConsoleReporter{}

if err := s.ParseUsing([]string{"-m", "msg", "--count", "2"}, reporter); err != nil {
	fmt.Println(err)
}
Output:

Example Options Block: [Display Message:msg, Message Count:2, Unset Option:This can't get set]

Index

Examples

Constants

View Source
const (
	SET   = "SET"
	UNSET = "UNSET"
)

Common values used by Sanitise.

Variables

View Source
var (
	// ErrLoadingConfig is returned if a config file cannot be read from the
	// path or URL given.
	ErrLoadingConfig = errors.New("error loading config")

	// ErrParsingConfig is returned if the config file is not valid JSON.
	ErrParsingConfig = errors.New("error parsing config")

	// ErrInvalidDuration is returned if the config file contains an invalid
	// duration string.
	ErrInvalidDuration = errors.New("invalid duration value")

	// ErrConversionFailure is returned if a value from a config file cannot be
	// applied to the Option for that value.
	ErrConversionFailure = errors.New("cannot convert config type")
)
View Source
var (
	// ErrFlagError is returned when there is an error parsing the flags. It is
	// used to wrap errors from the flags package which are not wrapped errors.
	ErrFlagError = errors.New("flag parse error")

	// ErrInvalidTypeOption is returned when the type on the option cannot be
	// set via a flag.
	ErrInvalidTypeOption = errors.New("invalid option type for flag")

	// ErrInvalidDefault is returned if the default value cannot be used on the
	// type set for the option.
	ErrInvalidDefault = errors.New("cannot use default option")
)
View Source
var (
	// ErrNoPointer is returned by Option.Check() if no pointer is set on the
	// Option.
	ErrNoPointer = errors.New("no pointer set on option")

	// ErrNotPointer is returned by Option.Check() if the type set on
	// Option.Pointer is not a pointer.
	ErrNotPointer = errors.New("incorrect type on option")
)
View Source
var ErrFailedToReadURI = errors.New("failed to read URI for config file")

ErrFailedToReadURI is returned if the URI for the config file is not in the expected format.

View Source
var ErrInvalidDataFormat = errors.New("invalid data format")

ErrInvalidDataFormat is returned by Settings.Parse and Settings.ParseUsing if the Block.Data function returns a struct that will not fit in a

map[string]interface{}
View Source
var ErrInvalidDataType = errors.New("invalid data type")

ErrInvalidDataType is returned by Settings.Parse and Settings.ParseUsing if the Block.Data function returns a struct with data types that are not supported by goconfigure.

View Source
var Help bool

Help will be set to true if the HelpOption has been registered and the relevant flag set on the command line. If Help is true then the application should output usage information and exit.

Functions

func Sanitise added in v0.4.1

func Sanitise(v, set, unset interface{}) string

Sanitise a value, returning unset if v is nil or has a zero value, and set otherwise.

Example
package main

import (
	"fmt"

	"bitbucket.org/idomdavis/goconfigure"
)

func main() {
	fmt.Println(goconfigure.Sanitise(nil, goconfigure.SET, goconfigure.UNSET))
	fmt.Println(goconfigure.Sanitise(1, goconfigure.SET, goconfigure.UNSET))

}
Output:

UNSET
SET

Types

type Block added in v0.5.0

type Block interface {

	// Description of the option block. Description is used when reporting on
	// the options set.
	Description() string

	// Register the options in the block against the given OptionSet.
	Register(opts OptionSet)

	// Data set by the options. The type returned by Data should map to a
	// map[string]interface{} and is used when reporting on the options set
	// with the data being displayed as key/value pairs. All data to be
	// displayed must be on public properties and the display key can be set
	// using the json tag.
	Data() interface{}
}

Block of Options. The block can Register itself against a set of Options and return its Description and current Data. The block Data should map to a map[string]interface{}.

type ConsoleReporter added in v0.5.0

type ConsoleReporter struct{}

ConsoleReporter reports information to STDOUT.

func (ConsoleReporter) Report added in v0.5.0

func (c ConsoleReporter) Report(description string, data map[string]interface{})

Report to STDOUT.

type Env added in v0.4.0

type Env struct {
	Stub string
	Data map[string]value.Data
}

Env holds the settings passed from environment variables.

func NewEnv added in v0.4.0

func NewEnv() *Env

NewEnv returns a new, empty set of environment variable settings.

func (*Env) Register added in v0.4.0

func (e *Env) Register(o *Option) error

Register an option, getting the environment variable for the option if it's set. If a Stub is set on the Env this is prepended to the EnvVar name with a _ in the form Stub_o.EnvVar.

Example
package main

import (
	"fmt"
	"os"

	"bitbucket.org/idomdavis/goconfigure"
)

func main() {
	var (
		s string
		i int
	)

	const (
		stub = "GOCONFIGURE"
		name = "TEST_ENV_VAR"
		env  = stub + "_" + name
	)

	_ = os.Setenv(env, "set")
	defer func() { _ = os.Setenv(env, "") }()

	e := goconfigure.NewEnv()
	e.Stub = stub

	settings := goconfigure.NewSettings(stub)

	o := settings.Options.Option(&i, "", name, "")

	if err := e.Register(o); err != nil {
		fmt.Println("failed")
	}

	o.Pointer = &s

	if err := e.Register(o); err != nil {
		fmt.Println(err)
	}

	fmt.Println(e.Data[env].Pointer)

}
Output:

failed
set

type External added in v0.4.0

type External map[string]interface{}

External configuration data. This can either be loaded as a JSON file from a URI (path or URL), or be set explicitly.

func (*External) Load added in v0.4.0

func (e *External) Load(uri string) error

Load external configuration from a URI. The configuration details for an Option can be retrieved using Value.

func (*External) Value added in v0.5.0

func (e *External) Value(o *Option) (value.Data, error)

Value for the given Option. If the Option has not been set in the external config, or the external config has not been loaded, then an empty, unset value.Data is returned. Returns ErrConversionFailure if the value set in config cannot be applied to the Option. Returns ErrInvalidDuration if an invalid duration string is given.

type Flags added in v0.4.0

type Flags struct {
	Data map[string]value.Data
	// contains filtered or unexported fields
}

Flags holds the settings passed in from the command line.

func NewFlags added in v0.4.0

func NewFlags() *Flags

NewFlags returns a new, empty set of flags.

func (*Flags) Parse added in v0.4.0

func (f *Flags) Parse(args []string) error

Parse the flags using the given arguments.

func (*Flags) Register added in v0.4.0

func (f *Flags) Register(o *Option) error

Register an option. If they are set the long and short value for that option are set once Flags.Parse is called.

type LogrusReporter added in v0.5.0

type LogrusReporter struct {
	Logger *logrus.Logger
}

LogrusReporter reports via Logrus.

func (LogrusReporter) Report added in v0.5.0

func (l LogrusReporter) Report(description string, data map[string]interface{})

Report to Logrus.

Example
package main

import (
	"os"

	"bitbucket.org/idomdavis/goconfigure"
	"github.com/sirupsen/logrus"
)

func main() {
	var l goconfigure.Reporter

	logger := logrus.New()
	logger.SetOutput(os.Stdout)
	logger.SetFormatter(&logrus.TextFormatter{
		DisableColors:    true,
		DisableTimestamp: true,
	})

	l = goconfigure.LogrusReporter{}
	l.Report("data", map[string]interface{}{"k": "v"})
	l.Report("message", nil)

	l = goconfigure.LogrusReporter{Logger: logger}
	l.Report("data", map[string]interface{}{"k": "v"})
	l.Report("message", nil)

	l = goconfigure.ConsoleReporter{}
	l.Report("data", map[string]interface{}{"k": "v"})
	l.Report("message", nil)

	l = goconfigure.NullReporter{}
	l.Report("data", map[string]interface{}{"k": "v"})
	l.Report("message", nil)

}
Output:

level=info msg=data k=v
level=info msg=message
data: [k:v]
message

type NullReporter added in v0.5.2

type NullReporter struct{}

NullReporter will not report anything.

func (NullReporter) Report added in v0.5.2

func (NullReporter) Report(string, map[string]interface{})

Report nothing.

type Option

type Option struct {
	// ShortFlag defines a short flag for setting the Option from the command
	// line. For example:
	//
	//     option.ShortFlag = 'f'
	//
	// allows the flag:
	//
	//     myApp -f value
	//
	// Setting this does nothing if Parse has already been called.
	ShortFlag rune

	// LongFlag defines a long flag for setting the Option from the command
	// line. For example:
	//
	//     option.LongFlag = "flag"
	//
	// allows the flag:
	//
	//     myApp --flag value
	//
	// Setting this does nothing if Parse has already been called.
	LongFlag string

	// EnvVar defines the name of an environment variable that can be used to
	// set this option. The string value of the environment variable must be
	// convertible to the type of the Option.
	//
	// Setting this does nothing if Parse has already been called.
	EnvVar string

	// ConfigKey defines the key this option can use to set itself from a JSON
	// configuration file. The value stored under this key must be convertible
	// to the Option Type.
	//
	// Setting this does nothing if Parse has already been called.
	ConfigKey string

	// Description is used when producing usage information. It should explain
	// what the Option is used for.
	Description string

	// Pointer to a variable of the type of this option (*bool, *int, *int64,
	// *uint, *uint64, *float64, *string, *time.Duration). Pointer will be set
	// when the option is Set.
	Pointer interface{}

	// Default defines a value that will be used by the option if no flags,
	// environment variables, or configuration file values are set or found. The
	// value must be the same type as the Option Pointer.
	Default interface{}
}

Option represents a configuration option that can be set either by flag, configuration file, environment variable, or a default value with the value to use being chosen in that order. Options must be one of bool, int, int64, uint, unit64, float64, string, or time.Duration.

func HelpOption added in v0.4.2

func HelpOption() *Option

HelpOption returns a flag configured on `h` and `help` which will be set to goconfigure.Help when parsed.

Example
package main

import (
	"fmt"

	"bitbucket.org/idomdavis/goconfigure"
)

func main() {
	opts := goconfigure.Options{}
	opts.Add(goconfigure.HelpOption())

	_ = opts.ParseUsing([]string{"-h"})

	fmt.Println(goconfigure.Help)

}
Output:

true

func (*Option) Check added in v0.4.0

func (o *Option) Check() error

Check the Option is valid, returning an error if it is not.

func (*Option) Set added in v0.4.0

func (o *Option) Set(short, long, env, external value.Data) error

Set the value for this option from one of the given values, or the default if none are set. The value chosen uses the precedence: short, long, env, external, Default.

func (*Option) Usage added in v0.4.0

func (o *Option) Usage() string

Usage information for this option. nolint: gocyclo,cyclop

Example
package main

import (
	"fmt"

	"bitbucket.org/idomdavis/goconfigure"
)

func main() {
	var (
		boolOpt   bool
		intOpt    int
		stringOpt string
	)
	o := &goconfigure.Option{
		ConfigKey:   "config-key",
		Description: "What the option does",
		Pointer:     &boolOpt,
	}

	fmt.Println(o.Usage())

	o.LongFlag = "flag"
	o.Pointer = &intOpt
	o.Default = 1

	fmt.Println(o.Usage())

	o.ShortFlag = 'f'
	o.EnvVar = "ENV"
	o.Pointer = &stringOpt
	o.Default = "unset"

	fmt.Println(o.Usage())

}
Output:

No CLI option
        What the option does
        Use 'config-key' to set this in the config file.

  --flag
        What the option does (default 1)
        Use 'config-key' to set this in the config file.

  -f, --flag
        What the option does (default "unset")
        Use $ENV to set this using environment variables.
        Use 'config-key' to set this in the config file.

type OptionSet added in v0.5.0

type OptionSet interface {
	Add(option *Option)
	Flag(i interface{}, name rune, description string) *Option
	Option(i, d interface{}, name, description string) *Option
}

OptionSet that can be added to.

type Options

type Options struct {
	// Stub is an optional stub that can be prepended to environment variable
	// names in the format Stub_Name.
	Stub string
	// contains filtered or unexported fields
}

Options holds a set of configuration options which can be provided by the command line, environment variables, configuration files, or default values.

func (*Options) Add

func (o *Options) Add(option *Option)

Add an Option to this set of Options.

Example
package main

import (
	"fmt"
	"time"

	"bitbucket.org/idomdavis/goconfigure"
)

func main() {
	var options struct {
		boolean      bool
		integer      int
		long         int64
		unsigned     uint
		unsignedLong uint64
		float        float64
		text         string
		duration     time.Duration
	}

	opts := &goconfigure.Options{}

	opts.Add(opts.Flag(&options.boolean, 'b', "boolean"))

	opts.Add(opts.Flag(&options.integer, 'i', "integer"))
	opts.Add(opts.Flag(&options.long, 'l', "long"))
	opts.Add(opts.Flag(&options.unsigned, 'u', "unsigned"))
	opts.Add(opts.Flag(&options.unsignedLong, 'z', "unsignedLong"))
	opts.Add(opts.Flag(&options.float, 'f', "float"))
	opts.Add(opts.Flag(&options.text, 't', "text"))
	opts.Add(opts.Flag(&options.duration, 'd', "duration"))

	err := opts.ParseUsing([]string{
		"-b", "-i", "1", "-l", "2", "-u", "3", "-z", "4",
		"-f", "5.6", "-t", "words", "-d", "60s",
	})

	if err == nil {
		fmt.Println(options)
	} else {
		fmt.Println(err)
	}

}
Output:

{true 1 2 3 4 5.6 words 60000000000}

func (*Options) Flag added in v0.5.3

func (o *Options) Flag(i interface{}, name rune, description string) *Option

Flag returns an option that only uses a command line flag. i is the Option.Pointer to set (which must be one of *bool, *int, *int64, *uint, *uint64, *float64, *string, or *time.Duration) The description is used when producing usage information. Providing an invalid type for i will not error here, but will generate an error when Option.Check is called.

func (*Options) LoadUsing added in v0.4.0

func (o *Options) LoadUsing(option *Option)

LoadUsing configuration file, the URI for which will be obtained from the given option. The option must resolve to a string. By definition this option can't be read from the configuration file and must come from a flag or environment variable.

Example
package main

import (
	"fmt"

	"bitbucket.org/idomdavis/goconfigure"
)

func main() {
	var options struct {
		config     string
		numeric    int
		text       string
		overridden string
	}

	opts := &goconfigure.Options{Stub: "STUB"}
	opts.Add(opts.Option(&options.text, "unset", "", "unset option"))
	opts.Add(opts.Option(&options.numeric, 0, "numeric", "unset option"))

	opts.Add(&goconfigure.Option{
		Pointer:   &options.overridden,
		ShortFlag: 'o',
	})

	opt := opts.Flag(&options.config, 'f', "config file")
	opts.Add(opt)
	opts.LoadUsing(opt)

	if err := opts.ParseUsing([]string{
		"-f", "testdata/config.json",
		"-o", "overridden",
	}); err != nil {
		fmt.Println(err)
	}

	fmt.Println(options)

}
Output:

{testdata/config.json 4 unset overridden}

func (*Options) Option added in v0.5.3

func (o *Options) Option(i, d interface{}, name, description string) *Option

Option returns an option with i being the Option.Pointer to set (which must be one of *bool, *int, *int64, *uint, *uint64, *float64, *string, or *time.Duration) and d being the default value of the same type, or nil for no default. The description is used when producing usage information. Providing an invalid type for i or d will not error here, but will generate an error when Option.Check is called. The Option.LongFlag and Option.ConfigKey are both set to name. Option.EnvVar is set to name with all `-` characters changed to `_`, and the entire string in upper case.

func (*Options) Parse

func (o *Options) Parse() error

Parse the Options.

func (*Options) ParseUsing

func (o *Options) ParseUsing(args []string) error

ParseUsing uses the given arguments as the set of command line arguments.

Example
package main

import (
	"fmt"

	"bitbucket.org/idomdavis/goconfigure"
)

func main() {
	var options struct {
		value string
	}
	opts := &goconfigure.Options{Stub: "STUB"}
	opts.Add(opts.Option(&options.value, "", "value", "value to set"))

	if err := opts.ParseUsing([]string{"--value", "set"}); err != nil {
		fmt.Println(err)
	}

	fmt.Println(options)

}
Output:

{set}

func (*Options) ToEnv added in v0.5.3

func (o *Options) ToEnv(name string) string

ToEnv will turn a name into an environment variable by replacing all non word characters with '_'.

func (*Options) Usage

func (o *Options) Usage(w io.Writer)

Usage displays the usage information for this set of options to the given writer. An error writing the usage will cause a panic. A nil writer will do nothing.

func (*Options) UsageString

func (o *Options) UsageString() string

UsageString building of custom usage output by providing just the usage details for the defined options.

type Reporter added in v0.5.0

type Reporter interface {

	// Report the configuration block. The description is an arbitrary text
	// string used to identify the block, the data is the set of options. It is
	// save to assume the data will be a primitive, or fmt.Stringer.
	Report(description string, data map[string]interface{})
}

A Reporter is used to report configuration information.

type Settings added in v0.5.0

type Settings struct {
	Help   bool
	Blocks []Block

	*Options
	// contains filtered or unexported fields
}

Settings all for configuration of an app via Options and sets of Block types.

func NewSettings added in v0.5.0

func NewSettings(stub string) *Settings

NewSettings returns an empty Settings type set to use the given stub for environment variables.

func (*Settings) Add added in v0.5.0

func (s *Settings) Add(block Block)

Add a Block to the Settings. Settings may have any number of blocks, but generally have at least 1.

func (*Settings) AddConfigFile added in v0.5.0

func (s *Settings) AddConfigFile()

AddConfigFile to the Settings. This will register `-c` and `--config`, plus check <stub>_CONFIG_FILE and load settings from the file if these are set.

func (*Settings) AddHelp added in v0.5.0

func (s *Settings) AddHelp()

AddHelp to the Settings. This will register `-h` and `--help. Settings.Help will be set to true if the flag is passed.

func (*Settings) Parse added in v0.5.0

func (s *Settings) Parse(reporter Reporter) error

Parse the Settings using the Reporter to output the results.

func (*Settings) ParseUsing added in v0.5.0

func (s *Settings) ParseUsing(args []string, reporter Reporter) error

ParseUsing allows Settings to be run using custom arguments rather than os.Args.

Directories

Path Synopsis
Package value handles mapping values to the settings type.
Package value handles mapping values to the settings type.

Jump to

Keyboard shortcuts

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