Tom MacWright

tom@macwright.com

zeroarg

zeroarg logo: zero-config argument parser

zeroarg is a weird new kind of argument parser.

Argument parsers, like yargs, commander, and meow, are utilities that make command-line interfaces possible, by parsing arguments that you give to a program in a shell like bash or zsh into variables in a language, like JavaScript.

I’ve written a bunch of command-line utilities, and argument parsing feels increasingly weird, foreign - kind of wrong. Documentation.js, for instance, has a lot of argument parsing code that mirrors the code that handles its Node API. I want consistent behavior between the two - for the same defaults to come into play regardless of whether you’re plugging in with the Node API or the CLI. But I end up restating a lot of stuff, wiffle-waffling over whether to express something in yargs, or keep argument parsing simple and do validation and such in the application level.

Especially when programs express more and more with type systems like Flow, it feels weird restating those types whenever we parse arguments.

meow is interesting, in that, instead of a fancy chaining JavaScript API, it parses documentation and generates a parser that aligns to that documentation.

zeroarg, instead, generates a CLI argument parser based on code. It uses the magic of documentation.js to infer parameter types & defaults, and read examples and a description for a function, and then configures yargs, under the hood, to generate a CLI utility for that function.

For example, here’s a command-line utility that takes numbers and returns their sum:

#!/usr/bin/env node

var zeroarg = require('zeroarg');

zeroarg(function () {
  /**
   * Add numbers together
   * @param {Array<number>} numbers
   */
  return function add(numbers) {
    console.log(numbers.reduce((sum, num) => sum + num, 0));
  }
});

And using it:

$ add run 1 2 3
6

It’s smart enough to handle options, too:

#!/usr/bin/env node

var zeroarg = require('zeroarg');

zeroarg(function () {
  /**
   * Add numbers together
   * @param {number} numbers
   * @param {Object} options
   * @param {string} options.a
   * @param {number} options.b
   * @param {wiffles|waffles} [options.c=wiffles]
   */
  return function(hello, { a, b, c }) {
    console.log(hello, a, b, c);
  }
});

And it reads JSDoc types and enforces those types in yargs: in this example, the option c (--c) becomes a choice between wiffles & waffles.

Under the hood:

  • zeroarg exposes a single function as an API, to which you provide a function that returns a function.
  • When you run the CLI utility,
  • It runs .toString() on that method, and uses documentation.js to figure out the arguments that the function wants and other documentation
  • It then configures yargs to fit those argument types
  • It runs yargs to get an argv value with parsed arguments, or to show an error or help message
  • It then runs the function you give it, and then runs the function that that one returns.

So: the thesis is:

  1. Argument parsing right now is weird in a world of type systems.
  2. What if we used existing types to generate argument parsers.
  3. And CLI utilities didn’t need to configure - or really ‘know about’ their use as CLI utilities. They could be plain functions.

zeroarg is bleeding-edge, experimental technology: if you feel the same way about traditional argument parsing as I do, I really invite you to try it out and contribute if you see ways for it to advance. Some things that I’ve had on the top of my head these few days:

  • Supporting customizable backends, not just yargs
  • Generating argument parsers, so that zeroarg really does disappear: it’d generate code that doesn’t require any library or module dependencies to run
  • Supporting multiple commands as multiple functions
  • zeroargs providing a bin entry point of its own, so that modules don’t even need another file for a CLI interface.
  • Real support for Flow annotations - right now Node doesn’t support Flow, and zeroarg works at runtime, not at compile time, so it has no chance to access Flow type annotations.
  • Let your imagination be the guide!