========
cli 开源在 github 上,欢迎大家前去 star
使用go get获取
go get github.com/mkideal/cli
上一篇介绍了使用cli
构建http服务的示例。本篇介绍一个用cli
创建的实用程序 exp
这是一个用于求值表达式的命令行程序,使用栗子:
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)
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型的变量
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时,将从标准输入流中读取表达式,比如
$> echo "x+y" | exp -i -Dx=2 -Dy=3
5
FreedomArgs
在代码28行调用了cli.Context
的一个获取自由参数数组的函数
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数组由多个元素,那么美个元素都是一个表达式,会依次进行求值,每个值输出到一行。所以上面第二个栗子的输出像这样:
5
2
结语
这个栗子很简单,但是却构建了一个很好用的命令行表达式求值程序。在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了。
有疑问加站长微信联系(非本文作者)
