go flag
各种flag.String,flag.Float64Var……方法,最终都会调用以下Var方法,将选项名与该类型选项的处理器(Flag,含有该类型的一个新指针,该类型的Set方法将解析得的选项值赋值到变量指针所指位置)的对应关系,保存到formal里
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]
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) // Happens only if flags are declared with identical names
}
if f.formal == nil {
f.formal = make(map[string]*Flag)
}
f.formal[name] = flag
}
flag.Parse方法会取命令行的第一个参数,即程序名作为选项集(FlagSet)的名字,然后调用该选项集的Parse方法
var CommandLine = NewFlagSet(os.Args[0], ExitOnError)
func NewFlagSet(name string, errorHandling ErrorHandling) *FlagSet {
f := &FlagSet{
name: name,
errorHandling: errorHandling,
}
f.Usage = f.defaultUsage
return f
}
func Parse() {
CommandLine.Parse(os.Args[1:])
}
而选项集的Parse方法则是循环调用parseOne,迭代每一个参数来处理
func (f *FlagSet) Parse(arguments []string) error {
f.parsed = true
f.args = arguments
for {
seen, err := f.parseOne()
if seen {
continue
}
if err == nil {
break
}
switch f.errorHandling {
case ContinueOnError:
return err
case ExitOnError:
if err == ErrHelp {
os.Exit(0)
}
os.Exit(2)
case PanicOnError:
panic(err)
}
}
return nil
}
parseOne方法会将形式为-p=1、--p=1、-p 1的参数值解析出来,通过Set方法赋值到变量指针所指位置,详细如下
func (f *FlagSet) parseOne() (bool, error) {
// 如果没有参数剩余了,则结束选项解析
if len(f.args) == 0 {
return false, nil
}
s := f.args[0]
// 如果选项长度为0或1,或者不是-开头,则结束选项解析
if len(s) < 2 || s[0] != '-' {
return false, nil
}
numMinuses := 1
// 如果选项是--,则结束选项解析
if s[1] == '-' {
numMinuses++
if len(s) == 2 { // "--" terminates the flags
f.args = f.args[1:]
return false, nil
}
}
// 如果去掉开头两个--后,没有选项名称,或者还有-,或者直接等号没有选项名,则报错
name := s[numMinuses:]
if len(name) == 0 || name[0] == '-' || name[0] == '=' {
return false, f.failf("bad flag syntax: %s", s)
}
// 如果选项与值以等号相连,则分离它们
f.args = f.args[1:]
hasValue := false
value := ""
for i := 1; i < len(name); i++ { // equals cannot be first
if name[i] == '=' {
value = name[i+1:]
hasValue = true
name = name[0:i]
break
}
}
m := f.formal
// 如果该选项未被定义,则报错(如果选项是help,则打印help)
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)
}
// 如果是bool选项……
if fv, ok := flag.Value.(boolFlag); ok && fv.IsBoolFlag() { // special case: doesn't need an arg
if hasValue {
if err := fv.Set(value); err != nil {
return false, f.failf("invalid boolean value %q for -%s: %v", value, name, err)
}
} else {
if err := fv.Set("true"); err != nil {
return false, f.failf("invalid boolean flag %s: %v", name, err)
}
}
} else {
// 如果没用等号,则选项值应为下一参数
if !hasValue && len(f.args) > 0 {
// value is the next arg
hasValue = true
value, f.args = f.args[0], f.args[1:]
}
// 如果还是没能设置值,则报错
if !hasValue {
return false, f.failf("flag needs an argument: -%s", name)
}
// 如果值类型错误,则报错
if err := flag.Value.Set(value); err != nil {
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
return true, nil
}
值类型的Set方法实现如下:
其实我们也可以自定义值类型:
type plusOne int
func (i *plusOne) Set(s string) error {
v, err := strconv.ParseInt(s, 0, strconv.IntSize)
if err != nil {
err = numError(err)
}
*i = plusOne(v) + 1
return err
}
func (i *plusOne) String() string { return strconv.Itoa(int(*i)) }
func main() {
var num plusOne
flag.Var(&num, "addOne", "加一")
flag.Parse()
log.Print(num)
}