| 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__ |