2024-10-22 01:21:09 +08:00
|
|
|
|
package command
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"errors"
|
|
|
|
|
"flag"
|
|
|
|
|
"reflect"
|
2024-10-24 07:15:25 +08:00
|
|
|
|
"regexp"
|
2024-10-22 01:21:09 +08:00
|
|
|
|
"strconv"
|
2024-10-24 07:15:25 +08:00
|
|
|
|
"strings"
|
2024-10-22 01:21:09 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
2024-10-24 07:15:25 +08:00
|
|
|
|
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
|
|
|
|
|
}
|
2024-10-22 01:21:09 +08:00
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
}
|
2024-10-24 07:15:25 +08:00
|
|
|
|
flaName = ToKebabCase(flaName)
|
|
|
|
|
flShort := stField.Tag.Get("flag_short")
|
2024-10-22 01:21:09 +08:00
|
|
|
|
defVal := stField.Tag.Get("flag_default")
|
|
|
|
|
usageStr := stField.Tag.Get("flag_usage")
|
|
|
|
|
if usageStr == "" {
|
|
|
|
|
usageStr = "Set " + flaName + "(default " + defVal + ")"
|
|
|
|
|
}
|
2024-10-24 07:15:25 +08:00
|
|
|
|
addr := field.Addr()
|
2024-10-22 01:21:09 +08:00
|
|
|
|
if field.CanSet() {
|
2024-10-24 07:15:25 +08:00
|
|
|
|
switch field.Interface().(type) {
|
2024-10-22 01:21:09 +08:00
|
|
|
|
case string:
|
2024-10-24 07:15:25 +08:00
|
|
|
|
flags.StringVar(addr.Interface().(*string), flaName, defVal, usageStr)
|
|
|
|
|
if flShort != "" {
|
|
|
|
|
flags.StringVar(addr.Interface().(*string), flShort, defVal, usageStr)
|
|
|
|
|
}
|
2024-10-22 01:21:09 +08:00
|
|
|
|
break
|
|
|
|
|
case int:
|
|
|
|
|
v, _ := strconv.Atoi(defVal)
|
2024-10-24 07:15:25 +08:00
|
|
|
|
flags.IntVar(addr.Interface().(*int), flaName, v, usageStr)
|
|
|
|
|
if flShort != "" {
|
|
|
|
|
|
|
|
|
|
flags.IntVar(addr.Interface().(*int), flShort, v, usageStr)
|
|
|
|
|
}
|
2024-10-22 01:21:09 +08:00
|
|
|
|
break
|
|
|
|
|
case bool:
|
2024-10-24 07:15:25 +08:00
|
|
|
|
flags.BoolVar(addr.Interface().(*bool), flaName, false, usageStr)
|
|
|
|
|
if flShort != "" {
|
|
|
|
|
flags.BoolVar(addr.Interface().(*bool), flShort, false, usageStr)
|
|
|
|
|
}
|
2024-10-22 01:21:09 +08:00
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
return nil, errors.New("unsupported type")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-11-15 13:17:19 +08:00
|
|
|
|
flags.BoolFunc("help", "Show help", func(s string) error {
|
|
|
|
|
flags.Usage()
|
|
|
|
|
return nil
|
|
|
|
|
})
|
2024-10-22 01:21:09 +08:00
|
|
|
|
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")
|
|
|
|
|
}
|