perlancar / perl-Getopt-Long-More

1 stars 1 forks source link

GLM causes GL to die when given a mix of implicit/explicit handlers (i.e. linkages) #3

Closed tabulon closed 5 years ago

tabulon commented 5 years ago

While working on issue #1 ("hash-storage" support), I stumbled upon an independent problem.

In fact, it seems like GLM (Getopt::Long::More) had already a bug for the case of mixing implicit/explicit handlers, i.e. "linkages" as GoL (Getopt::Long) calls them.

Note that this issue may first seem of minor importance, given that using GLOBAL variables has long fallen out of fashion, but since the bug would also cripple proper support of the hash-storage" mode (which reflects a more popular GoL usage pattern), it proves to be quite a hurdle for GoL => GLM migration path.

STEPS TO REPRODUCE

The following code snippet demonstrates the problem more concretely:

use Getopt::Long::More;

my $opts = {};
my $res = GetOptions(
  'flag1|1',
  'flag2|f',
  'bool|b!'        => \$opts{bool},
  'int=i'          => optspec( handler => \$opts{int}   ),
  'flag3|k',
  'module|M=s@'    => optspec( handler => \$opts{module} ),
);

CURRENT (FAILING) MODE

The above snippet does NOT work on v0.004 or current MASTER .

Instead, it results in the death of the underlying GoL (), with the following message 👎

Invalid option linkage for "baz=s

Note that although it happens to be GoL that ultimately dies, the problem appears to lie within GLM, and in particular in its naive expectation that the arguments to Getoptions would always come in pairs (optspec => handler); which is not necessarily the case in real life GoL usage.

EXPECTED RESULT

The above test snippet should not cause GoL/GLM to die.

Instead, GoL should just be falling back to its usual default (implicit) handling for those options where no explicit handlers are given, i.e. in our case : flag1, flag2, flag3.

In the absence of any hash-storage mode, like in this particular example, the associated implicit handling happens be populating the corresponding GLOBAL variables ($flag1, $flag2, $flag3), as if we had written the following snippet:

use Getopt::Long::More;

my $opts = {};
my $res = GetOptions(
  'flag1|1'       => \$flag1,
  'flag2|f'       => \$flag2,    
  'bool|b!'       => \$opts{bool},
  'int=i'         => optspec( handler => \$opts{int}   ),
  'flag3|k'       => \$flag3,    
  'module|M=s@'   => optspec( handler =>\ $opts{module} ),
);

VANILLA GoL BEHAVIOUR (without GLM)

GoL happily accepts this particular usage pattern, as can be verified with the following equivalent snippet 👍

use Getopt::Long;

my $opts = {};
my $res = GetOptions(
  'flag1|1',
  'flag2|f',
  'bool|b!'                 => \$opts{bool},
  'int=i'                     => sub { $opts{int }         = $_[1]  ),
  'flag3|k',
  'module|M=s@'     => sub { $opts{module }  = $_[1]  ),
);

Note that, since GoL does not itself recognize OptSpec objects, this snippet simulates the situation by replacing those with equivalent subroutine handlers, just to demonstrate the case.

The CULPRIT :-)

As mentioned above, the problem appears to arise from GLM's naive expectation that the arguments to Getoptions would always come in pairs (optspec => handler); which is not necessarily the case in real life (documented) GoL usage.

PROPOSED SOLUTION

I have a proposed solution which involves dropping reliance on systematic pairwise (% 2) processing of the arguments passed to Getopts* and friends. It will be packaged in the upcoming PR.

The code changes are actually within the GetoptsFromArray function,.