Thread overview
String argument with optional value in std.getopt
Aug 08, 2020
Hassan
Aug 09, 2020
Hassan
August 08, 2020
Hello

I'm trying to get getopt to recognize an argument that may or may not take a value. Here's an example :

./hashtrack --list
./hashtrack --list filter

The problem is that if I point list to a string variable, the first call fails with "Missing value for argument --list".

I tried having it point to a callback with an optional second parameter :

void cb(string option, string value = "")
{
    writeln("--list");
    option.writeln();
    value.writeln();
}

But I get the same error. I also tried having two callbacks with the same name. The first one takes a single argument, and the second one accepts two :

void cb(string option)
{
    writeln("--list");
    option.writeln();
}

void cb(string option, string value)
{
    writeln("--list");
    option.writeln();
    value.writeln();
}

But it only calls the first one. I'm using DMD64 D Compiler v2.090.0. Any pointers ?
August 09, 2020
On 8/8/20 7:58 PM, Hassan wrote:
> Hello
> 
> I'm trying to get getopt to recognize an argument that may or may not take a value. Here's an example :
> 
> ../hashtrack --list
> ../hashtrack --list filter
> 
> The problem is that if I point list to a string variable, the first call fails with "Missing value for argument --list".
> 
> I tried having it point to a callback with an optional second parameter :
> 
> void cb(string option, string value = "")
> {
>      writeln("--list");
>      option.writeln();
>      value.writeln();
> }
> 
> But I get the same error. I also tried having two callbacks with the same name. The first one takes a single argument, and the second one accepts two :
> 
> void cb(string option)
> {
>      writeln("--list");
>      option.writeln();
> }
> 
> void cb(string option, string value)
> {
>      writeln("--list");
>      option.writeln();
>      value.writeln();
> }
> 
> But it only calls the first one. I'm using DMD64 D Compiler v2.090.0. Any pointers ?

getopt doesn't support optional parameters.

Two things I can think of:

1. Have 2 options that do the same thing, but only one accepts a parameter (e.g. `--list` and `--listf filter`)
2. If your optional parameters are not tied to the option itself, then don't accept them via getopt. In other words, if `hashtrack filter` is supposed to be valid, then filter isn't an option after all, it's a standard parameter.

-Steve
August 09, 2020
Thanks for the reply Steve

On Sunday, 9 August 2020 at 12:20:16 UTC, Steven Schveighoffer wrote:
> 2. If your optional parameters are not tied to the option itself, then don't accept them via getopt. In other words, if `hashtrack filter` is supposed to be valid, then filter isn't an option after all, it's a standard parameter.

"filter" can be any string really. It's like an input to the list option : hashtrack --list filter_by_this_word

I wasn't aware that there was a distinction between parameters and options. The tool I'm writing is a D port of an existing Go command line utility that uses parameters everywhere, things like "hashtrack list" instead of "hashtrack --list". I decided to use options as opposed to parameters because then I would get to use getopt to handle "args" and display a nicely formatted --help output.

I decided to go with this second solution even though "hashtrack filter" isn't technically valid. Here's what I did :

void main()
{
    //....
    bool list;

    auto opts = args.getopt(
        "login", "Creates a session token and store it in the local filesystem in a config file", &login,
        "logout", "Remove the locally stored session token", &logout,
        "track", "Tracks one or more hashtags", &track,
        "untrack", "Untracks one or more previously tracked hashtags", &untrack,
        "tracks", "Displays the hashtags you are tracking", &tracks,
        "list", "Displays the latest 50 captured tweets", &list,
        "watch", "Stream and display the captured tweets in real-time", &watch,
        "status", "Displays who you are, if logged in", &status,
        "endpoint", "Point to another server", &endpoint,
        "config", "Load a custom config file", &config
    );
    //....
    if(list)
    {
        string filter = args.length > 1 ? args[1] : "";
        tracking.list(filter).each!writeln;
    }
}