File Coverage

blib/lib/Getopt/Long/Spec/Parser.pm
Criterion Covered Total %
statement 97 98 98.9
branch 60 76 78.9
condition 18 23 78.2
subroutine 10 10 100.0
pod 2 2 100.0
total 187 209 89.4


line stmt bran cond sub pod time code
1 4     4   5813 use strict;
  4         13  
  4         153  
2 4     4   21 use warnings;
  4         7  
  4         241  
3              
4             package Getopt::Long::Spec::Parser;
5             {
6             $Getopt::Long::Spec::Parser::VERSION = '0.002';
7             }
8              
9             # ABSTRACT: Parse a Getopt::Long option spec into a set of attributes
10 4     4   22 use Carp;
  4         7  
  4         300  
11 4     4   2153 use Data::Dumper;
  4         13067  
  4         7074  
12              
13             # holds the current opt spec, used for error and debugging code...
14             my $CUR_OPT_SPEC;
15              
16             # holds the parameters for the current parse
17             my $CUR_OPTS;
18              
19             sub new {
20 1     1 1 485 my ( $class, %params ) = @_;
21 1         4 my $self = bless {%params}, $class;
22 1         5 return $self;
23             }
24              
25             sub parse {
26 73     73 1 83464 my ( $self, $spec, $params ) = @_;
27              
28             # temporary globals...
29 73         104 $CUR_OPT_SPEC = $spec;
30 73 50       78 $CUR_OPTS = { %{ $params || {} }, %{ ref( $self ) ? $self : {} } };
  73 100       355  
  73         219  
31              
32 73 50       223 print "DEBUG: spec: [$spec]\n" if $CUR_OPTS->{debug};
33 73 50       219 print "DEBUG: params: " . Dumper $CUR_OPTS if $CUR_OPTS->{debug};
34              
35 73 50       283 croak "Invalid option specification: [$spec]"
36             if $spec !~ /^ ([|a-zA-Z_-]+) ([=:!+]?) (.*) /x;
37              
38 73         130 my $name_spec = $1;
39 73 100       175 my $opt_type = $2 ? $2 : '';
40 73 100       163 my $arg_spec = $3 ? $3 : '';
41              
42 73         159 my %name_params = $self->_process_name_spec( $name_spec );
43 73         216 my %arg_params = $self->_process_arg_spec( $opt_type, $arg_spec );
44              
45             ### It is necessary to compute these here for compat. with GoL
46             ### I feel that this block should be relocated... but WHERE?
47 67 100       163 if ( $arg_params{negatable} ) {
48 6         25 my @neg_names = $self->_generate_negation_names(
49             $name_params{long},
50             $name_params{short},
51 6         14 @{ $name_params{aliases} },
52             );
53 6         12 push @{ $name_params{negations} }, @neg_names;
  6         28  
54             }
55              
56 67         75 undef $CUR_OPT_SPEC; # done with global var.
57 67         66 undef $CUR_OPTS; # ditto
58              
59 67         360 my %result = ( %name_params, %arg_params );
60              
61 67 100       504 return wantarray ? %result : \%result;
62             }
63              
64             our $NAME_SPEC_QR = qr{
65             ( [a-zA-Z_-]+ ) # option name as $1
66             (
67             (?: [|] [a-zA-Z?_-]+ )* # aliases as $2 (split on |)
68             )
69             }x;
70              
71             sub _process_name_spec {
72 73     73   123 my ( $self, $spec ) = @_;
73              
74 73 50       468 croak "Could not parse the name part of the option spec [$CUR_OPT_SPEC]."
75             if $spec !~ $NAME_SPEC_QR;
76              
77 73         75 my %params;
78              
79 73         159 $params{long} = $1;
80 83         199 $params{aliases} = [
81 83 100 100     564 grep { defined $_ }
      50        
82             map {
83 147         282 ( length( $_ ) == 1 and !$params{short} )
84             ? ( $params{short} = $_ and undef )
85             : $_
86             }
87 73         191 grep { $_ }
88             split( '[|]', $2 )
89             ];
90              
91 73         437 return %params;
92             }
93              
94             our $ARG_SPEC_QR = qr{
95             (?:
96             ( [siof] ) # value_type as $1
97             | ( \d+ ) # default_num as $2 (not always valid)
98             | ( [+] ) # increment type as $3 (not always valid)
99             )
100             ( [@%] )? # destination data type as $4
101             (?:
102             [{]
103             (\d+)? # min_vals as $5
104             (?:
105             [,]
106             (\d*)? # max_vals as $6
107             )?
108             [}]
109             )?
110             }x;
111              
112             sub _process_arg_spec {
113 73     73   126 my ( $self, $opt_type, $arg_spec ) = @_;
114              
115             # do some validation and set some params based on the option type
116 73         155 my %params = $self->_process_opt_type( $opt_type, $arg_spec );
117              
118 68 100       195 return %params unless $arg_spec;
119              
120             # parse the arg spec...
121 42 100       255 croak "Could not parse the argument part of the option spec [$CUR_OPT_SPEC]\n"
122             if $arg_spec !~ $ARG_SPEC_QR;
123 41         73 my $val_type = $1; # [siof]
124 41         55 my $default_num = $2; # \d+
125 41         53 my $incr_type = $3; # \+
126 41         52 my $dest_type = $4; # [@%]
127 41 100       103 $params{min_vals} = $5 if defined $5; # \d+
128 41 100       92 $params{max_vals} = $6 if defined $6; # \d+
129              
130 41 50 66     107 croak "can't use an + here unless opt_type is ':'\n"
131             if defined $incr_type and $opt_type ne ':';
132 41 100       74 if ( defined $incr_type ) {
133 4         8 $params{opt_type} = 'incr';
134             }
135              
136 41 50 66     96 croak "can't use a default number unless opt_type is ':'\n"
137             if defined $default_num and $opt_type ne ':';
138 41 100       72 if ( defined $default_num ) {
139 4         9 $params{default_num} = $default_num;
140             }
141              
142 41 50 66     193 croak "can't specify a val_type unless opt_type is ':' or '='\n"
143             if defined $val_type and $opt_type !~ /[:=]/;
144              
145 41 50 100     215 croak "repeat can only be used with a required value\n"
      66        
146             if ( exists $params{min_vals} or exists $params{max_vals} )
147             and $opt_type ne '=';
148              
149             # one repetition value, no comma...
150 41 100 100     125 if ( exists $params{min_vals} and !exists $params{max_vals} ) {
151 6         15 $params{num_vals} = delete $params{min_vals};
152             }
153              
154 41 100       76 if ( $val_type ) {
155 33 0       93 $params{val_type} =
    0          
    50          
    100          
156             $val_type eq 's' ? 'string'
157             : $val_type eq 'i' ? 'integer'
158             : $val_type eq 'o' ? 'extint'
159             : $val_type eq 'f' ? 'real'
160             : die "This should never happen. Ever.";
161 33         47 $params{opt_type} = 'simple';
162             }
163              
164 41 100       74 if ( defined $dest_type ) {
165 24 50       71 $params{dest_type} =
    100          
166             $dest_type eq '%' ? 'hash'
167             : $dest_type eq '@' ? 'array'
168             : croak "Invalid destination type [$dest_type]\n";
169             }
170              
171 41         214 return %params;
172             }
173              
174             # About the optiontype...
175             # = - option requires an argument
176             # : - option argument optional (defaults to '' or 0)
177             # ! - option is a flag and may be negated (0 or 1)
178             # + - option is an int starting at 0 and incremented each time specified
179             # - option is a flag to be turned on when used
180             sub _process_opt_type {
181 73     73   102 my ( $self, $opt_type, $arg_spec ) = @_;
182              
183 73         76 my %params;
184              
185             # set params and do some checking based on what we now know...
186 73 100       246 if ( $opt_type =~ /[+!]|^$/ ) {
187 27 100       51 if ( $arg_spec ) {
188 1         11 croak "Invalid option spec [$CUR_OPT_SPEC]: option type "
189             . "[$opt_type] does not take an argument spec.";
190             }
191 26 100       102 if ( $opt_type eq '+' ) {
192 4         10 $params{opt_type} = 'incr';
193             }
194 26 100       50 if ( $opt_type eq '!' ) {
195 6         12 $params{opt_type} = 'flag';
196 6         11 $params{negatable} = 1;
197             }
198 26 100       52 if ( $opt_type eq '' ) {
199 16         28 $params{opt_type} = 'flag';
200             }
201 26         101 return %params;
202             }
203              
204 46         71 $params{opt_type} = 'simple';
205              
206 46 100       101 if ( $opt_type eq '=' ) {
    50          
207 31         41 $params{val_required} = 1;
208             }
209             elsif ( $opt_type eq ':' ) {
210 15         24 $params{val_required} = 0;
211             }
212             else {
213 0         0 croak "Invalid option spec [$CUR_OPT_SPEC]: option type [$opt_type] is invalid.\n";
214             }
215              
216 46 100       88 if ( !$arg_spec ) {
217 4         74 croak "Invalid option spec [$CUR_OPT_SPEC]: option type "
218             . "[$opt_type] requires an argument spec.\n";
219             }
220              
221 42         186 return %params;
222             }
223              
224             ### if the spec shows that negation is allowed,
225             ### generate "no* names" for each name and alias.
226             sub _generate_negation_names {
227 6     6   17 my ( $self, @names ) = @_;
228 6         12 my @neg_names = map { ( "no-$_", "no$_" ) } grep { length } @names;
  20         83  
  20         57  
229 6         34 return @neg_names;
230             }
231              
232             1 && q{there's nothing like re-inventing the wheel!}; # truth
233              
234              
235             =pod
236              
237             =head1 NAME
238              
239             Getopt::Long::Spec::Parser - Parse a Getopt::Long option spec into a set of attributes
240              
241             =head1 VERSION
242              
243             version 0.002
244              
245             =head1 SYNOPSIS
246              
247             This module parses an option specification as would normally be used with
248             Getopt::Long, and produces a hash showing the meaning/parameters the spec
249             describes... if that makes any sense at all...
250              
251             Perhaps a little code snippet.
252              
253             use Getopt::Long::Spec::Parser;
254              
255             my $parser = Getopt::Long::Spec::Parser->new();
256             my %spec_info = $parser->parse( 'foo|f=s@{1,5}' );
257              
258             # OR...
259              
260             my %spec_info =
261             Getopt::Long::Spec::Parser->parse( 'foo|f=s@{1,5}' );
262              
263             %spec_info should be a hash containing info about the parsed Getopt::Long
264             option specification
265              
266             =head1 METHODS
267              
268             =head2 new
269              
270             construct a new parser.
271              
272             my $parser = Getopt::Long::Spec::Parser->new();
273             # OR...
274             my $parser = Getopt::Long::Spec::Parser->new(
275             debug => 1,
276             );
277              
278             =head2 parse
279              
280             parse an option specification
281              
282             my %spec_info = $parser->parse( 'foo' );
283             # OR...
284             my %spec_info = Getopt::Long::Spec::Parser->parse( 'foo' );
285              
286             return the info parsed from the spec as a hash, or hashref,
287             depending on context.
288              
289             In scalar context, returns a hashref, in list context, returns a hash.
290              
291             =head1 NOTES on PARSING Getopt::Long OPTION SPECIFICATIONS
292              
293             Described as a grammar:
294              
295             opt_spec ::= name_spec (arg_spec)? # if no arg_spec, option is a flag.
296              
297             name_spec ::= opt_name ("|" opt_alias)*
298             opt_alias ::= /\w+/
299             opt_name ::= /\w+/
300              
301             arg_spec ::= "=" val_type (dest_type)? (repeat)? # simple required
302             | ":" (val_type | /\d+/ | "+") (dest_type)? # simple optional
303             | "!" # flag negatable
304             | "+" # flag incremental
305              
306             arg_type ::= "s" | "i" | "o" | "f" # string, integer, extint, float
307             dest_type ::= "@" | "%" # array or hash
308             repeat ::= "{" (min_val)? ("," max_val)? "}" # multiple-values per use
309             min_vals ::= /\d+/
310             max_vals ::= /\d*/
311              
312             =head1 AUTHOR
313              
314             Stephen R. Scaffidi
315              
316             =head1 COPYRIGHT AND LICENSE
317              
318             This software is copyright (c) 2012 by Stephen R. Scaffidi.
319              
320             This is free software; you can redistribute it and/or modify it under
321             the same terms as the Perl 5 programming language system itself.
322              
323             =cut
324              
325              
326             __END__