获取 cli
========
cli 开源在 [github](https://github.com/mkideal/cli) 上,欢迎大家前去 star :-)
使用go get获取
```shell
go get github.com/mkideal/cli
```
[上一篇](http://studygolang.com/topics/1498)介绍了`cli` 是什么以及最简单的使用方法。本篇将开始一实例分析讲解`cli`的其他特性。
实例代码
=======
```go
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的公有属性如下:
```go
// 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行所示
```go
13 Argv: func() interface{} { return new(argT) },
```
`Argv`是一个构建命令行参数对象的工厂函数,创建出来的对象将根据参数数组os.Args[1:]进行解析。
`CanSubRoute`的含义稍微复杂一点。如果在终端键入
```shell
./app hello world -a --xyz=1
```
那么`./app`是会被理解为应用的名字
`hello` 被理解为应用程序的一个子命令
`world` 被理解为`hello`的一个子命令
当然我们程序里果真是有`hello`子命令,`hello`也果真有`world`子命令,那么一切OK。但是如果程序的`hello`子命令(假设有)没有`world`自命令,那么`CanSubRoute`就起作用了
* 此时如果`CanSubRoute`为true - 将执行`hello`,自`world`开始全是参数
* 反之,会报出命令未找到的错误
### Context对象
`Context` 是命令执行时的上下文,它没有导出的共有属性,但提供几组公有方法。
####参数相关的方法
```go
// 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
```
####与命令相关的方法
```go
// Command 返回当前命令
func (ctx *Context) Command() *Command
// Usage 返回当前命令的使用方法
func (ctx *Context) Usage() string
// WriteUsage 输出当前命令的使用方法
func (ctx *Context) WriteUsage()
```
####输出类方法
```go
// 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
```
### 注册子命令
#### **RegisterFunc**
25~28行使用`Command`的`RegisterFunc`方法注册子命令。`RegisterFunc`包含3个入参
* name - 子命令名
* fn - 子命令的Fn
* argvFn - 子命令的Argv
```go
func (cmd *Command) RegisterFunc(name string, fn CommandFunc, argvFn ArgvFunc) *Command
```
#### **Register**
30~44行适应`Command`的`Register`方法注册子命令。`Register`方法以Command对象做为入参
```go
func (cmd *Command) Register(child *Command) *Command
```
## 运行根命令
46行调用根命令`app`的`Run`函数运行程序。`Run`函数定义如下:
```go
func (cmd *Command) Run(args []string) error {
return cmd.RunWith(args, nil)
}
```
它调用了`RunWith`方法
```go
func (cmd *Command) RunWith(args []string, writer io.Writer, httpMethods ...string) error
```
## 结语
今天将讲到这里了,今天讲述的栗子可参见[github](https://github.com/mkideal/cli/blob/master/examples/simple-command/main.go)
有疑问加站长微信联系(非本文作者)