File Coverage

blib/lib/Getopt/LL.pm
Criterion Covered Total %
statement 229 230 99.5
branch 78 82 95.1
condition 34 37 91.8
subroutine 33 33 100.0
pod 21 21 100.0
total 395 403 98.0


line stmt bran cond sub pod time code
1             # $Id: LL.pm,v 1.17 2007/07/13 00:00:13 ask Exp $
2             # $Source: /opt/CVS/Getopt-LL/lib/Getopt/LL.pm,v $
3             # $Author: ask $
4             # $HeadURL$
5             # $Revision: 1.17 $
6             # $Date: 2007/07/13 00:00:13 $
7             package Getopt::LL;
8 19     19   515561 use strict;
  19         45  
  19         625  
9 19     19   103 use warnings;
  19         34  
  19         632  
10 19     19   99 use Carp qw(croak);
  19         39  
  19         1330  
11 19     19   15371 use Getopt::LL::DLList;
  19         53  
  19         633  
12 19     19   11952 use English qw($PROGRAM_NAME);
  19         51267  
  19         182  
13 19     19   5829 use version qw(qv); our $VERSION = qv('1.0.0');
  19         92  
  19         111  
14 19     19   1673 use 5.006_001;
  19         76  
  19         814  
15             {
16              
17              
18 19     19   10929 use Getopt::LL::SimpleExporter qw(getoptions opt_String opt_Digit opt_Flag);
  19         45  
  19         125  
19              
20 19     19   114 use Class::Dot 1.0 qw( property isa_Hash isa_Array isa_Object );
  19         369  
  19         129  
21              
22             #========================================================================
23             # - CLASS PROPERTIES -
24             #========================================================================
25             property rules => isa_Hash;
26             property aliases => isa_Hash;
27             property options => isa_Hash;
28             property help => isa_Hash;
29             property result => isa_Hash;
30             property leftovers => isa_Array;
31             property dll => isa_Object('Getopt::LL::DLList');
32              
33             my $RE_SHORT_ARGUMENT = qr{
34             \A # starts with...
35             - # single dash.
36             (?!-) # with no dash after that.
37             .
38             }xms;
39              
40             my $RE_LONG_ARGUMENT = qr{
41             \A # starts with...
42             -- [^-]? # two dashes.
43             (?!-) # with no dash after that.
44             .
45             }xms;
46              
47             my $RE_ASSIGNMENT = qr{
48             (?
49             = # in front of it.
50             }xms;
51              
52             my %TYPE_CHECK = (
53             digit => \&is_digit,
54             string => \&is_string,
55             );
56              
57             my %RULE_ACTION = (
58             digit => \&get_next_arg,
59             string => \&get_next_arg,
60             flag => sub {
61             return 1;
62             },
63             );
64              
65             my %DEFAULT_OPTIONS = (
66             allow_unspecified => 0,
67             die_on_type_mismatch => 0,
68             silent => 0,
69             end_on_dashdash => 0,
70             split_multiple_shorts => 0,
71             style => 'default',
72             long_option => 'flag',
73             short_option => 'string',
74             );
75              
76             my %DEFAULT_OPTIONS_GNU = (
77              
78             # GNU-style arguments ends argument processing on empty '--'
79             end_on_dashdash => 1,
80             split_multiple_shorts => 1,
81             );
82              
83             my $EXIT_FAILURE = 1;
84              
85             # When set to true, parseopts stop processing options.
86             my $end_processing;
87              
88             #========================================================================
89             # - CONSTRUCTOR -
90             #========================================================================
91             sub new {
92 26     26 1 8489 my ($class, $rules_ref, $options_ref, $argv_ref) = @_;
93 26   100     178 $argv_ref ||= \@ARGV;
94              
95 26         59 my $self = {};
96 26         75 bless $self, $class;
97              
98             # If there are no rules, we must allowed unspecified
99             # arguments. (also check if we a have a reference to an empty hash).
100 26 100 66     205 if (!$rules_ref || (ref $rules_ref && !scalar keys %{$rules_ref})) {
  25   66     152  
101 5         17 $options_ref->{allow_unspecified} = 1;
102             }
103              
104 26         192 while (my ($option, $default_value) = each %DEFAULT_OPTIONS) {
105 208 100       926 if (!defined $options_ref->{$option}) {
106 125         472 $options_ref->{$option} = $default_value;
107             }
108             }
109 26 100       129 if ($options_ref->{style} eq 'GNU') {
110 2         12 while (my ($option, $value) = each %DEFAULT_OPTIONS_GNU) {
111 4         15 $options_ref->{$option} = $value;
112             }
113             }
114              
115 26         148 $self->set_options($options_ref);
116 26         321 $self->rules_prepare($rules_ref);
117              
118 26 50       283 if (scalar @{ $argv_ref }) {
  26         151  
119 26         238 $self->set_dll( Getopt::LL::DLList->new($argv_ref) );
120 26         180 $self->_init();
121             }
122              
123 19         177 $self->rules_postactions( );
124              
125 18         55 return $self;
126             }
127              
128             #========================================================================
129             # - INSTANCE METHODS -
130             #========================================================================
131              
132             sub _init {
133 26     26   53 my ($self) = @_;
134 26         106 my $dll = $self->dll;
135              
136 26         181 $end_processing = 0;
137 26         145 $dll->traverse($self, 'parseoption');
138              
139 19         93 return $self->result;
140             }
141              
142             sub rules_prepare {
143 26     26 1 53 my ($self, $rules_ref) = @_;
144 26         149 my $options_ref = $self->options;
145 26         280 my $help_ref = $self->help;
146              
147 26         691 my %final_rules = ();
148 26         69 my %aliases = ();
149              
150 103         402 RULE:
151 26         63 while (my ($rule_name, $rule_spec) = each %{$rules_ref}) {
152              
153             # User can type:
154             # '-arg' => 'string',
155             # instead of:
156             # '-arg' => { type => 'string' }
157             # and we will convert it here.
158 77 100       206 if (ref $rule_spec ne 'HASH') {
159 58         142 $rule_spec = {type => $rule_spec};
160             }
161              
162             # If the rule has a help field; save it into help.
163 77 100       235 if ($rule_spec->{help}) {
164 7         19 $help_ref->{$rule_name} = $rule_spec->{help};
165             }
166            
167 77         220 my($rule_name_final, @aliases)
168             = split m/\|/xms, $rule_name;
169              
170             # Split out the aliases (which are delimited by |)
171              
172             # Aliases can also be inside the spec, like this:
173             # '-arg' => { alias => '-gra' };
174             # or a list of aliases:
175             # '-arg' => { alias => ['-gra', '-rag', '-rga'] };
176             #
177 77         140 my $aliases_inside_spec = $rule_spec->{alias};
178 77 100       180 if ($aliases_inside_spec) {
179 2         7 @aliases =
180             ref $aliases_inside_spec eq 'ARRAY'
181 3 100       9 ? (@aliases, @{$aliases_inside_spec})
182             : (@aliases, $aliases_inside_spec);
183             }
184              
185             # if the name of the rule ends with !, remove the !
186             # and set it as required.
187 77 100       265 if ($rule_name_final =~ s/!\z//xms) {
188 1         2 $rule_spec->{required} = 1;
189             }
190              
191             # a default value can be defined inside parentheses.
192             # i.e:
193             # '-arg(defaultValue)' => 'string';
194 77 100       181 if ($rule_name_final =~ s/\( (.+?) \)//xms) {
195 1         3 $rule_spec->{default} = $1;
196             }
197              
198             # Remove leading and trailing whitespace.
199 77         167 $rule_name_final =~ s/\A \s+ //xms;
200 77         135 $rule_name_final =~ s/ \s+ \z//xms;
201              
202             # Save the final version of the rule.
203 77         143 $final_rules{$rule_name_final} = $rule_spec;
204              
205             # Save aliases to this rule.
206 77         309 for my $alias (@aliases) {
207 8         24 $aliases{$alias} = $rule_name_final;
208             }
209            
210             }
211              
212 26         189 $self->set_aliases( \%aliases );
213 26         2428 $self->set_rules( \%final_rules );
214              
215 26         196 return;
216             }
217              
218             sub rules_postactions {
219 19     19 1 45 my ($self) = @_;
220 19         70 my $rules_ref = $self->rules;
221 19         154 my $result = $self->result;
222              
223 19         123 while (my ($rule_name, $rule_spec) = each %{ $rules_ref }) {
  84         281  
224              
225             # Die if this is a required argument that we don't have.
226 66 100 100     206 if ($rule_spec->{required} && !$result->{$rule_name}) {
227 1         16 die "Missing required argument: $rule_name\n";
228             }
229              
230             # Set this argument to the default if it doesn't exist
231             # and a default value for this rule exists.
232 65 100 100     240 if ($rule_spec->{default} && !$result->{$rule_name}) {
233 6         22 $result->{$rule_name} = $rule_spec->{default};
234             }
235             }
236              
237 18         76 return;
238             }
239             sub parseoption {
240 147     147 1 1127 my ($self, $argument, $node) = @_;
241 147         371 my $result_argv = $self->result;
242 147         1210 my $leftovers = $self->leftovers;
243 147         1627 my $rules = $self->rules;
244 147         979 my $options_ref = $self->options;
245 147         1073 my $aliases = $self->aliases;
246              
247 147         892 my $is_arg_of_type = $self->find_arg_type($argument);
248              
249             # We stop processing options if this is a naked long option, ( '^--$' )
250             # and the 'end_on_dashdash' option is set.
251 147 100 100     858 if ($argument eq q{--} && $options_ref->{end_on_dashdash}) {
    100 100        
252 2         5 $end_processing++;
253             }
254              
255             # if find_arg_type said we have a special argument, start processing
256             # it (as long as processing is not stopped).
257             elsif ($is_arg_of_type && !$end_processing) {
258              
259 86         196 my @arguments = ($argument);
260              
261 86 100 100     345 if ($is_arg_of_type eq 'short' && $options_ref->{split_multiple_shorts}) {
262 4         17 $argument =~ s/^-//xms;
263 4         15 @arguments = map { "-$_" } split m//xms, $argument;
  11         41  
264             };
265              
266              
267 86         158 for my $argument (@arguments) {
268 93         154 my $argument_name = $argument;
269 93         110 my $argument_value = q{};
270              
271             # ###
272             # case: --argument_name=value
273             # if argument name contains an equal sign, the value is embedded in the
274             # argument. an example of inline assignement could be:
275             # --input-filename=/Users/ask/tmplog.txt
276 93 100       378 if ($argument =~ $RE_ASSIGNMENT) {
277 4         22 my @fields = split $RE_ASSIGNMENT, $argument;
278 4         21 ($argument_name, $argument_value) = @fields;
279             }
280              
281             # Try to find the rule for this argument...
282 93         158 my $opt_has_rule = $rules->{$argument_name};
283              
284             # if we can't find this rule, check if it's an alias.
285 93 100 100     351 if (!$opt_has_rule && $aliases->{$argument_name}) {
286              
287             # set the argument name to the name of the original.
288             # and set the rule to the rule of the original.
289 3         6 $argument_name = $aliases->{$argument_name};
290 3         4 $opt_has_rule = $rules->{$argument_name};
291             }
292              
293 93 100 66     284 if (!$opt_has_rule && !$options_ref->{allow_unspecified}) {
294 1         5 $self->unknown_argument_error($argument);
295             }
296              
297 92 100 100     459 $result_argv->{$argument_name} =
298             $opt_has_rule
299             ? $self->handle_rule($argument_name, $opt_has_rule, $node,
300             $argument_value)
301             : (
302             $argument_value || 1
303             );
304             }
305              
306             }
307             else {
308 59         83 push @{$leftovers}, $argument;
  59         123  
309             }
310              
311 140         6138 return;
312             }
313              
314             sub find_arg_type {
315 147     147 1 224 my ($self, $argument) = @_;
316              
317 147 100       815 if ($argument =~ $RE_LONG_ARGUMENT) {
318 53         124 return 'long';
319             }
320              
321 94 100       444 if ($argument =~ $RE_SHORT_ARGUMENT) {
322 37         101 return 'short';
323             }
324              
325             # return nothing if this is not a special argument/option.
326 57         103 return;
327             }
328              
329             sub is_string {
330              
331             # we have no limits for what a string can be.
332 45     45 1 148 return 1;
333             }
334              
335             sub is_digit {
336 37     37 1 16195 my ($self, $value, $option_name, $value_ref) = @_;
337 37 100       126 return 0 if $value eq q{};
338 36         47 my $is_digit = 0;
339              
340 36         77 my $first_two_chars = substr $value, 0, 2;
341 36 100       194 if ($first_two_chars eq '0x') {
    100          
342              
343             # starts with 0x: is hexadecimal number
344 7         23 $value = substr $value, 2, length $value;
345 7 100       41 if ($value =~ m/\A [\dA-Fa-f]+ \z/xms) {
346              
347             # We get a reference to the value as argument #4.
348             # convert the value to hex.
349 5         13 ${$value_ref} = hex $value;
  5         9  
350 5         8 $is_digit = 1;
351             }
352             }
353             elsif ($value =~ m/\A [-+]? \d+ \z/xms) {
354 17         28 $is_digit = 1;
355             }
356              
357 36 100       98 if (!$is_digit) {
358 14         53 return $self->type_mismatch_error('digit',
359             "$option_name must be a digit (0-9).");
360             }
361              
362 22         89 return 1;
363             }
364              
365             sub type_mismatch_error {
366 14     14 1 25 my ($self, $type, $message) = @_;
367 14         57 my $options_ref = $self->options;
368              
369 14 100       640 $options_ref->{die_on_type_mismatch}
370             ? croak $message, "\n"
371             : warn $message, "\n"
372             ;
373              
374 12         104 return 0;
375             }
376              
377             sub unknown_argument_error {
378 1     1 1 3 my ($self, $argument) = @_;
379              
380 1         209 croak "Unknown argument: $argument.\n";
381             }
382              
383             sub handle_rule {
384 67     67 1 131 my ($self, $arg_name, $rule_ref, $node, $arg_value) = @_;
385 67         75 my $rule_data;
386              
387 67         141 my $rule_type = $rule_ref->{type};
388              
389 67 100       262 if (ref $rule_type eq 'CODE') {
    100          
390 10         32 return $rule_type->($self, $node, $arg_name, $arg_value);
391             }
392             elsif (ref $rule_type eq 'Regexp') {
393 19     19   49323 no warnings 'uninitialized'; ## no critic
  19         44  
  19         25933  
394 7         16 my $next_arg = $self->get_next_arg($node);
395 7 100       20 if ($next_arg !~ $rule_type) {
396 3 100       26 if (! defined $next_arg) {
397 1         2 $next_arg = '';
398             }
399 3         10 croak sprintf('Argument %s [%s] does not match %s', ## no critic
400             $arg_name, $next_arg, _regex_as_text($rule_type)
401             );
402             }
403 4         63 return $next_arg;
404              
405             }
406              
407              
408 50 100       124 if ($RULE_ACTION{$rule_type}) {
409              
410 49   100     190 $arg_value ||= $RULE_ACTION{$rule_type}->($self, $node);
411              
412 49 100       148 if ($TYPE_CHECK{$rule_type}) {
413 34         261 $TYPE_CHECK{$rule_type}
414             ->($self,$arg_value, $arg_name, \$arg_value);
415             }
416             }
417             else {
418 1         2 $Carp::CarpLevel = 2; ## no critic;
419 1         300 croak "Unknown rule type [$rule_type] for argument [$arg_name]";
420             }
421              
422 47         222 return $arg_value;
423             }
424              
425             sub get_next_arg {
426 44     44 1 2003 my ($self, $node) = @_;
427              
428 44         165 return $self->delete_arg( $node->next );
429             }
430              
431             sub get_prev_arg {
432 5     5 1 26 my ($self, $node) = @_;
433              
434 5         19 return $self->delete_arg( $node->prev );
435             }
436              
437             sub peek_next_arg {
438 6     6 1 1905 my ($self, $node) = @_;
439 6 100       22 if ($node->next) {
440 5         48 return $node->next->data;
441             }
442 1         9 return;
443             }
444              
445             sub peek_prev_arg {
446 5     5 1 747 my ($self, $node) = @_;
447 5 100       16 if ($node->prev) {
448 4         36 return $node->prev->data;
449             }
450 1         16 return;
451             }
452              
453             sub delete_arg {
454 49     49 1 348 my ($self, $node) = @_;
455 49         137 my $dll = $self->dll;
456              
457 49         372 return $dll->delete_node($node);
458             }
459              
460             # XXX this is not very complete.
461             sub show_help {
462 1     1 1 879 my ($self) = @_;
463              
464 1         2 while (my ($arg, $help) = each %{ $self->help }) {
  5         15  
465 4         33 my $ret = print {*STDERR} "$arg\t\t\t$help\n";
  4         23  
466 4 50       28 croak 'I/O Error. Cannot print to terminal' if !$ret;
467             }
468              
469 1         9 return;
470             }
471              
472             # XXX this is not very complete.
473             sub show_usage {
474 2     2 1 3417 my ($self) = @_;
475              
476 2         8 my $program_name = $self->options->{program_name};
477              
478 2 100       30 if (! $program_name) {
479 1         4 $program_name = $PROGRAM_NAME;
480             }
481              
482 2         17 require File::Basename;
483 2         107 $program_name = File::Basename::basename($program_name);
484              
485 2         3 my @arguments;
486 2         4 while (my ($arg, $spec) = each %{ $self->rules }) {
  10         31  
487 8 100 100     101 if ($spec->{type} eq 'string' || $spec->{type} eq 'digit') {
488 4         12 push @arguments, "$arg ";
489             }
490             else {
491 4         12 push @arguments, $arg;
492             }
493             }
494              
495 2         21 my $arguments = join q{|}, @arguments;
496              
497 2         4 my $ret = print {*STDERR} "Usage: $program_name [$arguments]\n";
  2         16  
498 2 50       18 croak 'I/O Error. Cannot print to terminal' if !$ret;
499              
500 2         7 return;
501             }
502              
503             #========================================================================
504             # - CLASS METHODS -
505             #========================================================================
506              
507             #------------------------------------------------------------------------
508             # getoptions(\%rules, \%options, \@opt_argv)
509             #
510             #------------------------------------------------------------------------
511             sub getoptions {
512 21     21 1 16760 my ($rules_ref, $options_ref, $argv_ref) = @_;
513              
514 21         202 my $getopts =
515             __PACKAGE__->new($rules_ref, $options_ref, $argv_ref); ## no critic;
516 13         52 my $result = $getopts->result();
517              
518             # ARGV should be set to what is left of the argument vector.
519 13         121 @ARGV = @{ $getopts->leftovers };
  13         48  
520              
521 13         170 return $result;
522             }
523              
524             sub opt_String { ## no critic
525 3     3 1 10091 my ($help) = @_;
526             return {
527 3         80 type => 'string',
528             help => $help,
529             };
530             }
531              
532             sub opt_Digit { ## no critic
533 3     3 1 9 my ($help) = @_;
534             return {
535 3         18 type => 'digit',
536             help => $help,
537             };
538             }
539              
540             sub opt_Flag { ## no critic
541 4     4 1 9 my ($help) = @_;
542             return {
543 4         30 type => 'flag',
544             help => $help,
545             };
546             }
547              
548             sub _regex_as_text {
549 8     8   1493 my $regex_as_text = scalar shift;
550 8         11 my $regex_modifiers;
551              
552             # The quoted regex (?xmsi:hello) should look something like this
553             # /hello/xmsi
554             # The job is to remove the (?: and capture xmsi into $1.
555 8         39 my $ret = $regex_as_text =~ s{
556             \A # beginning of string.
557             \(\? # a paren start and a question mark.
558             ([\w-]+)? # none or more word characters captured to $1
559             : # ends with a colon.
560             }{}xms;
561              
562 8 100       29 if ($ret) {
563 1         3 $regex_modifiers = $1;
564             }
565              
566             # remove the closing paren at the end.
567 8         43 $regex_as_text =~ s/\) \z//xms;
568              
569             # The final text we return should be:
570             # /hello/xmsi
571             # if the regex we got was:
572             # (?xmsi:hello)
573 8         22 $regex_as_text = "/$regex_as_text/";
574 8 50       17 if ($regex_modifiers) {
575 0         0 $regex_as_text .= $regex_modifiers;
576             }
577              
578 8         1012 return $regex_as_text;
579             }
580             }
581              
582             1;
583              
584             __END__