Command line options.

2 views
Skip to first unread message

Kris Kowal

unread,
May 24, 2009, 5:40:27 AM5/24/09
to jac...@googlegroups.com
REGRESSION: The changes I'm about to describe included modifying
system.args so that system.args[0] would be the path to the command
that invoked you, in all platforms and I've only tested Rhino myself.
This is how we should have done it before, but I somehow overlooked
it. This means that some things will break if or when these commits
make it into your repository. I've verified that the fix in Jackup is
as simple as shifting it off before parsing args. Sorry for the
inconvenience.

--

I tackled the task of getting Narwhal to support command line options
today. To that end, I've created an "args" module (vaguely inspired
by Python's optparse module) that contains a Parser object that
supports jQuery-like chaining for specifying options. The parser is
prototypically inheritable, and supports the usual bovid options.

-s -- short option
-d: -- short option with a value
-sd: -- two short options, the last of which has a value
--long -- long option
--long=value -- long option with a value
--long value -- long option with a value
-- -s --long -- everything after -- is treated as a plain argument

Defining options on the new args.Parser() object calls for an
.option() method that takes a variadic list of short and long option
names, and optionally the name of the option which is used for setting
values in the resultant options object, and for the formatted help.

You can chain your option with a variety of tools and shortcuts, but
the workhorses are .action(callback(options, name, [value], [...]))
and .validate(callback(value)). The callback you give .action() gets
called when an option is encountered, and .validate() is used to
convert and verify the validity of the value or values. The parser
intuits the number of values to consume based on the number of values
accepted by the action callback, using Function().length. .validate()
must throw an error if the value is not valid. There's also a
.def(value) for setting the initial/default value for an option.

The first order of conveniences are .set([value]) and .push(). .set()
with no specific value configures the option to set the option to a
single value. .set(value) configures the option to be a flag that
sets itself to a particular value, like .set(false) or .set(true).
.push() is similar, but it implicitly calls .def([]) and each time the
option is encountered on the command args, an additional value is
validated and pushed onto the option's array.

The next order of conveniences are for particular types. .number(),
.integer(), .whole(), .natural(), .octal(), and .hex() validate the
value to particular numeric domains and formats. .inc() and .dec()
are useful for multi-level verbosity or optimization flags as they
increment and decrement a value, implicitly defaulting the option to
0. .input() and .output() validate the given file name to a stream
object, and recognize '-' as either standard input or output
respectively. .choices(array) validates the value to one of the
strings in the array and .choices(object) translates the value by
looking it up on the object mapping.

Then there are little decorations, .hidden() which suppresses
mentioning the option when parser.printHelp() is called,
.help(description), and .halt() which signifies that this option
terminates option parsing if it's encountered.

The choices and whether an option halts are both automatically
mentioned in the help message when you call parser.printHelp(), or
when your user invokes it, if you've set up:

parser.option('-h', '--help').action(parser.printHelp);

The parser object also has a .usage(text) and .help(text) function,
and you can override parser.print and parser.exit to redirect those to
your own devices.

The invocation looks like:
var options = parser.parse([system.args]);

The parser statefully consumes the given array and returns the
resulting options object. It also sets options.command to the first
argument consumed if it wasn't a switch; this is handy for consuming
the invocation command on system.args so you don't have to do a
slice(1) on your arguments object.

Narwhal's shiny new option parser description is in lib/narwhal.js
http://github.com/kriskowal/narwhal/blob/master/lib/narwhal.js

Here's the help text:

kris@imoo:~/narwhal $ narwhal --help
Usage: /Users/kris/narwhal/platforms/rhino/bin/narwhal-rhino [options] [script]
Runs the Narwhal JavaScript interpreter.
If no script is specified, runs interactively.
-e -c --command command: evaluate command (final option)
-r --require module: pre-load a module
-m --module main: run a library module as a script (final option)
-p --package packagePrefixes: add a package prefix directory
-d --debug: debug
-P --no-packages: don't load packages automatically
-l --log level: set the log level (critical, error, warn, info, debug)
-V --version: print Narwhal version number and exit.
-h --help

Some options are hidden either because they're not yet implemented or
because they're easter eggs.

While I was at it, I ported minimal versions of some of the
conveniences I've grown accustomed to and put them in a "base" module.
It contains implementations of "repr", "keys", "eq", "enquote", and
"trim" among other things, with more to come for sure.

I also added Object.keys, Array.every, Array.some, and Function().bind
to platforms/default/lib/global.js per the new ES spec (I think,
mostly; I don't think I handled the constructor case for bind).

This is all a very hacky one-day product, so please feel free to
fiddle with it or replace it.

Kris Kowal

Tom Robinson

unread,
May 24, 2009, 7:04:34 PM5/24/09
to jac...@googlegroups.com
Awesome! Looks great.
Reply all
Reply to author
Forward
0 new messages