8

我是 Golang 的新手,我一直无法使用 flag 找到解决此问题的方法。

如何使用标志,以便我的程序可以处理这样的调用,其中 -term 标志可能出现可变次数,包括 0 次:

./myprogram -f flag1
./myprogram -f flag1 -term t1 -term t2 -term  t3
4

2 回答 2

12

您需要声明自己的实现 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

于 2017-08-03T20:48:35.380 回答
0

这个问题很有趣,可以有很多变化。

  • 大批
  • 地图
  • 结构

核心内容跟@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)
        }
    }
}

go playground

于 2022-02-10T08:04:04.290 回答