File Coverage

bin/generate_wrap_config.pl
Criterion Covered Total %
statement 119 119 100.0
branch 25 32 78.1
condition 8 10 80.0
subroutine 21 21 100.0
pod n/a
total 173 182 95.0


line stmt bran cond sub pod time code
1             #!/usr/bin/env perl
2             package OpenTracing::WrapScope::ConfigGenerator;
3 2     2   702 use strict;
  2         4  
  2         47  
4 2     2   8 use warnings;
  2         3  
  2         39  
5 2     2   871 use autodie;
  2         25178  
  2         7  
6 2     2   11124 use feature qw/say state/;
  2         2  
  2         196  
7 2     2   12 use Carp qw/croak/;
  2         3  
  2         101  
8 2     2   1276 use Getopt::Long qw/GetOptionsFromArray/;
  2         17241  
  2         6  
9 2     2   1067 use IO::File;
  2         7922  
  2         208  
10 2     2   934 use List::MoreUtils qw/notall/;
  2         20770  
  2         16  
11 2     2   1753 use List::Util qw/uniq/;
  2         4  
  2         140  
12 2     2   888 use PPI;
  2         173390  
  2         83  
13 2     2   867 use Perl::Critic::Utils::McCabe qw/calculate_mccabe_of_sub/;
  2         66237  
  2         32  
14 2     2   1071 use Pod::Usage qw/pod2usage/;
  2         88851  
  2         216  
15 2     2   401 use YAML::XS;
  2         2313  
  2         2215  
16              
17             exit run(@ARGV) unless caller;
18              
19             sub run {
20 8 50   8   49943 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       6269 pod2usage -verbose => 1, -noperldoc => 1 if $help;
35              
36 8         10 my %args;
37 8 50       17 %args = %{ YAML::XS::LoadFile($spec_file) } if defined $spec_file;
  8         26  
38 8         962 push @{ $args{files} }, @files;
  8         24  
39 8         12 push @{ $args{ignore} }, @ignore;
  8         18  
40 8         8 push @{ $args{include} }, @include;
  8         17  
41 8         11 push @{ $args{exclude} }, @exclude;
  8         17  
42 8         8 push @{ $args{filters} }, @filters;
  8         18  
43              
44 8         22 my @subs = OpenTracing::WrapScope::ConfigGenerator::examine_files(%args);
45              
46 8 50       57 open my $fh_out, '>', $output_file if $output_file;
47 8   50     3869 $fh_out //= \*STDOUT;
48              
49 8         23 say {$fh_out} $_ foreach @subs;
  56         139  
50              
51 8         1091 return 0;
52             }
53              
54             sub _generate_filters {
55 16     16   28 my ($filter_specs) = @_;
56 16 100       35 return if not $filter_specs;
57              
58             state $GENERATORS = {
59             exclude_private => sub {
60             return sub {
61 18         23 my ($sub) = @_;
62 18         40 return $sub->name !~ /(?:\A|::)_\w+\z/;
63 2     2   11 };
64             },
65             complexity => sub {
66 2     2   4 my ($threshold) = @_;
67 2 50       5 croak 'No arguments for complexity filter' if not $threshold;
68             return sub {
69 12         18 my ($sub) = @_;
70 12         30 return calculate_mccabe_of_sub($sub) >= $threshold;
71 2         11 };
72             },
73 10         22 };
74              
75 10         13 my @filters;
76 10         38 foreach (@$filter_specs) {
77 4         17 my ($filter, $arg) = split /=/;
78 4 50       15 my $generator = $GENERATORS->{$filter} or croak "No such filter: $_";
79 4         11 push @filters, $generator->($arg);
80             }
81 10         21 return \@filters;
82             }
83              
84             sub examine_files {
85 16     16   40228 my %args = @_;
86 16   50     55 my $files_base = $args{files} // [];
87 16   100     58 my $files_ignore = $args{ignore} // [];
88 16   100     46 my $subs_include = $args{include} // [];
89 16   100     39 my $subs_exclude = $args{exclude} // [];
90 16         48 my $filters = _generate_filters($args{filters});
91              
92 16         42 my @files = grep { -f } map { glob } @$files_base;
  78         834  
  30         3053  
93 16         53 my %file_ignored = map { $_ => undef } map { glob } @$files_ignore;
  8         24  
  4         194  
94 16         35 my %sub_excluded = map { $_ => undef } @$subs_exclude;
  4         11  
95              
96 16         35 my @subs = @$subs_include;
97 16         26 foreach my $file (@files) {
98 66 100       148 next if exists $file_ignored{$file};
99              
100 58         136 foreach my $sub (list_subs($file, $filters)) {
101 126 100       19599 next if exists $sub_excluded{$sub};
102 122         212 push @subs, $sub;
103             }
104             }
105 16         235 return uniq @subs;
106             }
107              
108             sub list_subs {
109 63     63   13847 my ($filename, $filters) = @_;
110              
111 63         245 my $doc = PPI::Document->new($filename);
112 63         464111 my $subs = $doc->find('PPI::Statement::Sub');
113 63 50       101193 return if not $subs;
114              
115 63         103 my @subs;
116 63         122 foreach my $sub (@$subs) {
117 162 100   30   4930 next if notall { $_->($sub) } @$filters;
  30         48  
118              
119 150 100       15156 if ($sub->name =~ /'|::/) { # qualified
120 2         38 push @subs, $sub->name;
121             }
122             else {
123 148         3013 my $pkg = _detect_package($sub);
124 148 100       441 $pkg = $pkg ? $pkg->namespace : 'main';
125 148         2684 push @subs, $pkg . '::' . $sub->name;
126             }
127             }
128 63         4043 return @subs;
129             }
130              
131             sub _detect_package {
132 176     176   285 my ($elem) = @_;
133 176 100       360 return unless $elem;
134 168 100       448 return $elem if $elem->isa('PPI::Statement::Package');
135              
136 162         166 my $prev = $elem;
137 162         355 while ($prev = $prev->sprevious_sibling) {
138 258 100       5960 return $prev if $prev->isa('PPI::Statement::Package');
139             }
140 28         413 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