File Coverage

blib/lib/Zoidberg/Utils/GetOpt.pm
Criterion Covered Total %
statement 118 158 74.6
branch 85 130 65.3
condition 25 48 52.0
subroutine 7 10 70.0
pod 4 5 80.0
total 239 351 68.0


line stmt bran cond sub pod time code
1             package Zoidberg::Utils::GetOpt;
2              
3             our $VERSION = '0.981';
4              
5 18     18   2467 use strict;
  18         53  
  18         766  
6 18     18   695 use Zoidberg::Utils::Error qw/error bug/;
  18         23  
  18         264  
7 18     18   3210 use Zoidberg::Utils::Output qw/output debug/;
  18         52  
  18         162  
8             use Exporter::Tidy
9 18         172 default => ['getopt'],
10 18     18   1947 other => [qw/help usage version path2hashref/] ;
  18         36  
11              
12             our $ERROR_CALLER = 1;
13              
14             sub getopt { # hic sunt leones
15 87     87 1 45868 my ($conf, @args) = @_;
16 87         226 my (%conf, @opts, %opts, $args);
17 87 100       456 if (ref $conf) {
18 1         6 %conf = %$conf;
19 1         7 goto PARSE_OPTS;
20             }
21              
22             # parse config
23 86 100       2707 $conf{_args} = $1 if $conf =~ s/(?
24 86 100 100     1565 goto PARSE_ARGS unless $conf and $args[0] =~ /^[+-]/;
25 15         81 for (split /\s+/, $conf) {
26 53 100       191 my $arg = s/([\$\@\%])$// ? $1 : 0;
27 53         187 my ($opt, @al) = split ',', $_;
28 53 100       137 unless ($opt =~ s/\*$//) {
29 51         169 $conf{$opt} = $arg;
30 51         300 $conf{_alias}{$_} = $opt for @al;
31             }
32             else {
33 2 50 33     12 error 'config syntax error' if @al || ! length $_;
34 2         6 $conf{$opt} = $arg;
35 2   100     8 $conf{_glob} ||= [];
36 2         3 push @{$conf{_glob}}, $opt;
  2         9  
37             }
38             }
39 15 100       54 $conf{_glob} = '^('.join('|', map {s/^\+/\\+/; $_} @{$conf{_glob}}).')(?!-)' if $conf{_glob};
  2         6  
  2         7  
  1         5  
40             #use Data::Dumper; print STDERR 'conf: ', Dumper \%conf;
41              
42             PARSE_OPTS:
43 16         121 for ( # set default options
44             [qw/h help/, \&help ],
45             [qw/u usage/, \&usage ],
46             [qw/v version/, \&version]
47             ) {
48 48 50       119 next if exists $conf{$$_[1]};
49 48         114 $conf{$$_[1]} = $$_[2];
50 48 100 66     372 $conf{_alias}{'-'.$$_[0]} = $$_[1]
51             unless exists $conf{_alias}{'-'.$$_[0]} or exists $conf{_alias}{$$_[0]};
52             }
53              
54 16         51 my $delim = 0;
55 16         42 while (@args) { # parse opts
56 34 100       125 last unless $args[0] =~ /^(-|\+.)/;
57 28         52 $_ = shift @args;
58 28         103 /^(--|-|\+)(.*)/;
59 28 100 50     85 ++$delim and last unless length $2;
60 27         102 my ($pre, $opt, $arg) = ($1, split '=', $2, 2);
61              
62 27         43 my (@chars, $type);
63 27         47 my $raw = $pre.$opt;
64 27 100 100     295 if (exists $conf{_alias}{$raw} or exists $conf{$raw}) { $opt = $raw }
  10 100 66     26  
    100 100        
65             elsif (exists $conf{_glob} and $raw =~ /$conf{_glob}/) {
66 3         4 $opt = $raw;
67 3         5 $type = $conf{$1};
68             }
69             elsif ($pre ne '--' and length $opt > 1) { # try short options
70 3         17 @chars = split '', $opt;
71             }
72              
73             PARSE_OPT:
74 30 100       68 $opt = shift @chars if @chars;
75 30 100       91 $opt = $conf{_alias}{$opt} if exists $conf{_alias}{$opt};
76            
77 30 100       60 unless (defined $type) { # type is set if glob
78 27 100       104 if (exists $conf{$opt}) { $type = $conf{$opt} }
  26         43  
79 1         9 else { error "unrecognized option '$opt'" }
80             }
81              
82 29         52 push @opts, $opt;
83 29 100       82 if (! $type) { # no arg
    50          
84 18 50       34 error "option '$opt' doesn't take an argument" if defined $arg;
85 18 100       53 $opts{$opt} = ($pre eq '+') ? 0 : 1;
86             }
87             elsif (ref $type) { # CODE ... for default opts
88 0         0 output $type->( (caller(1))[3], (caller)[0] ); # subroutine, package
89 0         0 error {silent => 1, exit_status => 0}, 'getopt needed to pop stack';
90             }
91             else {
92 11 100       36 $arg = defined($arg) ? $arg : shift(@args);
93 11 50       23 error "option '$opt' requires an argument" unless defined $arg;
94 11 100       66 if ($type eq '$') { $opts{$opt} = $arg }
  4 50       10  
95             elsif ($type eq '@') {
96 7 100       20 if (ref $arg) {
97 1 50       5 error 'argument is not a ARRAY reference'
98             if ref($arg) ne 'ARRAY';
99 1 50       4 if ($opts{$opt}) { push @{$opts{$opt}}, @$arg }
  0         0  
  0         0  
100 1         3 else { $opts{$opt} = $arg }
101             }
102             else {
103 6   100     31 $opts{$opt} ||= [];
104 6         11 push @{$opts{$opt}}, $arg;
  6         19  
105             }
106             }
107             }
108 29         38 $arg = $type = undef;
109 29 100       102 goto PARSE_OPT if @chars;
110             };
111 15 50 100     104 error @opts
    100          
112             ? "option '$opts[-1]' doesn't take an argument"
113             : 'options found after first argument' if !$delim and grep /^-/, @args;
114 14 50       63 $opts{_opts} = \@opts if @opts; # keep %opts empty unless there are opts
115              
116             PARSE_ARGS: # parse args
117 85 100       616 unless ($conf{_args}) { $args = [@args] }
  9 100       17  
    100          
    50          
118             elsif ($conf{_args} eq '@') {
119 35 50       247 error "argument should be a ARRAY reference"
120 34 50       81 if grep {ref($_) and ref($_) ne 'ARRAY'} @args;
121 34 50 33     276 if (ref $args[0] and @args == 1) { $args = $args[0] }
  0         0  
122 34 50       76 else { $args = [ map {ref($_) ? @$_ : $_} @args] }
  35         212  
123             }
124             elsif ($conf{_args} eq '%') {
125 22 100       259 error "argument should be a HASH reference"
126 21 50       58 if grep {ref($_) and ref($_) ne 'HASH'} @args;
127 21 100 66     164 if (ref $args[0] and @args == 1) { $args = $args[0] }
  19         41  
128             else {
129 2         13 my $error;
130             $args = { map {
131 2 50       10 if (ref $_) { (%$_) }
  3         15  
  0         0  
132             else {
133 3 50       35 m/(.*?)=(.*)/ or $error++;
134 3         29 ($1 => $2)
135             }
136             } @args };
137 2 50       19 error 'syntax error, should be \'key=value\'' if $error;
138             }
139             }
140             elsif ($conf{_args} eq '*') {
141 21         46 my (@keys, %vals);
142 21         88 for (@args) {
143 28 100       222 if (ref $_) {
    100          
144 2         4 my $t = ref $_;
145 2 100       9 if ($t eq 'ARRAY') { push @keys, @$_ }
  1 50       3  
146             elsif ($t eq 'HASH') {
147 1         4 push @keys, keys %$_;
148 1         8 %vals = (%vals, %$_);
149             }
150 0         0 else { error "can't handle $t reference argument" }
151             }
152             elsif (m/(.*?)=(.*)/) {
153 12         51 push @keys, $1;
154 12         70 $vals{$1} = $2;
155             }
156 14         40 else { push @keys, $_ }
157             }
158 21         206 return \%opts, \@keys, \%vals;
159             }
160              
161 64         613 return \%opts, $args;
162             }
163              
164             sub usage {
165 0     0 1 0 $_[2] = 1;
166 0         0 goto &help;
167             }
168              
169             sub help {
170 0     0 0 0 my ($cmd, $file, $usage) = @_;
171              
172 0   0     0 $cmd ||= (caller(1))[3];
173 0 0       0 $file = $1 if $cmd =~ s/(.*):://;
174 0         0 $file =~ s/::/\//g;
175 0         0 $file =~ s/\.pm$//;
176 0 0       0 ($file) = grep {-e $_} map {("$_/$file.pod", "$_/$file.pm")} @INC
  0         0  
  0         0  
177             unless $file =~ m#^/#;
178              
179 0   0     0 open POD, $file || error "Could not read $file";
180 0         0 my ($help, $p, $o) = ('', 0, 0);
181 0         0 while () {
182 0 0       0 if ($p) {
    0          
183 0 0       0 if (/^=over/) { $o++ }
  0 0       0  
    0          
184 0         0 elsif (/^=back/) { $o-- }
185             elsif (/^=(item(?!\s+$cmd)|back|cut)/) {
186 0 0       0 last unless $o;
187             }
188 0 0 0     0 $help .= $_ unless $usage and ! $o and ! /^=item\s+$cmd/;
      0        
189             # only return 'item' lines if short format
190             }
191             elsif (/^=item\s+$cmd/) {
192 0         0 $p = 1;
193 0         0 $help = $_;
194             }
195             }
196 0         0 close POD;
197              
198 0         0 $help =~ s/^\s+|\s+$//g;
199 0 0       0 if ($usage) {
200 0         0 $help =~ s/^=item\s+/ /gm;
201 0         0 $help = "usage:\n".$help;
202             }
203 0         0 else { $help =~ s/^=\w+\s+/= /gm }
204 0         0 $help =~ s/(\w)<<(.*?)>>|\w<(.*?)>/
205 0 0       0 ($1 eq 'B') ? "\"$2$3\"" :
    0          
206             ($1 eq 'C') ? "`$2$3`" : "'$2$3'"
207             /ge;
208 0         0 return $help;
209             }
210              
211             sub version {
212 0     0 1 0 my (undef, $class) = @_;
213 18     18   43230 no strict 'refs';
  18         51  
  18         4127  
214 0   0     0 return ${$class.'::LONG_VERSION'} || $class.' '.${$class.'::VERSION'};
215             }
216              
217             sub path2hashref {
218 10     10 1 34 my ($hash, $key) = @_;
219 10         38 my $path = '/';
220 10         111 while ($key =~ s#^/*(.+?)/##) {
221 10         51 $path .= $1 . '/';
222 10 100       93 if (! defined $$hash{$1}) { $$hash{$1} = {} }
  2 50       19  
223 0         0 elsif (ref($$hash{$1}) ne 'HASH') { return undef, undef, $path } # bail out
224 10         71 $hash = $$hash{$1};
225             }
226 10         63 return $hash, $key, $path;
227             }
228              
229             1;
230              
231             __END__