File Coverage

blib/lib/Getopt/LongUsage.pm
Criterion Covered Total %
statement 169 179 94.4
branch 70 90 77.7
condition 34 72 47.2
subroutine 16 16 100.0
pod 3 3 100.0
total 292 360 81.1


line stmt bran cond sub pod time code
1             package Getopt::LongUsage;
2              
3 1     1   28818 use 5.008001;
  1         3  
  1         34  
4 1     1   5 use strict;
  1         2  
  1         28  
5 1     1   4 use warnings;
  1         5  
  1         31  
6 1     1   5 use Exporter;
  1         1  
  1         46  
7 1     1   5 use Carp;
  1         2  
  1         90  
8 1     1   2074 use Getopt::Long 2.37;
  1         21739  
  1         31  
9 1     1   4696 use Data::Dumper;
  1         13583  
  1         416  
10              
11             BEGIN {
12 1     1   11 use vars qw(@ISA @EXPORT @EXPORT_OK);
  1         2  
  1         111  
13 1     1   20 @ISA = qw(Exporter);
14 1         3 @EXPORT = qw(&GetLongUsage);
15 1         3 @EXPORT_OK = qw(&GetLongUsage);
16              
17 1     1   6 use vars qw($REF_NAME);
  1         2  
  1         48  
18 1         3 $REF_NAME = "Getopt::LongUsage"; # package name
19              
20 1     1   5 use vars qw( $VERSION );
  1         3  
  1         119  
21 1         3359 $VERSION = '0.12';
22             }
23              
24              
25             # new
26             sub new {
27 1     1 1 598 my $pkg = shift;
28 1   33     7 my $class = ref($pkg) || $pkg;
29 1         3 my $self = bless {}, $class;
30 1         4 return $self;
31             }
32              
33              
34             sub ParseGetoptLongConfig (@) {
35 5 50 50 5 1 40 my $self = shift if ref($_[0]) eq $REF_NAME || undef;
36 5         15 my @optionlist = @_;
37 5         184 my $opctl = {};
38 5         10 my $error = '';
39              
40             # Since we are reading a configuration for Getopt::Long, we have to do some
41             # of the same things as that module, however we do not need all the same
42             # resulting information, so we will shift the unneeded stuff into oblivion
43              
44             # from Getopt::Long
45             # Check for ref HASH as first argument.
46             # First argument may be an object.
47 5 50 33     45 if ( @optionlist &&
      33        
48             ref($optionlist[0]) &&
49             UNIVERSAL::isa($optionlist[0],'HASH') ) {
50 5         8 shift @optionlist;
51             }
52              
53             # from Getopt::Long
54             # See if the first element of the optionlist contains option
55             # starter characters.
56             # Be careful not to interpret '<>' as option starters.
57             # for Getopt::LongUsage
58             # This is where Getopt::Long defined the $prefix as a regex
59 5 50 33     255 if ( @optionlist &&
      0        
      33        
60             $optionlist[0] =~ /^\W+$/ &&
61             !( $optionlist[0] eq '<>' &&
62             @optionlist > 0 &&
63             ref($optionlist[1])) ) {
64 0         0 shift (@optionlist);
65             }
66              
67             # from Getopt::Long
68             # Verify correctness of optionlist.
69 5         13 while (@optionlist) {
70 28         64 my $opt = shift (@optionlist);
71              
72 28 50       241 unless ( defined $opt ) {
73 0         0 $error .= "Undefined argument in option spec\n";
74 0         0 next;
75             }
76              
77             # from Getopt::Long
78             # Strip leading prefix so people can specify "--foo=i" if they like.
79             # $opt = $+ if $opt =~ /^$prefix+(.*)$/s;
80             # for Getopt::LongUsage
81             # We are not honoring $prefix in the configuration option list.
82             # the option list should not contain prefixes, just the option name
83             # i.e., configure with 'debug|d:i' and not '--debug|-d:i'
84             # I guess it could be added in if requested
85             # This is the globally Getopt::Long configured $prefix value
86              
87             # from Getopt::Long
88             # Parse option spec.
89 28         68 my ($name, $orig) = Getopt::Long::ParseOptionSpec($opt,$opctl);
90 28 50       1127 unless ( defined $name ) {
91             # Failed. $orig contains the error message. Sorry for the abuse.
92 0         0 $error .= $orig;
93             }
94 28 50 66     164 shift (@optionlist) if @optionlist && ref($optionlist[0]);
95             }
96 5         10 return $opctl;
97             }
98              
99              
100             # GetLongUsage
101             #
102             # TODO - add support for 'cols' property
103             #=item * cols
104             #
105             #The number of columns the output should be formatted for. Text will be wrapped
106             #around in order to stay within this column boundry.
107             #The default is C<0> (zero), which is no defined column width.
108             #
109             sub GetLongUsage (@) {
110 5 50 50 5 1 9568 my $self = shift if ref($_[0]) eq $REF_NAME || undef;
111 5         22 my %args = @_;
112 5 100       14 my %format = @{$args{'format'}} if exists $args{'format'};
  2         7  
113              
114             # Setup the description map
115             # my %descriptions = @{$args{'descriptions'}} if exists $args{'descriptions'};
116             # We have to do it this alternate way, otherwise perl will throw an error if
117             # any key is undef. We do not care if the value is undef.
118 5         182 my %descriptions;
119 5 100       14 if (exists $args{'descriptions'}) {
120 4         5 my @temp_desc = @{$args{'descriptions'}}; # make a copy
  4         14  
121 4         10 while (@temp_desc) {
122 19         22 my $k = shift @temp_desc;
123 19         21 my $v = shift @temp_desc;
124 19 50 33     73 next if ! defined $k || $k eq "";
125 19         49 $descriptions{$k} = $v;
126             }
127             }
128             #DEBUG# print Dumper {"descriptions", \%descriptions};
129              
130             # Some private methods
131             # &$elementexists()
132             # return true if a value (element) exists in a given array ref
133             my $elementexists = sub {
134 57     57   70 my $element = shift;
135 57         74 my $array = shift;
136 57         79 foreach my $a (@$array) {
137 42 100       117 return 1 if lc $element eq lc $a;
138             }
139 51         145 return 0;
140 5         217 };
141             # &$gethashvalue()
142             # return the value of a given key in a hash ref, case insensitive
143             my $gethashvalue = sub {
144 34     34   45 my $key = shift;
145 34         40 my $hash = shift;
146 34 100       96 return $hash->{$key} if exists $hash->{$key};
147 25         71 foreach my $hk (keys %$hash) {
148 57 100       125 if (lc $key eq lc $hk) {
149 9         37 return $hash->{$hk};
150             }
151             }
152 16         70 return undef;
153 5         29 };
154             # End private methods
155              
156             # These are defined constants inside Getopt::Long, as of version 2.38
157             # If these constants change inside Getopt::Long, this module will break.
158 5   50     857 my %m = ( CTL_TYPE => $Getopt::Long::CTL_TYPE || 0,
      50        
      50        
      50        
      50        
      50        
      50        
      50        
      50        
      50        
159             CTL_CNAME => $Getopt::Long::CTL_CNAME || 1,
160             CTL_DEFAULT => $Getopt::Long::CTL_DEFAULT || 2,
161             CTL_DEST => $Getopt::Long::CTL_DEST || 3,
162             CTL_DEST_SCALAR => $Getopt::Long::CTL_DEST_SCALAR || 0,
163             CTL_DEST_ARRAY => $Getopt::Long::CTL_DEST_ARRAY || 1,
164             CTL_DEST_HASH => $Getopt::Long::CTL_DEST_HASH || 2,
165             CTL_DEST_CODE => $Getopt::Long::CTL_DEST_CODE || 3,
166             CTL_AMIN => $Getopt::Long::CTL_AMIN || 4,
167             CTL_AMAX => $Getopt::Long::CTL_AMAX || 5
168             );
169              
170             # Retrieve the Getopt::Long config map resulting from parsing the options
171 5 50       15 if (! exists $args{'Getopt_Long'}) { warn "GetLongUsage(): Argument Getopt_Long is required."; return undef; }
  0         0  
  0         0  
172 5         6 my $optionmap = ParseGetoptLongConfig(@{$args{'Getopt_Long'}});
  5         195  
173             #DEBUG# print Dumper {"optionmap", $optionmap};
174              
175             # Create the map for the user preferred order of displaying the options.
176 5         7 my $ordermap = {}; # This is the map
177 5         7 my $orderindex = 0; # we'll use this one later, keeping the last value, when populating '@output'
178 5         6 my $tmp_ordernumber = 0;
179 5 100 66     27 if ((exists $args{'descriptions'}) && (ref($args{'descriptions'}) eq "ARRAY")) {
180 4         10 while ($tmp_ordernumber < scalar @{$args{'descriptions'}}) {
  23         53  
181 19 100       43 if ($elementexists->($args{'descriptions'}->[$tmp_ordernumber],$args{'hidden_opts'})) {
182 3         5 $tmp_ordernumber += 2;
183 3         4 next;
184             }
185 16 100       44 if (!exists $optionmap->{ lc $args{'descriptions'}->[$tmp_ordernumber] }) {
186 1         203 carp ("Item \"".$args{'descriptions'}->[$tmp_ordernumber]."\" in descriptions argument is not defined as an option in Getopt_Long argument.");
187             } else {
188 15 50 33     68 unless ((! defined $args{'descriptions'}->[$tmp_ordernumber]) || ($args{'descriptions'}->[$tmp_ordernumber] eq "")) {
189 15         43 $ordermap->{ $optionmap->{ lc $args{'descriptions'}->[$tmp_ordernumber] }[$m{'CTL_CNAME'}] } = $orderindex;
190             }
191 15         17 $orderindex++;
192             }
193 16         117 $tmp_ordernumber += 2;
194             }
195             #DEBUG# print Dumper {"ordermap", $ordermap};
196             }
197              
198             # Create the usage message map for the options;
199 5         10 my $usagemap = {}; # This is the map
200 5         15 foreach my $opt (keys %$optionmap) {
201 38 50 33     209 next if !defined $opt || $opt eq "";
202 38         63 my $ctlname = $optionmap->{$opt}[$m{CTL_CNAME}];
203 38 50       65 next if !defined $ctlname;
204 38 100       78 unless (exists $usagemap->{ $ctlname }) {
205 28         79 $usagemap->{ $ctlname } = [[],[]]; # [[alias1,alias2],[descline1,descline2]]
206             }
207 38 100       82 unless (lc $opt eq lc $ctlname) {
208 10         13 push (@{$usagemap->{ $ctlname }[0]},$opt)
  10         19  
209             }
210 38 100 100     43 if ((@{$usagemap->{ $ctlname }[1]} == 0) && (my $desc = $gethashvalue->($opt,\%descriptions))) {
  38         139  
211 18         40 my @lines = split("\n", $desc);
212 18         57 $usagemap->{ $ctlname }[1] = \@lines;
213             }
214             }
215             #DEBUG# print Dumper {"usagemap",$usagemap};
216              
217             # Format the text usage message for the options
218             # Getopt::Long defines 'longprefix = "(--)"' in ConfigDefaults() as the
219             # variable $Getopt::Long::longprefix, but does not define any "shortprefix"
220             # At this time, instead of trying to figure this out, we will just use the
221             # default assumed short and long prefixes.
222             # It can be changed later if I get a request to do so, with suggestions on
223             # how it can be accomplished.
224             # For now, I will allow it as a formatting option.
225 5 100       14 my $longprefix = defined $format{'longprefix'} ? $format{'longprefix'} : "--";
226 5 100       11 my $shortprefix = defined $format{'shortprefix'} ? $format{'shortprefix'} : "-";
227 5 50       11 my $cols = defined $format{'cols'} ? $format{'cols'} : 0;
228 5 100       11 my $tab = defined $format{'tab'} ? $format{'tab'} : 2;
229 5 100       32 my $indent = defined $format{'indent'} ? $format{'indent'} : 0;
230             # Short options are assumed to be those which are only a single character
231             # I do not see how options are specially identified as short in Getopt::Long
232             # as it appears that both '--h' and '-h' are acceptable input
233             # Short options go first, then the main long option, then aliases
234 5         5 my @output; # ([[opt column],[desc column]], [[],[]], etc...);
235 5         6 my $maxoptwidth = 0;
236 5         13 foreach my $optname (keys %$usagemap) {
237 28         31 my (@shortopt, $mainopt, @aliasopt);
238 28 100       59 unless ($elementexists->($optname,$args{'hidden_opts'})) {
239 25 100       43 if (length($optname) == 1) {
240 10         17 push (@shortopt, ($shortprefix . $optname));
241             } else {
242 15         20 $mainopt = ($longprefix . $optname);
243             }
244             }
245 28         40 foreach my $opt (@{$usagemap->{$optname}[0]}) {
  28         56  
246 10 50       28 next unless defined $opt;
247 10 50       22 next if lc $opt eq lc $optname;
248 10 50       16 if (length($opt) == 1) {
249 0 0       0 push (@shortopt, ($shortprefix . $opt)) unless $elementexists->($opt,$args{'hidden_opts'});
250 0         0 next;
251             }
252 10 50       26 push(@aliasopt,($longprefix . $opt)) unless $elementexists->($opt,$args{'hidden_opts'});
253             }
254 28 100 100     118 next if @shortopt == 0 && !defined $mainopt && @aliasopt == 0;
      66        
255 25         28 my @opttext;
256 25 100       52 push (@opttext, @shortopt) if @shortopt > 0;
257 25 100       50 push (@opttext, $mainopt) if defined $mainopt;
258 25 100       49 push (@opttext, @aliasopt) if @aliasopt > 0;
259 25         44 my $opttext = join(', ',@opttext);
260 25 100       51 $maxoptwidth = length($opttext) if length($opttext) > $maxoptwidth;
261             #push (@output, [ [$opttext], $usagemap->{$optname}[1] ] );
262 25         26 my $tmp_ordernumber;
263 25 100       44 if (exists $ordermap->{ $optname }) {
264 15         22 $tmp_ordernumber = $ordermap->{ $optname };
265             } else {
266 10         11 $tmp_ordernumber = $orderindex;
267 10         12 $orderindex++;
268             }
269 25         93 $output[$tmp_ordernumber] = [ [$opttext], $usagemap->{$optname}[1] ];
270             }
271             #DEBUG# print Dumper ("output",@output);
272              
273             # Assemble the usage text message
274 5         9 my @usage;
275 5 100       30 push (@usage, split("/$/",$args{'header'})) if defined $args{'header'};
276 5 100       26 push (@usage, split("/$/",$args{'cli_use'})) if defined $args{'cli_use'};
277 5         6 my $opttext;
278 5         8 foreach my $outline (@output) {
279 25         32 $opttext .= " "x$tab;
280 25 50 33     161 if ((! defined $outline) || (ref $outline !~ /ARRAY/) || (! defined $outline->[0][0])) {
      33        
281 0         0 $opttext .= " "x$maxoptwidth;
282 0         0 $opttext .= " "x$tab;
283             } else {
284 25         36 $opttext .= $outline->[0][0];
285 25         39 $opttext .= " "x($maxoptwidth - length($outline->[0][0]));
286 25         35 $opttext .= " "x$tab;
287 25         37 $opttext .= join(("\n"." "x($tab + $maxoptwidth + $tab)),@{$outline->[1]});
  25         43  
288             }
289 25         39 $opttext .= "\n";
290             }
291 5         11 chomp ($opttext);
292 5         7 push (@usage, $opttext);
293 5 100       21 push (@usage, split("/$/",$args{'footer'})) if defined $args{'footer'};
294 5         8 my $usage;
295 5         8 foreach my $line (@usage) {
296 14         25 $usage .= " "x$indent;
297 14         30 $line =~ s/\n/("\n"." "x$indent)/eg;
  20         49  
298 14         17 $usage .= $line;
299 14         23 $usage .= "\n";
300             }
301              
302             # Return the usage text message to the caller
303 5         108 return $usage;
304             }
305              
306              
307             1;
308             __END__