File Coverage

blib/lib/App/iperlmoddir/Utils.pm
Criterion Covered Total %
statement 137 137 100.0
branch 31 44 70.4
condition 2 6 33.3
subroutine 22 22 100.0
pod 0 2 0.0
total 192 211 91.0


line stmt bran cond sub pod time code
1             package App::iperlmoddir::Utils;
2             $App::iperlmoddir::Utils::VERSION = '1.02';
3 1     1   77331 use strict;
  1         3  
  1         34  
4 1     1   5 use warnings;
  1         2  
  1         30  
5 1     1   6 use feature 'say';
  1         2  
  1         126  
6              
7 1     1   484 use Module::Info;
  1         6596  
  1         34  
8 1     1   551 use Module::Metadata;
  1         5990  
  1         41  
9              
10             # use Module::Load;
11 1     1   444 use Module::Util qw(module_path);
  1         2878  
  1         65  
12              
13 1     1   8 use File::Basename;
  1         2  
  1         85  
14 1     1   826 use List::Compare;
  1         20781  
  1         43  
15 1     1   8 use List::Util qw(max uniq);
  1         2  
  1         107  
16 1     1   572 use List::MoreUtils qw(each_arrayref);
  1         12444  
  1         6  
17 1     1   1495 use Package::Constants;
  1         2197  
  1         29  
18 1     1   7 use Carp;
  1         2  
  1         60  
19              
20 1     1   471 use Data::Dump qw(dd);
  1         7194  
  1         1581  
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 2630 my ( $dir, $exclude_list, $v ) = @_;
40              
41 1 50       4 say "Inspecting modules in " . $dir if $v;
42 1 50 33     5 say "Skip modules : " . join( ',', @$exclude_list )
43             if ( $v && @$exclude_list );
44              
45 1 50       52 opendir( my $dh, $dir ) or die $!;
46              
47 1         3 my @files;
48 1         34 while ( my $file = readdir($dh) ) {
49 9 100       114 next unless ( -f "$dir/$file" );
50 7 100       35 next unless ( $file =~ m/\.pm$/ );
51              
52             # $file=~ s/(\w+).pm/$1/;
53 6         33 push @files, $file;
54             }
55 1         18 closedir($dh);
56              
57 1         13 my $lc = List::Compare->new( \@files, $exclude_list );
58 1         174 return $lc->get_Lonly_ref;
59             }
60              
61             sub _substr_aldc {
62 23     23   1188 my ($str) = @_;
63 23 100       89 return if !defined $str;
64 22         82 my @x = split( '::', $str );
65 22         360 return $x[-1];
66             }
67              
68             sub _extract_base {
69 8     8   2371 my (%p) = @_;
70              
71 8 50       40 croak "Abs path is not defined" if !defined $p{abs};
72 8 50       29 croak "Rel path is not defined" if !defined $p{rel};
73              
74             # https://stackoverflow.com/questions/7283274/check-whether-a-string-contains-a-substring
75 8 100       42 if ( index( $p{abs}, $p{rel} ) == -1 ) {
76             croak "Relative path "
77             . $p{rel}
78             . " is not part of full path "
79             . $p{abs}
80 2         80 . ", maybe corresponded module placed in wrong dir";
81             }
82              
83 6         30 my $to_rel_idx = length( $p{abs} ) - length( $p{rel} ) - 1;
84 6         32 substr $p{abs}, 0, $to_rel_idx;
85             }
86              
87             sub _validate_module_fullname {
88 9     9   3062 my ($fullname) = @_;
89 9         531 my ( $name, $path, $suffix ) = fileparse( $fullname, 'pm' );
90              
91 9 100       76 carp
92             "Provided module name ($fullname) contain path so results may not correct"
93             if ( $path ne './' );
94              
95             # croak "Provided module name ($fullname) is not perl module" if ( $suffix ne 'pm' );
96 9 100       347 return 0 if ( $suffix ne 'pm' );
97              
98 8         48 return $name . $suffix;
99             }
100              
101              
102             sub parse_modules {
103 1     1 0 2653 my ( $files, $v ) = @_;
104              
105 1 50       6 say "Start parsing modules : \n" . join( "\n", @$files ) if $v;
106              
107 1         3 my @res = ();
108 1         13 for my $f ( sort @$files ) {
109              
110 6         602 _validate_module_fullname($f);
111              
112 6         125 my $info = Module::Metadata->new_from_file($f);
113              
114 6         4328 my $name = $info->name;
115              
116 6 50 33     60 say "Module::Metadata can not define module name of $f"
117             if ( $v && !defined $name );
118 6 50       21 next if !defined $name;
119              
120 6         23 my $abs_filename = $info->filename;
121 6         85 my $rel_filename = module_path $name;
122              
123             eval {
124 6         24 my $inc =
125             _extract_base( abs => $abs_filename, rel => $rel_filename );
126 5         32 push @INC, $inc;
127 5         25 1;
128 6 100       467 } or do {
129 1         620 say "Module $name skipped from analysis. Exception : " . $@;
130 1         10 next;
131             };
132              
133             # Package::Constants need module to be loaded
134 5         2587 require $f;
135              
136             # or autoload $name;
137              
138 5         5060 my $mod = Module::Info->new_from_module($name);
139              
140 5 50       1147 croak "Problems with getting Module::Info at module " . $name
141             if !defined $mod;
142              
143 5         27 my %subs = $mod->subroutines;
144 5         863369 my @used = sort { "\L$a" cmp "\L$b" } $mod->modules_used;
  10         817592  
145              
146 5         506237 my @consts = Package::Constants->list($name);
147             push @res,
148             {
149             name => $name,
150             subs =>
151 9         104 [ map { _substr_aldc $_ } sort { "\L$a" cmp "\L$b" } keys %subs ],
  6         25  
152             used => \@used,
153 5         920 consts => [ sort { "\L$a" cmp "\L$b" } @consts ]
  2         96  
154             };
155              
156             }
157              
158 1         186 return \@res;
159             }
160              
161             sub _cols2rows {
162 1     1   2677 my ($colls_arr) = @_;
163              
164 1         2 my @sizes;
165             my @rows;
166              
167 1         4 for my $max_idx (@$colls_arr) {
168 3         7 push @sizes, $#$max_idx;
169             }
170              
171 1         5 for my $i ( 0 .. $#$colls_arr ) {
172 3         11 for my $j ( 0 .. max @sizes ) {
173 12         23 $rows[$j][$i] = _substr_aldc( $colls_arr->[$i][$j] );
174             }
175             }
176              
177 1         5 return \@rows;
178             }
179              
180             sub _add_header_to_cols_AoA {
181 1     1   2453 my ( $AoA, $header ) = @_;
182              
183 1 50       5 croak "Different size of header and table rows amount"
184             if ( $#$AoA != $#$header );
185              
186 1         25 my $ea = each_arrayref( $AoA, $header );
187 1         13 while ( my ( $a, $b ) = $ea->() ) {
188 3         15 unshift @$a, $b;
189             }
190              
191 1         8 return $AoA;
192             }
193              
194             sub _rm_header_from_cols_AoA {
195 1     1   3225 my ($AoA) = @_;
196              
197 1         2 my @header;
198 1         3 for my $col (@$AoA) {
199 3         6 push @header, $col->[0];
200 3         6 shift @$col;
201             }
202 1         4 return \@header;
203             }
204              
205             # See more
206             # https://stackoverflow.com/questions/54333145/sort-table-or-2-dimensional-array-by-same-values-in-column
207             # https://perldoc.pl/perllol
208              
209             sub _sort_cols_AoA_by_neighbour {
210 2     2   3032 my ( $data, $has_header ) = @_;
211              
212             # if first element is column header start idx = 1
213 2 50       7 my $i = ( $has_header ? 1 : 0 );
214 2         4 my @all;
215              
216 2         4 for my $col_arr (@$data) {
217 6         17 push @all, @$col_arr[ $i .. $#$col_arr ];
218             }
219              
220 2 50       17 my $header = _rm_header_from_cols_AoA($data) if $has_header;
221 2         14 @all = sort { "\L$a" cmp "\L$b" } grep { $_ } uniq @all;
  8         19  
  10         22  
222              
223 2         7 for my $ary (@$data) {
224              
225 6         9 my $cmp_at = 0;
226 6         8 my @res;
227 6         13 for my $i ( 0 .. $#all ) {
228              
229 24 100       60 if ( !defined $ary->[$cmp_at] ) {
    100          
230 3         6 push @res, undef;
231             }
232             elsif ( $ary->[$cmp_at] eq $all[$i] ) {
233 12         20 push @res, $ary->[$cmp_at];
234 12         17 ++$cmp_at;
235             }
236             else {
237 9         13 push @res, undef;
238             }
239             }
240 6         15 $ary = \@res;
241             }
242              
243 2 50       13 _add_header_to_cols_AoA( $data, $header ) if $has_header;
244              
245 2         10 return $data;
246             }
247              
248             1;
249              
250             __END__