Data as code

  Todd        2013-08-08 22:40:36       6,816        2    

What is a good command line parser API? A good command line parser should consider below 5 aspects:

  1. Support convenient help information generation
  2. Support sub commands, for example, git has push,pull,commit sub commands.
  3. Support single character option, word option, flag option and option with parameter.
  4. Support default option, for example, if no -port is set, set it as 5037
  5. 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:   Source : http://coolshell.cn/articles/10337.html

COMMAND LINE  API 

       

  RELATED


  2 COMMENTS


IE [Reply]@ 2013-08-19 23:57:50

为什么我觉得看coolshell的都是中国人?

NightCat [Reply]@ 2013-08-20 03:14:03

That's because foreigners don't understand Chinese.



  RANDOM FUN

Example of bad design