www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.announce - Argon: an alternative parser for command-line arguments

reply Markus Laker <d20160302.20.mlaker spamgourmet.com> writes:
https://github.com/markuslaker/Argon

Let me know if you do something interesting with it.

Markus
Mar 02 2016
next sibling parent reply Chris Wright <dhasenan gmail.com> writes:
On Wed, 02 Mar 2016 19:50:30 +0000, Markus Laker wrote:

 https://github.com/markuslaker/Argon
 
 Let me know if you do something interesting with it.
 
 Markus
You might want to take a minute to shill it here. What's great about it? How do I use it? Why should I use it instead of std.getopt? This is redundant, but it means I can get an idea of your project without having to click the link.
Mar 02 2016
parent reply Markus Laker <markus.laker gmail.com> writes:
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
Mar 03 2016
next sibling parent reply Johannes Pfau <nospam example.com> writes:
Am Thu, 03 Mar 2016 09:09:38 +0000
schrieb Markus Laker <markus.laker gmail.com>:

 * 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.
The rest of this list sounds quite good, but please reconsider automatically opening files: https://media.ccc.de/v/32c3-7130-the_perl_jam_2 I guess the scenario can't happen in D as our open file methods won't execute programs (!) but still....
Mar 03 2016
parent Markus Laker <d20160302.20.mlaker spamgourmet.com> writes:
On Thursday, 3 March 2016 at 09:33:38 UTC, Johannes Pfau wrote:
 The rest of this list sounds quite good, but please reconsider 
 automatically opening files: 
 https://media.ccc.de/v/32c3-7130-the_perl_jam_2

 I guess the scenario can't happen in D as our open file methods 
 won't execute programs (!) but still....
I think we're safe: msl james:~/d/argon$ perl -wE 'open my $fh, "ls |" or die; print for (<$fh>)[0..2]' argon argon.d argon.html msl james:~/d/argon$ rdmd --eval='try auto f = std.stdio.File("ls |", "r"); catch (Exception e) e.msg.writeln' Cannot open file `ls |' in mode `r' (No such file or directory) msl james:~/d/argon$ Of course, if you can demonstrate a vulnerability, I'll certainly fix it. Markus
Mar 03 2016
prev sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 03/03/2016 04:09 AM, Markus Laker wrote:
 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. :-)
[snip] Very nice! I think we should adopt some of these ideas for std.getopt as well. -- Andrei
Mar 03 2016
prev sibling next sibling parent reply Jason White <54f9byee3t32 gmail.com> writes:
On Wednesday, 2 March 2016 at 19:50:30 UTC, Markus Laker wrote:
 https://github.com/markuslaker/Argon

 Let me know if you do something interesting with it.

 Markus
Looks nice! Can it support sub-commands (e.g., git status)? I suppose that can be done by passing through unused arguments and parsing those again. Also, you'll get more users if it's a dub package and on code.dlang.org. I was also dissatisfied with std.getopt and wrote a command line argument parser (competition!): https://github.com/jasonwhite/darg Though it's not quite feature-complete yet, it does everything I need it to. It uses compile-time introspection to fill out the fields of a struct. It can also generate the help and usage strings at compile-time.
Mar 02 2016
next sibling parent Markus Laker <markus.laker gmail.com> writes:
On Thursday, 3 March 2016 at 04:48:42 UTC, Jason White wrote:
 Looks nice! Can it support sub-commands (e.g., git status)? I 
 suppose that can be done by passing through unused arguments 
 and parsing those again.
Yes, that's what I'd do.
 Also, you'll get more users if it's a dub package and on 
 code.dlang.org.
Thanks for the tip. I'm going to have to research those; I'm an experienced programmer, but I'm new to D.
 I was also dissatisfied with std.getopt and wrote a command 
 line argument parser (competition!):

     https://github.com/jasonwhite/darg
Looks interesting. Thanks. I'll take a proper look this evening, when I can give it the time it deserves. Markus
Mar 03 2016
prev sibling parent Markus Laker <d20160302.20.mlaker spamgourmet.com> writes:
On Thursday, 3 March 2016 at 04:48:42 UTC, Jason White wrote:
 I was also dissatisfied with std.getopt and wrote a command 
 line argument parser (competition!):

     https://github.com/jasonwhite/darg

 Though it's not quite feature-complete yet, it does everything 
 I need it to. It uses compile-time introspection to fill out 
 the fields of a struct. It can also generate the help and usage 
 strings at compile-time.
I've not explored D's UDAs yet, so it took a moment to click, but I think that's very clever. There's an obvious run-time benefit to doing things like that. Markus
Mar 03 2016
prev sibling next sibling parent reply Jacob Carlborg <doob me.com> writes:
On 2016-03-02 20:50, Markus Laker wrote:
 https://github.com/markuslaker/Argon

 Let me know if you do something interesting with it.
Does it support some longer documentation for the flags, i.e. "git status -h" vs "git status --help"? -- /Jacob Carlborg
Mar 03 2016
parent reply Markus Laker <d20160302.20.mlaker spamgourmet.com> writes:
On Thursday, 3 March 2016 at 15:08:37 UTC, Jacob Carlborg wrote:
 Does it support some longer documentation for the flags, i.e. 
 "git status -h" vs "git status --help"?
Yes. You can give an option a description, like this: // ... uint nr_doors; // ... Named("doors", nr_doors, 0) ("number of doors").AddRange(1, 4); If you do this, the user has a nice, short name to type: --doors 2 But the description will be used in error messages ("The number of doors must be between 1 and 4") and in auto-generated syntax summaries ("--doors <number of doors>"). For a positional parameter, the user never types the name, and so you just do this: Pos("number of doors", nr_doors, 0).AddRange(1, 4); Then the syntax element we auto-generate will be "<number of doors>" and error messages will be the same as for the equivalent named option. Markus
Mar 03 2016
parent reply Jacob Carlborg <doob me.com> writes:
On 2016-03-03 21:55, Markus Laker wrote:

 Yes.  You can give an option a description, like this:

 // ...
 uint nr_doors;
 // ...
 Named("doors", nr_doors, 0) ("number of doors").AddRange(1, 4);

 If you do this, the user has a nice, short name to type:

 --doors 2

 But the description will be used in error messages ("The number of doors
 must be between 1 and 4") and in auto-generated syntax summaries
 ("--doors <number of doors>").
No, I mean a longer description, more like documentation. Look at the help for git when using --help, it has different behavior than -h. The first one is more like a man page. -- /Jacob Carlborg
Mar 04 2016
parent reply Markus Laker <d20160302.20.mlaker spamgourmet.com> writes:
On Friday, 4 March 2016 at 12:21:25 UTC, Jacob Carlborg wrote:
 No, I mean a longer description, more like documentation. Look 
 at the help for git when using --help, it has different 
 behavior than -h. The first one is more like a man page.
Ah, I see. Sorry for the misunderstanding. An app could do that trivially: have a --short-help option with shortcut -h and a --help option with no shortcut, and then respond to the two switches differently. Mark the --short-help option as undocumented, and then it won't appear in an auto-generated syntax summary. Markus
Mar 04 2016
parent reply karabuta <karabutaworld gmail.com> writes:
On Friday, 4 March 2016 at 17:34:08 UTC, Markus Laker wrote:
 On Friday, 4 March 2016 at 12:21:25 UTC, Jacob Carlborg wrote:
 No, I mean a longer description, more like documentation. Look 
 at the help for git when using --help, it has different 
 behavior than -h. The first one is more like a man page.
Ah, I see. Sorry for the misunderstanding. An app could do that trivially: have a --short-help option with shortcut -h and a --help option with no shortcut, and then respond to the two switches differently. Mark the --short-help option as undocumented, and then it won't appear in an auto-generated syntax summary. Markus
I think he meant: [git status --help], where you have three attributes with the last one being the flag. So in addition to: [status --help] by default, you also have: [git status --help] to get help on status only. By the way, that styles used by git seems confusing. Why not make it show the default help when you do: [git --help], whilst you can do: [git --help=status] OR [git --help status] for help on status only? git --help git -h git --help=status git --help status git -h=status
Mar 05 2016
parent reply Markus Laker <d20160302.20.mlaker spamgourmet.com> writes:
On Saturday, 5 March 2016 at 16:28:25 UTC, karabuta wrote:
 I think he meant: [git status --help], where you have three 
 attributes with the last one being the flag. So in addition to: 
 [status --help] by default, you also have: [git status --help] 
 to get help on status only.
Odd: I wrote a reply to this a few days ago, and it's nowhere to be seen. I'll save a copy of this reply until I see it appearing on the forum. Argon doesn't directly support subcommands. That probably stems from a bias of mine: that subcommands make it harder for the author to parse the command and to generate good error messages, and also that they make it harder for users to use unfamiliar commands, because users must read a man page that documents eleven things they have no interest in doing just to get to the one thing that they need to do in order to get on with their day. At work, where I have written and I still maintain many hundreds of commands, I've moved away from subcommands completely: every operation gets a command of its own. But I know that not everyone agrees with me, and that's OK. If we want to debate this topic further, we should probably move the discussion from Announce to General. To support git-style syntax while using Argon, I'd do this: 1. Find the (possibly empty) initial sequence of tokens that start with a dash. Pass them to an Argon-derived class which we'll call `Stem', which parses them. 2. If no more tokens exist (as in "my-command --help"), do what we can with the options we've seen, and then exit. 3. Otherwise, the next token must be a subcommand name: we've seen something "my-command --verbose display-widgets --paginate". Use that token to select a leaf class, also derived from Argon. There's one leaf class per subcommand. 4. Pass the remaining tokens (in this example, just "--paginate") to the selected leaf pass for parsing. Also pass a reference to Stem, so that the leaf code can use any options garnered by Stem. It shouldn't be hard to write some reusable code to do this, if it were a common requirement.
Mar 09 2016
parent karabuta <karabutaworld gmail.com> writes:
On Wednesday, 9 March 2016 at 18:56:10 UTC, Markus Laker wrote:
 On Saturday, 5 March 2016 at 16:28:25 UTC, karabuta wrote:
 I think he meant: [git status --help], where you have three 
 attributes with the last one being the flag. So in addition 
 to: [status --help] by default, you also have: [git status 
 --help] to get help on status only.
 Argon doesn't directly support subcommands.  That probably 
 stems from a bias of mine: that subcommands make it harder for 
 the author to parse the command and to generate good error 
 messages, and also that they make it harder for users to use 
 unfamiliar commands, because users must read a man page that 
 documents eleven things they have no interest in doing just to 
 get to the one thing that they need to do in order to get on 
 with their day.
  At work, where I have written and I still maintain many 
 hundreds of commands, I've moved away from subcommands 
 completely: every operation gets a command of its own.  But I 
 know that not everyone agrees with me, and that's OK.  If we 
 want to debate this topic further, we should probably move the 
 discussion from Announce to General.
 ......
 It shouldn't be hard to write some reusable code to do this, if 
 it were a common requirement.
I don't like subcommands myself. That's why Linux is such as mess with so much inconsistencies.
Mar 10 2016
prev sibling parent Nick Sabalausky <SeeWebsiteToContactMe semitwist.com> writes:
On 03/02/2016 02:50 PM, Markus Laker wrote:
 https://github.com/markuslaker/Argon

 Let me know if you do something interesting with it.

 Markus
Reminds me of one I used years ago for C#: I like the approach, it's a good one. Getopt by comparison, while very good, always seemed like a kludge to accomplish a similar thing without the benefit of user attributes (which D lacked at the time).
Mar 03 2016