Markus Laker
Posted in reply to Chris Wright
| 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
|