command/flag.go

179 lines
4.6 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package command
import (
"errors"
"flag"
"reflect"
"regexp"
"strconv"
"strings"
)
type FVSet struct {
Name string
Value *any
flags flag.FlagSet
}
func (f *FVSet) Parse(args []string) error {
return f.flags.Parse(args)
}
func isPtr(v interface{}) bool {
return reflect.TypeOf(v).Kind() == reflect.Ptr
}
func (f *FVSet) Args() interface{} {
return f.flags.Args()
}
func (f *FVSet) Usage() {
f.flags.Usage()
}
type FVSets struct {
flagSets []*FVSet
subcommands []string
}
func (f *FVSets) Add(v interface{}) error {
fv, err := NewFVSet(v)
if err != nil {
return err
}
fv.flags.Usage = func() {
f.Usage()
}
f.flagSets = append(f.flagSets, fv)
return nil
}
func (f *FVSets) Parse(args []string) error {
for _, v := range f.flagSets {
err := v.Parse(args)
if err != nil {
return err
}
}
return nil
}
func (f *FVSets) Usage() {
//for _, v := range f.flagSets {
//
//}
}
func ToKebabCase(input string) string {
// 将字符串中的大写字母前插入连字符,并转换为小写
re := regexp.MustCompile("([a-z])([A-Z])")
kebab := re.ReplaceAllString(input, "${1}-${2}")
kebab = strings.ToLower(kebab)
// 去除字符串开头和结尾的连字符
kebab = strings.Trim(kebab, "-")
return kebab
}
// NewFVSet 创建一个新的FVSet实例用于处理命令行标志。
// 参数v是一个指向结构体的指针该结构体的字段将被用作命令行标志。
// 返回值是一个指向FVSet的指针和一个错误对象如果操作成功错误对象为nil。
// 如果v不是指向结构体的指针函数将返回错误。
func NewFVSet(v interface{}) (*FVSet, error) {
// 判断v是不是指针
vv := reflect.ValueOf(v)
if !isPtr(v) {
return nil, errors.New("v must be a pointer")
}
// 判断指针指向的值是不是结构体
vvv := vv.Elem()
if vvv.Kind() != reflect.Struct {
return nil, errors.New("v must be a pointer to a struct")
}
flags := flag.NewFlagSet(vvv.Type().Name(), flag.ContinueOnError)
name := vvv.Type().Name()
// 遍历结构体字段
for i := 0; i < vvv.NumField(); i++ {
//skip private field
stField := vvv.Type().Field(i)
if stField.PkgPath != "" {
continue
}
// 获取字段名
field := vvv.Field(i)
nameTag := stField.Tag.Get("flag_name")
flaName := nameTag
if flaName == "" {
flaName = stField.Name
}
flaName = ToKebabCase(flaName)
flShort := stField.Tag.Get("flag_short")
defVal := stField.Tag.Get("flag_default")
usageStr := stField.Tag.Get("flag_usage")
if usageStr == "" {
usageStr = "Set " + flaName + "(default " + defVal + ")"
}
addr := field.Addr()
if field.CanSet() {
switch field.Interface().(type) {
case string:
flags.StringVar(addr.Interface().(*string), flaName, defVal, usageStr)
if flShort != "" {
flags.StringVar(addr.Interface().(*string), flShort, defVal, usageStr)
}
break
case int:
v, _ := strconv.Atoi(defVal)
flags.IntVar(addr.Interface().(*int), flaName, v, usageStr)
if flShort != "" {
flags.IntVar(addr.Interface().(*int), flShort, v, usageStr)
}
break
case bool:
flags.BoolVar(addr.Interface().(*bool), flaName, false, usageStr)
if flShort != "" {
flags.BoolVar(addr.Interface().(*bool), flShort, false, usageStr)
}
break
default:
return nil, errors.New("unsupported type")
}
}
}
// flags.BoolFunc("help", "Show help", func(s string) error {
// flags.Usage()
// return nil
// })
return &FVSet{
Name: name,
Value: &v,
flags: *flags,
}, nil
}
// Parse is used to parse command line arguments based on one or more flag sets.
// args represents the list of command line arguments.
// flagSet is a variable-length parameter that contains the flag sets to be parsed.
// If no flag set is provided, it throws a panic exception.
// If only one flag set is provided, it directly parses the arguments with that flag set.
// If multiple flag sets are provided, it attempts to match and parse based on the first argument in args.
// If no matching flag set is found, it returns an error.
func Parse(args []string, flagSet ...*FVSet) error {
// Check if the flag set is missing, if so, throw a panic exception.
if len(flagSet) == 0 {
panic("flag set missing")
}
// If only one flag set is provided, directly parse the arguments with that flag set.
if len(flagSet) == 1 {
return flagSet[0].Parse(args)
}
// Iterate through the flag sets to find a match for parsing.
for _, f := range flagSet {
// If a matching flag set is found, parse the remaining arguments with that flag set.
if f.Name == args[0] {
return f.Parse(args[1:])
}
}
// If no matching flag set is found, return an error.
return errors.New("flag set not found")
}