Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
623 lines (591 sloc) 16.6 KB
package flaggy
import (
"errors"
"fmt"
"net"
"reflect"
"strconv"
"strings"
"time"
)
// Flag holds the base methods for all flag types
type Flag struct {
ShortName string
LongName string
Description string
rawValue string // the value as a string before being parsed
Hidden bool // indicates this flag should be hidden from help and suggestions
AssignmentVar interface{}
defaultValue string // the value (as a string), that was set by default before any parsing and assignment
parsed bool // indicates that this flag has already been parsed
}
// HasName indicates that this flag's short or long name matches the
// supplied name string
func (f *Flag) HasName(name string) bool {
name = strings.TrimSpace(name)
if f.ShortName == name || f.LongName == name {
return true
}
return false
}
// identifyAndAssignValue identifies the type of the incoming value
// and assigns it to the AssignmentVar pointer's target value. If
// the value is a type that needs parsing, that is performed as well.
func (f *Flag) identifyAndAssignValue(value string) error {
var err error
// Only parse this flag default value once. This keeps us from
// overwriting the default value in help output
if !f.parsed {
f.parsed = true
// parse the default value as a string and remember it for help output
f.defaultValue, err = f.returnAssignmentVarValueAsString()
if err != nil {
return err
}
}
debugPrint("attempting to assign value", value, "to flag", f.LongName)
f.rawValue = value // remember the raw value
// depending on the type of the assignment variable, we convert the
// incoming string and assign it. We only use pointers to variables
// in flagy. No returning vars by value.
switch f.AssignmentVar.(type) {
case *string:
v, _ := (f.AssignmentVar).(*string)
*v = value
case *[]string:
v := f.AssignmentVar.(*[]string)
splitString := strings.Split(value, ",")
new := append(*v, splitString...)
*v = new
case *bool:
v, err := strconv.ParseBool(value)
if err != nil {
return err
}
a, _ := (f.AssignmentVar).(*bool)
*a = v
case *[]bool:
// parse the incoming bool
b, err := strconv.ParseBool(value)
if err != nil {
return err
}
// cast the assignment var
existing := f.AssignmentVar.(*[]bool)
// deref the assignment var and append to it
v := append(*existing, b)
// pointer the new value and assign it
a, _ := (f.AssignmentVar).(*[]bool)
*a = v
case *time.Duration:
v, err := time.ParseDuration(value)
if err != nil {
return err
}
a, _ := (f.AssignmentVar).(*time.Duration)
*a = v
case *[]time.Duration:
t, err := time.ParseDuration(value)
if err != nil {
return err
}
existing := f.AssignmentVar.(*[]time.Duration)
// deref the assignment var and append to it
v := append(*existing, t)
// pointer the new value and assign it
a, _ := (f.AssignmentVar).(*[]time.Duration)
*a = v
case *float32:
v, err := strconv.ParseFloat(value, 32)
if err != nil {
return err
}
float := float32(v)
a, _ := (f.AssignmentVar).(*float32)
*a = float
case *[]float32:
v, err := strconv.ParseFloat(value, 32)
if err != nil {
return err
}
float := float32(v)
existing := f.AssignmentVar.(*[]float32)
new := append(*existing, float)
*existing = new
case *float64:
v, err := strconv.ParseFloat(value, 64)
if err != nil {
return err
}
a, _ := (f.AssignmentVar).(*float64)
*a = v
case *[]float64:
v, err := strconv.ParseFloat(value, 64)
if err != nil {
return err
}
existing := f.AssignmentVar.(*[]float64)
new := append(*existing, v)
*existing = new
case *int:
v, err := strconv.Atoi(value)
if err != nil {
return err
}
e := f.AssignmentVar.(*int)
*e = v
case *[]int:
v, err := strconv.Atoi(value)
if err != nil {
return err
}
existing := f.AssignmentVar.(*[]int)
new := append(*existing, v)
*existing = new
case *uint:
v, err := strconv.ParseUint(value, 10, 64)
if err != nil {
return err
}
existing := f.AssignmentVar.(*uint)
*existing = uint(v)
case *[]uint:
v, err := strconv.ParseUint(value, 10, 64)
if err != nil {
return err
}
existing := f.AssignmentVar.(*[]uint)
new := append(*existing, uint(v))
*existing = new
case *uint64:
v, err := strconv.ParseUint(value, 10, 64)
if err != nil {
return err
}
existing := f.AssignmentVar.(*uint64)
*existing = v
case *[]uint64:
v, err := strconv.ParseUint(value, 10, 64)
if err != nil {
return err
}
existing := f.AssignmentVar.(*[]uint64)
new := append(*existing, v)
*existing = new
case *uint32:
v, err := strconv.ParseUint(value, 10, 32)
if err != nil {
return err
}
existing := f.AssignmentVar.(*uint32)
*existing = uint32(v)
case *[]uint32:
v, err := strconv.ParseUint(value, 10, 32)
if err != nil {
return err
}
existing := f.AssignmentVar.(*[]uint32)
new := append(*existing, uint32(v))
*existing = new
case *uint16:
v, err := strconv.ParseUint(value, 10, 16)
if err != nil {
return err
}
val := uint16(v)
existing := f.AssignmentVar.(*uint16)
*existing = val
case *[]uint16:
v, err := strconv.ParseUint(value, 10, 16)
if err != nil {
return err
}
existing := f.AssignmentVar.(*[]uint16)
new := append(*existing, uint16(v))
*existing = new
case *uint8:
v, err := strconv.ParseUint(value, 10, 8)
if err != nil {
return err
}
val := uint8(v)
existing := f.AssignmentVar.(*uint8)
*existing = val
case *[]uint8:
var newSlice []uint8
v, err := strconv.ParseUint(value, 10, 8)
if err != nil {
return err
}
newV := uint8(v)
existing := f.AssignmentVar.(*[]uint8)
newSlice = append(*existing, newV)
*existing = newSlice
case *int64:
v, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return err
}
existing := f.AssignmentVar.(*int64)
*existing = v
case *[]int64:
v, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return err
}
existingSlice := f.AssignmentVar.(*[]int64)
newSlice := append(*existingSlice, v)
*existingSlice = newSlice
case *int32:
v, err := strconv.ParseInt(value, 10, 32)
if err != nil {
return err
}
converted := int32(v)
existing := f.AssignmentVar.(*int32)
*existing = converted
case *[]int32:
v, err := strconv.ParseInt(value, 10, 32)
if err != nil {
return err
}
existingSlice := f.AssignmentVar.(*[]int32)
newSlice := append(*existingSlice, int32(v))
*existingSlice = newSlice
case *int16:
v, err := strconv.ParseInt(value, 10, 16)
if err != nil {
return err
}
converted := int16(v)
existing := f.AssignmentVar.(*int16)
*existing = converted
case *[]int16:
v, err := strconv.ParseInt(value, 10, 16)
if err != nil {
return err
}
existingSlice := f.AssignmentVar.(*[]int16)
newSlice := append(*existingSlice, int16(v))
*existingSlice = newSlice
case *int8:
v, err := strconv.ParseInt(value, 10, 8)
if err != nil {
return err
}
converted := int8(v)
existing := f.AssignmentVar.(*int8)
*existing = converted
case *[]int8:
v, err := strconv.ParseInt(value, 10, 8)
if err != nil {
return err
}
existingSlice := f.AssignmentVar.(*[]int8)
newSlice := append(*existingSlice, int8(v))
*existingSlice = newSlice
case *net.IP:
v := net.ParseIP(value)
existing := f.AssignmentVar.(*net.IP)
*existing = v
case *[]net.IP:
v := net.ParseIP(value)
existing := f.AssignmentVar.(*[]net.IP)
new := append(*existing, v)
*existing = new
case *net.HardwareAddr:
v, err := net.ParseMAC(value)
if err != nil {
return err
}
existing := f.AssignmentVar.(*net.HardwareAddr)
*existing = v
case *[]net.HardwareAddr:
v, err := net.ParseMAC(value)
if err != nil {
return err
}
existing := f.AssignmentVar.(*[]net.HardwareAddr)
new := append(*existing, v)
*existing = new
case *net.IPMask:
v := net.IPMask(net.ParseIP(value).To4())
existing := f.AssignmentVar.(*net.IPMask)
*existing = v
case *[]net.IPMask:
v := net.IPMask(net.ParseIP(value).To4())
existing := f.AssignmentVar.(*[]net.IPMask)
new := append(*existing, v)
*existing = new
default:
return errors.New("Unknown flag assignmentVar supplied in flag " + f.LongName + " " + f.ShortName)
}
return err
}
const argIsPositional = "positional" // subcommand or positional value
const argIsFlagWithSpace = "flagWithSpace" // -f path or --file path
const argIsFlagWithValue = "flagWithValue" // -f=path or --file=path
const argIsFinal = "final" // the final argument only '--'
// determineArgType determines if the specified arg is a flag with space
// separated value, a flag with a connected value, or neither (positional)
func determineArgType(arg string) string {
// if the arg is --, then its the final arg
if arg == "--" {
return argIsFinal
}
// if it has the prefix --, then its a long flag
if strings.HasPrefix(arg, "--") {
// if it contains an equals, it is a joined value
if strings.Contains(arg, "=") {
return argIsFlagWithValue
}
return argIsFlagWithSpace
}
// if it has the prefix -, then its a short flag
if strings.HasPrefix(arg, "-") {
// if it contains an equals, it is a joined value
if strings.Contains(arg, "=") {
return argIsFlagWithValue
}
return argIsFlagWithSpace
}
return argIsPositional
}
// parseArgWithValue parses a key=value concatenated argument into a key and
// value
func parseArgWithValue(arg string) (key string, value string) {
// remove up to two minuses from start of flag
arg = strings.TrimPrefix(arg, "-")
arg = strings.TrimPrefix(arg, "-")
// debugPrint("parseArgWithValue parsing", arg)
// break at the equals
args := strings.SplitN(arg, "=", 2)
// if its a bool arg, with no explicit value, we return a blank
if len(args) == 1 {
return args[0], ""
}
// if its a key and value pair, we return those
if len(args) == 2 {
// debugPrint("parseArgWithValue parsed", args[0], args[1])
return args[0], args[1]
}
fmt.Println("Warning: attempted to parseArgWithValue but did not have correct parameter count.", arg, "->", args)
return "", ""
}
// parseFlagToName parses a flag with space value down to a key name:
// --path -> path
// -p -> p
func parseFlagToName(arg string) string {
// remove minus from start
arg = strings.TrimLeft(arg, "-")
arg = strings.TrimLeft(arg, "-")
return arg
}
// flagIsBool determines if the flag is a bool within the specified parser
// and subcommand's context
func flagIsBool(sc *Subcommand, p *Parser, key string) bool {
for _, f := range append(sc.Flags, p.Flags...) {
if f.HasName(key) {
_, isBool := f.AssignmentVar.(*bool)
_, isBoolSlice := f.AssignmentVar.(*[]bool)
if isBool || isBoolSlice {
return true
}
}
}
// by default, the answer is false
return false
}
// returnAssignmentVarValueAsString returns the value of the flag's
// assignment variable as a string. This is used to display the
// default value of flags before they are assigned (like when help is output).
func (f *Flag) returnAssignmentVarValueAsString() (string, error) {
debugPrint("returning current value of assignment var of flag", f.LongName)
var err error
// depending on the type of the assignment variable, we convert the
// incoming string and assign it. We only use pointers to variables
// in flagy. No returning vars by value.
switch f.AssignmentVar.(type) {
case *string:
v, _ := (f.AssignmentVar).(*string)
return *v, err
case *[]string:
v := f.AssignmentVar.(*[]string)
return strings.Join(*v, ","), err
case *bool:
a, _ := (f.AssignmentVar).(*bool)
return strconv.FormatBool(*a), err
case *[]bool:
value := f.AssignmentVar.(*[]bool)
var ss []string
for _, b := range *value {
ss = append(ss, strconv.FormatBool(b))
}
return strings.Join(ss, ","), err
case *time.Duration:
a := f.AssignmentVar.(*time.Duration)
return (*a).String(), err
case *[]time.Duration:
tds := f.AssignmentVar.(*[]time.Duration)
var asSlice []string
for _, td := range *tds {
asSlice = append(asSlice, td.String())
}
return strings.Join(asSlice, ","), err
case *float32:
a := f.AssignmentVar.(*float32)
return strconv.FormatFloat(float64(*a), 'f', 2, 32), err
case *[]float32:
v := f.AssignmentVar.(*[]float32)
var strSlice []string
for _, f := range *v {
formatted := strconv.FormatFloat(float64(f), 'f', 2, 32)
strSlice = append(strSlice, formatted)
}
return strings.Join(strSlice, ","), err
case *float64:
a := f.AssignmentVar.(*float64)
return strconv.FormatFloat(float64(*a), 'f', 2, 64), err
case *[]float64:
v := f.AssignmentVar.(*[]float64)
var strSlice []string
for _, f := range *v {
formatted := strconv.FormatFloat(float64(f), 'f', 2, 64)
strSlice = append(strSlice, formatted)
}
return strings.Join(strSlice, ","), err
case *int:
a := f.AssignmentVar.(*int)
return strconv.Itoa(*a), err
case *[]int:
val := f.AssignmentVar.(*[]int)
var strSlice []string
for _, i := range *val {
str := strconv.Itoa(i)
strSlice = append(strSlice, str)
}
return strings.Join(strSlice, ","), err
case *uint:
v := f.AssignmentVar.(*uint)
return strconv.FormatUint(uint64(*v), 10), err
case *[]uint:
values := f.AssignmentVar.(*[]uint)
var strVars []string
for _, i := range *values {
strVars = append(strVars, strconv.FormatUint(uint64(i), 10))
}
return strings.Join(strVars, ","), err
case *uint64:
v := f.AssignmentVar.(*uint64)
return strconv.FormatUint(*v, 10), err
case *[]uint64:
values := f.AssignmentVar.(*[]uint64)
var strVars []string
for _, i := range *values {
strVars = append(strVars, strconv.FormatUint(i, 10))
}
return strings.Join(strVars, ","), err
case *uint32:
v := f.AssignmentVar.(*uint32)
return strconv.FormatUint(uint64(*v), 10), err
case *[]uint32:
values := f.AssignmentVar.(*[]uint32)
var strVars []string
for _, i := range *values {
strVars = append(strVars, strconv.FormatUint(uint64(i), 10))
}
return strings.Join(strVars, ","), err
case *uint16:
v := f.AssignmentVar.(*uint16)
return strconv.FormatUint(uint64(*v), 10), err
case *[]uint16:
values := f.AssignmentVar.(*[]uint16)
var strVars []string
for _, i := range *values {
strVars = append(strVars, strconv.FormatUint(uint64(i), 10))
}
return strings.Join(strVars, ","), err
case *uint8:
v := f.AssignmentVar.(*uint8)
return strconv.FormatUint(uint64(*v), 10), err
case *[]uint8:
values := f.AssignmentVar.(*[]uint8)
var strVars []string
for _, i := range *values {
strVars = append(strVars, strconv.FormatUint(uint64(i), 10))
}
return strings.Join(strVars, ","), err
case *int64:
v := f.AssignmentVar.(*int64)
return strconv.FormatInt(int64(*v), 10), err
case *[]int64:
values := f.AssignmentVar.(*[]int64)
var strVars []string
for _, i := range *values {
strVars = append(strVars, strconv.FormatInt(i, 10))
}
return strings.Join(strVars, ","), err
case *int32:
v := f.AssignmentVar.(*int32)
return strconv.FormatInt(int64(*v), 10), err
case *[]int32:
values := f.AssignmentVar.(*[]int32)
var strVars []string
for _, i := range *values {
strVars = append(strVars, strconv.FormatInt(int64(i), 10))
}
return strings.Join(strVars, ","), err
case *int16:
v := f.AssignmentVar.(*int16)
return strconv.FormatInt(int64(*v), 10), err
case *[]int16:
values := f.AssignmentVar.(*[]int16)
var strVars []string
for _, i := range *values {
strVars = append(strVars, strconv.FormatInt(int64(i), 10))
}
return strings.Join(strVars, ","), err
case *int8:
v := f.AssignmentVar.(*int8)
return strconv.FormatInt(int64(*v), 10), err
case *[]int8:
values := f.AssignmentVar.(*[]int8)
var strVars []string
for _, i := range *values {
strVars = append(strVars, strconv.FormatInt(int64(i), 10))
}
return strings.Join(strVars, ","), err
case *net.IP:
val := f.AssignmentVar.(*net.IP)
return val.String(), err
case *[]net.IP:
val := f.AssignmentVar.(*[]net.IP)
var strSlice []string
for _, ip := range *val {
strSlice = append(strSlice, ip.String())
}
return strings.Join(strSlice, ","), err
case *net.HardwareAddr:
val := f.AssignmentVar.(*net.HardwareAddr)
return val.String(), err
case *[]net.HardwareAddr:
val := f.AssignmentVar.(*[]net.HardwareAddr)
var strSlice []string
for _, mac := range *val {
strSlice = append(strSlice, mac.String())
}
return strings.Join(strSlice, ","), err
case *net.IPMask:
val := f.AssignmentVar.(*net.IPMask)
return val.String(), err
case *[]net.IPMask:
val := f.AssignmentVar.(*[]net.IPMask)
var strSlice []string
for _, m := range *val {
strSlice = append(strSlice, m.String())
}
return strings.Join(strSlice, ","), err
default:
return "", errors.New("Unknown flag assignmentVar found in flag " + f.LongName + " " + f.ShortName + ". Type not supported: " + reflect.TypeOf(f.AssignmentVar).String())
}
}
You can’t perform that action at this time.