我是 Golang 的新手,我一直无法使用 flag 找到解决此问题的方法。
如何使用标志,以便我的程序可以处理这样的调用,其中 -term 标志可能出现可变次数,包括 0 次:
./myprogram -f flag1
./myprogram -f flag1 -term t1 -term t2 -term t3
您需要声明自己的实现 Value 接口的类型。这是一个例子。
// Created so that multiple inputs can be accecpted
type arrayFlags []string
func (i *arrayFlags) String() string {
// change this, this is just can example to satisfy the interface
return "my string representation"
}
func (i *arrayFlags) Set(value string) error {
*i = append(*i, strings.TrimSpace(value))
return nil
}
然后在您解析标志的主要功能中
var myFlags arrayFlags
flag.Var(&myFlags, "term", "my terms")
flag.Parse()
现在所有术语都包含在切片中myFlags
这个问题很有趣,可以有很多变化。
核心内容跟@reticentroot回答的一样,
完成该接口的定义:Flag.Value
以下是示例,尽可能分享并提供相关链接
预期用途:
type Books []string
func (*Books) String() string { return "" }
func (*Books) Set(string) error { return nil }
type Dict map[string]string
func (*Dict) String() string { return "" }
func (*Dict) Set(string) error { return nil }
type Person struct {
Name string
Age int
}
func (*Person) String() string { return "" }
func (*Person) Set(string) error { return nil }
func pseudocode() {
flagSetTest := flag.NewFlagSet("test", flag.ContinueOnError)
books := Books{}
flagSetTest.Var(&books, "book", "-book C++ -book Go -book javascript")
// expected output: books: []string{C++,Go,javascript}
dict := Dict{}
flagSetTest.Var(&dict, "dict", "-dict A:65|B:66")
// expected output: dict: map[string]string{"A":"65", "B":"66"}
// map
person := Person{}
flagSetTest.Var(&person, "person", "-person Name:foo|Age:18")
// output: {Name:foo Age:18}
flagSetTest.Parse(os.Args[1:])
fmt.Println(person, books, dict)
}
完整代码
package main
import (
"bufio"
"errors"
"flag"
"fmt"
"os"
"reflect"
"strconv"
"strings"
)
type BooksValue []string
// https://github.com/golang/go/blob/2580d0e/src/flag/flag.go#L298
func (arr *BooksValue) String() string {
/*
value.String(): https://github.com/golang/go/blob/2580d0e/src/flag/flag.go#L870
DefValue string:
- https://github.com/golang/go/blob/2580d0e/src/flag/flag.go#L348
- https://github.com/golang/go/blob/2580d0e/src/flag/flag.go#L914-L920
- https://github.com/golang/go/blob/2580d0e/src/flag/flag.go#L529-L536
- https://github.com/golang/go/blob/2580d0e/src/flag/flag.go#L464
*/
return ""
}
// https://github.com/golang/go/blob/2580d0e/src/flag/flag.go#L299
func (arr *BooksValue) Set(value string) error {
/*
value: https://github.com/golang/go/blob/2580d0e/src/flag/flag.go#L947
bool: Set(value): https://github.com/golang/go/blob/2580d0e/src/flag/flag.go#L966-L975
else: Set(value): https://github.com/golang/go/blob/2580d0e/src/flag/flag.go#L986-L988
*/
*arr = append(*arr, strings.TrimSpace(value))
return nil
}
type DictValue map[string]string
func (m *DictValue) String() string {
return ""
}
func (m *DictValue) Set(value string) error {
arr := strings.Split(value, "|") // "key1:val1|key2:val2|..."
for _, curPairStr := range arr {
itemArr := strings.Split(curPairStr, ":")
key := itemArr[0]
val := itemArr[1]
(*m)[key] = val
}
return nil
}
type PersonValue struct {
Name string
Age int
Msg string
IsActive bool
}
func (s *PersonValue) String() string {
return ""
}
func (s *PersonValue) Set(value string) error {
arr := strings.Split(value, "|") // "Field1:Value1|F2:V2|...|FN:VN"
for _, curPairStr := range arr {
itemArr := strings.Split(curPairStr, ":")
key := itemArr[0]
val := itemArr[1]
// [Access struct property by name](https://stackoverflow.com/a/66470232/9935654)
pointToStruct := reflect.ValueOf(s)
curStruct := pointToStruct.Elem()
curField := curStruct.FieldByName(key)
if !curField.IsValid() {
return errors.New("not found")
}
// CanSet one of conditions: Name starts with a capital
if !curField.CanSet() {
return errors.New("can't set")
}
t := reflect.TypeOf(*s)
structFieldXXX, isFound := t.FieldByName(key)
if !isFound {
return errors.New("not found")
}
switch structFieldXXX.Type.Name() {
case "int":
// https://github.com/golang/go/blob/2580d0e/src/flag/flag.go#L146-L153
intValue, err := strconv.ParseInt(val, 0, strconv.IntSize)
if err != nil {
return errors.New("parse error: [int]")
}
curField.SetInt(intValue)
case "bool":
// https://github.com/golang/go/blob/2580d0e/src/flag/flag.go#L117-L121
boolValue, err := strconv.ParseBool(val)
if err != nil {
return errors.New("parse error: [bool]")
}
curField.SetBool(boolValue)
case "string":
curField.SetString(val)
default:
return errors.New("not support type=" + structFieldXXX.Type.Name())
}
}
return nil
}
func main() {
flagSetTest := flag.NewFlagSet("test", flag.ContinueOnError)
// array
books := BooksValue{}
flagSetTest.Var(&books, "book", "-book Go -book javascript ...")
// map
myMap := DictValue{}
flagSetTest.Var(&myMap, "map", "-dict A:65|B:66")
// struct
person := PersonValue{Msg: "Hello world"}
flagSetTest.Var(&person, "person", "-person Name:string|Age:int|Msg:string|IsActive:bool")
testArgs := []string{"test",
"-book", "Go", "-book", "javascript", // testArray
"-map", "A:65|B:66|Name:Carson", // testMap
"-person", "Name:Carson|Age:30|IsActive:true", // testStruct
}
testFunc := func(args []string, reset bool) {
if reset {
books = BooksValue{}
myMap = DictValue{}
person = PersonValue{}
}
if err := flagSetTest.Parse(args); err != nil {
fmt.Printf(err.Error())
}
fmt.Printf("%+v\n", books)
fmt.Printf("%+v\n", myMap)
fmt.Printf("%+v\n", person)
}
testFunc(testArgs[1:], false)
// ↓ play by yourself
scanner := bufio.NewScanner(os.Stdin)
for {
fmt.Println("Enter CMD: ") // example: test -book item1 -book item2 -map key1:value1|key2:v2 -person Age:18|Name:Neil|IsActive:true
scanner.Scan() // Scans a line from Stdin(Console)
text := scanner.Text() // Holds the string that scanned
args := strings.Split(text, " ")
switch args[0] {
case "quit":
return
case "test":
testFunc(args[1:], true)
}
}
}