本次flag介绍 部分引用书《Go语言编程之旅》 很不错的书 推荐指数⭐⭐⭐⭐⭐文末会附书封面
package main
import (
"flag"
)
func main() {
var name string
flag.Parse()
//调用newflagset会返回一个带有指定名称和错误处理的空命令集
goCmd := flag.NewFlagSet("go",flag.ExitOnError)
goCmd.StringVar(&name,"name","Go语言","帮助信息")
phpCmd := flag.NewFlagSet("php",flag.ExitOnError)
phpCmd.StringVar(&name,"n","php语言","帮助信息")
args := flag.Args()
if len(args) < 1 {
flag.Usage()
return
}
switch args[0] {
case "go":
goCmd.Parse(args[1:])
case "php":
phpCmd.Parse(args[1:])
}
}
//newflagset源码
func NewFlagSet(name string, errorHandling ErrorHandling) *FlagSet {
f := &FlagSet{
name: name,
errorHandling: errorHandling,
}
f.Usage = f.defaultUsage
return f
}
type FlagSet struct {
// Usage is the function called when an error occurs while parsing flags.
// The field is a function (not a method) that may be changed to point to
// a custom error handler. What happens after Usage is called depends
// on the ErrorHandling setting; for the command line, this defaults
// to ExitOnError, which exits the program after calling Usage.
Usage func()
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
}
type ErrorHandling int
// These constants cause FlagSet.Parse to behave as described if the parse fails.
const (
ContinueOnError ErrorHandling = iota // Return a descriptive error.
ExitOnError // Call os.Exit(2).
PanicOnError // Call panic with a descriptive error.
)
// CommandLine is the default set of command-line flags, parsed from os.Args.
// The top-level functions such as BoolVar, Arg, and so on are wrappers for the
// methods of CommandLine.
var CommandLine = NewFlagSet(os.Args[0], ExitOnError)
// Parse parses the command-line flags from os.Args[1:]. Must be called
// after all flags are defined and before flags are accessed by the program.
func Parse() {
// Ignore errors; CommandLine is set for ExitOnError.
CommandLine.Parse(os.Args[1:])
}
//parse 也是用newflagset实例化了一个命令的集合,通过调用os.args 把新的孔明零集作为外部参数传进去
//parse传入的是ExitOnError 也就是说出现问题会直接退出,如果不希望直接退出那么需要做一些额外的处理
// Parse parses flag definitions from the argument list, which should not
// include the command name. Must be called after all flags in the FlagSet
// are defined and before flags are accessed by the program.
// The return value will be ErrHelp if -help or -h were set but not defined.
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:
os.Exit(2)
case PanicOnError:
panic(err)
}
}
return nil
}
//flagset。parse是对解析的进一步封装,通过parseOne,而解析过程中遇到重复解析,异常处理等,均可直接又flagset.parse处理,这是一个分层明显,结构清晰的方法
// 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]
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)
}
//以上代码主要是针对一些不符合命令行参数绑定规则的校验斤西瓜处理
/*
命令行参数长度为0
长度小于2或者不满足flag标识符-
如果flag标志为--,则中断处理并且跳过,后续会议-进行处理
处理flag标志为,如果渠道的参数名不符合规则,也将中断处理
*/
// it's a flag. does it have an argument?
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
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)
}
//下面部分代码 :在设置参数值,会对值类型进行判断,如果是布尔类型 调用定制的boolflag类型做处理,最后通过该flag的value.set方法将参数设置到对应的flag中
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 {
// It must have a value, which might be the next argument.
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
}
定义参数类型
经过前面的分析 flag的命令行参数类型可以自定义,也就是说在value.Set方法中我们需要实现对应的两个接口
type Value interface {
String() string
Set(string) error
}
package main
import (
"errors"
"flag"
"fmt"
"log"
)
type Name string
func (n *Name) String() string{
return fmt.Sprint(*n)
}
func (n *Name) Set(value string) error {
if len(*n) > 0 {
return errors.New("name flag already set ")
}
*n = Name("eddycjy"+ value)
return nil
}
func main() {
var name Name
flag.Var(&name,"name","help")
flag.Parse()
log.Printf("name: %s",name)
}
也就是说我们实现了下面的接口,就可以做自己定制化操作了
type Value interface {
String() string
Set(string) error
}