File Coverage

blib/lib/App/iperlmoddir/Utils.pm
Criterion Covered Total %
statement 131 131 100.0
branch 25 32 78.1
condition 1 3 33.3
subroutine 22 22 100.0
pod 0 2 0.0
total 179 190 94.2


line stmt bran cond sub pod time code
1             package App::iperlmoddir::Utils;
2             $App::iperlmoddir::Utils::VERSION = '1.0';
3 1     1   80696 use strict;
  1         3  
  1         30  
4 1     1   5 use warnings;
  1         2  
  1         28  
5 1     1   7 use feature 'say';
  1         2  
  1         139  
6              
7 1     1   544 use Module::Info;
  1         6999  
  1         44  
8 1     1   588 use Module::Metadata;
  1         6045  
  1         45  
9              
10             # use Module::Load;
11 1     1   498 use Module::Util qw(module_path);
  1         3091  
  1         71  
12              
13 1     1   8 use File::Basename;
  1         2  
  1         99  
14 1     1   913 use List::Compare;
  1         21265  
  1         50  
15 1     1   8 use List::Util qw(max uniq);
  1         2  
  1         115  
16 1     1   603 use List::MoreUtils qw(each_arrayref);
  1         12904  
  1         6  
17 1     1   1560 use Package::Constants;
  1         2184  
  1         29  
18 1     1   7 use Carp;
  1         3  
  1         61  
19              
20 1     1   486 use Data::Dump qw(dd);
  1         7386  
  1         1589  
21              
22             require Exporter;
23             our @ISA = qw(Exporter);
24             our @EXPORT_OK = qw(
25             get_inspected_modules_list
26             parse_modules
27             _extract_base
28             _validate_module_fullname
29             _substr_aldc
30             _cols2rows
31             _sort_cols_AoA_by_neighbour
32             _rm_header_from_cols_AoA
33             _add_header_to_cols_AoA
34             );
35             our %EXPORT_TAGS = ( 'all' => [@EXPORT_OK] );
36              
37              
38             sub get_inspected_modules_list {
39 1     1 0 2652 my ( $dir, $exclude_list, $v ) = @_;
40              
41 1 50       6 say "Inspecting modules in " . $dir if $v;
42 1 50 33     6 say "Skip modules : " . join( ',', @$exclude_list )
43             if ( $v && @$exclude_list );
44              
45 1 50       67 opendir( my $dh, $dir ) or die $!;
46              
47 1         3 my @files;
48 1         37 while ( my $file = readdir($dh) ) {
49 9 100       117 next unless ( -f "$dir/$file" );
50 7 100       35 next unless ( $file =~ m/\.pm$/ );
51              
52             # $file=~ s/(\w+).pm/$1/;
53 6         29 push @files, $file;
54             }
55 1         19 closedir($dh);
56              
57 1         13 my $lc = List::Compare->new( \@files, $exclude_list );
58 1         195 return $lc->get_Lonly_ref;
59             }
60              
61             sub _substr_aldc {
62 23     23   1260 my ($str) = @_;
63 23 100       101 return if !defined $str;
64 22         110 my @x = split( '::', $str );
65 22         489 return $x[-1];
66             }
67              
68             sub _extract_base {
69 8     8   2363 my (%p) = @_;
70              
71             # https://stackoverflow.com/questions/7283274/check-whether-a-string-contains-a-substring
72 8 100       57 if ( index( $p{abs}, $p{rel} ) == -1 ) {
73             croak "Relative path "
74             . $p{rel}
75             . " is not part of full path "
76             . $p{abs}
77 2         94 . ", maybe corresponded module placed in wrong dir";
78             }
79              
80 6         34 my $to_rel_idx = length( $p{abs} ) - length( $p{rel} ) - 1;
81 6         37 substr $p{abs}, 0, $to_rel_idx;
82             }
83              
84             sub _validate_module_fullname {
85 9     9   3071 my ($fullname) = @_;
86 9         708 my ( $name, $path, $suffix ) = fileparse( $fullname, 'pm' );
87              
88 9 100       77 carp
89             "Provided module name ($fullname) contain path so results may not correct"
90             if ( $path ne './' );
91              
92             # croak "Provided module name ($fullname) is not perl module" if ( $suffix ne 'pm' );
93 9 100       404 return 0 if ( $suffix ne 'pm' );
94              
95 8         35 return $name . $suffix;
96             }
97              
98              
99             sub parse_modules {
100 1     1 0 2612 my ($files) = @_;
101              
102 1         3 my @res = ();
103 1         7 for my $f ( sort @$files ) {
104              
105 6         731 _validate_module_fullname($f);
106              
107 6         178 my $info = Module::Metadata->new_from_file($f);
108 6         5592 my $name = $info->name;
109 6         55 my $abs_filename = $info->filename;
110 6         99 my $rel_filename = module_path $name;
111              
112             eval {
113 6         42 my $inc =
114             _extract_base( abs => $abs_filename, rel => $rel_filename );
115 5         41 push @INC, $inc;
116 5         45 1;
117 6 100       583 } or do {
118 1         779 say "Module $name skipped from analysis. Exception : " . $@;
119 1         20 next;
120             };
121              
122             # Package::Constants need module to be loaded
123 5         3292 require $f;
124              
125             # or autoload $name;
126              
127 5         5462 my $mod = Module::Info->new_from_loaded($name);
128 5         934 my %subs = $mod->subroutines;
129 5         885778 my @used = sort { "\L$a" cmp "\L$b" } $mod->modules_used;
  10         841708  
130              
131 5         513463 my @consts = Package::Constants->list($name);
132             push @res,
133             {
134             name => $name,
135             subs =>
136 9         116 [ map { _substr_aldc $_ } sort { "\L$a" cmp "\L$b" } keys %subs ],
  6         33  
137             used => \@used,
138 5         1217 consts => [ sort { "\L$a" cmp "\L$b" } @consts ]
  2         99  
139             };
140              
141             }
142              
143 1         221 return \@res;
144             }
145              
146             sub _cols2rows {
147 1     1   2666 my ($colls_arr) = @_;
148              
149 1         3 my @sizes;
150             my @rows;
151              
152 1         3 for my $max_idx (@$colls_arr) {
153 3         8 push @sizes, $#$max_idx;
154             }
155              
156 1         6 for my $i ( 0 .. $#$colls_arr ) {
157 3         11 for my $j ( 0 .. max @sizes ) {
158 12         22 $rows[$j][$i] = _substr_aldc( $colls_arr->[$i][$j] );
159             }
160             }
161              
162 1         5 return \@rows;
163             }
164              
165             sub _add_header_to_cols_AoA {
166 1     1   2487 my ( $AoA, $header ) = @_;
167              
168 1 50       6 croak "Different size of header and table rows amount"
169             if ( $#$AoA != $#$header );
170              
171 1         18 my $ea = each_arrayref( $AoA, $header );
172 1         10 while ( my ( $a, $b ) = $ea->() ) {
173 3         25 unshift @$a, $b;
174             }
175              
176 1         10 return $AoA;
177             }
178              
179             sub _rm_header_from_cols_AoA {
180 1     1   3257 my ($AoA) = @_;
181              
182 1         2 my @header;
183 1         3 for my $col (@$AoA) {
184 3         7 push @header, $col->[0];
185 3         6 shift @$col;
186             }
187 1         4 return \@header;
188             }
189              
190             # See more
191             # https://stackoverflow.com/questions/54333145/sort-table-or-2-dimensional-array-by-same-values-in-column
192             # https://perldoc.pl/perllol
193              
194             sub _sort_cols_AoA_by_neighbour {
195 2     2   3006 my ( $data, $has_header ) = @_;
196              
197             # if first element is column header start idx = 1
198 2 50       7 my $i = ( $has_header ? 1 : 0 );
199 2         4 my @all;
200              
201 2         5 for my $col_arr (@$data) {
202 6         19 push @all, @$col_arr[ $i .. $#$col_arr ];
203             }
204              
205 2 50       17 my $header = _rm_header_from_cols_AoA($data) if $has_header;
206 2         17 @all = sort { "\L$a" cmp "\L$b" } grep { $_ } uniq @all;
  8         20  
  10         24  
207              
208 2         8 for my $ary (@$data) {
209              
210 6         11 my $cmp_at = 0;
211 6         6 my @res;
212 6         15 for my $i ( 0 .. $#all ) {
213              
214 24 100       50 if ( !defined $ary->[$cmp_at] ) {
    100          
215 3         18 push @res, undef;
216             }
217             elsif ( $ary->[$cmp_at] eq $all[$i] ) {
218 12         19 push @res, $ary->[$cmp_at];
219 12         20 ++$cmp_at;
220             }
221             else {
222 9         16 push @res, undef;
223             }
224             }
225 6         15 $ary = \@res;
226             }
227              
228 2 50       5 _add_header_to_cols_AoA( $data, $header ) if $has_header;
229              
230 2         9 return $data;
231             }
232              
233             1;
234              
235             __END__