File Coverage

blib/lib/CPANPLUS/Dist/Debora/Util.pm
Criterion Covered Total %
statement 123 161 76.4
branch 27 62 43.5
condition 11 19 57.8
subroutine 27 29 93.1
pod 11 11 100.0
total 199 282 70.5


line stmt bran cond sub pod time code
1             package CPANPLUS::Dist::Debora::Util;
2              
3             # SPDX-License-Identifier: Artistic-1.0-Perl OR GPL-1.0-or-later
4              
5 9     9   26362 use 5.016;
  9         96  
6 9     9   85 use warnings;
  9         18  
  9         234  
7 9     9   47 use utf8;
  9         36  
  9         107  
8              
9             our $VERSION = '0.011';
10              
11 9     9   477 use parent qw(Exporter);
  9         35  
  9         54  
12              
13             our @EXPORT_OK = qw(
14             parse_version
15             module_is_distributed_with_perl
16             decode_utf8
17             slurp_utf8
18             spew_utf8
19             can_run
20             run
21             unix_path
22             filetype
23             find_most_recent_mtime
24             find_shared_objects
25             is_testing
26             );
27              
28 9     9   869 use Carp qw(croak);
  9         21  
  9         513  
29 9     9   65 use Cwd qw(cwd);
  9         19  
  9         454  
30 9     9   1852 use Encode qw(decode);
  9         31483  
  9         533  
31 9     9   975 use English qw(-no_match_vars);
  9         5359  
  9         98  
32 9     9   3591 use File::Spec::Functions qw(catfile splitdir splitpath);
  9         20  
  9         589  
33 9     9   65 use File::Spec::Unix qw();
  9         21  
  9         181  
34 9     9   4898 use IPC::Cmd qw(can_run);
  9         336239  
  9         567  
35 9     9   29004 use Module::CoreList 2.32;
  9         989295  
  9         89  
36 9     9   6496 use version 0.77;
  9         155  
  9         91  
37              
38 9     9   6342 use CPANPLUS::Error qw(error);
  9         71752  
  9         14593  
39              
40             # Avoid warnings from IO::Select by using IPC::Run.
41             $IPC::Cmd::USE_IPC_RUN = IPC::Cmd->can_use_ipc_run;
42              
43             my $perl_version = parse_version($PERL_VERSION);
44              
45             sub parse_version {
46 29     29 1 1772 my $string = shift;
47              
48 29         431 return version->parse($string);
49             }
50              
51             sub module_is_distributed_with_perl {
52 13     13 1 537 my ($module_name, $version) = @_;
53              
54 13         24 my $ok = 0;
55              
56             # cpan2dist is run with -w, which triggers a warning in Module::CoreList.
57 13         73 local $WARNING = 0;
58              
59 13         112 my $upper = Module::CoreList->removed_from($module_name);
60 13 100 66     18505 if (!defined $upper || $perl_version < parse_version($upper)) {
61 7         56 my $lower = Module::CoreList->first_release($module_name, $version);
62 7 100 66     3819 if (defined $lower && $perl_version >= parse_version($lower)) {
63 1         4 $ok = 1;
64             }
65             }
66              
67 13         205 return $ok;
68             }
69              
70             sub decode_utf8 {
71 7     7 1 23 my $bytes = shift;
72              
73 7         32 return decode('UTF-8', $bytes);
74             }
75              
76             sub slurp_utf8 {
77 3     3 1 10 my $filename = shift;
78              
79 3         6 my $data;
80              
81 3         121 my $ok = open my $fh, '<:encoding(UTF-8)', $filename;
82 3 50       239 if ($ok) {
83 3         27 local $RS = undef;
84 3         167 $data = <$fh>;
85 3 50       262 close $fh or $ok = 0;
86             }
87              
88 3         25 return $data;
89             }
90              
91             sub spew_utf8 {
92 1     1 1 4 my ($filename, $string) = @_;
93              
94 1     1   46 my $ok = open my $fh, '>:encoding(UTF-8)', $filename;
  1         7  
  1         2  
  1         7  
95 1 50       1330 if ($ok) {
96 1         2 $ok = print {$fh} $string;
  1         8  
97 1 50       75 close $fh or $ok = 0;
98             }
99              
100 1         27 return $ok;
101             }
102              
103             sub run {
104 0     0 1 0 my (%options) = @_;
105              
106 0         0 my $ok = 0;
107              
108 0         0 my $command = $options{command};
109 0 0       0 if (!$command) {
110 0         0 error('No command');
111 0         0 return $ok;
112             }
113              
114 0         0 my $dir = $options{dir};
115 0         0 delete $options{dir};
116              
117 0 0       0 if (!exists $options{buffer}) {
118 0         0 my $buf = q{};
119 0         0 $options{buffer} = \$buf;
120             }
121              
122             my $on_error = $options{on_error}
123 0   0 0   0 // sub { error("Could not run '$_[0]': $_[1]") };
  0         0  
124 0         0 delete $options{on_error};
125              
126 0         0 my $origdir;
127 0 0       0 if ($dir) {
128 0         0 $origdir = cwd;
129 0 0       0 if (!chdir $dir) {
130 0         0 return $ok;
131             }
132             }
133              
134 0         0 $ok = IPC::Cmd::run(%options);
135 0 0       0 if (!$ok) {
136 0         0 my $cmdline = join q{ }, @{$command};
  0         0  
137 0   0     0 my $output = ${$options{buffer}} // q{};
  0         0  
138 0         0 $on_error->($cmdline, $output);
139             }
140              
141 0 0       0 if ($origdir) {
142 0 0       0 if (!chdir $origdir) {
143 0         0 $ok = 0;
144             }
145             }
146              
147 0         0 return $ok;
148             }
149              
150             sub unix_path {
151 6     6 1 17 my $path = shift;
152              
153 6         18 (undef, $path) = splitpath($path, 1);
154 6         48 $path = File::Spec::Unix->catfile(splitdir($path));
155              
156 6         69 return $path;
157             }
158              
159             sub filetype {
160 7     7 1 26 my $filename = shift;
161              
162 7         158 my %type_for = (
163             '1' => 'text',
164             '1p' => 'text',
165             '3' => 'text',
166             '3perl' => 'text',
167             '3pm' => 'text',
168             'bat' => 'script',
169             'dll' => 'executable',
170             'dylib' => 'executable',
171             'exe' => 'executable',
172             'pl' => 'script',
173             'pm' => 'text',
174             'pod' => 'text',
175             'so' => 'executable',
176             );
177              
178 7         91 my @magic = (
179             [0, 4, '7F454C46', 'executable'], # ELF
180             [0, 4, 'FEEDFACE', 'executable'], # Mach-O
181             [0, 4, 'CEFAEDFE', 'executable'], # Mach-O
182             [0, 4, 'FEEDFACF', 'executable'], # Mach-O
183             [0, 4, 'CFFAEDFE', 'executable'], # Mach-O
184             [0, 2, '4D5A', 'executable'], # PE
185             [0, 2, '2321', 'script'], # Shebang
186             );
187              
188 7         20 my $type = 'data';
189              
190 7 50       84 if ($filename =~ m{[.]([^.]+) \z}xms) {
191 7         42 my $suffix = lc $1;
192 7 50       25 if (exists $type_for{$suffix}) {
193 7         29 $type = $type_for{$suffix};
194             }
195             }
196              
197 7 50       35 if ($type eq 'data') {
198 0 0       0 if (open my $fh, '<:raw', $filename) {
199 0 0       0 if (read $fh, my $data, 16) {
200             TYPE:
201 0         0 for (@magic) {
202 0 0       0 if (substr($data, $_->[0], $_->[1]) eq pack 'H*', $_->[2]) {
203 0         0 $type = $_->[3];
204 0         0 last TYPE;
205             }
206             }
207             }
208 0 0       0 close $fh or undef;
209             }
210             }
211              
212 7         74 return $type;
213             }
214              
215             sub find_most_recent_mtime {
216 6     6 1 93 my $sourcedir = shift;
217              
218 6         96 my $most_recent_mtime = 0;
219              
220             my $find = sub {
221 230     230   489 my $dir = shift;
222              
223 230 50       6834 opendir my $dh, $dir or croak "Could not traverse '$dir': $OS_ERROR";
224             ENTRY:
225 230         5889 while (defined(my $entry = readdir $dh)) {
226 1642 100 100     6931 next ENTRY if $entry eq q{.} || $entry eq q{..};
227              
228 1182         5747 my $path = catfile($dir, $entry);
229              
230             # Skip symbolic links.
231 1182 50       20081 next ENTRY if -l $path;
232              
233 1182 100       14231 if (-d $path) {
234 224         910 __SUB__->($path);
235             }
236             else {
237 958         11908 my @stat = stat $path;
238 958 50       2991 if (@stat) {
239 958         1604 my $mtime = $stat[9];
240 958 100       5046 if ($most_recent_mtime < $mtime) {
241 12         76 $most_recent_mtime = $mtime;
242             }
243             }
244             }
245             }
246 230         2684 closedir $dh;
247              
248 230         1677 return;
249 6         187 };
250 6         70 $find->($sourcedir);
251              
252 6         135 return $most_recent_mtime;
253             }
254              
255             sub find_shared_objects {
256 3     3 1 10 my $stagingdir = shift;
257              
258 3         15 my @shared_objects;
259              
260             my $find = sub {
261 18     18   37 my $dir = shift;
262              
263 18 50       406 opendir my $dh, $dir
264             or croak "Could not traverse '$dir': $OS_ERROR";
265             ENTRY:
266 18         266 while (defined(my $entry = readdir $dh)) {
267 57 100 100     319 next ENTRY if $entry eq q{.} || $entry eq q{..};
268              
269 21         118 my $path = catfile($dir, $entry);
270              
271             # Skip symbolic links.
272 21 50       266 next ENTRY if -l $path;
273              
274 21 100       235 if (-d $path) {
275 15         98 __SUB__->($path);
276             }
277             else {
278 6 50       53 if (filetype($path) eq 'executable') {
279 0         0 push @shared_objects, $path;
280             }
281             }
282             }
283 18         169 closedir $dh;
284              
285 18         156 return;
286 3         45 };
287 3         15 $find->($stagingdir);
288              
289 3         60 return \@shared_objects;
290             }
291              
292             sub is_testing {
293 2   33 2 1 26 return $ENV{AUTOMATED_TESTING} || $ENV{RELEASE_TESTING};
294             }
295              
296             1;
297             __END__