File Coverage

blib/lib/CmdArguments.pm
Criterion Covered Total %
statement 147 179 82.1
branch 23 52 44.2
condition 11 25 44.0
subroutine 24 27 88.8
pod 1 8 12.5
total 206 291 70.7


line stmt bran cond sub pod time code
1             package CmdArguments;
2              
3 1     1   161552 use strict;
  1         2  
  1         41  
4              
5 1     1   5 use vars qw($VERSION);
  1         3  
  1         75  
6              
7             $VERSION = '1.00';
8              
9             =head1 NAME
10              
11             CmdArguments - Module to process arguments passed on command line
12              
13             =head1 SYNOPSIS
14              
15             # program name args.pl
16             use CmdArguments;
17              
18             my $var1 = 10; # initialize variable
19             my $var2 = 0; # with default values.
20             my @var3 = ( 1, 2, 3); # well, if you like to.
21             my @var4; # but, not necessary
22              
23             my $parse_ref = [
24             [ "arg1", \$var1 ], # argTypeScalar is assumed
25             [ "arg2", \$var2,
26             {TYPE => argTypeSwitch}], # explicit argTypeSwitch
27             [ "arg3", \@var3 ], # argTypeArray assumed
28             [ "arg4", \@var4,
29             {UNIQUE => 1}], # argTypeArray assumed
30             ];
31              
32             CmdArguments::parse(@ARGV, $parse_ref);
33              
34             print "var1 = $var1\n";
35             print "var2 = $var2\n";
36             print "var3 = @var3\n";
37             print "var4 = @var4\n";
38              
39             exit 0;
40              
41             test command ...
42              
43             args.pl -arg1 23 -arg2 -arg3 2 4 3 2 5 -arg4 2 4 3 2 4
44              
45             should generate following output...
46              
47             var1 = 23
48             var2 = 1
49             var3 = 2 4 3 2 5
50             var4 = 2 4 3
51              
52             =head1 DESCRIPTION
53              
54             This module provides some handy functions to process
55             command line options.
56              
57             When this module is included it introduces following
58             constants in the calling program namespace...
59              
60             argTypeScalar = 0
61             argTypeArray = 1
62             argTypeSwitch = 2
63              
64             =cut
65              
66             sub BEGIN {
67 1     1   6 use constant argTypeScalar => 0;
  1         6  
  1         79  
68 1     1   4 use constant argTypeArray => 1;
  1         1  
  1         45  
69 1     1   4 use constant argTypeSwitch => 2;
  1         1  
  1         35  
70 1     1   3 use constant argTypeHash => 3;
  1         2  
  1         35  
71              
72 1     1   3 my $pkg = caller;
73 1     1   9 no strict 'refs';
  1         1  
  1         155  
74 1         1 *{"${pkg}::argTypeScalar"} = sub () { argTypeScalar };
  1         5  
75 1         2 *{"${pkg}::argTypeArray"} = sub () { argTypeArray };
  1         4  
76 1         3 *{"${pkg}::argTypeSwitch"} = sub () { argTypeSwitch };
  1         4  
77 1         1 *{"${pkg}::argTypeHash"} = sub () { argTypeHash };
  1         72  
78             }
79              
80             =over 1
81              
82             =item B
83              
84             Simplest way to use this program is to call B (static function).
85              
86             Calling syntax is...
87              
88             I, L<$array_ref|$array_ref>,
89             I>, I>)>
90              
91             =over 2
92              
93             =item I<@arguments>
94              
95             array of command line arguments. So, @ARGV could be passed instead.
96              
97             =item I<$array_ref>
98              
99             reference to an array containing information about how to
100             parse data in @arguments.
101              
102             basic structure of $array_ref is...
103              
104             $array_ref = [ I<$array_ref_for_individual_tag>, ...];
105              
106             $array_ref_for_individual_tag = [I>
107             , I>,
108             I>]; # $hash_ref is optional
109              
110             =over 3
111              
112             =item I<$hash_ref>
113              
114             reference to a hash containing supplementary information about $option_tag
115              
116             $hash_ref = {
117             TYPE => argType..., # argTypeSwitch
118             # argTypeArray or argTypeScalar
119              
120             UNIQUE => 1, # 1 or 0
121              
122             USAGE => "help information", # try giving -h or -help
123             # on command line
124              
125             FUNC => sub { eval $_[0] }
126             };
127              
128             =over 4
129              
130             =item TYPE
131              
132             this specifies what kind of variable reference is passed in
133             $ref_of_variable. If TYPE is argTypeScalar or argTypeSwitch
134             it assumes reference to a scalar. If TYPE is argTypeArray it
135             assumes reference to an array.
136              
137             if TYPE tag is not provided then ...
138              
139             1. I is assumed if $ref_of_variable is a scalar reference
140              
141             2. I is assumed if $ref_of_variable is an array reference
142              
143             =over 5
144              
145             =item What is argType...?
146              
147             =over 6
148              
149             =item argTypeSwitch
150              
151             on command line you can not provide value for an option.
152              
153             =item argTypeScalar
154              
155             on command line you must provide one and only one value
156              
157             =item argTypeArray
158              
159             on command line you can provide zero or more values
160              
161             =back 6
162              
163             =back 5
164              
165             =item UNIQUE
166              
167             this tag is applicable for option type I only.
168             it can be 0 or 1. 1 means make unique array. So, if an
169             option is defined as UNIQUE then on command line if you
170             give say 2 3 4 5 3 4 6 7 then array will hold 2 3 4 5 6 7.
171             If it was not unique then it will hold 2 3 4 5 3 4 6 7.
172              
173             =item FUNC
174              
175             Holds a reference to a function. Function should take
176             a scalar argument and return a scalar if option is
177             argTypeScalar and return an array if option is
178             argTypeArray. This is not used for option type argTypeSwitch.
179              
180             Example: if option type is an argTypeArray. and function is
181             defined like
182              
183             FUNC => sub { eval $_[0] }
184              
185             and if on the command line something like 1..3 or 1,2,3
186             is passed then it will generate an array having values 1 2 3.
187              
188             =back 4
189              
190             =item I<$ref_of_variable>
191              
192             Can pass reference of a scalar or an array variable
193             depending on what require from command line.
194              
195             =item I<$option_tag>
196              
197             It is the name of the option tag. if option tag is I then
198             on command line you have to specify option like I<-opt>.
199              
200             =back 3
201              
202             =item $text_or_func1
203              
204             =item $text_or_func2
205              
206             pass text or reference to a function. If function is passed
207             it should return text or should itself print message on
208             STDERR. Try experimenting by passing -h or -help in the argument.
209             $text_or_func1 is printed after the help text is printed and
210             $text_or_func1 is used before printing helptext.
211              
212             =back 2
213              
214             =back 1
215              
216             =cut
217              
218             sub parse (\@@) {
219 1     1 1 101 my ($arg_ref, $process, $postusage, $preusage) = @_;
220              
221 1     1   5 use constant argTagField => 0;
  1         1  
  1         48  
222 1     1   5 use constant argVarField => 1;
  1         1  
  1         47  
223 1     1   4 use constant argHashField => 2;
  1         3  
  1         2335  
224              
225 1         9 my %functions = (argTypeScalar+0 => "argScalar",
226             argTypeArray+0 => "argArray",
227             argTypeHash+0 => "argHash",
228             argTypeSwitch+0 => "argSwitch");
229              
230 1         9 my $args = CmdArguments->beginArg(@$arg_ref);
231 1         3 foreach my $argsyntax (@$process) {
232 4 100       11 my $typehash = (defined $argsyntax->[argHashField]
233             ? $argsyntax->[argHashField] : {});
234              
235 4         7 my $tag = $argsyntax->[argTagField];
236 4         5 my $var = $argsyntax->[argVarField];
237 4         13 my $type = _value($typehash->{TYPE});
238 4         14 my $sub = _value($typehash->{FUNC});
239 4         12 my $unique = _value($typehash->{UNIQUE});
240 4         11 my $usage = _value($typehash->{USAGE});
241 4         12 my $dispOpt = _value($typehash->{DISPOPTION});
242 4         11 my $params = _value($typehash->{PARAMS});
243              
244 4 100       33 unless (defined $type) {
245 3 100       11 $type = argTypeScalar if ref($var) eq 'SCALAR';
246 3 100       9 $type = argTypeArray if ref($var) eq 'ARRAY';
247 3 50       7 $type = argTypeHash if ref($var) eq 'HASH';
248 3 50       8 unless (defined $type) {
249 0         0 die "ERROR: option ($tag) - variable should be a reference\n";
250             }
251             }
252              
253 4         13 my @arguments = ($tag => $var, usage => $usage,
254             dispOption => $dispOpt,
255             func => $sub, unique => $unique, params => $params);
256              
257 4 50       9 if (exists $functions{$type}) {
258 4         6 my $function = $functions{$type};
259 4         22 $args->$function(@arguments);
260             } else {
261 0         0 die "Please check type ($type)\n";
262             }
263             }
264              
265 1         3 my @return = ();
266 1 50       4 if (wantarray) {
267 0         0 @return = $args->endArg;
268             } else {
269 1         5 $args->endArg;
270             }
271 1         8 $args->usage($preusage, $postusage);
272 1         18 return @return;
273             }
274              
275             # Start Argument processing
276             # usage: my $arg = CmdArguments->beginArg(@ARGV);
277             sub beginArg {
278 1     1 0 8 my ($class, @argv) = @_;
279              
280 1         2 my $self = {};
281 1         3 bless $self, $class;
282              
283             # trap the arguments
284 1 50       11 $self->{ARGS} = @argv ? [@argv] : \@ARGV;
285             # usage string in case of help or error
286 1         3 $self->{USAGE} = "";
287             # required for generating variable names
288 1         3 $self->{_TMPNUM} = 0;
289              
290             # trap the original accumulator;
291 1         4 $self->{_ACCUMULATOR} = $^A;
292              
293             # temporay variable
294             # to store help status
295 1         2 my $tmpHelpVar = 0;
296 1         2 $self->{_HELPSAT} = \$tmpHelpVar;
297              
298             # hash where reference user supplied
299             # variables are stored
300 1         3 $self->{_VARIABLES} = {};
301             # hash where user defined functions are stored
302 1         3 $self->{_FUNCTIONS} = {};
303              
304             # used in case wrong option is given
305 1         3 $self->{_UNKNOWN_OPTIONS} = [];
306              
307             # begin generating main loop
308 1         3 $self->{LOOP_STRING} = <<'BEGINARG';
309             while (@{$self->{ARGS}}) {
310             $_ = shift @{$self->{ARGS}};
311             BEGINARG
312              
313 1         4 return $self;
314             }
315              
316             # process scalar argument
317             # usage: $arg->argScalar(option => \$scalar_variable,
318             # usage => "description",
319             # func => sub { return $_[0] });
320             sub argScalar {
321 1     1 0 2 my $self = shift;
322              
323             # get user supplied argument and variable (where
324             # value is to be stored) and other options
325 1         5 my ($arg, $variable, %options) = _makeOptions(@_);
326              
327             # store user supplied function and variable
328 1   50     12 my ($varName, $funName) = $self->_getVarAndFuncName($variable,
329             $options{func}
330             || undef);
331             # generate code to handle scalar option
332 1         10 $self->{LOOP_STRING} .= <
333             \/^-($arg)\$\/ && ( do { my \$value = shift(\@{\$self->{ARGS}});
334             \${\$self->{_VARIABLES}{$varName}}
335             = \$self->{_FUNCTIONS}{$funName}->(\$value);
336             }, next
337             );
338             OPRIONARG
339              
340             # make usage
341 1         5 $self->_makeUsage($arg, %options);
342             }
343              
344             # process switch argument
345             # passed variable will be turned on or off
346             # usage: $arg->argScalar(option => \$switch_variable,
347             # usage => "description");
348             sub argSwitch {
349 2     2 0 3 my $self = shift;
350              
351             # get user supplied argument and variable (where
352             # value is to be stored) and other options
353 2         5 my ($arg, $variable, %options) = _makeOptions(@_);
354              
355             # store user supplied function and variable
356 2   50     16 my ($varName, $funName) = $self->_getVarAndFuncName($variable,
357             $options{func}
358             || undef);
359              
360             # generate code to handle switch option
361 2         10 $self->{LOOP_STRING} .= <
362             \/^-($arg)\$\/ && ( \${\$self->{_VARIABLES}{$varName}}
363             = \!\${\$self->{_VARIABLES}{$varName}}+0 , next);
364             OPRIONARG
365              
366             # make usage
367 2         9 $self->_makeUsage($arg, %options);
368             }
369              
370             # process array argument
371             # usage: $arg->argArray(option => \@array_variable,
372             # usage => "description",
373             # unique => 1,
374             # func => sub { return @_ });
375             sub argArray {
376 2     2 0 3 my $self = shift;
377              
378             # get user supplied argument and variable (where
379             # value is to be stored) and other options
380 2         5 my ($arg, $variable, %options) = _makeOptions(@_);
381              
382             # uniqe list required (default: yes)
383 2 50 100     15 my $unique = exists $options{unique} ? ($options{unique} || 0) : 1;
384              
385             # store user supplied function and variable
386 2   50     13 my ($varName, $funName) = $self->_getVarAndFuncName($variable,
387             $options{func}
388             || undef);
389 2         3 my $param = $options{params};
390 2 50       7 $param = 'undef' unless defined $param;
391 2         5 $self->{_PARAMS}{$varName} = $param;
392              
393             # generate code to handle array option
394 2         16 $self->{LOOP_STRING} .= <
395             \/^-($arg)\$\/ &&
396             (do { my \%tmp = map { (\$_, 1)
397             } \@{\$self->{_VARIABLES}{$varName}};
398             while (\@{\$self->{ARGS}} and \$self->{ARGS}[0] !~ /^-/) {
399             my \$value = shift \@{\$self->{ARGS}};
400             my \@values
401             = \$self->{_FUNCTIONS}
402             {$funName}->(\$value,
403             \$self->{_PARAMS}{$varName});
404             if ($unique) {
405             \@values = grep { my \$stat = exists \$tmp{\$_};
406             \$stat ||= 0;
407             \$tmp{\$_} = 1 unless \$stat;
408             !\$stat
409             } \@values;
410             }
411             push(\@{\$self->{_VARIABLES}{$varName}}, \@values)
412             if \@values;
413             }}, next
414             );
415             OPRIONARG
416              
417             # make usage
418 2         9 $self->_makeUsage($arg, %options);
419             }
420              
421             # process hash argument
422             # usage: $arg->argHash(option => \%hash_variable,
423             # usage => "description",
424             # func => sub { ... });
425             sub argHash {
426 0     0 0 0 my $self = shift;
427              
428             # get user supplied argument and variable (where
429             # value is to be stored) and other options
430 0         0 my ($arg, $variable, %options) = _makeOptions(@_);
431              
432             # uniqe list required (default: yes)
433 0 0 0     0 my $unique = exists $options{unique} ? ($options{unique} || 0) : 1;
434              
435             # store user supplied function and variable
436 0   0     0 my ($varName, $funName) = $self->_getVarAndFuncName($variable,
437             $options{func}
438             || undef);
439 0         0 my $param = $options{params};
440 0 0       0 $param = 'undef' unless defined $param;
441 0         0 $self->{_PARAMS}{$varName} = $param;
442              
443             # generate code to handle hash option
444 0         0 $self->{LOOP_STRING} .= <
445             \/^-($arg)\$\/ &&
446             (do { while (\@{\$self->{ARGS}} and \$self->{ARGS}[0] !~ /^-/) {
447             my \$value = shift \@{\$self->{ARGS}};
448             my \$values
449             = \$self->{_FUNCTIONS}
450             {$funName}->(\$value,
451             \$self->{_PARAMS}{$varName});
452             my \$ref = ref(\$values);
453             unless (\$ref) {
454             \$self->{_VARIABLES}{$varName}{\$values} = 1;
455             } elsif ( \$ref eq 'HASH') {
456             foreach my \$key (keys \%\$values) {
457             \$self->{_VARIABLES}{$varName}{\$key}
458             = \$values->{\$key};
459             }
460             }
461             }}, next
462             );
463             OPRIONARG
464              
465             # make usage
466 0         0 $self->_makeUsage($arg, %options);
467             }
468              
469             # finish the main loop
470             # usage: $arg->endArg;
471             sub endArg {
472 1     1 0 3 my $self = shift;
473              
474             # generate code to provide help
475 1         5 $self->argSwitch("h|help" => $self->{_HELPSAT},
476             usage => < " ");
477             show this help.
478             HELP
479              
480 1         2 my @return = ();
481              
482              
483 1   50     7 my $wantarray = wantarray || 0;
484              
485             # end the main loop
486             # and push unhandled options
487 1         2 $self->{LOOP_STRING} .= <
488             if (\$wantarray && \$_ !~ /^-/) {
489             push \@return, \$_;
490             } else {
491             push \@{\$self->{_UNKNOWN_OPTIONS}}, \$_;
492             }
493             }
494             ENDLOOP
495              
496             # run the main loop
497 1         683 eval "$self->{LOOP_STRING}";
498 1 50       8 if ($@) {
499 0         0 print STDERR "OPS: $@ \n";
500 0         0 my @array = split "\n", $self->{LOOP_STRING};
501 0         0 my $i = 1;
502 0         0 print STDERR map { sprintf("%3d: %s\n", $i++, $_) } @array;
  0         0  
503 0         0 exit 1;
504             }
505              
506             # reset format accumulator
507 1         2 $^A = $self->{_ACCUMULATOR};
508              
509 1         2 return @return;
510             }
511              
512             # display usage if require
513             # usage: $arg->usage($pre, $post);
514             # $pre: string or function reference
515             # $post: string or function reference
516             # NOTE: if not used help will not be generated
517             sub usage {
518 1     1 0 2 my ($self, $pre, $pst) = @_;
519              
520             # generate string for unknown options
521 1 50       18 my $unknown_options = (@{$self->{_UNKNOWN_OPTIONS}}
  1         6  
522 0         0 ? "(@{$self->{_UNKNOWN_OPTIONS}})" : "");
523 1 50       4 $unknown_options = "$0: Unknown options $unknown_options\n"
524             if $unknown_options;
525              
526             # handle error or simply help...
527 1 50 33     1 if (${$self->{_HELPSAT}} || $unknown_options) {
  1         8  
528 0 0   0   0 my $prefunc = ref($pre) eq 'CODE' ? $pre : sub { $pre || "" };
  0 0       0  
529 0 0   0   0 my $pstfunc = ref($pst) eq 'CODE' ? $pst : sub { $pst || "" };
  0 0       0  
530              
531 0         0 print STDERR $unknown_options;
532 0   0     0 print STDERR &$prefunc || "";
533 0         0 print STDERR $self->{USAGE};
534 0   0     0 print STDERR &$pstfunc || "";
535 0 0       0 $unknown_options ? exit 100 : exit 0;
536             }
537             }
538              
539             # core code for formatting help
540             sub _makeUsage {
541 5     5   16 my ($self, $option, %desc) = @_;
542              
543 5   100     19 my $description = $desc{usage} || "not ready yet!.";
544 5   100     16 my $opts = $desc{dispOption} || "opts";
545              
546 5         9 my $olen = length($option.$opts) + 2;
547 5         6 my $format = '@>>>>>>>>>>>>>>>>>>: ';
548 5 50       10 if ($olen > 19) {
549 0         0 $format = '@' . '>' x $olen . "\n" . " " x 19 . ": ";
550             }
551              
552 5         5 my $len = 60;
553 5         12 my $dformat = '^' . '<' x $len . '~';
554 5         6 my $dlen = length($description);
555 5         9 my $line = int($dlen / $len);
556              
557 5         5 $line += 2;
558 5         11 $format .= join "\n" . " " x 21, map {$dformat} 1..$line;
  10         22  
559 5         15 my $str = '$^A = ""; formline($format, "-" . $option . '
560             . '" $opts ", ' . ('$description, ' x $line) . ' ); $^A;';
561 5         493 $str = eval $str;
562 5         22 chomp($str);
563 5         6 $str .= "\n";
564 5         70 $self->{USAGE} .= $str;
565             }
566              
567             sub _getVariableName {
568 10     10   11 my $self = shift;
569              
570 10         30 return "VAR_" . (++$self->{_TMPNUM});
571             }
572              
573             sub _makeOptions {
574 5     5   6 my $option = shift;
575 5         7 my $variable = shift;
576 5         26 return ($option, $variable, @_);
577             }
578              
579             sub _getVarAndFuncName {
580 5     5   8 my ($self, $variable, $function) = @_;
581              
582 5         14 my $varName = $self->_getVariableName;
583 5         12 $self->{_VARIABLES}{$varName} = $variable;
584 5         11 my $funName = $self->_getVariableName;
585 5     11   61 $self->{_FUNCTIONS}{$funName} = sub { $_[0] };
  11         244  
586 5 50       13 if ($function) {
587 0 0       0 if (ref($function) eq 'CODE') {
588 0         0 $self->{_FUNCTIONS}{$funName} = $function;
589             } else {
590 0         0 die "ERROR: func should be a reference to a function\n";
591             }
592             }
593              
594 5         14 return ($varName, $funName);
595             }
596              
597             sub _value {
598 24     24   33 my $val = shift;
599 24 100       51 return defined $val ? $val : undef;
600             }
601              
602             =head1 AUTHOR
603              
604             Navneet Kumar, EFE
605              
606             =cut
607              
608             1;