www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Recovering options to getopt after a GetOptException is raised

reply Charles McAnany <dlang charlesmcanany.com> writes:
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.
Oct 07 2015
parent reply Andrew Pennebaker <andrew.pennebaker gmail.com> writes:
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!
Dec 08 2018
parent Andrew Pennebaker <andrew.pennebaker gmail.com> writes:
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); } }
Dec 08 2018