: error c2062: 意外的类型“int”_Go 命令行解析 flag 包之扩展新类型
上篇文章 說到,除布爾類型 Flag,flag 支持的還有整型(int、int64、uint、uint64)、浮點型(float64)、字符串(string)和時長(duration)。
flag 內置支持能滿足大部分的需求,但某些場景,需要自定義解析規則。一個優秀的庫肯定要支持擴展的。本文將介紹如何為 flag 擴展一個新的類型支持?
擴展目標
在 gvg 這個小工具中,list 子命令支持獲取 Go 的版本列表。但版本的信息來源有多處,比如 installed(已安裝)、local(本地倉庫)和 remote(遠程倉庫)。
查看下 list 的幫助信息,如下:
NAME:gvg list - list go versionsUSAGE:gvg list [command options] [arguments...]OPTIONS:--origin value the origin of version information , such as installed, local, remote (default: "installed")可以看出,list 子命令支持一個 Flag 選項,--origin。它用于指定版本信息的來源,允許值的范圍是 installed、local 和 remote。
如果要求不嚴格,用 StringVar 也可以實現。但問題是,使用 String,即使輸入不在指定范圍也能成功解析,不夠嚴謹。雖說在獲取后也可以檢查,但還是不夠靈活、可配置型也差。
接下來,我們要實現一個新的類型的 Flag,使選項的值必需在指定范圍,否則要給出一定的錯誤提示信息。
實現思路
如何展一個新類型呢?
可以參考 flag 包內置類型的實現思路,比如 flag.DurationVar。Duration 不是基礎類型,解析結果是存放到了 time.Duration 類型中,可能更有參考價值。
進入到 flag.DurationVar 查看源碼,如下:
func DurationVar(p *time.Duration, name string, value time.Duration, usage string) {CommandLine.Var(newDurationValue(value, p), name, usage)
}
通過 newDurationValue 創建了一個類型為 durationValue 的變量,并傳入到了 CommandLine.Var 方法中。
如果繼續往下追,會根據 Value 創建一個 Flag 變量。 如下:
func (f *FlagSet) Var(value Value, name string, usage string) {flag := &Flag{name, usage, value, value.String()}...
}
從 Var 的定義可以看出,它的第一個參數類型是 Value 接口類型,也就說,durationValue 是實現了 Value 接口的類型。
注意,源碼中出現的 FlagSet 可以先忽略,它是下篇介紹子命令時重點關注的對象。
看下 Value 的定義,如下:
type Value interface {String() stringSet(string) error
}
那么,durationValue 的實現代碼如何?
// 傳入參數分別是默認值和獲取 Flag 值的變量地址
func newDurationValue(val time.Duration, p *time.Duration) *durationValue {// 將默認值設置到 p 上*p = val// 使用 p 創建新的類型,保證可以獲取到解析的結果return (*durationValue)(p)
}// Set 方法負責解析傳入的值
func (d *durationValue) Set(s string) error {v, err := time.ParseDuration(s)if err != nil {err = errParse}*d = durationValue(v)return err
}// 獲取真正的值
func (d *durationValue) String() string { return (*time.Duration)(d).String() }
核心在兩個地方。
一個是創建新類型變量時,要使用傳入的變量地址創建新類型變量,以實現將解析結果放到其中,讓前端能獲取到,二是 Set 方法中實現命令行傳入字符串的解析。
邏輯梳理
看完上個小節,基本已經了解如何擴展一個新類型了。本質是是實現 Value 接口。
再看下之前提到的幾個變量,分別是存放解析結果的指針、解析命令行輸入的 Value 和表示一個選項的 Flag。對應于 flag.DurationVar,這個變量的類型分別是 *time.Duration、durationValue 和 Flag。
比如有 duration=1h,大致流程是首先從 os.Args 獲取參數,按規則解析出選項名稱 duration,查找是否存在名稱為 duration 的 Flag,如果存在,使用 Flag.Value.Set 解析 1h,如果不滿足 duration 的要求,將給出錯誤提示。
實現新類型
現在實現文章開頭要求的目標。
新類型定義如下:
type stringEnumValue struct {options []stringp *string
}
名為 StringEnumValue,即字符串枚舉。它有 options 和 p 兩個成員,options 指定一定范圍的值,p 是 string 指針,保存解析結果的變量的地址。
下面定義創建 StringEnumValue 變量的函數 newStringEnumValue,代碼如下:
func newStringEnumValue(val string, p *string, options []string) *StringEnumValue {*option = valreturn &stringEnumValue{options: options, p: p}
}
除了 val 和 p 兩個必要的輸入外,還有一個 string 切片類型的數,名為 options,它用于范圍的限定。而函數主體,首先設置默認值,然后使用 options 和 p 創建變量返回。
Set 是核心方法,解析命令行傳入字符串。代碼如下:
func (s *StringEnumValue) Set(v string) error {for _, option := range s.options {if v == option {*(s.p) = vreturn nil}}return fmt.Errorf("must be one of %v", s.options)
}
循環檢查輸入參數 v 是否滿足要求。定義如下:
最后是 String() 方法,
func (s *StringEnumValue) String() string {return *(s.p)
}
返回 p 指針中的值。前面分析實現思路時,Flag 在設置默認值時就調用了它。
使用 StringEnumValue
直接看代碼吧。如下:
var origin string
func init() {flag.Var(newStringEnumValue("installed", // 默認值&origin,[]string{"installed", "local", "remote"},),"origin",`the origin of version information, such as installed, local, remote (default: "installed")`,)
}func main() {flag.Parse()fmt.Println(option)
}
重點就是 flag.Var(newStringEnumValue(...),...)。如果覺得有點啰嗦,希望和其他類型新建過程相同,在這個基礎上可以再包裝。代碼如下:
func StringEnumVar(p *string, name string, options []string, defVal string, usage string) {flag.Var(newStringEnumValue(defVal, p, options), name, usage)
}
編譯測試下,結果如下:
$ gvg --origin=any
invalid value "any" for flag -origin: must be one of [installed local remote]
Usage of gvg:-origin valuethe origin of version information, such as installed, local, remote (default installed)
$ gvg --origin=remote
origin remote總結
本文介紹了如何為 flag 擴展一個類型支持,通過分析源碼理清實現思路。最后創建了一個只接收指定范圍值 Value。
歡迎關注我的微信公眾號。
總結
以上是生活随笔為你收集整理的: error c2062: 意外的类型“int”_Go 命令行解析 flag 包之扩展新类型的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java static 可见性_Java
- 下一篇: 透镜多少钱啊?