File Coverage

blib/lib/Paranoid/Args.pm
Criterion Covered Total %
statement 261 325 80.3
branch 114 164 69.5
condition 56 87 64.3
subroutine 25 25 100.0
pod 3 3 100.0
total 459 604 75.9


line stmt bran cond sub pod time code
1             # Paranoid::Args -- Command-line argument parsing functions
2             #
3             # $Id: lib/Paranoid/Args.pm, 2.10 2022/03/08 00:01:04 acorliss Exp $
4             #
5             # This software is free software. Similar to Perl, you can redistribute it
6             # and/or modify it under the terms of either:
7             #
8             # a) the GNU General Public License
9             # as published by the
10             # Free Software Foundation ; either version 1
11             # , or any later version
12             # , or
13             # b) the Artistic License 2.0
14             # ,
15             #
16             # subject to the following additional term: No trademark rights to
17             # "Paranoid" have been or are conveyed under any of the above licenses.
18             # However, "Paranoid" may be used fairly to describe this unmodified
19             # software, in good faith, but not as a trademark.
20             #
21             # (c) 2005 - 2020, Arthur Corliss (corliss@digitalmages.com)
22             # (tm) 2008 - 2020, Paranoid Inc. (www.paranoid.com)
23             #
24             #####################################################################
25              
26             #####################################################################
27             #
28             # Environment definitions
29             #
30             #####################################################################
31              
32             package Paranoid::Args;
33              
34 2     2   949 use 5.008;
  2         6  
35              
36 2     2   8 use strict;
  2         2  
  2         31  
37 2     2   6 use warnings;
  2         4  
  2         44  
38 2     2   8 use vars qw($VERSION @EXPORT @EXPORT_OK %EXPORT_TAGS);
  2         10  
  2         119  
39 2     2   11 use base qw(Exporter);
  2         3  
  2         122  
40 2     2   9 use Paranoid;
  2         11  
  2         98  
41 2     2   815 use Paranoid::Debug qw(:all);
  2         4  
  2         443  
42              
43             ($VERSION) = ( q$Revision: 2.10 $ =~ /(\d+(?:\.\d+)+)/sm );
44              
45             @EXPORT = qw(parseArgs);
46             @EXPORT_OK = ( @EXPORT, qw(PA_DEBUG PA_VERBOSE PA_HELP PA_VERSION) );
47             %EXPORT_TAGS = (
48             all => [@EXPORT_OK],
49             template => [qw(PA_DEBUG PA_VERBOSE PA_HELP PA_VERSION)],
50             );
51              
52             # I know, this really doesn't protect the contents...
53 2         114 use constant PA_DEBUG => {
54             Short => 'D',
55             Long => 'debug',
56             CountShort => 1,
57 2     2   13 };
  2         4  
58 2         116 use constant PA_VERBOSE => {
59             Short => 'v',
60             Long => 'verbose',
61             CountShort => 1,
62 2     2   36 };
  2         5  
63 2         92 use constant PA_HELP => {
64             Short => 'h',
65             Long => 'help',
66 2     2   11 };
  2         3  
67 2         5606 use constant PA_VERSION => {
68             Short => 'V',
69             Long => 'version',
70 2     2   9 };
  2         4  
71              
72             #####################################################################
73             #
74             # Module code follows
75             #
76             #####################################################################
77              
78             {
79              
80             # Internal boolean flag for noOptions
81             my $noOptions = 0;
82              
83             sub _NOOPTIONS : lvalue {
84              
85             # Purpose: Gets/sets value of boolean flag $noOptions
86             # Returns: Value of $noOptions
87             # Usage: $flag = _NOOPTIONS;
88             # Usage: _NOOPTIONS = 1;
89              
90 70     70   192 $noOptions;
91             }
92              
93             # Internal errors array
94             my @errors;
95              
96             sub _resetErrors {
97              
98             # Purpose: Empties @errors
99             # Returns: True (1)
100             # Usage: resetErrors();
101              
102 14     14   23 @errors = ();
103 14         16 return 1;
104             }
105              
106             sub _pushErrors {
107              
108             # Purpose: Pushes a new string onto the @errors array
109             # Returns: Same argument as called with
110             # Usage: _pushErrors($message);
111              
112 7     7   10 my $message = shift;
113 7         9 push @errors, $message;
114 7         8 return $message;
115             }
116              
117             sub listErrors {
118              
119             # Purpose: Gets the contents of @errors
120             # Returns: Contents of @errors
121             # Usage: @errors = listErrors();
122              
123 6     6 1 11 my ( %messages, $n, @indices );
124              
125             # Filter out redundant messages
126 6         7 $n = 0;
127 6         11 foreach (@errors) {
128 7         12 $messages{$_}++;
129 7 100       16 push @indices, $n if $messages{$_} > 1;
130 7         9 $n++;
131             }
132 6         13 foreach ( sort { $b <=> $a } @indices ) {
  0         0  
133 2         5 splice @errors, $_, 1;
134             }
135              
136 6         14 return @errors;
137             }
138              
139             # Internal options hash
140             my %options;
141              
142             sub _getOption {
143              
144             # Purpose: Gets the template associated with passed option
145             # Returns: Reference to template hash or undef should the
146             # requested option not be defined
147             # Usage: $tref = _getOption($option);
148              
149 502     502   558 my $option = shift;
150              
151 502 100       892 return exists $options{$option} ? $options{$option} : undef;
152             }
153              
154             sub _setOption {
155              
156             # Purpose: Associates the passed option to the passed template in
157             # %options
158             # Returns: True (1)
159             # Usage: _setOption($option, $tref);
160              
161 203     203   211 my $option = shift;
162 203         191 my $tref = shift;
163              
164 203         259 $options{$option} = $tref;
165              
166 203         224 return 1;
167             }
168              
169             sub _optionsKeys {
170              
171             # Purpose: Returns a list of keys from %options
172             # Returns: keys %options
173             # Usage: @keys = _optionsKeys();
174              
175 14     14   48 return keys %options;
176             }
177              
178             sub _resetOptions {
179              
180             # Purpose: Empties the %options
181             # Returns: True (1)
182             # Usage: _resetOptions();
183              
184 14     14   118 %options = ();
185              
186 14         16 return 1;
187             }
188              
189             # Internal arguments list
190             my @arguments;
191              
192             sub _getArgRef {
193              
194             # Purpose: Gets a reference the argument array
195             # Returns: Array reference
196             # Usage: $argRef = _getArgRef();
197              
198 64     64   84 return \@arguments;
199             }
200              
201             sub clearMemory {
202              
203             # Purpose: Empties all internal data structures
204             # Returns: True (1)
205             # Usage: clearMemory();
206              
207 14     14 1 22 _NOOPTIONS = 0;
208 14         29 _resetErrors();
209 14         22 _resetOptions();
210 14         15 @{ _getArgRef() } = ();
  14         19  
211              
212 14         15 return 1;
213             }
214             }
215              
216             sub _tLint {
217              
218             # Purpose: Performs basic checks on a given option template for
219             # correctness
220             # Returns: True (1) if all checks pass, False (0) otherwise
221             # Usage: $rv = _tLint($templateRef);
222              
223 108     108   114 my $tref = shift; # Reference to option template hash
224 108         113 my $rv = 1;
225 108         124 my ( $oname, @at );
226              
227 108         215 subPreamble( PDLEVEL2, '$', $tref );
228              
229             # Get the option name for reporting purposes (should have been populated
230             # within parseArgs below)
231 108         139 $oname = $$tref{Name};
232              
233             # Make sure a short or long option is declared
234 108 50       169 if ( !defined $oname ) {
235 0         0 _pushErrors('No short or long option name declared');
236 0         0 $rv = 0;
237             }
238              
239             # Make sure the argument template is defined
240 108 50       140 if ($rv) {
241 108 50       161 unless ( defined $$tref{Template} ) {
242 0         0 _pushErrors("$oname option declared without a template");
243 0         0 $rv = 0;
244             }
245             }
246              
247             # Make sure the template contains only supported characters
248 108 50       149 if ($rv) {
249 108 50 33     402 unless ( defined $$tref{Template}
250             && $$tref{Template} =~ /^[\$\@]*$/s ) {
251 0         0 _pushErrors( "$oname option declared with an invalid template"
252             . "($$tref{Template})" );
253 0         0 $rv = 0;
254             }
255             }
256              
257             # Make sure option names are sane
258 108 50       167 if ($rv) {
259 108 100       175 if ( defined $$tref{Short} ) {
260 95 50       176 unless ( $$tref{Short} =~ /^[a-zA-Z0-9]$/s ) {
261 0         0 _pushErrors(
262             "Invalid name for the short option ($$tref{Short})");
263 0         0 $rv = 0;
264             }
265             }
266 108 50       165 if ( defined $$tref{Long} ) {
267 108 50       248 unless ( $$tref{Long} =~ /^[a-zA-Z0-9-]{2,}$/s ) {
268 0         0 _pushErrors(
269             "Invalid name for the long option ($$tref{Long})");
270 0         0 $rv = 0;
271             }
272             }
273             }
274              
275             # Make sure '@' is only used once, if at all, and the option isn't
276             # set to allow bundling
277 108 50       159 if ($rv) {
278 108 100       196 if ( $$tref{Template} =~ /\@/sm ) {
279 26         63 @at = ( $$tref{Template} =~ m#(\@)#sg );
280 26 50       57 if ( @at > 1 ) {
281 0         0 _pushErrors( 'The \'@\' symbol can only be used once in the '
282             . "template for $oname: $_" );
283 0         0 $rv = 0;
284             }
285 26 50 33     55 if ( $$tref{CanBundle} and defined $$tref{Short} ) {
286 0         0 _pushErrors(
287             "Option $$tref{Short} must have CanBundle set to false "
288             . 'if the template contains \'@\'' );
289 0         0 $rv = 0;
290             }
291             }
292             }
293              
294             # Make sure all values in our lists are defined
295 108 50       146 if ($rv) {
296 108 50       193 unless ( ref( $$tref{ExclusiveOf} ) eq 'ARRAY' ) {
297 0         0 _pushErrors( "Option ${oname}'s parameter ExclusiveOf must be an "
298             . 'array reference' );
299 0         0 $rv = 0;
300             }
301 108 50       167 unless ( ref( $$tref{AccompaniedBy} ) eq 'ARRAY' ) {
302 0         0 _pushErrors(
303             "Option ${oname}'s parameter AccompaniedBy must be an "
304             . 'array reference' );
305 0         0 $rv = 0;
306             }
307 108 50       143 if ($rv) {
308 108 50       109 if ( grep { !defined } @{ $$tref{ExclusiveOf} } ) {
  26         53  
  108         203  
309 0         0 _pushErrors(
310             "Option $oname has undefined values in ExclusiveOf");
311 0         0 $rv = 0;
312             }
313 108 50       123 if ( grep { !defined } @{ $$tref{AccompaniedBy} } ) {
  26         72  
  108         180  
314 0         0 _pushErrors(
315             "Option $oname has undefined values in ExclusiveOf");
316 0         0 $rv = 0;
317             }
318             }
319             }
320              
321             # Make sure CountShort is enabled only for those with a template of ''
322             # or '$'
323 108 50       139 if ($rv) {
324              
325 108 100       160 if ( $$tref{CountShort} ) {
326 15 50       36 unless ( $$tref{Template} =~ /^\$?$/sm ) {
327 0         0 _pushErrors( "Option $oname has CountShort set but with an "
328             . 'incompatible template' );
329 0         0 $rv = 0;
330             }
331             }
332             }
333              
334 108         221 subPostamble( PDLEVEL2, '$', $rv );
335              
336 108         221 return $rv;
337             }
338              
339             sub _getArgs ($$\@) {
340              
341             # Purpose: Takes passed argument template and extracts the requisite
342             # arguments to satisfy it from the argument list. The
343             # results are stored in the passed option list.
344             # Results: True (1) if successful, False (0) if not
345             # Usage: $rv = _getArgs($option, $argTemplate, @optionArgs);
346              
347 36     36   41 my $option = shift; # Option name
348 36         37 my $argTemplate = shift; # Option argument template
349 36         37 my $lref = shift; # Array reference for retrieved arguments
350 36         42 my $rv = 1;
351 36         42 my $argRef = _getArgRef();
352 36         37 my @tmp;
353              
354 36         75 subPreamble( PDLEVEL2, '$$$', $option, $argTemplate, $lref );
355              
356             # Empty the array
357 36         45 @$lref = ();
358              
359 36         78 pdebug( 'contents of args: %s', PDLEVEL4, @$argRef );
360              
361             # Start checking the contents of $argTemplate
362 36 100       90 if ( $argTemplate eq '' ) {
    100          
363              
364             # Template is '' (boolean option)
365 17         21 @$lref = (1);
366              
367             } elsif ( $argTemplate =~ /\@/s ) {
368              
369             # Template has a '@' in it -- we'll need to
370             # grab as many of the next arguments as possible.
371              
372             # Check the noOptions flags
373 5 50       8 if (_NOOPTIONS) {
374              
375             # True: gobble up everything left
376 0         0 push @$lref, @$argRef;
377 0         0 @$argRef = ();
378              
379             } else {
380              
381             # False: gobble up to the next option-looking thing
382 5   100     44 while ( @$argRef and $$argRef[0] !~ /^--?(?:\w+.*)?$/s ) {
383 18         54 push @$lref, shift @$argRef;
384             }
385              
386             # Now, we check to see if the first remaining argument is '--'.
387             # If it is then we must set noOptions to true and gobble the
388             # rest.
389 5 100 100     15 if ( @$argRef and $$argRef[0] eq '--' ) {
390 1         3 _NOOPTIONS = 1;
391 1         2 shift @$argRef;
392 1         2 push @$lref, @$argRef;
393 1         2 @$argRef = ();
394             }
395             }
396              
397             } else {
398              
399             # The template is not empty and has no '@', so we'll just grab the next
400             # n arguments, n being the length of the template
401              
402             # Check the noOptions flag
403 14 50       21 if (_NOOPTIONS) {
404              
405             # True: grab everything we need
406 0   0     0 while ( @$argRef and @$lref < length $argTemplate ) {
407 0         0 push @$lref, shift @$argRef;
408             }
409              
410             } else {
411              
412             # False: grab as many non-option-looking things as we can
413 14   100     396 while ( @$argRef
      100        
414             and $$argRef[0] !~ /^--?(?:\w+.*)$/s
415             and @$lref < length $argTemplate ) {
416 14         59 push @$lref, shift @$argRef;
417             }
418              
419             # Now, we check to see if we still need more arguments and if
420             # the first remaining argument is '--'. If it is then we must
421             # set noOptions to true and gobble what we need.
422 14 0 33     36 if ( @$lref < length $argTemplate
      33        
423             and @$argRef
424             and $$argRef[0] eq '--' ) {
425 0         0 _NOOPTIONS = 1;
426 0         0 shift @$argRef;
427 0   0     0 while ( @$argRef and @$lref < length $argTemplate ) {
428 0         0 push @$lref, shift @$argRef;
429             }
430             }
431             }
432             }
433              
434             # Final check: did we get minimum requisite number of arguments?
435 36 100       59 if ( @$lref < length $argTemplate ) {
436 1         3 _pushErrors(
437             pdebug(
438             'Missing the minimum number of arguments for %s', PDLEVEL1,
439             $option
440             ) );
441 1         29 $rv = 0;
442             } else {
443 35         56 pdebug( 'extracted the following arguments: %s', PDLEVEL3, @$lref );
444             }
445              
446             # sublist '@' portions of multicharacter templates
447 36 100 100     117 if ( $rv and $argTemplate =~ /\@/sm and length $argTemplate > 1 ) {
      66        
448 4         9 @tmp = ( [], [], [] );
449              
450             # First, shift off all preceding '$'s
451 4 50       10 if ( $argTemplate =~ /^(\$+)/s ) {
452 4         11 @{ $tmp[0] } = splice @$lref, 0, length $1;
  4         6  
453             }
454              
455             # Next, pop off all trailing '$'
456 4 100       12 if ( $argTemplate =~ /(\$+)\$/s ) {
457 2         5 @{ $tmp[2] } = splice @$lref, -1 * length $1;
  2         3  
458             }
459              
460             # Everything left belongs to the '@'
461 4         6 @{ $tmp[1] } = @$lref;
  4         7  
462              
463             # Let's put it all together...
464 4         6 @$lref = ();
465 4 50       3 push @$lref, @{ $tmp[0] } if @{ $tmp[0] };
  4         6  
  4         9  
466 4         4 push @$lref, $tmp[1];
467 4 100       5 push @$lref, @{ $tmp[2] } if @{ $tmp[2] };
  2         2  
  4         7  
468              
469 4         7 pdebug( 'sublisted arguments into: %s', PDLEVEL3, @$lref );
470             }
471              
472 36         74 subPostamble( PDLEVEL2, '$', $rv );
473              
474 36         69 return $rv;
475             }
476              
477             sub _storeArgs ($$\@) {
478              
479             # Purpose: Stores the passed option arguments in the passed option
480             # template's Value, but in accordance with parameters in the
481             # template
482             # Returns: True (1)
483             # Usage: _storeArgs($optionTemplate, $argTemplate, @optionArgs);
484              
485 35     35   43 my $tref = shift;
486 35         40 my $argTemplate = shift;
487 35         33 my $lref = shift;
488              
489 35         69 subPreamble( PDLEVEL2, '$$$', $tref, $argTemplate, $lref );
490              
491 35         73 pdebug( 'adding values to %s', PDLEVEL3, $$tref{Name} );
492              
493             # Increment our usage counter
494 35         51 $$tref{Count}++;
495              
496             # Store arguments according to the template
497 35 100       55 if ( $argTemplate eq '' ) {
    100          
498              
499             # Template is ''
500 17 100       29 $$tref{Value} = 0 unless defined $$tref{Value};
501 17         20 $$tref{Value}++;
502 17         28 pdebug( 'Value is now %s', PDLEVEL3, $$tref{Value} );
503              
504             } elsif ( $argTemplate eq '$' ) {
505              
506             # Template is '$'
507 14 100 100     56 if ( not $$tref{Multiple} or $$tref{CountShort} ) {
508              
509             # Store the value directly since we
510             # can only be used once
511 12         19 $$tref{Value} = $$lref[0];
512 12         22 pdebug( 'Value is now %s', PDLEVEL3, $$tref{Value} );
513              
514             } else {
515              
516             # Store the value as part of a list since
517             # we can be used multiple times
518             $$tref{Value} = []
519             unless defined $$tref{Value}
520 2 100 66     9 and ref $$tref{Value} eq 'ARRAY';
521 2         3 push @{ $$tref{Value} }, $$lref[0];
  2         4  
522 2         3 pdebug( 'Value is now %s', PDLEVEL3, @{ $$tref{Value} } );
  2         4  
523             }
524              
525             } else {
526              
527             # Template is anything else
528 4 50       9 if ( not $$tref{Multiple} ) {
529              
530             # Store the values directly in a an array
531             # since we can only be used once
532 4         6 $$tref{Value} = [@$lref];
533 4         7 pdebug( 'Value is now %s', PDLEVEL3, @{ $$tref{Value} } );
  4         9  
534              
535             } else {
536              
537             # Store the values as an element of an
538             # array since we can be used multiple times
539             $$tref{Value} = []
540             unless defined $$tref{Value}
541 0 0 0     0 and ref $$tref{Value} eq 'ARRAY';
542 0         0 push @{ $$tref{Value} }, [@$lref];
  0         0  
543             pdebug( 'Value now has %d sets',
544 0         0 PDLEVEL3, scalar @{ $$tref{Value} } );
  0         0  
545             }
546             }
547              
548 35         70 subPostamble( PDLEVEL1, '$', 1 );
549              
550 35         78 return 1;
551             }
552              
553             sub parseArgs (\@\%;\@) {
554              
555             # Purpose: Extracts and validates all command-line arguments and options,
556             # storing them in an organized hash for easy retrieval
557             # Returns: True (1) if successful, False (0) if not
558             # Usage: $rv = parseArgs(@templates, %options);
559             # Usage: $rv = parseArgs(@templates, %options, @args);
560              
561 14     14 1 2460 my $tlref = shift; # Templates list ref
562 14         17 my $oref = shift; # Options hash ref
563 14         14 my $paref = shift; # Program argument list ref
564 14         14 my $rv = 1;
565 14         29 my ( $tref, $oname, $argRef, $arg, $argTemplate );
566 14         0 my ( @tmp, @oargs, $regex );
567              
568 14         36 subPreamble( PDLEVEL1, '$$$', $tlref, $oref, $paref );
569              
570             # Validate arguments
571 14 50       25 $paref = \@ARGV unless defined $paref;
572              
573             # Clear all internal data structures and reset flag
574 14         22 clearMemory();
575              
576             # Empty the passed options hash
577 14         26 %$oref = ();
578              
579             # Make a copy of the argument list
580 14         15 $argRef = _getArgRef();
581 14         28 @$argRef = (@$paref);
582              
583             # Assemble %options and lint-check the templates
584 14         25 foreach (@$tlref) {
585              
586             # Make sure the element is a hash reference
587 108 50       182 unless ( ref $_ eq 'HASH' ) {
588 0         0 _pushErrors('Illegal non-hash reference in templates array');
589 0         0 $rv = 0;
590 0         0 next;
591             }
592              
593             # Establish a base template and copy the contents of the passed hash
594             $tref = {
595 108         562 Short => undef,
596             Long => undef,
597             Template => '',
598             Multiple => 0,
599             ExclusiveOf => [],
600             AccompaniedBy => [],
601             CanBundle => 0,
602             CountShort => 0,
603             Value => undef,
604             %$_,
605             };
606              
607             # Set AllOptions for error message reporting
608             $$tref{Name} =
609             defined $$tref{Short}
610             && defined $$tref{Long} ? "-$$tref{Short}/--$$tref{Long}"
611             : defined $$tref{Short} ? "-$$tref{Short}"
612 108 50 66     493 : defined $$tref{Long} ? "--$$tref{Long}"
    50          
    100          
613             : undef;
614              
615             # Initialize our usage counter
616 108         155 $$tref{Count} = 0;
617              
618             # Anything that has CountShort enabled implies Multiple/CanBundle
619             # and a template of '$'
620 108 100       165 if ( $$tref{CountShort} ) {
621 15         21 $$tref{CanBundle} = $$tref{Multiple} = 1;
622 15 50       26 $$tref{Template} = '$' if defined $$tref{Long};
623             }
624              
625             # Anything that has a Short option and a template of '$' or ''
626             # implies CanBundle
627             $$tref{CanBundle} = 1
628 108 100 100     252 if defined $$tref{Short} and $$tref{Template} eq '';
629              
630             # We'll associate both the long and short options to the same hash
631             # to make sure that we count/collect everything appropriately.
632             #
633             # Store the short option
634 108 100 66     254 if ( defined $$tref{Short} and length $$tref{Short} ) {
635              
636             # See if a template is already defined
637 95 50       143 if ( defined _getOption( $$tref{Short} ) ) {
638              
639             # It is -- report the error
640             Paranoid::ERROR = _pushErrors(
641             pdebug(
642             'the %s option has more than one template',
643 0         0 PDLEVEL1, $$tref{Short} ) );
644 0         0 $rv = 0;
645              
646             } else {
647              
648             # It's not -- go ahead and store it
649 95         133 _setOption( $$tref{Short}, $tref );
650             }
651             }
652              
653             # Store the long option
654 108 50 33     273 if ( defined $$tref{Long} and length $$tref{Long} ) {
655              
656             # See if a template is already defined
657 108 50       141 if ( defined _getOption( $$tref{Long} ) ) {
658              
659             # It is -- report the error
660             Paranoid::ERROR = _pushErrors(
661             pdebug(
662             'the %s option has more than one template',
663 0         0 PDLEVEL1, $$tref{Long} ) );
664 0         0 $rv = 0;
665              
666             } else {
667              
668             # It's not -- go ahead and store it
669 108         160 _setOption( $$tref{Long}, $tref );
670             }
671             }
672              
673             # Do a basic lint-check on the template
674 108 50       127 $rv = 0 unless _tLint($tref);
675             }
676              
677 14 50       25 if ($rv) {
678              
679 14         20 while (@$argRef) {
680 35         47 $arg = shift @$argRef;
681 35 50       57 next unless defined $arg;
682              
683             # Start testing $arg
684 35 100 66     70 if ( $arg eq '--' and not _NOOPTIONS ) {
    100 100        
685              
686             # $arg is '--', so set the no options flag
687 1         2 _NOOPTIONS = 1;
688              
689             } elsif ( not _NOOPTIONS and $arg =~ /^--?/s ) {
690              
691             # '--' hasn't been passed yet and this looks
692             # like an option...
693              
694             # Test types of options
695 27 100       88 if ( $arg =~ /^-(\w.*)$/s ) {
    50          
696              
697             # With a single '-' it should be a short option. However,
698             # we'll split the option portion, in case there's more
699             # than one character
700 14         42 @tmp = split //s, $1;
701              
702             # If there's more than one character for the option name
703             # it must be either a bunch of bundled options or an
704             # option with a concatenated argument. In case of the
705             # latter (assuming that CanBundle is set to false (a
706             # prerequisite of argument concatenation) and it has a
707             # template of '$' (another prerequisite)) we'll unshift
708             # the rest of the characters back onto the argument list.
709             #
710             # Oh, but first we'll need to get the applicable
711             # option template and then start testing...
712 14         22 $tref = _getOption( $tmp[0] );
713 14 100 66     73 if ( $#tmp
      66        
      66        
714             and defined $tref
715             and $$tref{Template} eq '$'
716             and not $$tref{CanBundle} ) {
717 3         11 unshift @$argRef, join '', @tmp[ 1 .. $#tmp ];
718 3         6 splice @tmp, 1;
719             }
720              
721             # Start processing all remaining short options in @tmp
722 14         24 foreach (@tmp) {
723              
724             # Get the template
725 25         33 $tref = _getOption($_);
726              
727             # Make sure the option is supported
728 25 50       40 if ( defined $tref ) {
729              
730             # Make sure option allows bundling if bundled
731 25 100       31 if ($#tmp) {
732 16 50       28 unless ( $$tref{CanBundle} ) {
733 0         0 _pushErrors(
734             "Option $_ used bundled with "
735             . 'other options' );
736 0         0 $rv = 0;
737 0         0 next;
738             }
739             }
740              
741             # Get the argument template
742 25         36 $argTemplate = $$tref{Template};
743              
744             # Override the template if CountShort is true
745             $argTemplate = ''
746             if $argTemplate eq '$'
747 25 100 100     70 and $$tref{CountShort};
748              
749             # Get any accompanying arguments
750 25 50       50 unless ( _getArgs( "-$_", $argTemplate, @oargs ) )
751             {
752 0         0 $rv = 0;
753 0         0 next;
754             }
755              
756             # Check if we've call this more than once
757 25 50 66     64 if ( not $$tref{Multiple}
758             and $$tref{Count} > 0 ) {
759 0         0 _pushErrors(
760             "Option $$tref{Name} is only allowed "
761             . 'to be used once' );
762 0         0 $rv = 0;
763 0         0 next;
764             }
765              
766             # Store the values
767 25         42 _storeArgs( $tref, $argTemplate, @oargs );
768              
769             } else {
770              
771             # Warn that this is an unknown option
772 0         0 _pushErrors("Unknown short option used: $_");
773 0         0 $rv = 0;
774             }
775             }
776              
777             } elsif ( $arg =~ /^--([\w-]+)(?:=(.+))?$/sm ) {
778              
779             # Starts with '--', so must be a long option
780              
781             # Save the extracted option/argument portion
782 13         32 @tmp = ($1);
783 13 100 66     36 push @tmp, $2 if defined $2 and length $2;
784              
785             # If this option had an argument portion we need to
786             # unshift it back onto the argument list *provided* it was
787             # a legal argument, i.e., this option had a template of
788             # '$'.
789 13         21 $tref = _getOption( $tmp[0] );
790 13 100 66     27 if ( $#tmp and defined $tref ) {
791              
792             # Test for various templates
793 1 50       3 if ( $$tref{Template} eq '$' ) {
    0          
794              
795             # Legal invocation -- unshift away
796 1         3 unshift @$argRef, $tmp[1];
797              
798             } elsif ( $$tref{Template} eq '' ) {
799              
800             # Illegal, no arguments expected
801 0         0 _pushErrors( "--$tmp[0] does not require any "
802             . 'arguments' );
803 0         0 $rv = 0;
804 0         0 next;
805              
806             } else {
807              
808             # Illegal, can't use concatenated arguments in
809             # more complex templates
810 0         0 _pushErrors( "--$tmp[0] cannot be called like "
811             . 'this when multiple arguments are '
812             . 'required.' );
813             }
814             }
815              
816             # Handle known options
817 13 100       20 if ( defined $tref ) {
818              
819             # Get the argument template
820 11         14 $argTemplate = $$tref{Template};
821              
822             # Snarf extra arguments
823 11 100       30 unless (
824             _getArgs( "--$tmp[0]", $argTemplate, @oargs ) ) {
825 1         2 $rv = 0;
826 1         3 next;
827             }
828              
829             # Check if we've call this more than once
830 10 50 66     34 if ( not $$tref{Multiple} and $$tref{Count} > 0 ) {
831 0         0 _pushErrors(
832             "Option $$tref{Name} is only allowed to be used once"
833             );
834 0         0 $rv = 0;
835 0         0 next;
836             }
837              
838             # Store the values
839 10         17 _storeArgs( $tref, $argTemplate, @oargs );
840              
841             } else {
842              
843             # Unknown long option
844 2         7 _pushErrors("Unknown option: --$tmp[0]");
845 2         4 $rv = 0;
846             }
847              
848             } else {
849              
850             # Unknown option-looking thingy
851 0         0 _pushErrors("Unknown option thingy: $arg");
852 0         0 $rv = 0;
853             }
854              
855             } else {
856              
857             # Everything else is payload
858 7 100       15 $$oref{PAYLOAD} = [] unless exists $$oref{PAYLOAD};
859 7         7 push @{ $$oref{PAYLOAD} }, $arg;
  7         12  
860             }
861             }
862             }
863              
864             # Make a list of all the arguments that was used
865 14         21 @tmp = ();
866 14         18 foreach ( _optionsKeys() ) {
867 203 100       195 push @tmp, $_ if ${ _getOption($_) }{Count};
  203         224  
868             }
869              
870             # Final sanity check
871 14         46 foreach ( sort @tmp ) {
872 44         55 $tref = _getOption($_);
873              
874             # Make sure nothing was called that is exclusive of
875             # other called options
876 44 100       46 if ( @{ $$tref{ExclusiveOf} } ) {
  44         64  
877 2         3 $regex = '(?:' . join( '|', @{ $$tref{ExclusiveOf} } ) . ')';
  2         5  
878 2 50       31 if ( grep /^$regex$/sm, @tmp ) {
879             _pushErrors(
880             "$$tref{Name} cannot be called with the following options: "
881             . join ', ',
882 2         5 @{ $$tref{ExclusiveOf} } );
  2         7  
883 2         4 $rv = 0;
884             }
885             }
886              
887             # Make sure the option was called in conjunction with others
888 44         78 foreach $regex ( @{ $$tref{AccompaniedBy} } ) {
  44         87  
889 14 100       158 unless ( grep /^\Q$regex\E$/sm, @tmp ) {
890             _pushErrors(
891             "$$tref{Name} must be called with the following options: "
892             . join ', ',
893 2         5 @{ $$tref{AccompaniedBy} } );
  2         7  
894 2         3 $rv = 0;
895             }
896             }
897              
898             # Copy the values into %$oref
899 44         82 $$oref{$_} = $$tref{Value};
900             }
901              
902 14         30 subPostamble( PDLEVEL1, '$', $rv );
903              
904 14         60 return $rv;
905             }
906              
907             1;
908              
909             __END__