Skip to content

Commit

Permalink
refactor: xcmd support deprecated cmd
Browse files Browse the repository at this point in the history
  • Loading branch information
hui.wang committed Jan 24, 2022
1 parent aa742bd commit 69b8be8
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 74 deletions.
49 changes: 20 additions & 29 deletions xcmd/commander.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"flag"
"fmt"
"io"
"os"
"strings"

"github.com/sandwich-go/xconf"
Expand All @@ -31,32 +30,24 @@ func IsErrHelp(err error) bool {
// - Parser为Option传入的中间件,一般无需自行实现,Parser主要是将配置文件、FlagSet、Env等遵循XConf规则解析到Option传入的Bind对象上,如传nil,则会调用FlagSet的Parse方法
// - middleware执行的时候已经完成了参数对象的绑定解析
type Command struct {
name string
cc *config
Output io.Writer
commands []*Command

usageNamePath []string
parent *Command // 目前只用于确认命令是否已经有父节点

executer Executer
executerMiddleware []MiddlewareFunc // 记录设定Executer时的中间件,防止之后加入的中间件作用于Executer
executerMiddlewarePre []MiddlewareFunc

// 记录当前command上挂载的中间件
middleware []MiddlewareFunc
middlewarePre []MiddlewareFunc

bind interface{} // 命令绑定的参数结构
bindFieldPath []string // 命令绑定的参数FieldPath,如空则全部绑定

FlagArgs []string // 除去command名称的原始参数
FlagSet *flag.FlagSet
usage func()

// 缓存记录由parent继承而来的flag
flagInheritByMiddlewarePre []string
flagLocal []string
name string // 当前命令名称
cc *config // 当前命令配置项
Output io.Writer // Usage等输出地址,暂不可噢诶之
commands []*Command // 子命令
usageNamePath []string // 当前命令路径
parent *Command // 父节点,目前只用于确认命令是否已加入父命令
executer Executer // 指定的命令处理方法,如果设定为空,则使用DefaultExecuter
executerMiddleware []MiddlewareFunc // 记录设定Executer时的中间件,防止之后加入的中间件作用于Executer
executerMiddlewarePre []MiddlewareFunc // 记录设定Executer时的pre中间件,防止之后加入的中间件作用于Executer
middleware []MiddlewareFunc // 记录当前command上挂载的中间件
middlewarePre []MiddlewareFunc // 记录当前command上挂载的pre中间件
bind interface{} // 命令绑定的参数结构,由xconf负责解析
bindFieldPath []string // 命令绑定的参数FieldPath
FlagArgs []string // 除去command名称的原始参数
FlagSet *flag.FlagSet // 当前命令使用的FlagSet
usage func() // 当前命令的usage方法
flagInheritByMiddlewarePre []string // 缓存记录由parent继承而来的flag
flagLocal []string // 当前命令定义的flag参数,用于usage打印
}

// NewCommand 创建一条命令
Expand All @@ -69,7 +60,7 @@ func newCommandWithConfig(name string, cc *config) *Command {
c := &Command{
name: name,
cc: cc,
Output: os.Stdout,
Output: cc.Output,
}
c.usageNamePath = []string{name}
c.middlewarePre = append(c.middlewarePre, preMiddlewareBegin)
Expand Down Expand Up @@ -114,7 +105,7 @@ func (c *Command) Bind() interface{} { return c.bind }
// BindSet 设定参数绑定的对象,只在解析之前生效,并重置绑定
func (c *Command) BindSet(xconfVal interface{}) *Command {
c.bind = xconfVal
if c.bind == nil {
if c.bind != nil {
c.bindFieldPath = xconf.FieldPathList(c.bind, c.newXConf())
}
return c
Expand Down
98 changes: 57 additions & 41 deletions xcmd/explain.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,30 +24,32 @@ 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) }

func paragraph(w io.Writer, title string, content string) {
if content == "" {
return
}
fmt.Fprintf(w, "%s:\n", title)
contentLines := xutil.StringSliceWalk(strings.Split(xutil.StringTrim(content), "\n"), func(s string) (string, bool) {
return PaddingContent + xutil.StringTrim(s), true
})
fmt.Fprintf(w, "%s\n\n", strings.Join(contentLines, "\n"))
}

// explainGroup explains all the subcommands for a particular group.
func explainGroup(w io.Writer, c *Command) {
if len(c.commands) == 0 {
fmt.Fprintf(w, "USAGE: \n%s%s <flags> <args>\n\n", paddingContent, strings.Join(c.usageNamePath, " "))
fmt.Fprintf(w, "USAGE: \n%s%s <flags> <args>\n\n", PaddingContent, strings.Join(c.usageNamePath, " "))
} else {
fmt.Fprintf(w, "USAGE: \n%s%s <subcommand> <flags> <args>\n\n", paddingContent, strings.Join(c.usageNamePath, " "))
fmt.Fprintf(w, "USAGE: \n%s%s <subcommand> <flags> <args>\n\n", PaddingContent, strings.Join(c.usageNamePath, " "))
}

paragraph(w, "DEPRECATED", c.cc.Deprecated)
paragraph(w, "DESCRIPTION", c.cc.Description)
paragraph(w, "EXAMPLES", c.cc.Examples)

if len(c.commands) == 0 {
return
}
if c.cc.Description != "" {
fmt.Fprintf(w, "DESCRIPTION:\n")
usageLine := xutil.StringSliceWalk(strings.Split(xutil.StringTrim(c.cc.Description), "\n"), func(s string) (string, bool) {
return paddingContent + xutil.StringTrim(s), true
})
fmt.Fprintf(w, "%s\n\n", strings.Join(usageLine, "\n"))
}
if c.cc.Examples != "" {
fmt.Fprintf(w, "EXAMPLES:\n")
examplesLines := xutil.StringSliceWalk(strings.Split(xutil.StringTrim(c.cc.Examples), "\n"), func(s string) (string, bool) {
return paddingContent + xutil.StringTrim(s), true
})
fmt.Fprintf(w, "%s\n\n", strings.Join(examplesLines, "\n"))
}
sort.Sort(byGroupName(c.commands))
fmt.Fprintf(w, "AVAIABLE COMMANDS:\n")
sort.Sort(byGroupName(c.commands))
Expand All @@ -57,19 +59,19 @@ func explainGroup(w io.Writer, c *Command) {
fmt.Fprintf(w, "\n")
}

func getPrefix(lvl []bool) string {
func getPrefix(lvl []bool, padding string) string {
var levelPrefix string
var level = len(lvl)

for i := 0; i < level; i++ {
if level == 1 && lvl[i] {
levelPrefix += fmt.Sprintf("└%s ", applyPadding("─"))
levelPrefix += fmt.Sprintf("└%s ", applyPadding(padding))
} else if level == 1 && !lvl[i] {
levelPrefix += fmt.Sprintf("├%s ", applyPadding("─"))
levelPrefix += fmt.Sprintf("├%s ", applyPadding(padding))
} else if i+1 == level && !lvl[i] {
levelPrefix += fmt.Sprintf("├%s ", applyPadding("─"))
levelPrefix += fmt.Sprintf("├%s ", applyPadding(padding))
} else if i+1 == level && lvl[i] {
levelPrefix += fmt.Sprintf("└%s ", applyPadding("─"))
levelPrefix += fmt.Sprintf("└%s ", applyPadding(padding))
} else if lvl[i] {
levelPrefix += fmt.Sprintf(" %s ", applyPadding(" "))
} else {
Expand All @@ -80,21 +82,36 @@ func getPrefix(lvl []bool) string {
return levelPrefix
}

const padding = 4
const paddingContent = " "
const magic = "\x00"

var Padding = 6
var PaddingContent = " "
var PrintMiddlewareCount = false

func applyPadding(filler string) string {
var fill string
for i := 0; i < padding-2; i++ {
for i := 0; i < Padding-2; i++ {
fill += filler
}
return fill
}

const magic = "\x00"

func printCommand(c *Command, lvl []bool) (lines []string) {
lines = append(lines, fmt.Sprintf("%s%s%s(%d,%d) %s %s", paddingContent, getPrefix(lvl), c.name, len(c.middlewarePre), len(c.middleware), magic, c.cc.GetShort()))
line := ""
padding := "─"
if c.cc.Deprecated != "" {
padding = "x"
}
if PrintMiddlewareCount {
line = fmt.Sprintf("%s%s%s(%d,%d) %s %s", PaddingContent, getPrefix(lvl, padding), c.name, len(c.middlewarePre), len(c.middleware), magic, c.cc.GetShort())
} else {
line = fmt.Sprintf("%s%s%s %s %s", PaddingContent, getPrefix(lvl, padding), c.name, magic, c.cc.GetShort())
}
if c.cc.Deprecated != "" {
line += " [DEPRECATED]"
}
lines = append(lines, line)

var level = append(lvl, false)
for i := 0; i < len(c.commands); i++ {
if i+1 == len(c.commands) {
Expand All @@ -121,20 +138,19 @@ func (c *Command) updateUsage(x *xconf.XConf) {
if c.bind != nil && len(bindFieldPath) == 0 {
bindFieldPath = xconf.FieldPathList(c.bind, x)
}
local := c.flagLocal
for _, v := range bindFieldPath {
if xutil.ContainString(bindFieldPathParent, v) {
continue
}
local = append(local, v)
c.flagLocal = append(c.flagLocal, v)
}
var nowFlags []string
c.FlagSet.VisitAll(func(f *flag.Flag) {
nowFlags = append(nowFlags, f.Name)
})
var inherit []string
for _, v := range nowFlags {
if xutil.ContainString(local, v) {
if xutil.ContainString(c.flagLocal, v) {
continue
}
inherit = append(inherit, v)
Expand Down Expand Up @@ -168,7 +184,7 @@ func (c *Command) updateUsage(x *xconf.XConf) {
line += fmt.Sprintf("|%s| %s", tag, usage)
if xutil.ContainString(inherit, v.Name) {
linesGlobal = append(linesGlobal, line)
} else if xutil.ContainString(local, v.Name) {
} else if xutil.ContainString(c.flagLocal, v.Name) {
linesLocal = append(linesLocal, line)
} else {
panic("invalid flag name : " + v.Name)
Expand All @@ -184,29 +200,29 @@ func (c *Command) updateUsage(x *xconf.XConf) {

if len(linesGlobal) > 0 {
fmt.Fprintf(c.Output, "OPTIONS GLOBAL:\n")
fmt.Fprintln(c.Output, paddingContent+strings.Repeat("-", lineMaxLen))
fmt.Fprintln(c.Output, paddingContent+lineAllFormatted[0])
fmt.Fprintln(c.Output, paddingContent+strings.Repeat("-", lineMaxLen))
fmt.Fprintln(c.Output, PaddingContent+strings.Repeat("-", lineMaxLen))
fmt.Fprintln(c.Output, PaddingContent+lineAllFormatted[0])
fmt.Fprintln(c.Output, PaddingContent+strings.Repeat("-", lineMaxLen))
sorted := lineAllFormatted[1 : len(linesGlobal)+1]
sort.Strings(sorted)
for i := 0; i < len(linesGlobal); i++ {
fmt.Fprintln(c.Output, paddingContent+sorted[i])
fmt.Fprintln(c.Output, PaddingContent+sorted[i])
}

fmt.Fprintln(c.Output, paddingContent+strings.Repeat("-", lineMaxLen))
fmt.Fprintln(c.Output, PaddingContent+strings.Repeat("-", lineMaxLen))
fmt.Fprintln(c.Output)
}

if len(linesLocal) > 0 {
fmt.Fprintf(c.Output, "OPTIONS LOCAL:\n")
fmt.Fprintln(c.Output, paddingContent+strings.Repeat("-", lineMaxLen))
fmt.Fprintln(c.Output, paddingContent+lineAllFormatted[0])
fmt.Fprintln(c.Output, paddingContent+strings.Repeat("-", lineMaxLen))
fmt.Fprintln(c.Output, PaddingContent+strings.Repeat("-", lineMaxLen))
fmt.Fprintln(c.Output, PaddingContent+lineAllFormatted[0])
fmt.Fprintln(c.Output, PaddingContent+strings.Repeat("-", lineMaxLen))
sorted := lineAllFormatted[1+len(linesGlobal):]
for i := 0; i < len(linesLocal); i++ {
fmt.Fprintln(c.Output, paddingContent+sorted[i])
fmt.Fprintln(c.Output, PaddingContent+sorted[i])
}
fmt.Fprintln(c.Output, paddingContent+strings.Repeat("-", lineMaxLen))
fmt.Fprintln(c.Output, PaddingContent+strings.Repeat("-", lineMaxLen))
}
fmt.Fprintln(c.Output)
fmt.Fprintf(c.Output, "Use \"%s [command] --help\" for more information about a command.\n", path.Base(os.Args[0]))
Expand Down
40 changes: 37 additions & 3 deletions xcmd/gen_config_optiongen.go

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

10 changes: 9 additions & 1 deletion xcmd/option.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package xcmd

import (
"io"
"os"

"github.com/sandwich-go/xconf"
)

Expand All @@ -26,8 +29,13 @@ func configOptionDeclareWithDefault() interface{} {
// annotation@Parser(comment="配置解析")
"Parser": MiddlewareFunc(ParserXConf),
// annotation@Executer(comment="当未配置Parser时触发该默认逻辑")
"OnExecuterLost": Executer(DefaultExecuter),
"OnExecuterLost": Executer(DefaultExecuter),
// annotation@SuggestionsMinDistance(comment="推荐命令最低关联长度")
"SuggestionsMinDistance": 2,
// annotation@Output(comment="输出")
"Output": io.Writer(os.Stdout),
// annotation@Deprecated(comment="不推荐使用的命令说明,只有配置了该说明的命令才会显示Deprecated标签")
"Deprecated": "",
}
}

Expand Down
2 changes: 2 additions & 0 deletions xcmd/parser_xconf.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ func ParserXConf(ctx context.Context, cmd *Command, next Executer) error {
invalidKeys = append(invalidKeys, v)
}
}
} else {
ignorePath = keysList
}

if len(invalidKeys) > 0 {
Expand Down

0 comments on commit 69b8be8

Please sign in to comment.