journal/gojq-extended/flags.go
2024-11-10 08:13:25 +00:00

212 lines
5.1 KiB
Go

package main
import (
"fmt"
"reflect"
"strconv"
"strings"
)
func parseFlags(args []string, opts any) ([]string, error) {
rest := make([]string, 0, len(args))
val := reflect.ValueOf(opts).Elem()
typ := val.Type()
longToValue := map[string]reflect.Value{}
longToPositional := map[string]struct{}{}
shortToValue := map[string]reflect.Value{}
for i, l := 0, val.NumField(); i < l; i++ {
if flag, ok := typ.Field(i).Tag.Lookup("long"); ok {
longToValue[flag] = val.Field(i)
if _, ok := typ.Field(i).Tag.Lookup("positional"); ok {
longToPositional[flag] = struct{}{}
}
}
if flag, ok := typ.Field(i).Tag.Lookup("short"); ok {
shortToValue[flag] = val.Field(i)
}
}
mapKeys := map[string]struct{}{}
var positionalVal reflect.Value
for i := 0; i < len(args); i++ {
arg := args[i]
var (
val reflect.Value
ok bool
shortopts string
)
if arg == "--" {
if positionalVal.IsValid() {
for _, arg := range args[i+1:] {
positionalVal.Set(reflect.Append(positionalVal, reflect.ValueOf(arg)))
}
} else {
rest = append(rest, args[i+1:]...)
}
break
}
if strings.HasPrefix(arg, "--") {
if val, ok = longToValue[arg[2:]]; !ok {
if j := strings.IndexByte(arg, '='); j >= 0 {
if val, ok = longToValue[arg[2:j]]; ok {
if val.Kind() == reflect.Bool {
return nil, fmt.Errorf("boolean flag `%s' cannot have an argument", arg[:j])
}
args[i] = arg[j+1:]
arg = arg[:j]
i--
}
}
if !ok {
return nil, fmt.Errorf("unknown flag `%s'", arg)
}
}
} else if len(arg) > 1 && arg[0] == '-' {
var skip bool
for i := 1; i < len(arg); i++ {
opt := arg[i : i+1]
if val, ok = shortToValue[opt]; ok {
if val.Kind() != reflect.Bool {
break
}
} else if !("A" <= opt && opt <= "Z" || "a" <= opt && opt <= "z") {
skip = true
break
}
}
if !skip && (len(arg) > 2 || !ok) {
shortopts = arg[1:]
goto L
}
}
if !ok {
if positionalVal.IsValid() && len(rest) > 0 {
positionalVal.Set(reflect.Append(positionalVal, reflect.ValueOf(arg)))
} else {
rest = append(rest, arg)
}
continue
}
S:
switch val.Kind() {
case reflect.Bool:
val.SetBool(true)
case reflect.String:
if i++; i >= len(args) {
return nil, fmt.Errorf("expected argument for flag `%s'", arg)
}
val.SetString(args[i])
case reflect.Ptr:
if val.Type().Elem().Kind() == reflect.Int {
if i++; i >= len(args) {
return nil, fmt.Errorf("expected argument for flag `%s'", arg)
}
v, err := strconv.Atoi(args[i])
if err != nil {
return nil, fmt.Errorf("invalid argument for flag `%s': %w", arg, err)
}
val.Set(reflect.New(val.Type().Elem()))
val.Elem().SetInt(int64(v))
}
case reflect.Slice:
if _, ok := longToPositional[arg[2:]]; ok {
if positionalVal.IsValid() {
for positionalVal.Len() > val.Len() {
val.Set(reflect.Append(val, reflect.Zero(val.Type().Elem())))
}
}
positionalVal = val
} else {
if i++; i >= len(args) {
return nil, fmt.Errorf("expected argument for flag `%s'", arg)
}
val.Set(reflect.Append(val, reflect.ValueOf(args[i])))
}
case reflect.Map:
if i += 2; i >= len(args) {
return nil, fmt.Errorf("expected 2 arguments for flag `%s'", arg)
}
if val.IsNil() {
val.Set(reflect.MakeMap(val.Type()))
}
name := args[i-1]
if _, ok := mapKeys[name]; !ok {
mapKeys[name] = struct{}{}
val.SetMapIndex(reflect.ValueOf(name), reflect.ValueOf(args[i]))
}
}
L:
if shortopts != "" {
opt := shortopts[:1]
if val, ok = shortToValue[opt]; !ok {
return nil, fmt.Errorf("unknown flag `%s'", opt)
}
if val.Kind() != reflect.Bool && len(shortopts) > 1 {
if shortopts[1] == '=' {
args[i] = shortopts[2:]
} else {
args[i] = shortopts[1:]
}
i--
shortopts = ""
} else {
shortopts = shortopts[1:]
}
arg = "-" + opt
goto S
}
}
return rest, nil
}
func formatFlags(opts any) string {
val := reflect.ValueOf(opts).Elem()
typ := val.Type()
var sb strings.Builder
sb.WriteString("Command Options:\n")
for i, l := 0, typ.NumField(); i < l; i++ {
tag := typ.Field(i).Tag
if i == l-1 {
sb.WriteString("\nHelp Option:\n")
}
sb.WriteString(" ")
var short bool
if flag, ok := tag.Lookup("short"); ok {
sb.WriteString("-")
sb.WriteString(flag)
short = true
} else {
sb.WriteString(" ")
}
m := sb.Len()
if flag, ok := tag.Lookup("long"); ok {
if short {
sb.WriteString(", ")
} else {
sb.WriteString(" ")
}
sb.WriteString("--")
sb.WriteString(flag)
switch val.Field(i).Kind() {
case reflect.Bool:
sb.WriteString(" ")
case reflect.Map:
if strings.HasSuffix(flag, "file") {
sb.WriteString(" name file")
} else {
sb.WriteString(" name value")
}
default:
if _, ok = tag.Lookup("positional"); !ok {
sb.WriteString("=")
}
}
} else {
sb.WriteString("=")
}
sb.WriteString(" "[:24-sb.Len()+m])
sb.WriteString(tag.Get("description"))
sb.WriteString("\n")
}
return sb.String()
}