File Coverage

blib/lib/Module/List/Wildcard.pm
Criterion Covered Total %
statement 88 111 79.2
branch 52 84 61.9
condition 40 59 67.8
subroutine 5 5 100.0
pod 0 2 0.0
total 185 261 70.8


line stmt bran cond sub pod time code
1             package Module::List::Wildcard;
2              
3             our $DATE = '2020-12-22'; # DATE
4             our $VERSION = '0.004007'; # VERSION
5              
6             #IFUNBUILT
7             # # use strict 'subs', 'vars';
8             # # use warnings;
9             #END IFUNBUILT
10              
11             my $has_globstar;
12              
13             # do our own exporting to start faster
14             sub import {
15 1     1   8 my $pkg = shift;
16 1         2 my $caller = caller;
17 1         2 for my $sym (@_) {
18 1 50       4 if ($sym eq 'list_modules') { *{"$caller\::$sym"} = \&{$sym} }
  1         1  
  1         69735  
  1         3  
19 0         0 else { die "$sym is not exported!" }
20             }
21             }
22              
23             sub list_modules($$) {
24 12     12 0 20424 my($prefix, $options) = @_;
25 12         22 my $trivial_syntax = $options->{trivial_syntax};
26 12         20 my($root_leaf_rx, $root_notleaf_rx);
27 12         0 my($notroot_leaf_rx, $notroot_notleaf_rx);
28 12 50       23 if($trivial_syntax) {
29 0         0 $root_leaf_rx = $notroot_leaf_rx = qr#:?(?:[^/:]+:)*[^/:]+:?#;
30 0         0 $root_notleaf_rx = $notroot_notleaf_rx =
31             qr#:?(?:[^/:]+:)*[^/:]+#;
32             } else {
33 12         37 $root_leaf_rx = $root_notleaf_rx = qr/[a-zA-Z_][0-9a-zA-Z_]*/;
34 12         23 $notroot_leaf_rx = $notroot_notleaf_rx = qr/[0-9a-zA-Z_]+/;
35             }
36              
37 12         15 my $recurse = $options->{recurse};
38              
39             # filter by wildcard. we cannot do this sooner because wildcard can be put
40             # at the end or at the beginning (e.g. '*::Path') so we still need
41 12         13 my $re_wildcard;
42 12 100       25 if ($options->{wildcard}) {
43 8         36 require String::Wildcard::Bash;
44 8         12 my $orig_prefix = $prefix;
45 8         19 my @prefix_parts = split /::/, $prefix;
46 8         13 $prefix = "";
47 8         9 my $has_wildcard;
48 8         17 while (defined(my $part = shift @prefix_parts)) {
49 8 50       19 if (String::Wildcard::Bash::contains_wildcard($part)) {
50 8         215 $has_wildcard++;
51             # XXX limit recurse level to scalar(@prefix_parts), or -1 if has_globstar
52 8 100       16 $recurse = 1 if @prefix_parts;
53 8         23 last;
54             } else {
55 0         0 $prefix .= "$part\::";
56             }
57             }
58 8 50       14 if ($has_wildcard) {
59 8         15 $re_wildcard = convert_wildcard_to_re($orig_prefix);
60             }
61 8 100       19 $recurse = 1 if $has_globstar;
62             }
63              
64 12 50 33     113 die "bad module name prefix `$prefix'"
65             unless $prefix =~ /\A(?:${root_notleaf_rx}::
66             (?:${notroot_notleaf_rx}::)*)?\z/x &&
67             $prefix !~ /(?:\A|[^:]::)\.\.?::/;
68              
69 12         21 my $list_modules = $options->{list_modules};
70 12         16 my $list_prefixes = $options->{list_prefixes};
71 12         17 my $list_pod = $options->{list_pod};
72 12         14 my $use_pod_dir = $options->{use_pod_dir};
73 12 50 66     31 return {} unless $list_modules || $list_prefixes || $list_pod;
      33        
74 12         14 my $return_path = $options->{return_path};
75 12         22 my @prefixes = ($prefix);
76 12         16 my %seen_prefixes;
77             my %results;
78 12         21 while(@prefixes) {
79 40         83 my $prefix = pop(@prefixes);
80 40         95 my @dir_suffix = split(/::/, $prefix);
81 40 100       84 my $module_rx =
82             $prefix eq "" ? $root_leaf_rx : $notroot_leaf_rx;
83 40         395 my $pm_rx = qr/\A($module_rx)\.pmc?\z/;
84 40         337 my $pod_rx = qr/\A($module_rx)\.pod\z/;
85 40 100       77 my $dir_rx =
86             $prefix eq "" ? $root_notleaf_rx : $notroot_notleaf_rx;
87 40         211 $dir_rx = qr/\A$dir_rx\z/;
88 40         83 foreach my $incdir (@INC) {
89 80         194 my $dir = join("/", $incdir, @dir_suffix);
90 80 100       1584 opendir(my $dh, $dir) or next;
91 52         690 while(defined(my $entry = readdir($dh))) {
92 211 100 100     2299 if(($list_modules && $entry =~ $pm_rx) ||
    100 33        
      66        
      100        
      100        
      100        
      100        
      66        
93             ($list_pod &&
94             $entry =~ $pod_rx)) {
95 48         143 my $key = $prefix.$1;
96 48 100 100     312 next if $re_wildcard && $key !~ $re_wildcard;
97             $results{$key} = $return_path ? "$dir/$entry" : undef
98 21 50 66     124 if $return_path || !exists($results{$key});
    100          
99             } elsif(($list_prefixes || $recurse) &&
100             ($entry ne '.' && $entry ne '..') &&
101             $entry =~ $dir_rx &&
102             -d join("/", $dir,
103             $entry)) {
104 37         101 my $newmod = $prefix.$entry;
105 37         53 my $newpfx = $newmod."::";
106 37 50       72 next if exists $seen_prefixes{$newpfx};
107             $results{$newpfx} = $return_path ? "$dir/$entry/" : undef
108 37 50 33     140 if ($return_path || !exists($results{$newpfx})) && $list_prefixes;
    100 66        
109 37 100       137 push @prefixes, $newpfx if $recurse;
110             }
111             }
112 52 50 33     659 next unless $list_pod && $use_pod_dir;
113 0         0 $dir = join("/", $dir, "pod");
114 0 0       0 opendir($dh, $dir) or next;
115 0         0 while(defined(my $entry = readdir($dh))) {
116 0 0       0 if($entry =~ $pod_rx) {
117 0         0 my $key = $prefix.$1;
118 0 0 0     0 next if $re_wildcard && $key !~ $re_wildcard;
119 0 0       0 $results{$key} = $return_path ? "$dir/$entry" : undef;
120             }
121             }
122             }
123             }
124              
125             # we cannot filter prefixes early with wildcard because we need to dig down
126             # first and that would've been prevented if we had a wildcard like *::Foo.
127 12 100 100     37 if ($list_prefixes && $re_wildcard) {
128 2         7 for my $k (keys %results) {
129 6 50       18 next unless $k =~ /::\z/;
130 6         16 (my $k_nocolon = $k) =~ s/::\z//;
131 6 100 100     47 delete $results{$k} unless $k =~ $re_wildcard || $k_nocolon =~ $re_wildcard;
132             }
133             }
134              
135 12         64 return \%results;
136             }
137              
138             sub convert_wildcard_to_re {
139 8     8 0 9 $has_globstar = 0;
140 8         17 my $re = _convert_wildcard_to_re(@_);
141 8         147 $re = qr/\A$re\z/;
142             #print "DEBUG: has_globstar=<$has_globstar>, re=$re\n";
143 8         20 $re;
144             }
145              
146             # modified from String::Wildcard::Bash 0.040's convert_wildcard_to_re
147             sub _convert_wildcard_to_re {
148 8 50   8   16 my $opts = ref $_[0] eq 'HASH' ? shift : {};
149 8         10 my $str = shift;
150              
151 8   50     23 my $opt_brace = $opts->{brace} // 1;
152              
153 8         12 my @res;
154             my $p;
155 8         45 while ($str =~ /$String::Wildcard::Bash::RE_WILDCARD_BASH/g) {
156 1     1   88252 my %m = %+;
  1         330  
  1         289  
  25         149  
157 25 50       101 if (defined($p = $m{bash_brace_content})) {
    100          
    50          
    100          
    50          
    50          
158             push @res, quotemeta($m{slashes_before_bash_brace}) if
159 0 0       0 $m{slashes_before_bash_brace};
160 0 0       0 if ($opt_brace) {
161 0         0 my @elems;
162 0         0 while ($p =~ /($String::Wildcard::Bash::re_bash_brace_element)(,|\z)/g) {
163 0         0 push @elems, $1;
164 0 0       0 last unless $2;
165             }
166             #use DD; dd \@elems;
167             push @res, "(?:", join("|", map {
168 0         0 convert_wildcard_to_re({
  0         0  
169             bash_brace => 0,
170             }, $_)} @elems), ")";
171             } else {
172 0         0 push @res, quotemeta($m{bash_brace});
173             }
174              
175             } elsif (defined($p = $m{bash_joker})) {
176 10 50       23 if ($p eq '?') {
    100          
    50          
177 0         0 push @res, '[^:]';
178             } elsif ($p eq '*') {
179 9         44 push @res, '[^:]*';
180             } elsif ($p eq '**') {
181 1         2 $has_globstar++;
182 1         6 push @res, '.*';
183             }
184              
185             } elsif (defined($p = $m{literal_brace_single_element})) {
186 0         0 push @res, quotemeta($p);
187             } elsif (defined($p = $m{bash_class})) {
188             # XXX no need to escape some characters?
189 4         34 push @res, $p;
190             } elsif (defined($p = $m{sql_joker})) {
191 0         0 push @res, quotemeta($p);
192             } elsif (defined($p = $m{literal})) {
193 11         55 push @res, quotemeta($p);
194             }
195             }
196              
197 8         27 join "", @res;
198             }
199              
200             1;
201             # ABSTRACT: A fork of Module::List that groks wildcard
202              
203             __END__