几个小伙伴在考虑下面这个各个语言都会遇到的问题:
问题:设计一个命令行参数解析API
一个好的命令行参数解析库一般涉及到这几个常见的方面:
1) 支持方便地生成帮助信息
2) 支持子命令,比如:git包含了push, pull, commit等多种子命令
3) 支持单字符选项、多字符选项、标志选项、参数选项等多种选项和位置参数
4) 支持选项默认值,比如:–port选项若未指定认为5037
5) 支持使用模式,比如:tar命令的-c和-x是互斥选项,属于不同的使用模式
经过一番考察,小伙伴们发现了这个几个有代表性的API设计:
1. getopt():
getopt()是libc的标准函数,很多语言中都能找到它的移植版本。
- //C
- while ((c = getopt(argc, argv, "ac:d:")) != -1) {
- int this_option_optind = optind ? optind : 1;
- switch (c) {
- case 'a':
- printf ("option a");
- aopt = 1;
- break;
- case 'c':
- printf ("option c with value '%s'", optarg);
- copt = optarg;
- break;
- case 'd':
- printf ("option d with value '%s'", optarg);
- dopt = optarg;
- break;
- case '?':
- break;
- default:
- printf ("?? getopt returned character code 0%o ??", c);
- }
- }
getopt()的核心是一个类似printf的格式字符串的命令行参数描述串,如上面的”ac:d:”定义了”a”, “c”,”d”3个命令行参数,其中,a是一个标志符不需要参数,”c”和”d”需要跟参数。getopt()功能非常弱,只支持单个字符的标志选项和参 数选项。如果按上面的5点来比对,基本上只能说是勉强支持第3点,其他几项只能靠程序自己来实现了,所以,想直接基于getopt()实现一个像git这 样复杂的命令行参数是不可能的,只有自己来做很多的解析工作。小伙伴们看过getopt()之后一致的评价是:图样图森破。
2. Google gflags
接着,小伙伴们又发现了gflags这个Google出品C++命令行参数解析库。
- /C++
- DEFINE_bool(memory_pool, false, "If use memory pool");
- DEFINE_bool(daemon, true, "If started as daemon");
- DEFINE_string(module_id, "", "Server module id");
- DEFINE_int32(http_port, 80, "HTTP listen port");
- DEFINE_int32(https_port, 443, "HTTPS listen port");
- int main(int argc, char** argv) {
- ::google::ParseCommandLineFlags(&argc, &argv, true);
- printf("Server module id: %s", FLAGS_module_id.c_str());
- if (FLAGS_daemon) {
- printf("Run as daemon: %d", FLAGS_daemon);
- }
- if (FLAGS_memory_pool) {
- printf("Use memory pool: %d", FLAGS_daemon);
- }
- Server server;
- return 0;
- }
小伙伴们看了后不由得感叹“真心好用啊”!的确,gflags简单地通过几个宏就定义了命令行选项,基本上很好的支持了上面提到的1,3,4这几 项,比起getopt()来强多了。对于类似cp这样的小命令,gflags应该是够用了,但要达到git这种级别就显得有些单薄了。
3. Ruby Commander
接下来小伙伴们又发现了Ruby Commander库:
- //Ruby
- # :name is optional, otherwise uses the basename of this executable
- program :name, 'Foo Bar'
- program :version, '1.0.0'
- program :description, 'Stupid command that prints foo or bar.'
- command :bar do |c|
- c.syntax = 'foobar bar [options]'
- c.description = 'Display bar with optional prefix and suffix'
- c.option '--prefix STRING', String, 'Adds a prefix to bar'
- c.option '--suffix STRING', String, 'Adds a suffix to bar'
- c.action do |args, options|
- options.default :prefix => '(', :suffix => ')'
- say "#{options.prefix}bar#{options.suffix}"
- end
- end
- $ foobar bar
- # => (bar)
- $ foobar bar --suffix '}' --prefix '{'
- # => {bar}
Commander库利用Ruby酷炫的语法定义了一种描述命令行参数的内部DSL,看起来相当高端大气上档次。除了上面的第5项之外,其他几项都 有很好的支持,可以说Commander库的设计基本达到了git这种级别命令行参数解析的要求。只是,要搞懂Ruby这么炫的语法和这个库的使用方法恐 怕就不如getopt()和gflags容易了。有小伙伴当场表示想要学习Ruby,但是也有小伙伴表示再看看其他库再说。
4. Lisp cmdline库
接下来,小伙伴们发现了Lisp方言Racket的cmdline库。
- //Lisp
- (parse-command-line "compile" (current-command-line-arguments)
- `((once-each
- [("-v" "--verbose")
- ,(lambda (flag) (verbose-mode #t))
- ("Compile with verbose messages")]
- [("-p" "--profile")
- ,(lambda (flag) (profiling-on #t))
- ("Compile with profiling")])
- (once-any
- [("-o" "--optimize-1")
- ,(lambda (flag) (optimize-level 1))
- ("Compile with optimization level 1")]
- [("--optimize-2")
- ,(lambda (flag) (optimize-level 2))
- (("Compile with optimization level 2,"
- "which implies all optimizations of level 1"))])
- (multi
- [("-l" "--link-flags")
- ,(lambda (flag lf) (link-flags (cons lf (link-flags))))
- ("Add a flag <lf> for the linker" "lf")]))
- (lambda (flag-accum file) file)
- '("filename"))
这是神马浮云啊?括号套括号,看起来很厉害的样子,但又不是很明白。看到这样的设计,有的小伙伴连评价都懒得评价了,但也有的小伙伴对Lisp越发崇拜,表示Lisp就是所谓的终极语言了,没有哪门语言能写出这么不明觉历的代码来!小伙伴们正准备打完收工,突然…
5. Node.js的LineParser库
发现了Node.js的LineParser库:
- //JavaScript
- var meta = {
- program : 'adb',
- name : 'Android Debug Bridge',
- version : '1.0.3',
- subcommands : [ 'connect', 'disconnect', 'install' ],
- options : {
- flags : [
- [ 'h', 'help', 'print program usage' ],
- [ 'r', 'reinstall', 'reinstall package' ],
- [ 'l', 'localhost', 'localhost' ]
- ],
- parameters : [
- [ null, 'host', 'adb server hostname or IP address', null ],
- [ 'p', 'port', 'adb server port', 5037 ]
- ]
- },
- usages : [
- [ 'connect', ['host', '[port]'], null, 'connect to adb server', adb_connect ],
- [ 'connect', [ 'l' ], null, 'connect to the local adb server', adb_connect ],
- [ 'disconnect', null, null, 'disconnect from adb server', adb_disconnect ],
- [ 'install', ['r'], ['package'], 'install package', adb_install ],
- [ null, ['h'], null, 'help', adb_help ],
- ]
- };
- try {
- var lineparser = require('lineparser');
- var parser = lineparser.init(meta);
- parser.parse(['install', '-r', '/pkgs/bird.apk']); // adb_install will be invoked
- }
- catch (e) {
- console.error(e);
- }
天啊!?这是什么?我和小伙伴们彻底惊呆了!短短十几行代码就获得了上面5点的全面支持,重要的是小伙伴们居然一下子就看懂了,没有任何的遮遮掩掩 和故弄玄虚。本来以为Ruby和Lisp很酷,小伙伴们都想马上去学Ruby和Lisp了,看到这个代码之后怎么感觉前面全是在装呢?有个小伙伴居然激动 得哭着表示:我写代码多年,以为再也没有什么代码可以让我感动,没想到这段代码如此精妙,我不由得要赞叹了,实在是太漂亮了!
小伙伴们的故事讲完了,您看懂了吗?如果没有再看一遍标题吧!