In newer Getopt::Long How do I set default optional values
Asked Answered
E

4

5

In Perl's Getopt::Long version 2.39 I could use

use Getopt::Long qw( :config gnu_getopt );
GetOptions(
   \my %opts, 
   "codon-view|c:20",    # Optional value, default 20
   "consensus|C:50", 
   ...
)

to indicate that if I use -c the default value would be 20 put in %optsunder key codon-view when -c is given but no explicit value for it is there. On the other hand -c or --codon-view is not supplied, then no value in the hash table is stored for in %opts.

In 2.48 this no longer works and I don't see in Getopt::Long's documentation

$ perl -E'
   use Getopt::Long qw( :config gnu_getopt );
   say $Getopt::Long::VERSION;
   GetOptions(\my %opts, "codon-view|c:20");
   say $opts{"codon-view"} // "[undef]"
' -- -c
2.39
20

$ perl -E'
   use Getopt::Long qw( :config gnu_getopt );
   say $Getopt::Long::VERSION;
   GetOptions(\my %opts, "codon-view|c:20");
   say $opts{"codon-view"} // "[undef]"
' -- -c
2.48
[undef]

How can I achieve the old behavior?

Help!

Ergotism answered 1/6, 2016 at 21:35 Comment(4)
Are you sure that worked in 2.39? Going all the way back to 2.24 (released in 2000), the documentation for options with values consistently shows option specs of the form tag=s or tag:i, not tag:20.Month
I've modified the post to include this. What you get matches my behavior. It is only when -c or --codon-view is given without a value that the behavior changes betwee 2.39 and 2.48. Also there are changes in FindOption that change between those two versions with respect to gnu_getopt.Ergotism
@ThisSuitIsBlackNot, Search for : numberLoftin
This is a change introduced in 2.48. I'm not sure, but I think it was done unintentionally, so I filed a bug report.Loftin
L
5

This is a change introduced in 2.48.

$ perl -E'
   use Getopt::Long qw( :config gnu_getopt );
   say $Getopt::Long::VERSION;
   GetOptions(\my %opts, "codon-view|c:20");
   say $opts{"codon-view"} // "[undef]"
' -- -c
2.47
20

$ perl -E'
   use Getopt::Long qw( :config gnu_getopt );
   say $Getopt::Long::VERSION;
   GetOptions(\my %opts, "codon-view|c:20");
   say $opts{"codon-view"} // "[undef]"
' -- -c
2.48
[undef]

I'm not sure, but I think it was done unintentionally, so I filed a bug report.


use Getopt::Long qw( :config gnu_getopt );

is short for

use Getopt::Long qw( :config gnu_compat bundling permute no_getopt_compat );

How invested are you in using gnu_compat?

$ perl -E'
   use Getopt::Long qw( :config gnu_getopt );
   say $Getopt::Long::VERSION;
   GetOptions(\my %opts, "codon-view|c:20");
   say $opts{"codon-view"} // "[undef]"
' -- -c
2.48
[undef]

$ perl -E'
   use Getopt::Long qw( :config gnu_compat bundling permute no_getopt_compat );
   say $Getopt::Long::VERSION;
   GetOptions(\my %opts, "codon-view|c:20");
   say $opts{"codon-view"} // "[undef]"
' -- -c
2.48
[undef]

$ perl -E'
   use Getopt::Long qw( :config bundling permute no_getopt_compat );
   say $Getopt::Long::VERSION;
   GetOptions(\my %opts, "codon-view|c:20");
   say $opts{"codon-view"} // "[undef]"
' -- -c
2.48
20

gnu_compat controls whether --opt= is allowed, and what it should do. Without gnu_compat, --opt= gives an error. With gnu_compat, --opt= will give option opt and empty value. This is the way GNU getopt_long() does it.

So if you're ok with --codon-view= assigning zero to $opts{"codon-view"}, simply use

use Getopt::Long qw( :config bundling permute no_getopt_compat );

instead of

use Getopt::Long qw( :config gnu_getopt );
Loftin answered 2/6, 2016 at 4:1 Comment(5)
Since this is a usability thing, I'll need to ask users to experiment. What I've done for now is use the 2.39 code copied into the module. Given what you say, I suppose I can increase the version up to 2.47 or so.Ergotism
Look again. My solution won't affect any of your users. It simply makes --opt= stop throwing an error.Loftin
It looks like the documentation for gnu_compat is wrong. perl -MGetopt::Long=:config,gnu_compat -E'GetOptions(\%opts, "foo:1") or die; say "<$opts{foo}>"' -- --foo= prints <0>; removing gnu_compat prints <1> (instead of giving an error).Month
@ThisSuitIsBlackNot, It does die without gnu_compat for = options. /// Getopt::Long's test suite is woefully lacking.Loftin
@Loftin just checked. Yep this works as you say. And this should be fine. Thanks.Ergotism
S
3

Set the default value before the GetOptions() call. If the option is not provided on the command line, then the default value will not be overwritten.

$ perl -MGetopt::Long -E '$c=20;GetOptions("c=i"=>\$c); say $c' -- -c 14
14

$ perl -MGetopt::Long -E '$c=20;GetOptions("c=i"=>\$c); say $c' --
20

There is a trivial example in the Getopt::Long documentation.

Sublett answered 1/6, 2016 at 22:1 Comment(1)
I guess I wasn't clear about the previous behavior. If -c is not given then then that key isn't in a hash. If it is set, then and only then does the value for that key have a default value. I'll edit my question to reflect this.Ergotism
S
1

I like to assign my opts to a hash..

GetOptions(\ my %opt,
    'codon-view|c:i',
);

if ( exists $opt{'codon-view'} ) {
    print "User triggered '-c' flag\n";
    $opt{'codon-view'} ||= 20;
    printf( "codon-view: %d\n", $opt{'codon-view'} );
}

Now if a user runs ./you-app -c with no argument, the $opt{c} key gets created, but it's value is undef, so you much check if it was triggered with exists.

The ||= operator assigns the right-hand side to the left-hand side only if the left-hand side is falsey ( usually undef ). A caveat is that if someone does -c 0 it will assign the default... but I'm going to go ahead and assume 0 is probably a bad argument for your flag.

Scathe answered 2/6, 2016 at 0:49 Comment(1)
I'm sorry I didn't report my situation fully. First, yes, I do use an options hash. Also, I use use Getopt::Long qw(config gnu_getopt); Your example does work when gnu_getopt is not specified in the use statement. However when it is specified, then your example no longer works. Also it is a little more cumbersome and puts in two places what codon-view|c:20 does. If we can can address the problem when gnu_getopt is specified, I'll accept this if no better solution is found.Ergotism
E
1

Here is another possible but less good solution: include a copy of Getopt::Long.pm, It's only one file, but I have changed the package namespace to something different, e.g. MyPackage::GetoptLong.

This is not an ideal answer, but it is something to keep in mind if you need something to keep compatibility and don't have the better ikegami solution.

Ergotism answered 2/6, 2016 at 8:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.