golang標(biāo)準(zhǔn)庫(kù)之flag

flag包實(shí)現(xiàn)了簡(jiǎn)單的命令行參數(shù)解析,支持bool、int、int64、uint、uint64、float64、string和time.Duration八種類型的命令行解析。

使用方法

注冊(cè)flag流程如下:

import "flag"
var ip = flag.Int("flagname", 1234, "help message for flagname") // 返回指針類型,訪問(wèn)時(shí)需要加*
fmt.Println("ip has value ", *ip)

var flagvar int
func init() {
    flag.IntVar(&flagvar, "flagname", 1234, "help message for flagname") // 放在init函數(shù)中,確保初始化時(shí)完成flag的注冊(cè)
fmt.Println("flagvar has value ", flagvar)
}

在所有flag都注冊(cè)之后,調(diào)用:

flag.Parse()

需要注意的幾點(diǎn):

  • flag包對(duì)于其它類型支持-flag x-flag=x兩種情況;
  • flag包對(duì)于隱式bool類型支持-flag,默認(rèn)為True;
  • flag包對(duì)于顯式bool類型支持-flag=false
    顯式bool類型的支持并非flag包實(shí)現(xiàn),而是調(diào)用strconv包實(shí)現(xiàn)的
bool類型 支持的bool類型顯示字符串
true "1", "t", "T", "true", "TRUE", "True"
false "0", "f", "F", "false", "FALSE", "False"

源碼學(xué)習(xí)

接口設(shè)計(jì)
type Value interface {
    String() string
    Set(string) error
}

type Getter interface {
    Value
    Get() interface{}
}

// ErrorHandling定義了解析錯(cuò)誤時(shí)FlagSet.Parse怎么處理
type ErrorHandling int
const (
    ContinueOnError ErrorHandling = iota // Return a descriptive error.
    ExitOnError                          // Call os.Exit(2).
    PanicOnError                         // Call panic with a descriptive error.
)

// 結(jié)構(gòu)體Flag表示flag的狀態(tài)
type Flag struct {
    Name     string // name as it appears on command line
    Usage    string // help message
    Value    Value  // value as set
    DefValue string // default value (as text); for usage message
}

// FlagSet表示一組flag,空值的FlagSet沒(méi)有name,但是包含ContinueOnError。
// Flag的names在FlagSet內(nèi)唯一,定義已經(jīng)存在的name導(dǎo)致panic。
type FlagSet struct {
    name          string
    parsed        bool
    actual        map[string]*Flag
    formal        map[string]*Flag
    args          []string // arguments after flags
    errorHandling ErrorHandling
    output        io.Writer // nil means stderr; use out() accessor
    // 解析錯(cuò)誤時(shí)調(diào)用Usage函數(shù),也可以替換成錯(cuò)誤處理函數(shù)。
    // Usage函數(shù)調(diào)用之后,會(huì)根據(jù)ErrorHandling的值作出相應(yīng)處理。
    Usage func()
}
1. import "flag"

import 后會(huì)執(zhí)行初始化FlagSet的操作

var CommandLine = NewFlagSet(os.Args[0], ExitOnError) // 實(shí)例化FlagSet為CommandLine,注冊(cè)errorHandling為解析錯(cuò)誤時(shí)退出程序

func NewFlagSet(name string, errorHandling ErrorHandling) *FlagSet {
    f := &FlagSet{
        name:          name,
        errorHandling: errorHandling,
    }
    f.Usage = f.defaultUsage
    return f
}
2. 注冊(cè)flag

這部分主要工作是把nameflag以鍵值對(duì)存入字典formal,用于在Parse階段查詢。以int類型為例,注冊(cè)flag的方法主要有4個(gè),如下所示,本質(zhì)上都是在調(diào)用func (f *FlagSet) Var(value Value, name string, usage string)方法。

func (f *FlagSet) IntVar(p *int, name string, value int, usage string) {
    f.Var(newIntValue(value, p), name, usage)
}

func IntVar(p *int, name string, value int, usage string) {
    CommandLine.Var(newIntValue(value, p), name, usage)
}

func (f *FlagSet) Int(name string, value int, usage string) *int {
    p := new(int)
    f.IntVar(p, name, value, usage)
    return p
}

func Int(name string, value int, usage string) *int {
    return CommandLine.Int(name, value, usage)
}
func (f *FlagSet) Var(value Value, name string, usage string) {
    // Remember the default value as a string; it won't change.
    flag := &Flag{name, usage, value, value.String()}
    _, alreadythere := f.formal[name] // 查詢formal字典,是否存在新flag的name
    if alreadythere { // 已存在,觸發(fā)panic
        var msg string
        if f.name == "" {
            msg = fmt.Sprintf("flag redefined: %s", name)
        } else {
            msg = fmt.Sprintf("%s flag redefined: %s", f.name, name)
        }
        fmt.Fprintln(f.Output(), msg)
        panic(msg) // Happens only if flags are declared with identical names
    }
    if f.formal == nil {  // formal為空時(shí),需要初始化,否則無(wú)法填入
        f.formal = make(map[string]*Flag)
    }
    f.formal[name] = flag // 執(zhí)行成功,當(dāng)前flag填入formal
}

其中入?yún)?code>Value接口定義如下:

type intValue int

func newIntValue(val int, p *int) *intValue {
    *p = val
    return (*intValue)(p)
}

func (i *intValue) Set(s string) error {
    v, err := strconv.ParseInt(s, 0, strconv.IntSize)
    if err != nil {
        err = numError(err)
    }
    *i = intValue(v)
    return err
}

func (i *intValue) Get() interface{} { return int(*i) }

func (i *intValue) String() string { return strconv.Itoa(int(*i)) }
3. Parse

Parse函數(shù)循環(huán)調(diào)用parseOne,每次解析一個(gè)flag,解析失敗則觸發(fā)errorHandling。

func Parse() {
    CommandLine.Parse(os.Args[1:]) // os.Args[0]是程序名,os.Args[1:]為參數(shù),開(kāi)始解析參數(shù)
}

func (f *FlagSet) Parse(arguments []string) error {
    f.parsed = true
    f.args = arguments
    for {
        seen, err := f.parseOne() // 主功能,解析參數(shù)
        if seen {
            continue
        }
        if err == nil {
            break
        }
        switch f.errorHandling { // err非空時(shí),根據(jù)errorHandling執(zhí)行相應(yīng)操作
        case ContinueOnError:
            return err
        case ExitOnError:
            os.Exit(2)
        case PanicOnError:
            panic(err)
        }
    }
    return nil
}

parseOne完成了解析工作的主要內(nèi)容,函數(shù)如下所示:

// parseOne parses one flag. It reports whether a flag was seen.
func (f *FlagSet) parseOne() (bool, error) {
    if len(f.args) == 0 {
        return false, nil
    }
    s := f.args[0] // ---------------解析flag
    if len(s) < 2 || s[0] != '-' { // 必須以'-'開(kāi)頭才能識(shí)別
        return false, nil
    }
    numMinuses := 1
    if s[1] == '-' {
        numMinuses++
        if len(s) == 2 { // "--" 直接返回,停止解析
            f.args = f.args[1:]
            return false, nil
        }
    }
    name := s[numMinuses:] // -flag -> flag, --flag -> flag, 去除開(kāi)頭的一個(gè)或兩個(gè)"-", 存入name
    if len(name) == 0 || name[0] == '-' || name[0] == '=' {
        return false, f.failf("bad flag syntax: %s", s)
    }

    // it's a flag. does it have an argument?
    f.args = f.args[1:] // ---------------解析value
    hasValue := false
    value := ""
    for i := 1; i < len(name); i++ { // equals cannot be first
        if name[i] == '=' { // 解析-flag=value, --flag=value
            value = name[i+1:]
            hasValue = true
            name = name[0:i]
            break
        }
    }
    m := f.formal
    flag, alreadythere := m[name] // BUG
    if !alreadythere {
        if name == "help" || name == "h" { // special case for nice help message.
            f.usage()
            return false, ErrHelp
        }
        return false, f.failf("flag provided but not defined: -%s", name)
    }

    if fv, ok := flag.Value.(boolFlag); ok && fv.IsBoolFlag() { // bool類型,支持-flag和-flag=false兩種
        if hasValue {
            if err := fv.Set(value); err != nil { // 解析-flag=false形式
                return false, f.failf("invalid boolean value %q for -%s: %v", value, name, err)
            }
        } else {
            if err := fv.Set("true"); err != nil { // 解析-flag,默認(rèn)為True
                return false, f.failf("invalid boolean flag %s: %v", name, err)
            }
        }
    } else { // 其它類型,支持-flag=123、-flag 123兩種
        if !hasValue && len(f.args) > 0 {
            hasValue = true
            value, f.args = f.args[0], f.args[1:] // f.args = f.args[1:],索引0為該flag對(duì)應(yīng)的值,[1:]存入放f.args繼續(xù)解析
        }
        if !hasValue {
            return false, f.failf("flag needs an argument: -%s", name)
        }
        if err := flag.Value.Set(value); err != nil { // value寫入變量
            return false, f.failf("invalid value %q for flag -%s: %v", value, name, err)
        }
    }
    if f.actual == nil {
        f.actual = make(map[string]*Flag)
    }
    f.actual[name] = flag // 解析之后的有效flag存入f.actual
    return true, nil
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容