File Coverage

blib/lib/Getopt/CommandLineExports.pm
Criterion Covered Total %
statement 27 109 24.7
branch 0 30 0.0
condition 0 21 0.0
subroutine 9 13 69.2
pod 4 4 100.0
total 40 177 22.6


line stmt bran cond sub pod time code
1             package Getopt::CommandLineExports;
2              
3 1     1   25477 use 5.006;
  1         4  
  1         41  
4 1     1   6 use strict;
  1         2  
  1         34  
5 1     1   5 use warnings;
  1         15  
  1         31  
6 1     1   4772 use CGI;
  1         20033  
  1         8  
7              
8              
9             =head1 NAME
10              
11             Getopt::CommandLineExports - Allow suroutines within a script to export comand line options with bash auto completion
12              
13             =head1 VERSION
14              
15             Version 0.04
16              
17             =cut
18              
19             our $VERSION = '0.04';
20              
21              
22             =head1 SYNOPSIS
23              
24             Example Code:
25              
26             use strict;
27             use warnings;
28             use Getopt::CommandLineExports qw(®AC &parseArgsByPosition &parseArgs
29             &checkArgs $scriptName @exportedSubs %cmdLines);
30              
31             $scriptName = qq[TestCommandLineExports];
32             %cmdLines = (
33             twoScalars => [qw/ ONE=s TWO=s /],
34             oneHash => [qw/ ONE=s% /],
35             oneList => [qw/ ONE=s@ /],
36             );
37             sub twoScalars
38             {
39             my %h = (
40             ONE => undef,
41             TWO => undef,
42             ( parseArgs \@_, @{$cmdLines{twoScalars}}),
43             );
44             print "twoScalars missing required argument:\n"
45             . join( "\n", checkArgs \%h ) . "\n"
46             if ( checkArgs \%h );
47             return " $h{ONE} , $h{TWO} \n";
48             }
49              
50             sub oneHash
51             {
52             my %h = (
53             ONE => undef,
54             ( parseArgs \@_, @{$cmdLines{oneHash}}),
55             );
56             print "oneHash missing required argument:\n"
57             . join( "\n", checkArgs \%h ) . "\n"
58             if ( checkArgs \%h );
59             print "oneHash\n";
60             print join("\n", (%{$h{ONE}}));
61             }
62              
63             sub oneList
64             {
65             my %h = (
66             ONE => undef,
67             ( parseArgs \@_, @{$cmdLines{oneList}}),
68             );
69             print "oneList missing required argument:\n"
70             . join( "\n", checkArgs \%h ) . "\n"
71             if ( checkArgs \%h );
72             print "oneList\n";
73             print join("\n",@{$h{ONE}});
74             }
75              
76             # The "Main" subroutine. Not included in package, must be added manually to a script
77              
78             if ( defined $ARGV[0] )
79             {
80             if ( defined( &{ $ARGV[0] } ) )
81             {
82             no strict 'refs';
83             my $subRef = shift @ARGV;
84             print join( "\n", &$subRef(@ARGV) ) . "\n" unless $subRef =~ /regAC/ ;
85             &$subRef($scriptName, \@exportedSubs, \%cmdLines) if $subRef =~ /regAC/ ;
86             exit 0;
87             }
88             }
89              
90             # some unit test examples:
91             twoScalars "Hello1", "Hello2";
92             twoScalars {ONE => "Hello1", TWO => "Hello2"};
93             twoScalars "--ONE Hello1 --TWO Hello2";
94             twoScalars "--ONE", "Hello1", "--TWO", "Hello2";
95             twoScalars "--ONE", "Hello1", "--TWO", "Hello2", "--THREE", "Hello3"; # complains about "unknown option: three"
96              
97             =head1 PURPOSE
98              
99             This module is intended to provide the capability to have a single
100             script export many subcommands in a consistant manner.
101              
102             In the example above, the script is named "TestCommandLineExports".
103             On a bash style command line, the following commands would work:
104              
105             TestCommandLineExports twoScalars --ONE "Arg1" --TWO "Arg2"
106              
107             and would print:
108              
109             Arg1, Arg2
110              
111             while
112              
113             TestCommandLineExports twoScalars --TWO "Arg2"
114            
115             would print:
116              
117             twoScalars missing required argument:
118             --ONE
119              
120             TestCommandLineExports twoScalars may also be called through a CGI interface as well.
121              
122             The principle use of this was to provide an easy, consistant, method
123             to provide unit test ability for scripts. It also allows for a single
124             script to export multiple subcommands and, with the included bash
125             auto completion function, allows for the subcommands and options to
126             integrate nicely with the bash shell.
127              
128              
129             =head1 EXPORT
130              
131             A list of functions that can be exported. You can delete this section
132             if you don't export anything, such as for a purely object-oriented module.
133              
134             =head1 SUBROUTINES/METHODS
135              
136              
137             =head2 regAC
138              
139             Print a bash auto completion script.
140             Returns a script roughly sutiable for the bash_autocompletion functions:
141              
142             Include roughly the following in your script:
143              
144             # this hash uses perl's Getopt::Long format
145             my %cmdLines = (
146             regAC => [qw//],
147             SubCommandOne => [qw/DIRECTORY=s YES_OR_NO=s ANY_FILE=s/],
148             SubCommandTwo => {qw/INT=i/],
149             )
150             my @exportedSubs = keys %cmdLines;
151              
152             #you can use bash completion words here ("__directory__") to complete with directories
153             # The default is filename completion
154             my %additionalWordCompletions = (
155             SubCommandOne => {
156             DIRECTORY => [qw/__directory__/],
157             YES_OR_NO => [qw/yes no/],
158             },
159             );
160              
161             if ( defined $ARGV[0] )
162             {
163             if ( defined( &{ $ARGV[0] } ) )
164             {
165             no strict 'refs';
166             my $subRef = shift @ARGV;
167             print join( "\n", &$subRef(@ARGV) ) . "\n" unless $subRef =~ /regAC/ ;
168             &$subRef($scriptName, \@exportedSubs, \%cmdLines, \%additionalWordCompletions) if $subRef =~ /regAC/ ;
169             exit 0;
170             }
171             }
172              
173              
174             Run from the commandline as:
175              
176             ScriptName regAC > /etc/bash_completion.d/ScriptName
177             source /etc/bash_completion.d/ScriptName
178              
179             or
180              
181             sudo ScriptName regAC
182             source /etc/bash_completion.d/ScriptName
183              
184             and the script should be registered with all the commands in:
185              
186             @Getopt::CommandLineExports::exportedSubs
187              
188             and the command lines from:
189              
190             %Getopt::CommandLineExports::cmdLines
191              
192             =head2 parseArgs
193              
194             parse and argument list according to a command line spec in the Getopt::Long format.
195             Returns a hash of arguments and values.
196              
197             %cmdLines = (
198             function => [qw/ REQUIRED_ARGUMENT=s OPTIONAL_ARGUMENT_ONE=s OPTIONAL_ARGUMENT_TWO=s /],
199             );
200              
201             my %h = (
202             REQUIRED_ARGUMENT => undef, # undef means the argument is required
203             OPTIONAL_ARGUMENT_TWO => 'default value', # a default value is provided
204             # no mention of OPTIONAL_ARGUMENT_ONE means that it could be provided or could be undefined
205             # checkArgs below will NOT include this in the missing argument list
206             ( parseArgs \@_, @{$cmdLines{function}})
207             );
208              
209             =head2 parseArgsByPosition
210              
211             parse an argument list according to a command line spec in the Getopt::Long format.
212              
213             parseArgsByPosition( \@argv, \%args, @ComSpec);
214              
215             The first argument is the standard argv list.
216             The second is a reference to a hash to receive the arguments parsed from argv
217             (a reference is passed to allow for default values to be set.
218             The last argument is a reference to the argument spec in Getopt::Long format
219              
220             as an example:
221              
222             my %args = (ARG1 => "Default Value", ARG2 => undef);
223              
224             parseArgsByPosition( ["One", "Two", "Three"], \%args, qw/ARG1=s ARG2=s ARG3=s ARG4=s/);
225              
226             should set %args to be (ARG1 => "One", ARG2 => "Two", ARG3 => "Three")
227              
228             =head2 checkArgs
229              
230             checkArgs will return a list of arguments that are undefined. This can be used
231             to identify required arguments with:
232              
233             my %h = (
234             REQUIRED_ARGUMENT => undef,
235             ( parseArgs \@_, @{$cmdLines{function}})
236             );
237             print "function missing required argument:\n"
238             . join( "\n", checkArgs \%h ) . "\n"
239             if ( checkArgs \%h );
240              
241             =cut
242              
243              
244              
245             BEGIN {
246 1     1   127 use Exporter ();
  1         2  
  1         93  
247 1     1   2 our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS);
248             # set the version for version checking
249 1         3 $VERSION = 0.04;
250 1         19 @ISA = qw(Exporter);
251 1         3 @EXPORT_OK = qw(®AC &parseArgsByPosition &parseArgs &checkArgs);
252 1         27 %EXPORT_TAGS = ( ALL => [ qw(®AC &parseArgsByPosition &parseArgs &checkArgs) ], );
253              
254             #your exported package globals go here,
255             #as well as any optionally exported functions
256             }
257              
258              
259             # exported package globals go here
260              
261              
262              
263 1     1   1445 use Getopt::Long qw(GetOptionsFromString GetOptionsFromArray);
  1         14085  
  1         7  
264 1     1   224 use warnings;
  1         3  
  1         1297  
265             sub regAC
266             {
267 0     0 1   my $scriptName = shift;
268 0           my $esRef = shift;
269 0           my $cmdRef = shift;
270 0           my $addWordsRef = shift;
271 0           my @exportedSubs = @{$esRef};
  0            
272 0           my %cmdLines = %{$cmdRef};
  0            
273 0 0         my %addWords = %{$addWordsRef} if defined $addWordsRef;
  0            
274 0           my $cmdOptsText = "";
275 0           my $cmdoptMoreWordsText = "";
276 0           while (my ($cmdName, $params) =each %cmdLines)
277             {
278 0           $cmdOptsText .= "$cmdName => [qw/" . join(" ",@$params) . "/],\n";
279             }
280 0           while (my ($cmdName, $options) =each %addWords)
281             {
282 0           $cmdoptMoreWordsText .= "$cmdName => {\n";
283 0           while (my ($option, $params) =each %$options)
284             {
285 0           $cmdoptMoreWordsText .= "$option => [qw/" . join(" ",@$params) . "/],\n";
286             }
287 0           $cmdoptMoreWordsText .= "},\n";
288             }
289 0           my $bashFunc = <
290             _$scriptName()
291             EOF
292             ;
293 0           $bashFunc .= <<'EOF'
294             {
295             local cur prev cmds cmdOpts perlCmd
296             COMPREPLY=()
297             cur="${COMP_WORDS[COMP_CWORD]}"
298             prev="${COMP_WORDS[COMP_CWORD-1]}"
299             subcmd="${COMP_WORDS[1]}"
300             cmds="
301             EOF
302             ;
303 0           chomp($bashFunc);
304 0           $bashFunc = $bashFunc . join (" ", @exportedSubs);
305 0           $bashFunc = $bashFunc . '"' ."\n";
306 0           $bashFunc = $bashFunc . <
307             if [[ \$COMP_CWORD -eq 1 ]] ; then
308             COMPREPLY=( \$(compgen -W "\${cmds}" -- \${cur}) )
309             return 0
310             fi
311             perlCmd=\$(cat <
312             %cmdopt = (
313             EOF
314             ;
315 0           $bashFunc = $bashFunc . $cmdOptsText;
316 0           $bashFunc = $bashFunc . <<'EOF'
317              
318             );
319             \$noArgs = 1;
320             \$optionalArgs = 0;
321             \$cmd = \$ARGV[0];
322             \$arg = \$ARGV[1];
323             \$doAddWords = 1 if defined \$ARGV[2];
324             \$match = 0;
325             foreach (@{\$cmdopt{\$cmd}})
326             {
327             \$prevCmd = \$arg;
328             \$prevCmd =~ s/--//;
329             if (\$_ =~ m/^\$prevCmd/)
330             {
331             \$match = 1;
332             \$noArgs = 0 if m/[=][sfi]/;
333             \$optionalArgs = 1 if m/[:][sfi]/;
334             }
335             }
336             \$match = 1 if (\$cmd eq \$arg) and not \$doAddWords;
337             s/[=!+:].*// foreach (@{\$cmdopt{\$cmd}});
338             if (\$noArgs and \$match and not \$doAddWords)
339             {
340             print qq(--\$_\n) foreach( @{\$cmdopt{\$cmd}});
341             exit;
342             }
343             if (not \$arg =~ m/^--/ and not \$doAddWords)
344             {
345             print qq(--\$_\n) foreach( @{\$cmdopt{\$cmd}});
346             exit;
347             }
348             %cmdoptMoreWords = (
349             EOF
350             ;
351 0           $bashFunc = $bashFunc . $cmdoptMoreWordsText;
352 0           $bashFunc = $bashFunc . <<'EOF'
353             );
354             %cmdAddWords = ();
355             while ((\$key, \$val) = each %{\$cmdoptMoreWords{\$cmd}})
356             {
357             \$cmdAddWords{\$key} = \$val;
358             }
359             \$option = \$arg ;
360             \$option =~ s/--//;
361              
362             if (defined \$cmdAddWords{\$option})
363             {
364             if (\$doAddWords)
365             {
366             foreach( @{\$cmdAddWords{\$option}})
367             {
368             if (m/^__.*__$/) {
369             s/__//g;
370             print qq( -A \$_ );
371             }
372             }
373             }
374             else
375             {
376             foreach( @{\$cmdAddWords{\$option}})
377             {
378             print qq(\$_\n) unless m/^__.*__$/;
379             }
380             }
381             }
382             CMDLINE
383             )
384             EOF
385             ;
386              
387 0           $bashFunc = $bashFunc . <
388             cmdOpts=\$( perl -e "\${perlCmd}" -- "\${subcmd}" "\${prev}")
389             cmdAddOpts=\$( perl -e "\${perlCmd}" -- "\${subcmd}" "\${prev}" moreOpts )
390             if [[ -z \$cmdAddOpts ]]
391             then
392             if [[ -z \$cmdOpts ]]
393             then
394             cmdAddOpts=" -A file "
395             fi
396             fi
397             commas=\$( echo \${cur} | sed "s/\,[^\,]*\$//" )
398             cur=\$( echo \${cur} | sed "s/.*\,//" )
399             if [ "\$commas" == "\$cur" ]
400             then
401             COMPREPLY=( \$( compgen \$cmdAddOpts -W "\$cmdOpts" -- "\${cur}") )
402             else
403             COMPREPLY=( \$( compgen \$cmdAddOpts -W "\$cmdOpts" -- "\${cur}") )
404             LEN=\${#COMPREPLY[@]}
405             for ((i=0; i<\${LEN}; i++ ));
406             do
407             COMPREPLY[\$i]=\$( echo \${commas},\${COMPREPLY[\$i]} )
408             done
409             fi
410             }
411             complete -F _$scriptName $scriptName
412             EOF
413             ;
414              
415 0 0 0       if (-w "/etc/bash_completion.d/$scriptName" or -w "/etc/bash_completion.d/")
416             {
417 0 0         open my $fh, '>', "/etc/bash_completion.d/$scriptName" or die "Can not open /etc/bash_completion.d/$scriptName for writing\n";
418 0           print {$fh} $bashFunc;
  0            
419 0           close $fh;
420 0           print qq(Remember to "source /etc/bash_completion.d/$scriptName" to update your shell\n);
421             }
422             else
423             {
424 0           print $bashFunc;
425             }
426              
427             }
428              
429              
430             sub parseArgsByPosition
431             {
432 0     0 1   my $argvRef = shift;
433 0           my @argvCopy = @{$argvRef};
  0            
434 0           my $dstHashRef = shift;
435 0           my @optSpecs = @_;
436 0           foreach (@optSpecs) {
437 0           my $isList = m/@/;
438 0           my $isHash = m/%/;
439 0           s/[=!+:].*//;
440 0           my $val = shift @argvCopy;
441 0 0         if ($isList) {
    0          
442 0           $dstHashRef->{$_} = [split(/,/, $val)];
443             } elsif ($isHash) {
444 0           $dstHashRef->{$_} = [split(/,|=/, $val)];
445             } else {
446 0 0         $dstHashRef->{$_} = $val if defined $val;
447             }
448             }
449             }
450              
451             sub parseArgs
452             {
453             # The first argument is a reference to the original
454             # argv list
455             # The remaining arguments are the argument specifiers
456             # as defined by Getopt::long
457            
458 0     0 1   my %args = ();
459 0           my $firstarg = shift;
460 0           my @argvCopy = @{$firstarg};
  0            
461             # case CGI, called via CGI return hash parsed by CGI.pm
462 0 0 0       if (exists $ENV{GATEWAY_INTERFACE} and scalar(@argvCopy) == 0)
463             {
464 0           %args = %{CGI->new()->Vars};
  0            
465 0           foreach (keys %args)
466             {
467 0 0         $args{$_} = [split(/,|\x{00}/, $args{$_})] if scalar(split(/,|\x{00}/, $args{$_})) > 1;
468             }
469 0           return %args;
470             }
471             # case One, No arguments, just return
472 0 0         return if (scalar(@argvCopy) == 0);
473             # case Two, the first and only argument is a reference to a hash
474             # return a the hash unaltered (named parameter passing)
475 0 0         %args = %{ $argvCopy[0] } if ( ref( $argvCopy[0] ) eq "HASH" );
  0            
476 0           my $ret;
477 0 0 0       return %args if ref( $argvCopy[0] ) eq "HASH" and scalar(@argvCopy) == 1;
478            
479             # case Three, there is one argument and it starts with a dash '-'
480             # treat it as a command line string
481 0 0 0       if ( ( scalar(@argvCopy) == 1 ) and ( ref( $argvCopy[0] ) eq "" ) and ($argvCopy[0] =~ m/^-/ ))
    0 0        
      0        
      0        
482             {
483 0           $ret = GetOptionsFromString( $argvCopy[0], \%args, @_ );
484             }
485             # case Four, there is more than one argument, and the
486             # first argument starts with a dash '-'
487             # treat it as an array of command line options
488             elsif ( (scalar(@argvCopy) != 1) and ( ref( $argvCopy[0] ) eq "" ) and ($argvCopy[0] =~ m/^-/ ))
489             {
490 0           $ret = GetOptionsFromArray( \@argvCopy, \%args, @_ );
491             }
492             # case Five, there is more than one argument and the first argument does not start with a
493             # dash '-' or the first argument is a reference to something
494             # OR
495             # there is exactly one argument and that argument either:
496             # is not a reference to something and does not start with a dash '-'
497             # or is a reference to something
498             # treat it as a conventional call by position
499             else
500             {
501 0           parseArgsByPosition( \@argvCopy, \%args, @_);
502             }
503 0           my @optSpecs = @_;
504 0           foreach my $arg (keys %args)
505             {
506 0 0         @{$args{$arg}} = split(/,/,join(',',@{$args{$arg}})) if (ref $args{$arg} eq "ARRAY")
  0            
  0            
507             }
508 0           return %args;
509             }
510              
511             sub checkArgs
512             {
513 0     0 1   my @MissingArgs = ();
514 0           my $argRef = shift;
515 0           while ( my ( $key, $value ) = each %{$argRef} )
  0            
516             {
517 0 0         push @MissingArgs, $key if ( not defined($value) );
518             }
519 0           return @MissingArgs;
520             }
521              
522              
523 1     1   295 END { } # module clean-up code here (global destructor)
524             =head1 AUTHOR
525              
526             Robert Haxton, C<< >>
527              
528             =head1 BUGS
529              
530             Please report any bugs or feature requests to C, or through
531             the web interface at L. I will be notified, and then you'll
532             automatically be notified of progress on your bug as I make changes.
533              
534              
535              
536              
537             =head1 SUPPORT
538              
539             You can find documentation for this module with the perldoc command.
540              
541             perldoc Getopt::CommandLineExports
542              
543              
544             You can also look for information at:
545              
546             =over 4
547              
548             =item * RT: CPAN's request tracker (report bugs here)
549              
550             L
551              
552             =item * AnnoCPAN: Annotated CPAN documentation
553              
554             L
555              
556             =item * CPAN Ratings
557              
558             L
559              
560             =item * Search CPAN
561              
562             L
563              
564             =item * Code Repository
565              
566             L
567              
568             =back
569              
570             =head1 ACKNOWLEDGEMENTS
571              
572              
573             =head1 LICENSE AND COPYRIGHT
574              
575             Copyright 2008-2012 Robert Haxton.
576              
577             This program is free software; you can redistribute it and/or modify it
578             under the terms of either: the GNU General Public License as published
579             by the Free Software Foundation; or the Artistic License.
580              
581             See http://dev.perl.org/licenses/ for more information.
582              
583              
584             =cut
585              
586             1; # End of Getopt::CommandLineExports