數據即代碼,我和小伙伴們都驚呆了!
幾個小伙伴在考慮下面這個各個語言都會遇到的問題:
問題:設計一個命令行參數解析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了,看到這個代碼之后怎么感覺前面全是在裝呢?有個小伙伴居然激動 得哭著表示:我寫代碼多年,以為再也沒有什么代碼可以讓我感動,沒想到這段代碼如此精妙,我不由得要贊嘆了,實在是太漂亮了!
小伙伴們的故事講完了,您看懂了嗎?如果沒有再看一遍標題吧!