cli - 构建强大命令行程序的工具箱 (2)

获取 cli

cli 开源在 github 上,欢迎大家前去 star :-)

使用go get获取

go get github.com/mkideal/cli

上一篇介绍了cli 是什么以及最简单的使用方法。本篇将开始一实例分析讲解cli的其他特性。

实例代码

 1    package main
 2    
 3    import (
 4        "fmt"
 5        "os"
 6    
 7        "github.com/mkideal/cli"
 8    )
 9    
10    func main() {
11        app := &cli.Command{
12            Name: os.Args[0],
13            Argv: func() interface{} { return new(argT) },
14            Fn: func(ctx *cli.Context) error {
15                argv := ctx.Argv().(*argT)
16                if argv.Help {
17                    ctx.WriteUsage()
18                } else {
19                    ctx.String("argv=%v\n", *argv)
20                }
21                return nil
22            },
23        }
24    
25        app.RegisterFunc("help", func(ctx *cli.Context) error {
26            ctx.String("show help: sub commands: help/version\n")
27            return nil
28        }, nil)
29    
30        app.Register(&cli.Command{
31            // NOTE: Name is required, panic if ""
32            Name: "version",
33    
34            // NOTE: Fn is required, panic if nil
35            Fn: func(ctx *cli.Context) error {
36                ctx.String("version: v0.0.1\n")
37                return nil
38            },
39    
40            // Argv is optional
41    
42            Desc: "Desc represent command's abstract, optional",
43            Text: "Text represent command's detailed description, optional too",
44        })
45    
46        if err := app.Run(os.Args[1:]); err != nil {
47            fmt.Printf("%v\n", err)
48        }
49    }
50    
51    type argT struct {
52        Help bool   `cli:"h,help" usage:"show help"`
53        Host string `cli:"H,host" usage:"specify host address" dft:"127.0.0.1"`
54        Port uint16 `cli:"p,port" usage:"specify http port" dft:"8080"`
55    }

Command 对象

11~23行创建了一个名为app的Command。Command的公有属性如下:

// Command is the top-level instance in command-line app
    Command struct {
        Name        string      // Command name
        Desc        string      // Command abstract
        Text        string      // Command detailed description
        Fn          CommandFunc // Command handler
        Argv        ArgvFunc    // Command argument factory function
        CanSubRoute bool

        HTTPRouters []string
        HTTPMethods []string
        ......
    }
  • Name - 命令的名字(对于非根命令,Name不能为空)
  • Desc - 命令的简要描述(可空,但建议填写)
  • Text - 命令的详细描述(可空)
  • Fn - 命令的功能函数
  • Argv - 参数工厂函数(可空)
  • CanSubRoute - 是否允许子命令部分路由匹配
  • HTTPRouters HTTPMethods - 到讲述HTTP的篇再讲

如13行所示

13            Argv: func() interface{} { return new(argT) },

Argv是一个构建命令行参数对象的工厂函数,创建出来的对象将根据参数数组os.Args[1:]进行解析。

CanSubRoute的含义稍微复杂一点。如果在终端键入

./app hello world -a --xyz=1

那么./app是会被理解为应用的名字
hello 被理解为应用程序的一个子命令
world 被理解为hello的一个子命令

当然我们程序里果真是有hello子命令,hello也果真有world子命令,那么一切OK。但是如果程序的hello子命令(假设有)没有world自命令,那么CanSubRoute就起作用了

  • 此时如果CanSubRoute为true - 将执行hello,自world开始全是参数
  • 反之,会报出命令未找到的错误

Context对象

Context 是命令执行时的上下文,它没有导出的共有属性,但提供几组公有方法。

  1. 参数相关的方法
// Path 返回完整的命令路径
// 比如`./app hello world -a --xyz=1` 将返回 "hello world"
// 当然如果 `hello` 子命令没有`world`子命令,而且`hello`自命令的CanSubRoute为true
// 那么这里返回的将是"hello"
func (ctx *Context) Path() string

// Router 以数组格式返回完整的命令路径
// 比如`./app hello world -a --xyz=1` 将返回 ["hello" "world"]
// 当然如果 `hello` 子命令没有`world`子命令,而且`hello`自命令的CanSubRoute为true
// 那么这里返回的将是["hello"]
func (ctx *Context) Router() []string

// Args返回 除去命令外的原生参数
// 比如`./app hello world -a --xyz=1` 将返回 ["-a" "--xyz=1"]
// 当然如果 `hello` 子命令没有`world`子命令,而且`hello`自命令的CanSubRoute为true
// 那么这里返回的将是["world" "-a" "--xyz=1"]
func (ctx *Context) Args() []string

// Argv 返回解析所得的参数对象,如果Command的Argv为空,那么这里返回的就是空
func (ctx *Context) Argv() interface{}

// FormValues 返回 url.Values 格式的命令行参数
func (ctx *Context) FormValues() url.Values
  1. 与命令相关的方法
// Command 返回当前命令
func (ctx *Context) Command() *Command

// Usage 返回当前命令的使用方法
func (ctx *Context) Usage() string

// WriteUsage 输出当前命令的使用方法
func (ctx *Context) WriteUsage()
  1. 输出类方法
// Writer 返回输出流对象
func (ctx *Context) Writer() io.Writer

// Write 实现 io.Writer接口的Write方法
func (ctx *Context) Write(data []byte) (n int, err error)

// Color 返回输出流使用的颜色对象
func (ctx *Context) Color() *color.Color

// String 类似于fmt的Printf,只是输出到ctx的Writer里
func (ctx *Context) String(format string, args ...interface{}) *Context

// JSON 将参数obj进行JSON编码然后写入Writer
func (ctx *Context) JSON(obj interface{}) *Context

// JSONln 类似于JSON方法,不过追加了一个换行
func (ctx *Context) JSONln(obj interface{}) *Context

// JSONIndent 是美化的JSON方法
func (ctx *Context) JSONIndent(obj interface{}, prefix, indent string) *Context

// JSONIndentln 类似于JSONIndent,不过追加了一个换行
func (ctx *Context) JSONIndentln(obj interface{}, prefix, indent string) *Context

注册子命令

  1. RegisterFunc

25~28行使用CommandRegisterFunc方法注册子命令。RegisterFunc包含3个入参

  • name - 子命令名
  • fn - 子命令的Fn
  • argvFn - 子命令的Argv
func (cmd *Command) RegisterFunc(name string, fn CommandFunc, argvFn ArgvFunc) *Command
  1. Register

30~44行适应CommandRegister方法注册子命令。Register方法以Command对象做为入参

func (cmd *Command) Register(child *Command) *Command

运行根命令

46行调用根命令appRun函数运行程序。Run函数定义如下:

func (cmd *Command) Run(args []string) error {
    return cmd.RunWith(args, nil)
}

它调用了RunWith方法

func (cmd *Command) RunWith(args []string, writer io.Writer, httpMethods ...string) error

结语

今天将讲到这里了,今天讲述的栗子可参见github

共 0 个回复