Skip to content

Commit

Permalink
refactor: xcmd comments
Browse files Browse the repository at this point in the history
  • Loading branch information
hui.wang committed Jan 19, 2022
1 parent 2835e56 commit 39d1523
Show file tree
Hide file tree
Showing 19 changed files with 272 additions and 124 deletions.
78 changes: 70 additions & 8 deletions xcmd/commander.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,23 @@ import (
"io"
"os"
"strings"
)

type MiddlewareFunc = func(ctx context.Context, c *Command, ff *flag.FlagSet, args []string, next Executer) error
"github.com/sandwich-go/xconf/xflag"
)

// MiddlewareFunc 中间件方法
// cmd *Command : 为当前执行的命令对象
// ff *flag.FlagSet: 为当前命令对象解析中使用的FlagSet,如为pre中间件则未解析,否则为解析过后的FlagSet
// args []string : 为当前命令行参数,也就是FlagSet要去解析的参数列表
// next : 下一步要执行的方法,可能是下一个中间件或者目标Executer方法
type MiddlewareFunc = func(ctx context.Context, cmd *Command, ff *flag.FlagSet, args []string, next Executer) error

// Command 表征一条命令或一个命令Group
// 中间件分preMiddleware与middleware
// 执行顺序为:preMiddleware -> Parser -> middleware -> Executer
// - preMiddleware执行的时候并没有进行任何的FlagSet解析,可以在此时进行一些自定义的Flag创建
// - Parser为Option传入的中间件,一般无需自行实现,Parser主要是将配置文件、FlagSet、Env等遵循XConf规则解析到Option传入的Bind对象上,如传nil,则会调用FlagSet的Parse方法
// - middleware执行的时候已经完成了参数对象的绑定解析
type Command struct {
name string
cc ConfigInterface
Expand All @@ -20,12 +33,15 @@ type Command struct {
middleware []MiddlewareFunc
preMiddleware []MiddlewareFunc
usageNamePath []string
parent *Command // 目前只用于确认命令是否已经有父节点
}

// NewCommand 创建一条命令
func NewCommand(name string, opts ...ConfigOption) *Command {
return NewCommandWithConfig(name, NewConfig(opts...))
}

// NewCommandWithConfig 创建一条命令
func NewCommandWithConfig(name string, cc ConfigInterface) *Command {
c := &Command{
name: name,
Expand All @@ -36,22 +52,34 @@ func NewCommandWithConfig(name string, cc ConfigInterface) *Command {
return c
}

func (c *Command) Config() ConfigInterface { return c.cc }

// Use 添加中间件,在此之后添加的子命令都会继承该中间件
// 执行顺序为:preMiddleware -> Parser -> middleware -> Executer
func (c *Command) Use(middleware ...MiddlewareFunc) *Command {
c.middleware = append(c.middleware, middleware...)
return c
}

// UsePre 添加preMiddleware中间件,pre中间件运行在Parser之前
// 执行顺序为:preMiddleware -> Parser -> middleware -> Executer
func (c *Command) UsePre(preMiddleware ...MiddlewareFunc) *Command {
c.preMiddleware = append(c.preMiddleware, preMiddleware...)
return c
}

// AddCommand 添加一条子命令,可以携带中间件信息,等同于Add(xxxxx).Use或者AddCommand(xxxx).Use
func (c *Command) AddCommand(sub *Command, middleware ...MiddlewareFunc) {
if sub.parent != nil {
panic(fmt.Sprintf("command:%s has parent:%s", sub.name, sub.parent.name))
}
sub.usageNamePath = append(c.usageNamePath, sub.usageNamePath...)
sub.middleware = combineMiddlewareFunc(c.middleware, middleware...)
sub.preMiddleware = combineMiddlewareFunc(c.preMiddleware, sub.preMiddleware...)
sub.parent = c
// 如果该命令在添加子命令前没有父节点,则需要将父节点的中间件追加上
for _, v := range sub.commands {
v.middleware = combineMiddlewareFunc(c.middleware, v.middleware...)
v.preMiddleware = combineMiddlewareFunc(c.preMiddleware, v.preMiddleware...)
}
c.commands = append(c.commands, sub)
}

Expand All @@ -62,6 +90,10 @@ func combineMiddlewareFunc(middlewareNow []MiddlewareFunc, middleware ...Middlew
return m
}

// Config 获取配置,允许运行期调整,但只在Parser运行前生效
func (c *Command) Config() ConfigInterface { return c.cc }

// Add 添加一条子命令
func (c *Command) Add(name string, opts ...ConfigOption) *Command {
sub := NewCommand(name, opts...)
c.AddCommand(sub)
Expand All @@ -75,24 +107,43 @@ func (c *Command) wrapErr(err error) error {
return fmt.Errorf("command: %s err:%s", strings.Join(c.usageNamePath, " "), err.Error())
}

// Execute 执行参数解析驱动命令执行
func (c *Command) Execute(ctx context.Context, args ...string) error {
if len(args) != 0 {
// 尝试在当前命令集下寻找子命令
subCommandName := args[0]
for _, cmd := range c.commands {
if cmd.GetName() != subCommandName {
if cmd.Name() != subCommandName {
continue
}
return cmd.Execute(ctx, args[1:]...)
}
}
ff := flag.NewFlagSet(strings.Join(c.usageNamePath, "/"), flag.ContinueOnError)
// 默认 usage 无参
ff.Usage = func() {
c.Explain(c.Output)
xflag.PrintDefaults(ff)
}

var allMiddlewares []MiddlewareFunc
allMiddlewares = append(allMiddlewares, c.preMiddleware...)
allMiddlewares = append(allMiddlewares, c.cc.GetParser())
allMiddlewares = append(allMiddlewares, parser)
allMiddlewares = append(allMiddlewares, c.middleware...)
return ChainMiddleware(allMiddlewares...)(ctx, c, ff, args, exec)
}

func parser(ctx context.Context, c *Command, ff *flag.FlagSet, args []string, next Executer) error {
if c.cc.GetBind() == nil {
err := ff.Parse(args)
if err != nil {
return err
}
return next(ctx, c, ff, args)
}
return c.cc.GetParser()(ctx, c, ff, args, next)
}

func exec(ctx context.Context, c *Command, ff *flag.FlagSet, args []string) error {
executer := c.Config().GetExecute()
if executer == nil {
Expand All @@ -101,9 +152,19 @@ func exec(ctx context.Context, c *Command, ff *flag.FlagSet, args []string) erro
return executer(context.Background(), c, ff, args)
}

func (c *Command) GetName() string { return c.name }
func (c *Command) Usage() string { return c.cc.GetSynopsis() }
// Name 获取当前命令名称
func (c *Command) Name() string { return c.name }

// NamePath 获取当前命令路径
func (c *Command) NamePath() []string { return c.usageNamePath }

// Usage 获取当前命令路径
func (c *Command) Usage() string { return c.cc.GetUsage() }

// Short 获取当前命令Short Usage
func (c *Command) Short() string { return c.cc.GetShort() }

// SubCommand 由当前命令扩展子命令, 继承Bing,BindPath,XConfOption等参数
func (c *Command) SubCommand(name string, opts ...ConfigOption) *Command {
cc := NewConfig(
WithBind(c.cc.GetBind()),
Expand All @@ -116,6 +177,7 @@ func (c *Command) SubCommand(name string, opts ...ConfigOption) *Command {
return sub
}

// Check 检查当前命令及子命令是否有路径绑定错误等信息
func (c *Command) Check() error {
for _, v := range c.commands {
binder := c.cc.GetParser()
Expand Down
36 changes: 29 additions & 7 deletions xcmd/default.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,39 @@ var rootCmd = NewCommand(path.Base(os.Args[0]), WithExecute(func(ctx context.Con
return nil
}))

func Use(middleware ...MiddlewareFunc) *Command { return rootCmd.Use(middleware...) }
func UsePre(middleware ...MiddlewareFunc) *Command { return rootCmd.UsePre(middleware...) }
// Use 添加中间件,在此之后添加的子命令都会继承该中间件
// 执行顺序为:preMiddleware -> Parser -> middleware -> Executer
func Use(middleware ...MiddlewareFunc) *Command { return rootCmd.Use(middleware...) }

// UsePre 添加preMiddleware中间件,pre中间件运行在Parser之前
// 执行顺序为:preMiddleware -> Parser -> middleware -> Executer
func UsePre(middleware ...MiddlewareFunc) *Command { return rootCmd.UsePre(middleware...) }

// Add 添加一条子命令
func Add(name string, opts ...ConfigOption) *Command { return rootCmd.Add(name, opts...) }

// AddCommand 添加一条子命令,可以携带中间件信息,等同于Add(xxxxx).Use或者AddCommand(xxxx).Use
func AddCommand(sub *Command, middleware ...MiddlewareFunc) { rootCmd.AddCommand(sub, middleware...) }
func Config() ConfigInterface { return rootCmd.cc }
func Execute(ctx context.Context, args ...string) error { return rootCmd.Execute(ctx, args...) }
func Explain(w io.Writer) { rootCmd.Explain(w) }
func Check() error { return rootCmd.Check() }

// Config 获取配置,允许运行期调整,但只在Parser运行前生效
func Config() ConfigInterface { return rootCmd.Config() }

// Execute 执行参数解析驱动命令执行
func Execute(ctx context.Context, args ...string) error { return rootCmd.Execute(ctx, args...) }

// Explain 打印使用说明
func Explain(w io.Writer) { rootCmd.Explain(w) }

// Check 检查当前命令及子命令是否有路径绑定错误等信息
func Check() error { return rootCmd.Check() }

// SubCommand 由当前命令扩展子命令, 继承Bing,BindPath,XConfOption等参数
func SubCommand(name string, opts ...ConfigOption) *Command {
return rootCmd.SubCommand(name, opts...)
}

// SetRootCommand 设定当前默认的根命令
func SetRootCommand(root *Command) { rootCmd = root }
func RootCommand() *Command { return rootCmd }

// RootCommand 获取根命令
func RootCommand() *Command { return rootCmd }
6 changes: 4 additions & 2 deletions xcmd/error.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package xcmd

import (
"errors"
"flag"
"strings"
)

var ErrNoNeedBind = errors.New("no need bind")
// ErrHelp is the error returned if the -help or -h flag is invoked
// but no such flag is defined.
var ErrHelp = flag.ErrHelp

// IsErrHelp 检查错误是否是ErrHelp
func IsErrHelp(err error) bool {
if err == nil {
return false
Expand Down
11 changes: 6 additions & 5 deletions xcmd/explain.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import (
type byGroupName []*Command

func (p byGroupName) Len() int { return len(p) }
func (p byGroupName) Less(i, j int) bool { return p[i].GetName() < p[j].GetName() }
func (p byGroupName) Less(i, j int) bool { return p[i].Name() < p[j].Name() }
func (p byGroupName) Swap(i, j int) { p[i], p[j] = p[j], p[i] }

// Explain 打印使用说明
func (c *Command) Explain(w io.Writer) { explainGroup(w, c) }

// explainGroup explains all the subcommands for a particular group.
Expand All @@ -27,7 +28,7 @@ func explainGroup(w io.Writer, c *Command) {
fmt.Fprintf(w, "Available Commands:\n\n")
sort.Sort(byGroupName(c.commands))
var level = []bool{}
lines := PrintCommand(c, level)
lines := printCommand(c, level)
// lines = xutil.TableFormatLines(lines, magic)
fmt.Fprintln(w, strings.Join(lines, "\n"))
fmt.Fprintf(w, "\n")
Expand Down Expand Up @@ -68,14 +69,14 @@ func applyPadding(filler string) string {

const magic = "\x00"

func PrintCommand(c *Command, lvl []bool) (lines []string) {
lines = append(lines, fmt.Sprintf("%s%s(%d,%d) %s %s", getPrefix(lvl), c.name, len(c.preMiddleware), len(c.middleware), magic, c.cc.GetSynopsis()))
func printCommand(c *Command, lvl []bool) (lines []string) {
lines = append(lines, fmt.Sprintf("%s%s(%d,%d) %s %s", getPrefix(lvl), c.name, len(c.preMiddleware), len(c.middleware), magic, c.cc.GetShort()))
var level = append(lvl, false)
for i := 0; i < len(c.commands); i++ {
if i+1 == len(c.commands) {
level[len(level)-1] = true
}
subLines := PrintCommand(c.commands[i], level)
subLines := printCommand(c.commands[i], level)
lines = append(lines, subLines...)
}
return lines
Expand Down
12 changes: 12 additions & 0 deletions xcmd/field2flagset.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@ import (
)

// GenFieldPathStruct 生成filedPath struct
// todo 应随optiongen生成,手动指定FieldPath的时候可以防止出错,目前需要手动定义利用Command.Check检查
// type ConfigFieldPath struct {
// HttpAddress string
// Timeouts string
// }

// func NewConfigFieldPath() *ConfigFieldPath {
// return &ConfigFieldPath{
// HttpAddress: "http_address",
// Timeouts: "timeouts",
// }
// }
func GenFieldPathStruct(name string, fields map[string]xconf.StructFieldPathInfo) string {
var lines []string
structName := strings.Title(name) + "FieldPath"
Expand Down
30 changes: 15 additions & 15 deletions xcmd/gen_config_optiongen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 39d1523

Please sign in to comment.