thrawn01/args

View on GitHub
options.go

Summary

Maintainability
B
5 hrs
Test Coverage
package args

import (
    "bytes"
    "errors"
    "fmt"
    "os/user"
    "path/filepath"
    "reflect"
    "sort"
    "strings"

    "github.com/spf13/cast"
)

type Value interface {
    ToString(...int) string
    GetValue() interface{}
    GetRule() *Rule
    Seen() bool
}

type Options struct {
    log    StdLogger
    parser *ArgParser
    values map[string]Value
}

type RawValue struct {
    Value interface{}
    Rule  *Rule
}

func (self *RawValue) ToString(indent ...int) string {
    return fmt.Sprintf("%v", self.Value)
}

func (self *RawValue) GetValue() interface{} {
    return self.Value
}

func (self *RawValue) GetRule() *Rule {
    return self.Rule
}

func (self *RawValue) Seen() bool {
    if self.Rule.Flags&Seen != 0 {
        return true
    }
    return false
}

func (self *ArgParser) NewOptions() *Options {
    return &Options{
        values: make(map[string]Value),
        log:    self.log,
        parser: self,
    }
}

func (self *ArgParser) NewOptionsFromMap(values map[string]interface{}) *Options {
    options := self.NewOptions()
    for key, value := range values {
        // If the value is a map of interfaces
        obj, ok := value.(map[string]interface{})
        if ok {
            // Convert them to options
            options.SetWithOptions(key, self.NewOptionsFromMap(obj))
        } else {
            // Else set the value
            options.Set(key, value)
        }
    }
    return options
}

func (self *Options) GetOpts() *Options {
    return self.parser.GetOpts()
}

func (self *Options) GetValue() interface{} {
    return self
}

func (self *Options) GetRule() *Rule {
    return nil
}

func (self *Options) ToString(indented ...int) string {
    var buffer bytes.Buffer
    indent := 2
    if len(indented) != 0 {
        indent = indented[0]
    }

    buffer.WriteString("{\n")
    pad := strings.Repeat(" ", indent)

    // Sort the values so testing is consistent
    var keys []string
    for key := range self.values {
        keys = append(keys, key)
    }
    sort.Strings(keys)

    for _, key := range keys {
        buffer.WriteString(fmt.Sprintf("%s'%s' = %s\n", pad, key, self.values[key].ToString(indent+2)))
    }
    buffer.WriteString(pad[2:] + "}")
    return buffer.String()
}

func (self *Options) Group(key string) *Options {
    // "" is not a valid group
    if key == "" {
        return self
    }

    group, ok := self.values[key]
    // If group doesn't exist; always create it
    if !ok {
        group = self.parser.NewOptions()
        self.values[key] = group
    }
    // If user called Group() on this value, it *should* be an
    // *Option, map[string]string or map[string]interface{}
    options := self.ToOption(group.GetValue())
    if options == nil {
        self.log.Printf("Attempted to call Group(%s) on non *Option or map[string]interface type %s",
            key, reflect.TypeOf(group.GetValue()))
        // Do this so we don't panic if we can't cast this group
        options = self.parser.NewOptions()
    }
    return options
}

// Given an interface of map[string]string or map[string]string
// or *Option return an *Options with the same content.
// return nil if un-successful
func (self *Options) ToOption(from interface{}) *Options {
    if options, ok := from.(*Options); ok {
        return options
    }
    if stringMap, ok := from.(map[string]string); ok {
        result := make(map[string]interface{})
        for key, value := range stringMap {
            result[key] = value
        }
        return self.parser.NewOptionsFromMap(result)
    }

    if interfaceMap, ok := from.(map[string]interface{}); ok {
        return self.parser.NewOptionsFromMap(interfaceMap)
    }
    return nil
}

func (self *Options) ToMap() map[string]interface{} {
    result := make(map[string]interface{})
    for key, value := range self.values {
        // If the value is an *Option
        options, ok := value.(*Options)
        if ok {
            result[key] = options.ToMap()
        } else {
            result[key] = value.GetValue()
        }
    }
    return result
}

func (self *Options) Keys() []string {
    keys := make([]string, 0, len(self.values))
    for key := range self.values {
        keys = append(keys, key)
    }
    return keys
}

func (self *Options) Del(key string) *Options {
    delete(self.values, key)
    return self
}

func (self *Options) SetWithOptions(key string, value *Options) *Options {
    self.values[key] = value
    return self
}

// Just like Set() but also record the matching rule flags
func (self *Options) SetWithRule(key string, value interface{}, rule *Rule) *Options {
    self.values[key] = &RawValue{value, rule}
    return self
}

// Set an option with a key and value
func (self *Options) Set(key string, value interface{}) *Options {
    return self.SetWithRule(key, value, nil)
}

// Return true if any of the values in this Option object were seen on the command line
func (self *Options) Seen() bool {
    for _, opt := range self.values {
        if opt.Seen() {
            return true
        }
    }
    return false
}

/*
    Return true if none of the options where seen on the command line

    opts, _ := parser.Parse(nil)
    if opts.NoArgs() {
        fmt.Printf("No arguments provided")
        os.Exit(-1)
    }
*/
func (self *Options) NoArgs() bool {
    return !self.Seen()
}

func (self *Options) Int(key string) int {
    value, err := cast.ToIntE(self.Interface(key))
    if err != nil {
        self.log.Printf("%s for key '%s'", err.Error(), key)
    }
    return value
}

func (self *Options) String(key string) string {
    value, err := cast.ToStringE(self.Interface(key))
    if err != nil {
        self.log.Printf("%s for key '%s'", err.Error(), key)
    }
    return value
}

// Assumes the option is a string path and performs tilde '~' expansion if necessary
func (self *Options) FilePath(key string) string {
    path, err := cast.ToStringE(self.Interface(key))
    if err != nil {
        self.log.Printf("%s for key '%s'", err.Error(), key)
    }

    if len(path) == 0 || path[0] != '~' {
        return path
    }

    usr, err := user.Current()
    if err != nil {
        self.log.Printf("'%s': while determining user for '%s' expansion: %s", key, path, err)
        return path
    }
    return filepath.Join(usr.HomeDir, path[1:])
}

func (self *Options) Bool(key string) bool {
    value, err := cast.ToBoolE(self.Interface(key))
    if err != nil {
        self.log.Printf("%s for key '%s'", err.Error(), key)
    }
    return value
}

func (self *Options) StringSlice(key string) []string {
    value, err := cast.ToStringSliceE(self.Interface(key))
    if err != nil {
        self.log.Printf("%s for key '%s'", err.Error(), key)
    }
    return value
}

func (self *Options) StringMap(key string) map[string]string {
    group := self.Group(key)

    result := make(map[string]string)
    for _, key := range group.Keys() {
        result[key] = group.String(key)
    }
    return result
}

func (self *Options) KeySlice(key string) []string {
    return self.Group(key).Keys()
}

// Returns true if the argument value is set.
// Use IsDefault(), IsEnv(), IsArg() to determine how the parser set the value
func (self *Options) IsSet(key string) bool {
    if opt, ok := self.values[key]; ok {
        rule := opt.GetRule()
        if rule == nil {
            return false
        }
        return !(rule.Flags&NoValue != 0)
    }
    return false
}

// Returns true if this argument is set via the environment
func (self *Options) IsEnv(key string) bool {
    if opt, ok := self.values[key]; ok {
        rule := opt.GetRule()
        if rule == nil {
            return false
        }
        return (rule.Flags&EnvValue != 0)
    }
    return false
}

// Returns true if this argument is set via the command line
func (self *Options) IsArg(key string) bool {
    if opt, ok := self.values[key]; ok {
        rule := opt.GetRule()
        if rule == nil {
            return false
        }
        return (rule.Flags&Seen != 0)
    }
    return false
}

// Returns true if this argument is set via the default value
func (self *Options) IsDefault(key string) bool {
    if opt, ok := self.values[key]; ok {
        rule := opt.GetRule()
        if rule == nil {
            return false
        }
        return (rule.Flags&DefaultValue != 0)
    }
    return false
}

// Returns true if this argument was set via the command line or was set by an environment variable
func (self *Options) WasSeen(key string) bool {
    if opt, ok := self.values[key]; ok {
        rule := opt.GetRule()
        if rule == nil {
            return false
        }
        return (rule.Flags&Seen != 0) || (rule.Flags&EnvValue != 0)
    }
    return false
}

// Returns true only if all of the keys given have values set
func (self *Options) Required(keys []string) error {
    for _, key := range keys {
        if !self.IsSet(key) {
            return errors.New(key)
        }
    }
    return nil
}

func (self *Options) HasKey(key string) bool {
    _, ok := self.values[key]
    return ok
}

func (self *Options) Get(key string) interface{} {
    if opt, ok := self.values[key]; ok {
        return opt.GetValue()
    }
    return nil
}

func (self *Options) InspectOpt(key string) Value {
    if opt, ok := self.values[key]; ok {
        return opt
    }
    return nil
}

func (self *Options) Interface(key string) interface{} {
    if opt, ok := self.values[key]; ok {
        return opt.GetValue()
    }
    return nil
}

func (self *Options) FromChangeEvent(event *ChangeEvent) *Options {
    if event.Deleted {
        self.Group(event.Group).Del(event.KeyName)
    } else {
        self.Group(event.Group).Set(event.KeyName, string(event.Value))
    }
    return self
}

// TODO: Add these getters
/*Float64(key string) : float64
Time(key string) : time.Time
Duration(key string) : time.Duration*/