File Coverage

bin/generate_wrap_config.pl
Criterion Covered Total %
statement 118 118 100.0
branch 26 32 81.2
condition 8 10 80.0
subroutine 21 21 100.0
pod n/a
total 173 181 95.5


line stmt bran cond sub pod time code
1             #!/usr/bin/env perl
2             package OpenTracing::WrapScope::ConfigGenerator;
3 2     2   1041 use strict;
  2         5  
  2         66  
4 2     2   11 use warnings;
  2         2  
  2         64  
5 2     2   1108 use autodie;
  2         33114  
  2         11  
6 2     2   14147 use feature qw/say state/;
  2         13  
  2         254  
7 2     2   24 use Carp qw/croak/;
  2         3  
  2         162  
8 2     2   2003 use Getopt::Long qw/GetOptionsFromArray/;
  2         21981  
  2         14  
9 2     2   1819 use IO::File;
  2         10439  
  2         279  
10 2     2   1253 use List::MoreUtils qw/notall/;
  2         26452  
  2         20  
11 2     2   2288 use List::Util qw/uniq/;
  2         6  
  2         212  
12 2     2   1186 use PPI;
  2         221486  
  2         115  
13 2     2   1193 use Perl::Critic::Utils::McCabe qw/calculate_mccabe_of_sub/;
  2         84176  
  2         42  
14 2     2   1697 use Pod::Usage qw/pod2usage/;
  2         112749  
  2         355  
15 2     2   584 use YAML::XS;
  2         3002  
  2         2845  
16              
17             exit run(@ARGV) unless caller;
18              
19             sub run {
20 8 50   8   61407 GetOptionsFromArray(\@_,
21             'spec=s' => \my $spec_file,
22             'file=s' => \my @files,
23             'ignore=s' => \my @ignore,
24             'include=s' => \my @include,
25             'exclude=s' => \my @exclude,
26             'filter=s' => \my @filters,
27             'out=s' => \my $output_file,
28             'help' => \my $help,
29             ) or pod2usage (
30             -verbose => 1,
31             -noperldoc => 1,
32             -msg => 'Invalid options',
33             );
34 8 50       7584 pod2usage -verbose => 1, -noperldoc => 1 if $help;
35              
36 8         15 my %args;
37 8 50       22 %args = %{ YAML::XS::LoadFile($spec_file) } if defined $spec_file;
  8         27  
38 8         1221 push @{ $args{files} }, @files;
  8         26  
39 8         14 push @{ $args{ignore} }, @ignore;
  8         25  
40 8         18 push @{ $args{include} }, @include;
  8         22  
41 8         17 push @{ $args{exclude} }, @exclude;
  8         19  
42 8         17 push @{ $args{filters} }, @filters;
  8         14  
43              
44 8         33 my @subs = OpenTracing::WrapScope::ConfigGenerator::examine_files(%args);
45              
46 8 50       82 open my $fh_out, '>', $output_file if $output_file;
47 8   50     4925 $fh_out //= \*STDOUT;
48              
49 8         29 say {$fh_out} $_ foreach @subs;
  56         174  
50              
51 8         1279 return 0;
52             }
53              
54             sub _generate_filters {
55 16     16   38 my ($filter_specs) = @_;
56 16 100       41 return if not $filter_specs;
57              
58             state $GENERATORS = {
59             exclude_private => sub {
60             return sub {
61 18         34 my ($sub) = @_;
62 18         50 return $sub->name !~ /(?:\A|::)_\w+\z/;
63 2     2   13 };
64             },
65             complexity => sub {
66 2     2   5 my ($threshold) = @_;
67 2 50       8 croak 'No arguments for complexity filter' if not $threshold;
68             return sub {
69 12         24 my ($sub) = @_;
70 12         48 return calculate_mccabe_of_sub($sub) >= $threshold;
71 2         14 };
72             },
73 10         28 };
74              
75 10         14 my @filters;
76 10         37 foreach (@$filter_specs) {
77 4         21 my ($filter, $arg) = split /=/;
78 4 50       17 my $generator = $GENERATORS->{$filter} or croak "No such filter: $_";
79 4         11 push @filters, $generator->($arg);
80             }
81 10         22 return \@filters;
82             }
83              
84             sub examine_files {
85 16     16   49855 my %args = @_;
86 16   50     70 my $files_base = $args{files} // [];
87 16   100     88 my $files_ignore = $args{ignore} // [];
88 16   100     57 my $subs_include = $args{include} // [];
89 16   100     50 my $subs_exclude = $args{exclude} // [];
90 16         64 my $filters = _generate_filters($args{filters});
91              
92 16         40 my @files = map { glob } @$files_base;
  30         3987  
93 16         71 my %file_ignored = map { $_ => undef } map { glob } @$files_ignore;
  8         28  
  4         228  
94 16         36 my %sub_excluded = map { $_ => undef } @$subs_exclude;
  4         11  
95              
96 16         43 my @subs = @$subs_include;
97 16         41 foreach my $file (@files) {
98 78 100       485 next if exists $file_ignored{$file};
99              
100 70         161 foreach my $sub (list_subs($file, $filters)) {
101 126 100       23452 next if exists $sub_excluded{$sub};
102 122         250 push @subs, $sub;
103             }
104             }
105 16         283 return uniq @subs;
106             }
107              
108             sub list_subs {
109 75     75   18295 my ($filename, $filters) = @_;
110              
111 75         376 my $doc = PPI::Document->new($filename);
112 75         588057 my $subs = $doc->find('PPI::Statement::Sub');
113 75 100       129353 return if not $subs;
114              
115 63         117 my @subs;
116 63         217 foreach my $sub (@$subs) {
117 162 100   30   6355 next if notall { $_->($sub) } @$filters;
  30         64  
118              
119 150 100       18947 if ($sub->name =~ /'|::/) { # qualified
120 2         49 push @subs, $sub->name;
121             }
122             else {
123 148         3870 my $pkg = _detect_package($sub);
124 148 100       642 $pkg = $pkg ? $pkg->namespace : 'main';
125 148         3392 push @subs, $pkg . '::' . $sub->name;
126             }
127             }
128 63         4971 return @subs;
129             }
130              
131             sub _detect_package {
132 176     176   363 my ($elem) = @_;
133 176 100       499 return unless $elem;
134 168 100       568 return $elem if $elem->isa('PPI::Statement::Package');
135              
136 162         292 my $prev = $elem;
137 162         506 while ($prev = $prev->sprevious_sibling) {
138 258 100       7717 return $prev if $prev->isa('PPI::Statement::Package');
139             }
140 28         502 return _detect_package($elem->parent);
141             }
142              
143             1;
144             __END__
145             =pod
146              
147             =head1 NAME
148              
149             generate_wrap_config.pl - generate input for OpenTracing::WrapScope
150              
151             =head1 SYNOPSIS
152              
153             generate_wrap_config.pl --out wrapscope.conf --file 'lib/*.pm'
154             generate_wrap_config.pl --file 'bin/*.pl' --filter complexity=5
155              
156             =head1 OPTIONS
157              
158             =head2 --file $file_pattern
159              
160             Shell file pattern for files to include in the search.
161             Can be specified multiple times.
162              
163             =head2 --ignore $file_pattern
164              
165             Shell file pattern for files to ignore.
166             Can be specified multiple times.
167              
168             =head2 --include $subroutine
169              
170             A subroutine name to unconditionally include in the results.
171             Overrides all other settings.
172             Can be specified multiple times.
173              
174             =head2 --exclude $subroutine
175              
176             A subroutine name to remove from the results.
177             Can be specified multiple times.
178              
179             =head2 --filter $filter_name[=$filter_argument]
180              
181             A filter to apply. Currently supported filters:
182              
183             =over 4
184              
185             =item * exclude_private
186              
187             Removes all subroutines starting with an underscore.
188              
189             =item * complexity=INT
190              
191             Narrows down the results to subroutines with a
192             L<cyclomatic complexity score|https://en.wikipedia.org/wiki/Cyclomatic_complexity>
193             greater than or equal than the argument. The argument is required.
194              
195             =back
196              
197             Can be specified multiple times.
198              
199             =head2 --out $filename
200              
201             The filename where the resulting config file should be written.
202             If this argument is not specified,
203             results will be printed to standard output.
204              
205             =head2 --spec $filename
206              
207             The filename of a YAML file which contains options for this program in hash form.
208             The keys correspond directly to options, most options can be specified multiple
209             times and their values should be arrays in the YAML file.
210              
211             For example, given this file:
212              
213             wrapscope_gen_config.yaml
214              
215             file: [ 'bin/*.pl', 'lib/*.pm' ]
216             filter: [ 'exclude_private' ]
217             ignore: [ 'Private*' ]
218             out: wrapscope_config.txt
219              
220             calling:
221              
222             generate_wrap_config.pl --spec wrapscope_gen_config.yaml
223              
224             is equivalent to:
225              
226             generate_wrap_config.pl \
227             --file 'bin/*.pl' \
228             --file 'lib/*.pm' \
229             --filter exclude_private \
230             --out wrapscope_config.txt \
231             --ignore 'Private*'
232              
233             if other options are specified alongside a spec file, they will be merged.
234              
235             =head2 --help
236              
237             Show this help.
238              
239             =cut