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") } } } 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") }