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

Reply via email to