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

========

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.comwww.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了。

共 0 个回复