Skip to content

Latest commit

 

History

History
351 lines (287 loc) · 8.14 KB

File metadata and controls

351 lines (287 loc) · 8.14 KB

Table of Contents generated with DocToc

github.com/alecthomas/kingpin

当前项目主要靠其他人提 PR.

特点包括:

  • 命令行参数定义:Kingpin 支持以链式调用的方式定义命令行参数,可以指定其名称、类型、默认值、描述等。
  • 子命令和嵌套命令:Kingpin 允许创建多级的命令行结构,使得命令行工具可以有更好的组织结构。
  • 命令行参数校验:Kingpin 提供了校验器,可以验证命令行参数的有效性。你可以指定自定义的校验函数,以确保用户输入的数据符合要求。

源码

Application 结构体

type Application struct {
    // 最关键的信息在 cmd mixin 
	cmdMixin 
	initialized bool // 是否已经 init ,即调用 app.parseContext

	Name string
	Help string

    // ..
	
	// 帮助信息模版
	usageTemplate  string
	usageFuncs     template.FuncMap
	validator      ApplicationValidator
	terminate      func(status int) // See Terminate()
	noInterspersed bool             // can flags be interspersed with args (or must they come first)
	defaultEnvars  bool
	completion     bool

    // ..
}

type cmdMixin struct {
	*flagGroup
	*argGroup
	*cmdGroup
	actionMixin
}

初始化

func New(name, help string) *Application {
	a := &Application{
		Name:          name,
		Help:          help,
		errorWriter:   os.Stderr, // Left for backwards compatibility purposes.
		usageWriter:   os.Stderr,
		usageTemplate: DefaultUsageTemplate,
		terminate:     os.Exit,
	}
	// flag 相关
	a.flagGroup = newFlagGroup()
	// arg 相关
	a.argGroup = newArgGroup()
	// cmd 相关
	a.cmdGroup = newCmdGroup(a)
	a.HelpFlag = a.Flag("help", "Show context-sensitive help (also try --help-long and --help-man).")
	a.HelpFlag.Bool()
    // ...

	return a
}

flag 添加

func (f *flagGroup) Flag(name, help string) *FlagClause {
	flag := newFlag(name, help)
	f.long[name] = flag
	f.flagOrder = append(f.flagOrder, flag)
	return flag
}


func newFlag(name, help string) *FlagClause {
	f := &FlagClause{
		name: name,
		help: help,
	}
	return f
}

cmd 添加

func (a *Application) Command(name, help string) *CmdClause {
	return a.addCommand(name, help)
}


func (c *cmdGroup) addCommand(name, help string) *CmdClause {
	cmd := newCommand(c.app, name, help)
	c.commands[name] = cmd
	c.commandOrder = append(c.commandOrder, cmd)
	return cmd
}

arg 信息

type ArgClause struct {
	actionMixin
	parserMixin
	completionsMixin
	envarMixin // 环境变量
	name          string
	help          string
	defaultValues []string //默认值
	placeholder   string
	hidden        bool
	required      bool //是否必须
}

arg 添加

func (a *argGroup) Arg(name, help string) *ArgClause {
	arg := newArg(name, help)
	a.args = append(a.args, arg)
	return arg
}

func newArg(name, help string) *ArgClause {
	a := &ArgClause{
		name: name,
		help: help,
	}
	return a
}

解析

func (a *Application) Parse(args []string) (command string, err error) {
    // 解析 
	context, parseErr := a.ParseContext(args)
	selected := []string{}
	var setValuesErr error

	if context == nil {
		// Since we do not throw error immediately, there could be a case
		// where a context returns nil. Protect against that.
		return "", parseErr
	}

	if err = a.setDefaults(context); err != nil {
		return "", err
	}

	selected, setValuesErr = a.setValues(context)

	if err = a.applyPreActions(context, !a.completion); err != nil {
		return "", err
	}

	if a.completion {
		a.generateBashCompletion(context)
		a.terminate(0)
	} else {
		if parseErr != nil {
			return "", parseErr
		}

		a.maybeHelp(context)
		if !context.EOL() {
			return "", fmt.Errorf("unexpected argument '%s'", context.Peek())
		}

		if setValuesErr != nil {
			return "", setValuesErr
		}

		command, err = a.execute(context, selected)
		if err == ErrCommandNotSpecified {
			a.writeUsage(context, nil)
		}
	}
	return command, err
}

token 解析

func (p *ParseContext) Next() *Token {
	if len(p.peek) > 0 {
		return p.pop()
	}

	// End of tokens.
	if len(p.args) == 0 {
		return &Token{Index: p.argi, Type: TokenEOL}
	}

	if p.argi > 0 && p.argi <= len(p.rawArgs) && p.rawArgs[p.argi-1] == "--" {
		// If the previous argument was a --, from now on only arguments are parsed.
		p.argsOnly = true
	}
	arg := p.args[0]
	p.next()

	if p.argsOnly {
		return &Token{p.argi, TokenArg, arg}
	}

	if arg == "--" {
		return p.Next()
	}

	if strings.HasPrefix(arg, "--") {
		parts := strings.SplitN(arg[2:], "=", 2)
		token := &Token{p.argi, TokenLong, parts[0]}
		if len(parts) == 2 {
			p.Push(&Token{p.argi, TokenArg, parts[1]})
		}
		return token
	}

	if strings.HasPrefix(arg, "-") {
		if len(arg) == 1 {
			return &Token{Index: p.argi, Type: TokenArg}
		}
		shortRune, size := utf8.DecodeRuneInString(arg[1:])
		short := string(shortRune)
		flag, ok := p.flags.short[short]
		// Not a known short flag, we'll just return it anyway.
		if !ok {
		} else if fb, ok := flag.value.(boolFlag); ok && fb.IsBoolFlag() {
			// Bool short flag.
		} else {
			// Short flag with combined argument: -fARG
			token := &Token{p.argi, TokenShort, short}
			if len(arg) > size+1 {
				p.Push(&Token{p.argi, TokenArg, arg[size+1:]})
			}
			return token
		}

		if len(arg) > size+1 {
			p.args = append([]string{"-" + arg[size+1:]}, p.args...)
		}
		return &Token{p.argi, TokenShort, short}
	} else if EnableFileExpansion && strings.HasPrefix(arg, "@") {
		expanded, err := ExpandArgsFromFile(arg[1:])
		if err != nil {
			return &Token{p.argi, TokenError, err.Error()}
		}
		if len(p.args) == 0 {
			p.args = expanded
		} else {
			p.args = append(expanded, p.args...)
		}
		return p.Next()
	}

	return &Token{p.argi, TokenArg, arg}
}

第三方使用

  • teleport 堡垒机
  • node_exporter
// https://github.com/prometheus/node_exporter/blob/v1.8.2/node_exporter.go
func main() {
	// 使用 kingpin 定义了众多 flag
	var (
		
		metricsPath = kingpin.Flag(
			"web.telemetry-path",
			"Path under which to expose metrics.",
		).Default("/metrics").String()
		disableExporterMetrics = kingpin.Flag(
			"web.disable-exporter-metrics",
			"Exclude metrics about the exporter itself (promhttp_*, process_*, go_*).",
		).Bool()
		maxRequests = kingpin.Flag(
			"web.max-requests",
			"Maximum number of parallel scrape requests. Use 0 to disable.",
		).Default("40").Int()
		disableDefaultCollectors = kingpin.Flag(
			"collector.disable-defaults",
			"Set all collectors to disabled by default.",
		).Default("false").Bool()
		maxProcs = kingpin.Flag(
			"runtime.gomaxprocs", "The target number of CPUs Go will run on (GOMAXPROCS)",
		).Envar("GOMAXPROCS").Default("1").Int()
		toolkitFlags = kingpinflag.AddFlags(kingpin.CommandLine, ":9100")
	)

	promslogConfig := &promslog.Config{}
	flag.AddFlags(kingpin.CommandLine, promslogConfig)
	kingpin.Version(version.Print("node_exporter"))
	kingpin.CommandLine.UsageWriter(os.Stdout)
	kingpin.HelpFlag.Short('h')
	kingpin.Parse()
	logger := promslog.New(promslogConfig)

	if *disableDefaultCollectors {
		collector.DisableDefaultCollectors()
	}
	logger.Info("Starting node_exporter", "version", version.Info())
	logger.Info("Build context", "build_context", version.BuildContext())
	if user, err := user.Current(); err == nil && user.Uid == "0" {
		logger.Warn("Node Exporter is running as root user. This exporter is designed to run as unprivileged user, root is not required.")
	}
	runtime.GOMAXPROCS(*maxProcs)
	logger.Debug("Go MAXPROCS", "procs", runtime.GOMAXPROCS(0))

	http.Handle(*metricsPath, newHandler(!*disableExporterMetrics, *maxRequests, logger))
    // ..

	server := &http.Server{}
	if err := web.ListenAndServe(server, toolkitFlags, logger); err != nil {
		logger.Error(err.Error())
		os.Exit(1)
	}
}