File Coverage

blib/lib/PERLANCAR/Module/List.pm
Criterion Covered Total %
statement 82 107 76.6
branch 51 94 54.2
condition 41 62 66.1
subroutine 4 4 100.0
pod 0 2 0.0
total 178 269 66.1


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