========
cli 开源在 [github](https://github.com/mkideal/cli) 上,欢迎大家前去 star :-)
使用go get获取
```shell
go get github.com/mkideal/cli
```
[上一篇](http://studygolang.com/topics/1524)介绍了使用`cli`构建http服务的示例。本篇介绍一个用`cli`创建的实用程序 [`exp`](https://github.com/mkideal/tools/blob/master/exp/README.md)
这是一个用于求值表达式的命令行程序,使用栗子:
```shell
exp 1+2
exp -e 1+2
exp "1 + 2"
exp x -Dx=2.5
exp "x * y" -Dx=2 -Dy=6
exp "min(x, 4)" -Dx=3
exp "max(x, y, z)" -Dx=2 -Dy=6 -Dz=5
exp "rand() //rand in [0,10000)"
exp 'rand(n)' -Dn=100
exp 'rand(1,to)' -Dto=5
exp 'sum(1,2,3)'
exp 'aver(1,2,3)'
exp x y x+y x-y x*y x/y x%y x^y -Dx=7 -Dy=2
exp -e x y x+y x-y x*y x/y x%y x^y -Dx=7 -Dy=2
exp 'sin(pi)' 'sin(pi/2)'
exp e
exp pi
```
##代码(更完整的代码参见[exp](https://github.com/mkideal/tools/blob/master/exp/main.go))
```go
1 package main
2
3 import (
4 "fmt"
5 "io/ioutil"
6 "math"
7 "math/rand"
8 "os"
9 "strings"
10 "time"
11 "unicode"
12
13 "github.com/mkideal/cli"
14 "github.com/mkideal/pkg/expr"
15 )
16
17 type argT struct {
18 cli.Helper
19 Variables map[string]float64 `cli:"D" usage:"define variables, e.g. -Dx=3 -Dy=4"`
20 OutExpr bool `cli:"e" usage:"whther output native expression" dft:"false"`
21 File string `cli:"f,file" usage:"read expr from file"`
22 Stdin bool `cli:"i,stdin" usage:"read expr from stdin" sdt:"false"`
23
24 args []string `cli:"-"`
25 }
26
27 func (argv *argT) Validate(ctx *cli.Context) error {
28 argv.args = ctx.FreedomArgs()
29
30 dataList := make([]string, 0)
31 if argv.File != "" {
32 data, err := ioutil.ReadFile(argv.File)
33 if err != nil {
34 return err
35 }
36 dataList = append(dataList, string(data))
37 }
38 if argv.Stdin {
39 if data, err := ioutil.ReadAll(os.Stdin); err != nil {
40 return err
41 } else if len(data) > 0 {
42 dataList = append(dataList, string(data))
43 }
44 }
45 for _, data := range dataList {
46 args := strings.Split(strings.TrimSpace(data), "\n")
47 for _, arg := range args {
48 arg = strings.TrimFunc(arg, func(r rune) bool {
49 return unicode.IsSpace(r) || r == '"' || r == '\''
50 })
51 if arg != "" {
52 argv.args = append(argv.args, arg)
53 }
54 }
55 }
56 return nil
57 }
58
59 func run(ctx *cli.Context, argv *argT) error {
60 rand.Seed(time.Now().UnixNano())
61 if argv.Variables == nil {
62 argv.Variables = make(map[string]float64)
63 }
64 getter := expr.Getter(argv.Variables)
65 yellow := ctx.Color().Yellow
66
67 for k, v := range reservedWords {
68 if _, ok := getter[k]; ok {
69 return fmt.Errorf("%s is reserved word", yellow(k))
70 }
71 getter[k] = v
72 }
73
74 for _, s := range argv.args {
75 e, err := expr.New(s, pool)
76 if err != nil {
77 return err
78 }
79 ret, err := e.Eval(getter)
80 if err != nil {
81 return err
82 }
83 if argv.OutExpr {
84 ctx.String("%s: ", s)
85 }
86 ctx.String("%G\n", ret)
87 }
88 return nil
89 }
90
91 func main() {
92 cli.Run(new(argT), func(ctx *cli.Context) error {
93 argv := ctx.Argv().(*argT)
94 if argv.Help {
95 ctx.WriteUsage()
96 return nil
97 }
98 return run(ctx, argv)
99 }, `exp - evaluate expressions
100 examples:
101 exp 1+2
102 exp -e 1+2
103 exp "1 + 2"
104 exp x -Dx=2.5
105 exp "x * y" -Dx=2 -Dy=6
106 exp "min(x, 4)" -Dx=3
107 exp "max(x, y, z)" -Dx=2 -Dy=6 -Dz=5
108 exp "rand() //rand in [0,10000)"
109 exp 'rand(n)' -Dn=100
110 exp 'rand(1,to)' -Dto=5
111 exp 'sum(1,2,3)'
112 exp 'aver(1,2,3)'
113 exp x y x+y x-y x*y x/y x%%y x^y -Dx=7 -Dy=2
114 exp -e x y x+y x-y x*y x/y x%%y x^y -Dx=7 -Dy=2`)
115 }
116
117 var reservedWords = map[string]float64{
118 "e": math.E,
119 "E": math.E,
120 "pi": math.Pi,
121 "PI": math.Pi,
122 }
```
## Map类型作为参数
注意到了吗?在argT的定义中定义了一个map型的变量
```go
Variables map[string]float64 `cli:"D" usage:"define variables, e.g. -Dx=3 -Dy=4"`
```
`cli` 已经支持slice和map了。用法也简单,就两种形式:
-Dkey=value
-D key=value
本示例中 -D 用来给变量赋值
## 读取标准输入
示例中,argT中的Stdin为true时,将从标准输入流中读取表达式,比如
```shell
$> echo "x+y" | exp -i -Dx=2 -Dy=3
5
```
## FreedomArgs
在代码28行调用了`cli.Context`的一个获取自由参数数组的函数
```go
argv.args = ctx.FreedomArgs()
```
理论式的解释FreedomArgs的含义,还不如来几个栗子
1) exp x+y -Dx=2 -Dy=3 => FreedomArgs = ["x+y"]
2) exp x+y x -D x=2 -D y=3 => FreedomArgs = ["x+y", "x"]
概括的说,在命令行程序中,不是属于flag的参数就叫FreedomArg。上面的栗子里, `x=2` `y=3` 都是属于`-D`这个flag的,所以不是Freedom参数,而 x+y x 均是。
在`exp`这个程序里,如果FreedomArgs数组由多个元素,那么美个元素都是一个表达式,会依次进行求值,每个值输出到一行。所以上面第二个栗子的输出像这样:
```shell
5
2
```
## 结语
这个栗子很简单,但是却构建了一个很好用的命令行表达式求值程序。在[github.com/mkideal/tools](https://github.com/mkideal/tools)中还有其他栗子。比如traffic,这个用来做http重定向。假如你打算在一台机器上部署两个网站,对外都想直接用80端口,那么trafficd可以帮你的忙。假如你为自己的两个网站分别注册了域名地址 `www.a.com` 和`www.b.com`都绑到一个IP上 x.x.x.x。然后在主机x.x.x.x上起两个端口8080.9090服务你的两个网站。此时你是需要像这样访问你的两个网站的
```
http://www.a.com:8080
http://www.b.com:9090
```
现在在主机x.x.x.x上启动trafficd
```
sudo trafficd --port=80 -Mwww.a.com=www.a.com:8080 -Mwww.b.com=www.b.com:9090
```
这下可以不用输入8080或9090这样的端口就可以直接访问www.a.com和www.b.com了。
有疑问加站长微信联系(非本文作者)