mirror of
https://github.com/notherealmarco/WASAPhoto.git
synced 2025-03-14 06:06:15 +01:00
240 lines
5.9 KiB
Go
240 lines
5.9 KiB
Go
package conf
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/url"
|
|
"os"
|
|
"reflect"
|
|
"strings"
|
|
)
|
|
|
|
// ErrInvalidStruct indicates that a configuration struct is not the correct type.
|
|
var ErrInvalidStruct = errors.New("configuration must be a struct pointer")
|
|
|
|
// A FieldError occurs when an error occurs updating an individual field
|
|
// in the provided struct value.
|
|
type FieldError struct {
|
|
fieldName string
|
|
typeName string
|
|
value string
|
|
err error
|
|
}
|
|
|
|
func (err *FieldError) Error() string {
|
|
return fmt.Sprintf("conf: error assigning to field %s: converting '%s' to type %s. details: %s", err.fieldName, err.value, err.typeName, err.err)
|
|
}
|
|
|
|
// Sourcer provides the ability to source data from a configuration source.
|
|
// Consider the use of lazy-loading for sourcing large datasets or systems.
|
|
type Sourcer interface {
|
|
|
|
// Source takes the field key and attempts to locate that key in its
|
|
// configuration data. Returns true if found with the value.
|
|
Source(fld Field) (string, bool)
|
|
}
|
|
|
|
// Version provides the abitily to add version and description to the application.
|
|
type Version struct {
|
|
SVN string
|
|
Desc string
|
|
}
|
|
|
|
// ParseOSArgs parses the configuration allowing command line
|
|
// arguments to override settings. Function returns ErrHelpWanted
|
|
// for any information to be provided to the user.
|
|
func ParseOSArgs(prefix string, cfg interface{}) (string, error) {
|
|
err := Parse(os.Args[1:], prefix, cfg)
|
|
if err == nil {
|
|
return "", nil
|
|
}
|
|
|
|
switch err {
|
|
case ErrHelpWanted:
|
|
usage, err := Usage(prefix, cfg)
|
|
if err != nil {
|
|
return "", fmt.Errorf("generating config usage: %w", err)
|
|
}
|
|
return usage, ErrHelpWanted
|
|
|
|
case ErrVersionWanted:
|
|
version, err := VersionString(prefix, cfg)
|
|
if err != nil {
|
|
return "", fmt.Errorf("generating config version: %w", err)
|
|
}
|
|
return version, ErrHelpWanted
|
|
}
|
|
|
|
return "", fmt.Errorf("parsing config: %w", err)
|
|
}
|
|
|
|
// Parse parses configuration into the provided struct.
|
|
func Parse(args []string, namespace string, cfgStruct interface{}, sources ...Sourcer) error {
|
|
|
|
// Create the flag source.
|
|
flag, err := newSourceFlag(args)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Append default sources to any provided list.
|
|
sources = append(sources, newSourceEnv(namespace))
|
|
sources = append(sources, flag)
|
|
|
|
// Get the list of fields from the configuration struct to process.
|
|
fields, err := extractFields(nil, cfgStruct)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(fields) == 0 {
|
|
return errors.New("no fields identified in config struct")
|
|
}
|
|
|
|
// Process all fields found in the config struct provided.
|
|
for _, field := range fields {
|
|
|
|
// If the field is supposed to hold the leftover args then copy them in
|
|
// from the flags source.
|
|
if field.Field.Type() == argsT {
|
|
args := reflect.ValueOf(Args(flag.args))
|
|
field.Field.Set(args)
|
|
continue
|
|
}
|
|
|
|
// Set any default value into the struct for this field.
|
|
if field.Options.DefaultVal != "" {
|
|
if err := processField(field.Options.DefaultVal, field.Field); err != nil {
|
|
return &FieldError{
|
|
fieldName: field.Name,
|
|
typeName: field.Field.Type().String(),
|
|
value: field.Options.DefaultVal,
|
|
err: err,
|
|
}
|
|
}
|
|
}
|
|
|
|
// Process each field against all sources.
|
|
var everProvided bool
|
|
for _, sourcer := range sources {
|
|
if sourcer == nil {
|
|
continue
|
|
}
|
|
|
|
value, provided := sourcer.Source(field)
|
|
if !provided {
|
|
continue
|
|
}
|
|
everProvided = true
|
|
|
|
// A value was found so update the struct value with it.
|
|
if err := processField(value, field.Field); err != nil {
|
|
return &FieldError{
|
|
fieldName: field.Name,
|
|
typeName: field.Field.Type().String(),
|
|
value: value,
|
|
err: err,
|
|
}
|
|
}
|
|
}
|
|
|
|
// If this key is not provided by any source, check if it was
|
|
// required to be provided.
|
|
if !everProvided && field.Options.Required {
|
|
return fmt.Errorf("required field %s is missing value", field.Name)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Usage provides output to display the config usage on the command line.
|
|
func Usage(namespace string, v interface{}) (string, error) {
|
|
fields, err := extractFields(nil, v)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return fmtUsage(namespace, fields), nil
|
|
}
|
|
|
|
// VersionString provides output to display the application version and description on the command line.
|
|
func VersionString(namespace string, v interface{}) (string, error) {
|
|
fields, err := extractFields(nil, v)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
var str strings.Builder
|
|
for i := range fields {
|
|
if fields[i].Name == versionKey && fields[i].Field.Len() > 0 {
|
|
str.WriteString("Version: ")
|
|
str.WriteString(fields[i].Field.String())
|
|
continue
|
|
}
|
|
if fields[i].Name == descKey && fields[i].Field.Len() > 0 {
|
|
if str.Len() > 0 {
|
|
str.WriteString("\n")
|
|
}
|
|
str.WriteString(fields[i].Field.String())
|
|
break
|
|
}
|
|
}
|
|
return str.String(), nil
|
|
}
|
|
|
|
// String returns a stringified version of the provided conf-tagged
|
|
// struct, minus any fields tagged with `noprint`.
|
|
func String(v interface{}) (string, error) {
|
|
fields, err := extractFields(nil, v)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
var s strings.Builder
|
|
for i, fld := range fields {
|
|
if fld.Options.Noprint {
|
|
continue
|
|
}
|
|
|
|
s.WriteString(flagUsage(fld))
|
|
s.WriteString("=")
|
|
v := fmt.Sprintf("%v", fld.Field.Interface())
|
|
|
|
switch {
|
|
case fld.Options.Mask:
|
|
if u, err := url.Parse(v); err == nil {
|
|
userPass := u.User.String()
|
|
if userPass != "" {
|
|
v = strings.Replace(v, userPass, "xxxxxx:xxxxxx", 1)
|
|
s.WriteString(v)
|
|
break
|
|
}
|
|
}
|
|
s.WriteString("xxxxxx")
|
|
|
|
default:
|
|
s.WriteString(v)
|
|
}
|
|
|
|
if i < len(fields)-1 {
|
|
s.WriteString("\n")
|
|
}
|
|
}
|
|
|
|
return s.String(), nil
|
|
}
|
|
|
|
// Args holds command line arguments after flags have been parsed.
|
|
type Args []string
|
|
|
|
// argsT is used by Parse and Usage to detect struct fields of the Args type.
|
|
var argsT = reflect.TypeOf(Args{})
|
|
|
|
// Num returns the i'th argument in the Args slice. It returns an empty string
|
|
// the request element is not present.
|
|
func (a Args) Num(i int) string {
|
|
if i < 0 || i >= len(a) {
|
|
return ""
|
|
}
|
|
return a[i]
|
|
}
|