Thread overview
Recovering options to getopt after a GetOptException is raised
Oct 07, 2015
Charles McAnany
Dec 08, 2018
Andrew Pennebaker
Dec 08, 2018
Andrew Pennebaker
October 07, 2015
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.
December 08, 2018
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!
December 08, 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);
    }
}