Argon: an alternative parser for command-line arguments

Markus Laker via Digitalmars-d-announce digitalmars-d-announce at puremagic.com
Thu Mar 3 01:09:38 PST 2016


On Thursday, 3 March 2016 at 01:52:11 UTC, Chris Wright wrote:
> You might want to take a minute to shill it here. What's great 
> about it?

OK.  :-)

* It parses positional parameters, error-checks them and places 
them into type-safe variables: it doesn't just pick out named 
--switches and then leave you to pick everything else out of argv.

* It can open files specified at the command line.  It can do a 
simplified version of what cat(1) does and many Perl programs so, 
and open a file specified by the user or fall back to reading 
from stdin.  There's also a convention that the user can type "-" 
to mean stdin or stdout, depending on the open-mode you specify.

* You can apply range-checks to numeric input and length-checks 
to string input.

* For numeric arguments, you can change the default radix from 
decimal to hex, octal or binary, and users can choose their own 
radices at run time.

* You can error-check string input using a sequence of regular 
expressions with user-friendly error messages, and then the 
picked-apart input is ready for your program to use, so that you 
don't have to analyse it again.

* Users can abbreviate switch names and enum values.

* As well as default values, there are separate end-of-line 
defaults, so that `list-it`, `list-it --wrap' and `list-it --wrap 
132' are all valid: you might arrange things so that the first 
doesn't wrap, the second wraps to 80 columns by default, and the 
third wraps to the user-specified width.

* You can set up argument groups: between N and M of these 
arguments (e.g. you can have --to and --by, but not both); all or 
none of these arguments (can't have --length without --width or 
vice versa); and first-or-none arguments (can specify a file name 
without a block size, but not vice versa).  An argument can 
belong to more than one group, and groups can be applied to any 
mixture of positional arguments and --named-options.

* Error messages are friendly, and take into account (for 
example) whether the user specified a parameter by its long name 
or its short name, and (if there are alternatives, such as 
--colour and color) which of several long names was used.

* Users can take advantage of flexible syntax: for example, -t5, 
-t 5 and -t=5 are all permitted and, for Boolean switches, --foo 
reverses the default (typically turning a switch on), but there's 
also --foo=0, --foo=no and ==foo=false (or any abbreviations), 
and similarly for true values.

* Argon gently encourages you to improve program structure by 
moving command-line parsing and all your parameters into a 
separate class, rather than passing a dozen pieces of information 
between functions all over the code.

> How do I use it?

Here's the example from Github, showing off just the basic 
functionality:

#!/usr/bin/rdmd --shebang -unittest -g -debug -w

import argon;

import std.stdio;

// Imagine a program that creates widgets of some kind.

enum Colours {black, blue, green, cyan, red, magenta, yellow, 
white}

// Write a class that inherits from argon.Handler:

class MyHandler: argon.Handler {

     // Inside your class, define a set of data members.
     // Argon will copy user input into these variables.

     uint size;
     Colours colour;
     bool winged;
     uint nr_windows;
     string name;
     argon.Indicator got_name;

     // In your constructor, make a series of calls to Named(),
     // Pos() and (not shown here) Incremental().  These calls 
tell Argon
     // what kind of input to expect and where to deposit the 
input after
     // decoding and checking it.

     this() {
         // The first argument is positional (meaning that the 
user specifies
         // it just after the command name, with an 
--option-name), because we
         // called Pos().  It's mandatory, because the Pos() 
invocation doesn't
         // specify a default value or an indicator.  (Indicators 
are explained
         // below.)  The AddRange() call rejects user input that 
isn't between
         // 1 and 20, inclusive.
         Pos("size of the widget", size).AddRange(1, 20);

         // The second argument is also positional, but it's 
optional, because
         // we specified a default colour: by default, our program 
will create
         // a green widget.  The user specifies colours by their 
names ('black',
         // 'blue', etc.), or any unambiguous abbreviation.
         Pos("colour of the widget", colour, Colours.green);

         // The third argument is a Boolean option that is named, 
as all
         // Boolean arguments are.  That means a user who wants to 
override
         // the default has to specify it by typing "--winged", or 
some
         // unambiguous abbreviation of it.  We've also provided a 
-w shortcut.
         //
         // All Boolean arguments are optional.
         Named("winged", winged) ('w');

         // The fourth argument, the number of windows, is a named 
argument,
         // with a long name of --windows and a short name of -i, 
and it's
         // optional.  A user who doesn't specify a window count 
gets six
         // windows.  Our AddRange() call ensures that no widget 
has more
         // than twelve and, because we pass in a uint, Argon will 
reject
         // all negative numbers.  The string "number of windows" 
is called a
         // description, and helps Argon auto-generate a more 
helpful
         // syntax summary.
         Named("windows", nr_windows, 6) ('i') ("number of 
windows").AddRange(0, 12);

         // The user can specify a name for the new widget.  Since 
the user
         // could explicitly specify an empty name, our program 
uses an
         // indicator, got_name, to determine whether a name was 
specified or
         // not, rather than checking whether the name is empty.
         Named("name", name, got_name) ('n').LimitLength(0, 20);
     }

     // Now write a separate method that calls Parse() and does 
something with
     // the user's input.  If the input is valid, your class's 
data members will
     // be populated; otherwise, Argon will throw an exception.

     auto Run(string[] args) {
         try {
             Parse(args);
             writeln("Size:    ", size);
             writeln("Colour:  ", colour);
             writeln("Wings?   ", winged);
             writeln("Windows: ", nr_windows);
             if (got_name)
                 writeln("Name:    ", name);

             return 0;
         }
         catch (argon.ParseException x) {
             stderr.writeln(x.msg);
             stderr.writeln(BuildSyntaxSummary);
             return 1;
         }
     }
}

int main(string[] args) {
     auto handler = new MyHandler;
     return handler.Run(args);
}

> Why should I use it instead of std.getopt?

More functionality for you; more flexible syntax for your users.

Cheers,

Markus


More information about the Digitalmars-d-announce mailing list