digitalmars.D.announce - Argon: an alternative parser for command-line arguments
- Markus Laker (3/3) Mar 02 2016 https://github.com/markuslaker/Argon
- Chris Wright (5/10) Mar 02 2016 You might want to take a minute to shill it here. What's great about it?...
- Markus Laker (157/161) Mar 03 2016 OK. :-)
- Johannes Pfau (7/12) Mar 03 2016 The rest of this list sounds quite good, but please reconsider
- Markus Laker (14/19) Mar 03 2016 I think we're safe:
- Jim Balter (6/19) Jul 05 2018 No one wants to watch a 40 minute video just to find out what
- Andrei Alexandrescu (4/7) Mar 03 2016 [snip]
- Jason White (13/16) Mar 02 2016 Looks nice! Can it support sub-commands (e.g., git status)? I
- Markus Laker (7/15) Mar 03 2016 Thanks for the tip. I'm going to have to research those; I'm an
- Markus Laker (5/12) Mar 03 2016 I've not explored D's UDAs yet, so it took a moment to click, but
- Jacob Carlborg (5/7) Mar 03 2016 Does it support some longer documentation for the flags, i.e. "git
- Markus Laker (18/20) Mar 03 2016 Yes. You can give an option a description, like this:
- Jacob Carlborg (6/16) Mar 04 2016 No, I mean a longer description, more like documentation. Look at the
- Markus Laker (8/11) Mar 04 2016 Ah, I see. Sorry for the misunderstanding.
- karabuta (14/25) Mar 05 2016 I think he meant: [git status --help], where you have three
- Markus Laker (32/36) Mar 09 2016 Odd: I wrote a reply to this a few days ago, and it's nowhere to
- karabuta (3/25) Mar 10 2016 I don't like subcommands myself. That's why Linux is such as mess
- Victor Porton (6/23) Jan 26 2019 It is a wrong way, because switches may have arguments (not
- Nick Sabalausky (5/8) Mar 03 2016 Reminds me of one I used years ago for C#: I like the approach, it's a
https://github.com/markuslaker/Argon Let me know if you do something interesting with it. Markus
Mar 02 2016
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. MarkusYou 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
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: 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
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
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
On Thursday, 3 March 2016 at 09:33:38 UTC, Johannes Pfau wrote:Am Thu, 03 Mar 2016 09:09:38 +0000 schrieb Markus Laker <markus.laker gmail.com>:No one wants to watch a 40 minute video just to find out what your point is.* 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_2I guess the scenario can't happen in D as our open file methods won't execute programs (!) but still....But still? What other problem is there? What does it matter whether the command line parser opens the file or some other part of the program does?
Jul 05 2018
On 03/03/2016 04:09 AM, Markus Laker wrote:On Thursday, 3 March 2016 at 01:52:11 UTC, Chris Wright wrote:[snip] Very nice! I think we should adopt some of these ideas for std.getopt as well. -- AndreiYou might want to take a minute to shill it here. What's great about it?OK. :-)
Mar 03 2016
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. MarkusLooks 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
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/dargLooks interesting. Thanks. I'll take a proper look this evening, when I can give it the time it deserves. Markus
Mar 03 2016
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
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
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
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
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
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: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=statusNo, 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 05 2016
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
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
On Wednesday, 9 March 2016 at 18:56:10 UTC, Markus Laker wrote: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 is a wrong way, because switches may have arguments (not starting with a dash). I am now considering to write a new object-oriented command line parser from scratch because it is sometimes easier to write new code than to understand other's one.It shouldn't be hard to write some reusable code to do this, if it were a common requirement.
Jan 26 2019
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. Markusgood 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