mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-12-23 10:42:12 +00:00
570 lines
12 KiB
Go
570 lines
12 KiB
Go
|
package flags
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"fmt"
|
||
|
"os"
|
||
|
"reflect"
|
||
|
"strings"
|
||
|
"unicode/utf8"
|
||
|
)
|
||
|
|
||
|
// Option flag information. Contains a description of the option, short and
|
||
|
// long name as well as a default value and whether an argument for this
|
||
|
// flag is optional.
|
||
|
type Option struct {
|
||
|
// The description of the option flag. This description is shown
|
||
|
// automatically in the built-in help.
|
||
|
Description string
|
||
|
|
||
|
// The short name of the option (a single character). If not 0, the
|
||
|
// option flag can be 'activated' using -<ShortName>. Either ShortName
|
||
|
// or LongName needs to be non-empty.
|
||
|
ShortName rune
|
||
|
|
||
|
// The long name of the option. If not "", the option flag can be
|
||
|
// activated using --<LongName>. Either ShortName or LongName needs
|
||
|
// to be non-empty.
|
||
|
LongName string
|
||
|
|
||
|
// The default value of the option.
|
||
|
Default []string
|
||
|
|
||
|
// The optional environment default value key name.
|
||
|
EnvDefaultKey string
|
||
|
|
||
|
// The optional delimiter string for EnvDefaultKey values.
|
||
|
EnvDefaultDelim string
|
||
|
|
||
|
// If true, specifies that the argument to an option flag is optional.
|
||
|
// When no argument to the flag is specified on the command line, the
|
||
|
// value of OptionalValue will be set in the field this option represents.
|
||
|
// This is only valid for non-boolean options.
|
||
|
OptionalArgument bool
|
||
|
|
||
|
// The optional value of the option. The optional value is used when
|
||
|
// the option flag is marked as having an OptionalArgument. This means
|
||
|
// that when the flag is specified, but no option argument is given,
|
||
|
// the value of the field this option represents will be set to
|
||
|
// OptionalValue. This is only valid for non-boolean options.
|
||
|
OptionalValue []string
|
||
|
|
||
|
// If true, the option _must_ be specified on the command line. If the
|
||
|
// option is not specified, the parser will generate an ErrRequired type
|
||
|
// error.
|
||
|
Required bool
|
||
|
|
||
|
// A name for the value of an option shown in the Help as --flag [ValueName]
|
||
|
ValueName string
|
||
|
|
||
|
// A mask value to show in the help instead of the default value. This
|
||
|
// is useful for hiding sensitive information in the help, such as
|
||
|
// passwords.
|
||
|
DefaultMask string
|
||
|
|
||
|
// If non empty, only a certain set of values is allowed for an option.
|
||
|
Choices []string
|
||
|
|
||
|
// If true, the option is not displayed in the help or man page
|
||
|
Hidden bool
|
||
|
|
||
|
// The group which the option belongs to
|
||
|
group *Group
|
||
|
|
||
|
// The struct field which the option represents.
|
||
|
field reflect.StructField
|
||
|
|
||
|
// The struct field value which the option represents.
|
||
|
value reflect.Value
|
||
|
|
||
|
// Determines if the option will be always quoted in the INI output
|
||
|
iniQuote bool
|
||
|
|
||
|
tag multiTag
|
||
|
isSet bool
|
||
|
isSetDefault bool
|
||
|
preventDefault bool
|
||
|
clearReferenceBeforeSet bool
|
||
|
|
||
|
defaultLiteral string
|
||
|
}
|
||
|
|
||
|
// LongNameWithNamespace returns the option's long name with the group namespaces
|
||
|
// prepended by walking up the option's group tree. Namespaces and the long name
|
||
|
// itself are separated by the parser's namespace delimiter. If the long name is
|
||
|
// empty an empty string is returned.
|
||
|
func (option *Option) LongNameWithNamespace() string {
|
||
|
if len(option.LongName) == 0 {
|
||
|
return ""
|
||
|
}
|
||
|
|
||
|
// fetch the namespace delimiter from the parser which is always at the
|
||
|
// end of the group hierarchy
|
||
|
namespaceDelimiter := ""
|
||
|
g := option.group
|
||
|
|
||
|
for {
|
||
|
if p, ok := g.parent.(*Parser); ok {
|
||
|
namespaceDelimiter = p.NamespaceDelimiter
|
||
|
|
||
|
break
|
||
|
}
|
||
|
|
||
|
switch i := g.parent.(type) {
|
||
|
case *Command:
|
||
|
g = i.Group
|
||
|
case *Group:
|
||
|
g = i
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// concatenate long name with namespace
|
||
|
longName := option.LongName
|
||
|
g = option.group
|
||
|
|
||
|
for g != nil {
|
||
|
if g.Namespace != "" {
|
||
|
longName = g.Namespace + namespaceDelimiter + longName
|
||
|
}
|
||
|
|
||
|
switch i := g.parent.(type) {
|
||
|
case *Command:
|
||
|
g = i.Group
|
||
|
case *Group:
|
||
|
g = i
|
||
|
case *Parser:
|
||
|
g = nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return longName
|
||
|
}
|
||
|
|
||
|
// EnvKeyWithNamespace returns the option's env key with the group namespaces
|
||
|
// prepended by walking up the option's group tree. Namespaces and the env key
|
||
|
// itself are separated by the parser's namespace delimiter. If the env key is
|
||
|
// empty an empty string is returned.
|
||
|
func (option *Option) EnvKeyWithNamespace() string {
|
||
|
if len(option.EnvDefaultKey) == 0 {
|
||
|
return ""
|
||
|
}
|
||
|
|
||
|
// fetch the namespace delimiter from the parser which is always at the
|
||
|
// end of the group hierarchy
|
||
|
namespaceDelimiter := ""
|
||
|
g := option.group
|
||
|
|
||
|
for {
|
||
|
if p, ok := g.parent.(*Parser); ok {
|
||
|
namespaceDelimiter = p.EnvNamespaceDelimiter
|
||
|
|
||
|
break
|
||
|
}
|
||
|
|
||
|
switch i := g.parent.(type) {
|
||
|
case *Command:
|
||
|
g = i.Group
|
||
|
case *Group:
|
||
|
g = i
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// concatenate long name with namespace
|
||
|
key := option.EnvDefaultKey
|
||
|
g = option.group
|
||
|
|
||
|
for g != nil {
|
||
|
if g.EnvNamespace != "" {
|
||
|
key = g.EnvNamespace + namespaceDelimiter + key
|
||
|
}
|
||
|
|
||
|
switch i := g.parent.(type) {
|
||
|
case *Command:
|
||
|
g = i.Group
|
||
|
case *Group:
|
||
|
g = i
|
||
|
case *Parser:
|
||
|
g = nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return key
|
||
|
}
|
||
|
|
||
|
// String converts an option to a human friendly readable string describing the
|
||
|
// option.
|
||
|
func (option *Option) String() string {
|
||
|
var s string
|
||
|
var short string
|
||
|
|
||
|
if option.ShortName != 0 {
|
||
|
data := make([]byte, utf8.RuneLen(option.ShortName))
|
||
|
utf8.EncodeRune(data, option.ShortName)
|
||
|
short = string(data)
|
||
|
|
||
|
if len(option.LongName) != 0 {
|
||
|
s = fmt.Sprintf("%s%s, %s%s",
|
||
|
string(defaultShortOptDelimiter), short,
|
||
|
defaultLongOptDelimiter, option.LongNameWithNamespace())
|
||
|
} else {
|
||
|
s = fmt.Sprintf("%s%s", string(defaultShortOptDelimiter), short)
|
||
|
}
|
||
|
} else if len(option.LongName) != 0 {
|
||
|
s = fmt.Sprintf("%s%s", defaultLongOptDelimiter, option.LongNameWithNamespace())
|
||
|
}
|
||
|
|
||
|
return s
|
||
|
}
|
||
|
|
||
|
// Value returns the option value as an interface{}.
|
||
|
func (option *Option) Value() interface{} {
|
||
|
return option.value.Interface()
|
||
|
}
|
||
|
|
||
|
// Field returns the reflect struct field of the option.
|
||
|
func (option *Option) Field() reflect.StructField {
|
||
|
return option.field
|
||
|
}
|
||
|
|
||
|
// IsSet returns true if option has been set
|
||
|
func (option *Option) IsSet() bool {
|
||
|
return option.isSet
|
||
|
}
|
||
|
|
||
|
// IsSetDefault returns true if option has been set via the default option tag
|
||
|
func (option *Option) IsSetDefault() bool {
|
||
|
return option.isSetDefault
|
||
|
}
|
||
|
|
||
|
// Set the value of an option to the specified value. An error will be returned
|
||
|
// if the specified value could not be converted to the corresponding option
|
||
|
// value type.
|
||
|
func (option *Option) set(value *string) error {
|
||
|
kind := option.value.Type().Kind()
|
||
|
|
||
|
if (kind == reflect.Map || kind == reflect.Slice) && option.clearReferenceBeforeSet {
|
||
|
option.empty()
|
||
|
}
|
||
|
|
||
|
option.isSet = true
|
||
|
option.preventDefault = true
|
||
|
option.clearReferenceBeforeSet = false
|
||
|
|
||
|
if len(option.Choices) != 0 {
|
||
|
found := false
|
||
|
|
||
|
for _, choice := range option.Choices {
|
||
|
if choice == *value {
|
||
|
found = true
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if !found {
|
||
|
allowed := strings.Join(option.Choices[0:len(option.Choices)-1], ", ")
|
||
|
|
||
|
if len(option.Choices) > 1 {
|
||
|
allowed += " or " + option.Choices[len(option.Choices)-1]
|
||
|
}
|
||
|
|
||
|
return newErrorf(ErrInvalidChoice,
|
||
|
"Invalid value `%s' for option `%s'. Allowed values are: %s",
|
||
|
*value, option, allowed)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if option.isFunc() {
|
||
|
return option.call(value)
|
||
|
} else if value != nil {
|
||
|
return convert(*value, option.value, option.tag)
|
||
|
}
|
||
|
|
||
|
return convert("", option.value, option.tag)
|
||
|
}
|
||
|
|
||
|
func (option *Option) setDefault(value *string) error {
|
||
|
if option.preventDefault {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
if err := option.set(value); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
option.isSetDefault = true
|
||
|
option.preventDefault = false
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (option *Option) showInHelp() bool {
|
||
|
return !option.Hidden && (option.ShortName != 0 || len(option.LongName) != 0)
|
||
|
}
|
||
|
|
||
|
func (option *Option) canArgument() bool {
|
||
|
if u := option.isUnmarshaler(); u != nil {
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
return !option.isBool()
|
||
|
}
|
||
|
|
||
|
func (option *Option) emptyValue() reflect.Value {
|
||
|
tp := option.value.Type()
|
||
|
|
||
|
if tp.Kind() == reflect.Map {
|
||
|
return reflect.MakeMap(tp)
|
||
|
}
|
||
|
|
||
|
return reflect.Zero(tp)
|
||
|
}
|
||
|
|
||
|
func (option *Option) empty() {
|
||
|
if !option.isFunc() {
|
||
|
option.value.Set(option.emptyValue())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (option *Option) clearDefault() error {
|
||
|
if option.preventDefault {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
usedDefault := option.Default
|
||
|
|
||
|
if envKey := option.EnvKeyWithNamespace(); envKey != "" {
|
||
|
if value, ok := os.LookupEnv(envKey); ok {
|
||
|
if option.EnvDefaultDelim != "" {
|
||
|
usedDefault = strings.Split(value, option.EnvDefaultDelim)
|
||
|
} else {
|
||
|
usedDefault = []string{value}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
option.isSetDefault = true
|
||
|
|
||
|
if len(usedDefault) > 0 {
|
||
|
option.empty()
|
||
|
|
||
|
for _, d := range usedDefault {
|
||
|
err := option.setDefault(&d)
|
||
|
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
tp := option.value.Type()
|
||
|
|
||
|
switch tp.Kind() {
|
||
|
case reflect.Map:
|
||
|
if option.value.IsNil() {
|
||
|
option.empty()
|
||
|
}
|
||
|
case reflect.Slice:
|
||
|
if option.value.IsNil() {
|
||
|
option.empty()
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (option *Option) valueIsDefault() bool {
|
||
|
// Check if the value of the option corresponds to its
|
||
|
// default value
|
||
|
emptyval := option.emptyValue()
|
||
|
|
||
|
checkvalptr := reflect.New(emptyval.Type())
|
||
|
checkval := reflect.Indirect(checkvalptr)
|
||
|
|
||
|
checkval.Set(emptyval)
|
||
|
|
||
|
if len(option.Default) != 0 {
|
||
|
for _, v := range option.Default {
|
||
|
convert(v, checkval, option.tag)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return reflect.DeepEqual(option.value.Interface(), checkval.Interface())
|
||
|
}
|
||
|
|
||
|
func (option *Option) isUnmarshaler() Unmarshaler {
|
||
|
v := option.value
|
||
|
|
||
|
for {
|
||
|
if !v.CanInterface() {
|
||
|
break
|
||
|
}
|
||
|
|
||
|
i := v.Interface()
|
||
|
|
||
|
if u, ok := i.(Unmarshaler); ok {
|
||
|
return u
|
||
|
}
|
||
|
|
||
|
if !v.CanAddr() {
|
||
|
break
|
||
|
}
|
||
|
|
||
|
v = v.Addr()
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (option *Option) isValueValidator() ValueValidator {
|
||
|
v := option.value
|
||
|
|
||
|
for {
|
||
|
if !v.CanInterface() {
|
||
|
break
|
||
|
}
|
||
|
|
||
|
i := v.Interface()
|
||
|
|
||
|
if u, ok := i.(ValueValidator); ok {
|
||
|
return u
|
||
|
}
|
||
|
|
||
|
if !v.CanAddr() {
|
||
|
break
|
||
|
}
|
||
|
|
||
|
v = v.Addr()
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (option *Option) isBool() bool {
|
||
|
tp := option.value.Type()
|
||
|
|
||
|
for {
|
||
|
switch tp.Kind() {
|
||
|
case reflect.Slice, reflect.Ptr:
|
||
|
tp = tp.Elem()
|
||
|
case reflect.Bool:
|
||
|
return true
|
||
|
case reflect.Func:
|
||
|
return tp.NumIn() == 0
|
||
|
default:
|
||
|
return false
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (option *Option) isSignedNumber() bool {
|
||
|
tp := option.value.Type()
|
||
|
|
||
|
for {
|
||
|
switch tp.Kind() {
|
||
|
case reflect.Slice, reflect.Ptr:
|
||
|
tp = tp.Elem()
|
||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float32, reflect.Float64:
|
||
|
return true
|
||
|
default:
|
||
|
return false
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (option *Option) isFunc() bool {
|
||
|
return option.value.Type().Kind() == reflect.Func
|
||
|
}
|
||
|
|
||
|
func (option *Option) call(value *string) error {
|
||
|
var retval []reflect.Value
|
||
|
|
||
|
if value == nil {
|
||
|
retval = option.value.Call(nil)
|
||
|
} else {
|
||
|
tp := option.value.Type().In(0)
|
||
|
|
||
|
val := reflect.New(tp)
|
||
|
val = reflect.Indirect(val)
|
||
|
|
||
|
if err := convert(*value, val, option.tag); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
retval = option.value.Call([]reflect.Value{val})
|
||
|
}
|
||
|
|
||
|
if len(retval) == 1 && retval[0].Type() == reflect.TypeOf((*error)(nil)).Elem() {
|
||
|
if retval[0].Interface() == nil {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
return retval[0].Interface().(error)
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (option *Option) updateDefaultLiteral() {
|
||
|
defs := option.Default
|
||
|
def := ""
|
||
|
|
||
|
if len(defs) == 0 && option.canArgument() {
|
||
|
var showdef bool
|
||
|
|
||
|
switch option.field.Type.Kind() {
|
||
|
case reflect.Func, reflect.Ptr:
|
||
|
showdef = !option.value.IsNil()
|
||
|
case reflect.Slice, reflect.String, reflect.Array:
|
||
|
showdef = option.value.Len() > 0
|
||
|
case reflect.Map:
|
||
|
showdef = !option.value.IsNil() && option.value.Len() > 0
|
||
|
default:
|
||
|
zeroval := reflect.Zero(option.field.Type)
|
||
|
showdef = !reflect.DeepEqual(zeroval.Interface(), option.value.Interface())
|
||
|
}
|
||
|
|
||
|
if showdef {
|
||
|
def, _ = convertToString(option.value, option.tag)
|
||
|
}
|
||
|
} else if len(defs) != 0 {
|
||
|
l := len(defs) - 1
|
||
|
|
||
|
for i := 0; i < l; i++ {
|
||
|
def += quoteIfNeeded(defs[i]) + ", "
|
||
|
}
|
||
|
|
||
|
def += quoteIfNeeded(defs[l])
|
||
|
}
|
||
|
|
||
|
option.defaultLiteral = def
|
||
|
}
|
||
|
|
||
|
func (option *Option) shortAndLongName() string {
|
||
|
ret := &bytes.Buffer{}
|
||
|
|
||
|
if option.ShortName != 0 {
|
||
|
ret.WriteRune(defaultShortOptDelimiter)
|
||
|
ret.WriteRune(option.ShortName)
|
||
|
}
|
||
|
|
||
|
if len(option.LongName) != 0 {
|
||
|
if option.ShortName != 0 {
|
||
|
ret.WriteRune('/')
|
||
|
}
|
||
|
|
||
|
ret.WriteString(option.LongName)
|
||
|
}
|
||
|
|
||
|
return ret.String()
|
||
|
}
|
||
|
|
||
|
func (option *Option) isValidValue(arg string) error {
|
||
|
if validator := option.isValueValidator(); validator != nil {
|
||
|
return validator.IsValidValue(arg)
|
||
|
}
|
||
|
if argumentIsOption(arg) && !(option.isSignedNumber() && len(arg) > 1 && arg[0] == '-' && arg[1] >= '0' && arg[1] <= '9') {
|
||
|
return fmt.Errorf("expected argument for flag `%s', but got option `%s'", option, arg)
|
||
|
}
|
||
|
return nil
|
||
|
}
|