What is a good command line parser API? A good command line parser should consider below 5 aspects:
- Support convenient help information generation
- Support sub commands, for example, git has push,pull,commit sub commands.
- Support single character option, word option, flag option and option with parameter.
- Support default option, for example, if no -port is set, set it as 5037
- Support usage model, for example, tar's -c and -x is mutually exclusive, they belong to different usage models.
Here are some outstanding command line parse API.
1. getopt()
getopt() is the standard function of libc, we can find it in many different code examples.
//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); } }
The core of getopt() is a command line parameter description string powered by printf(). It also accepts 'a','c' and 'd' command options, 'a' is a flag option which doesn't need a parameter, while 'c' and 'd' need parameter. getopt() is not so powerful, it only supports single character option, flag option and option with parameter. It's almost impossible to implement a complex command line parser like git with getopt().
2. Google gflags
gflags is a C++ command line parse library produced by Google
//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 defines command line options with some macros, it basically conforms to the 1,3 and 4 above and it's much powerful than getopt().
3. Ruby Commander
There is also one command line parse library called 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 utilizes the cool syntax of Ruby to define an internal DSL of command line parameters. It can be used to implement git command line parser. However, it's not so convenient to use compared to getopt() and gflags.
4. Lisp cmdline
Next it's Racket's 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"))
So many brackets, looks very tough. Not so easy to understand.
5. 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); // adb_install will be invoked parser.parse(['install', '-r', '/pkgs/bird.apk']); } catch (e) { console.error(e); }
Just a few lines, it conforms to all 5 points above. And it's easy to understand. It's really just like what the article title says : data as code.
Author: Todd Source : http://coolshell.cn/articles/10337.html
为什么我觉得看coolshell的都是ä¸å›½äººï¼Ÿ