前言
用過golang的小伙伴應該都知道flag包,因此這邊就不做使用的介紹了衙熔。本文主要簡單分析flag包的源碼赶撰,以及簡單介紹下如何自定義自己的value類型蝌以。
本文測試代碼如下:
var kk *bool = flag.Bool("k",false,"just k")
func main() {????
????//flag.Usage()????
????flag.Parse()????????
????fmt.Println(*kk)
}
代碼分析
當我們 import? package時哪替,package內(nèi)的全局變量會進行初始化栋荸,并且init函數(shù)會執(zhí)行。對應flag包凭舶,代碼如下:
var CommandLine = NewFlagSet(os.Args[0], ExitOnError)
func init() {? ? ? ?
????CommandLine.Usage = commandLineUsage
}
CommandLine?代表的是當前可執(zhí)行程序的命令行集合晌块,也就是 FlagSet。
CommandLine.Usage從字面理解就是使用說明帅霜。
NewFlagSet函數(shù)用于構(gòu)造一個FlagSet匆背,這樣的函數(shù)命名也是golang推薦的。
對于FlagSet結(jié)構(gòu)體义屏,我們可以先看下它的定義:
type FlagSet struct {? ?
????Usage func()? ? ? ?
????name string? ? ? //FlagSet名字靠汁,被賦值為 os.Args[0]
????parsed bool? ? ?//是否被解析過
????actual map[string]*Flag? ? //在實際命令行中使用到的Flag
????formal map[string]*Flag? ? ?//在代碼中預定義的全部Flag
????args []string? ? ? ? ? ? ? ? ? ? ? //等于 os.Args[1:]
????errorHandling ErrorHandling????
????output io.Writer
}
注意從 Flag包中導出的函數(shù)最終都會調(diào)用到?CommandLine?對應的函數(shù)蜂大,所以這里我只分析?FlagSet?的對應函數(shù)闽铐。
Flag包初始化的過程之后,接下來就到測試代碼的分析:
var kk *bool = flag.Bool("k",false,"just k")
三個參數(shù)分別為?name,default value,usage奶浦。
func (f *FlagSet) Bool(name string, value bool, usage string) *bool {????
????p := new(bool)????
????f.BoolVar(p, name, value, usage)????
????return p
}
函數(shù)簡單明了兄墅,new出一個p,通過?FlagSet.BoolVar的處理澳叉,p會承接命令行中的值隙咸,然后直接返回p的指針沐悦。
所以最關(guān)鍵的函數(shù)在于?FlagSet.BoolVar?函數(shù)。
func (f *FlagSet) BoolVar(p *bool, name string, value bool, usage string) {????
????f.Var(newBoolValue(value, p), name, usage)
}
BoolVar?先調(diào)用?newBoolValue五督,之后調(diào)用?f.Var藏否。
func newBoolValue(val bool, p *bool) *boolValue{? ??
????*p = val????
????return (*boolValue)(p)
}
newBoolValue功能只有一個,把默認值賦給?p充包,并將p轉(zhuǎn)換成 boolValue指針類型副签,然后返回。
注意?boolValue?擁有成員函數(shù):?Set/String/Get基矮,因此boolValue實現(xiàn)了 flag.Value淆储,fmt.Stringer,flag.Getter接口家浇。
func (f *FlagSet) Var(value Value, name string, usage string) {? ?
? ? flag := &Flag{name, usage, value, value.String()}????
????_, alreadythere := f.formal[name]????
????if alreadythere {????????
????????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)?
? ?}????
????if f.formal == nil {????????
????????f.formal = make(map[string]*Flag)????
????}????
????f.formal[name] = flag
}
FlagSet.Var函數(shù)首先新建一個?Flag結(jié)構(gòu)體本砰,然后檢查對應的?flag的name是否存在于 FlagSet的formal?slice中,如果存在同名flag钢悲,直接panic点额。
如果不存在則直接把flag存放在 formal中。
至此莺琳,預定義的flag已經(jīng)存放在?CommandLine?中咖楣。
之后我們分析?flag.Parse函數(shù)。
func (f *FlagSet) Parse(arguments []string) error {????
????f.parsed = true????
????f.args = arguments????
????for {????????
????????seen, err := f.parseOne()????????
? ? ????......
????}
}
其中?arguments? ? 等于 os.Args[1:]芦昔,因此Flag.Args()得到的是純正的命令行參數(shù)诱贿,不包括 os.Args[0]。但是要千萬注意咕缎,parseOne?函數(shù)會一直更新 FlagSet.args slice珠十,從而導致?Parse結(jié)束后,F(xiàn)lag.Args()?得到的是一個空的 slice凭豪。
Parse函數(shù)中焙蹭,最主要的函數(shù)是?FlagSet.ParseOne?函數(shù)。
ParseOne?的函數(shù)同樣很簡單嫂伞,解析 “-”?符號孔厉,根據(jù) “=”?的位置,解析出?name?與 value帖努,其中很關(guān)鍵的代碼:
fv, ok := flag.Value.(boolFlag)
fv.Set(value)
這里?Set 由Value接口定義撰豺,因此如果我們自定義參數(shù)類型的話,必須實現(xiàn)?Value?接口拼余。
函數(shù)最后?flag會被添加到?FlagSet.actual?中污桦。
基本的代碼分析到這,里面還有一些比較簡單的函數(shù)匙监,比如usage相關(guān)凡橱,arg相關(guān)的函數(shù)小作,可以自己看看。
自定義Flag
根據(jù)上面代碼的分析稼钩,只要我們定義的Flag結(jié)構(gòu)體實現(xiàn)了Value接口顾稀,即能通過命令行初始化。下面代碼為例:
type myFlag struct {????
????name string????
????age int
}
func (mf *myFlag) Set(data string) error {????
????b := bytes.NewBufferString(data)????
????fmt.Fscanf(b,"%d%s",&mf.age,&mf.name)????
????return nil
}
func (mf *myFlag)String() string {????
????return fmt.Sprintf("%s now is %d",mf.name,mf.age)
}
var mf *myFlag = &myFlag{}
func init() {????
????flag.CommandLine.Var(mf,"na","name age")
}
func main() {????
//flag.Usage()????
flag.Parse()????????
fmt.Println(mf)
}
在實現(xiàn)?Value接口后坝撑,主要需要調(diào)用?flag.CommandLine.Var 础拨,當然也可以調(diào)用 flag.Var?函數(shù)。