Recovering options to getopt after a GetOptException is raised

Andrew Pennebaker andrew.pennebaker at gmail.com
Sat Dec 8 20:12:37 UTC 2018


On Saturday, 8 December 2018 at 19:58:16 UTC, Andrew Pennebaker 
wrote:
> On Wednesday, 7 October 2015 at 17:15:17 UTC, Charles McAnany 
> wrote:
>> Friends,
>>
>> I'm a bit puzzled by the behavior of this code:
>>
>> import std.getopt;
>> import std.stdio;
>>
>> void main(string[] args){
>>     string val;
>>     GetoptResult opts;
>>     try{
>>         opts = getopt(args, std.getopt.config.required, "val", 
>> "value you need to specify", &val);
>>     }catch(GetOptException e){
>>         writeln("You idiot. You had to give these options:");
>>         writeln(opts.options);
>>     }
>> }
>>
>> Expected result:
>> :) programName --without --correct --argument
>>    You idiot. You had to give these options:
>>    --val value you need to specify
>>
>> Actual result:
>> :) programName --without --correct --argument
>>    You idiot. You had to give these options:
>>    []
>>
>> It seems that the exception is thrown as soon as a missing 
>> option is encountered. It would be nice if getopt would 
>> populate GetoptResult.options before it threw the exception, 
>> because it would make the above code work smoothly. 
>> Alternatively, GetOptException could get a field containing 
>> the documentation that was provided in the call to getopt:
>>
>> catch(GetOptException e){
>>     writefln("You idiot. You didn't provide the required %s 
>> argument (%s)", e.optionName, e.optionDocumentation);
>> }
>>
>> If there is a way to handle this cleanly, I'd appreciate it. 
>> As it is, std.getopt.config.required seems like it's not very 
>> useful because it emits a rather unhelpful error message 
>> (containing only the name of the missing option) and I can't 
>> see a way to print a more useful message without checking all 
>> the options in my own code.
>>
>> Cheers,
>> Charles.
>
> Yup, I just came across the same problem. D's getopt() is 
> silly. GetoptException could have included a field representing 
> the option specification, for use with defaultGetoptPrinter(); 
> Or getopt() could have printed the -h usage and exit()'ed; Or 
> std.getopt could have separated option specification 
> construction vs. validation into distinct function calls, so 
> that the spec could be bound to a variable for later use in 
> printing the help message when a parse error might occur.
>
> But D's getopt doesn't do any of these things! Not the most 
> helpful. If D at least had splats like Ruby, then we could copy 
> the inputs that would go into the getopt() call to an array, 
> and reuse that spec, with a ["-h"] array of user arguments, to 
> actually print out a help message.
>
> Or, programmers can manually re-type the getopt() spec, yuck!
>
> Or, I can imagine adding a loop around the try/catch, so that a 
> failure sets a "fail" boolean and changes the user arguments to 
> ["-h"].
>
> For me, I have to gauge how much time I want to spend cleaning 
> up the stack trace-style usage message on invalid user input, 
> against more productive use of my time.
>
> By the way, the getopt() documentation seems to suggest that 
> when users supply "-h|--help", that a usage banner is 
> automatically printed. But in fact, the programmer must 
> manually check the .helpWanted field on the result and manually 
> run defaultGetoptPrinter(). Lame!

**Update**

Success! Apparently tuples can be expanded like splats, and then 
it's easier to workaround the getopt() error handling behavior to 
produce a cleaner usage message on user input error:

// CLI math tool

import arithmancy;

import core.stdc.stdlib;
import std.format;
import std.getopt;
import std.stdio;
import std.typecons;

int n;

// Show short CLI spec
void usage(string program, GetoptResult opts) {
     defaultGetoptPrinter(
         format("Usage: %s [OPTIONS]", program),
         opts.options
     );
}

// CLI entry point
version(unittest) {} else
void main(string[] args) {
     immutable program = args[0];

     auto spec = tuple(
         std.getopt.config.required,
         "n", "an integer", &n
     );

     try {
         auto opts = getopt((args ~ spec).expand);

         if (opts.helpWanted) {
             usage(program, opts);
             exit(0);
         }

         writeln("%d", addTwo(n));
     } catch (GetOptException e) {
         usage(program, getopt(([program, "-h"] ~ spec).expand));
         exit(1);
     }
}


More information about the Digitalmars-d mailing list