File Coverage

blib/lib/CPANPLUS/Dist/Debora/Util.pm
Criterion Covered Total %
statement 123 161 76.4
branch 27 62 43.5
condition 11 20 55.0
subroutine 27 29 93.1
pod 11 11 100.0
total 199 283 70.3


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   41428 use 5.016;
  9         51  
6 9     9   48 use warnings;
  9         16  
  9         600  
7 9     9   49 use utf8;
  9         26  
  9         62  
8              
9             our $VERSION = '0.018';
10              
11 9     9   550 use parent qw(Exporter);
  9         17  
  9         97  
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   1054 use Carp qw(croak);
  9         20  
  9         594  
29 9     9   51 use Cwd qw(cwd);
  9         41  
  9         506  
30 9     9   1693 use Encode qw(decode);
  9         52432  
  9         682  
31 9     9   1080 use English qw(-no_match_vars);
  9         4256  
  9         85  
32 9     9   3815 use File::Spec::Functions qw(catfile splitdir splitpath);
  9         16  
  9         631  
33 9     9   62 use File::Spec::Unix qw();
  9         17  
  9         272  
34 9     9   4636 use IPC::Cmd qw(can_run);
  9         363531  
  9         785  
35 9     9   39114 use Module::CoreList 2.32;
  9         1643304  
  9         91  
36 9     9   18744 use version 0.77;
  9         216  
  9         88  
37              
38 9     9   8128 use CPANPLUS::Error qw(error);
  9         76416  
  9         16021  
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 348113 my $string = shift;
47              
48 29         431 return version->parse($string);
49             }
50              
51             sub module_is_distributed_with_perl {
52 13     13 1 640 my ($module_name, $version) = @_;
53              
54 13         33 my $ok = 0;
55              
56             # cpan2dist is run with -w, which triggers a warning in Module::CoreList.
57 13         88 local $WARNING = 0;
58              
59 13         131 my $upper = Module::CoreList->removed_from($module_name);
60 13 100 66     43724 if (!defined $upper || $perl_version < parse_version($upper)) {
61 7         56 my $lower = Module::CoreList->first_release($module_name, $version);
62 7 100 66     9645 if (defined $lower && $perl_version >= parse_version($lower)) {
63 1         4 $ok = 1;
64             }
65             }
66              
67 13         364 return $ok;
68             }
69              
70             sub decode_utf8 {
71 7     7 1 23 my $bytes = shift;
72              
73 7         70 return decode('UTF-8', $bytes);
74             }
75              
76             sub slurp_utf8 {
77 3     3 1 10 my $filename = shift;
78              
79 3         8 my $data;
80              
81 3         171 my $ok = open my $fh, '<:encoding(UTF-8)', $filename;
82 3 50       326 if ($ok) {
83 3         37 local $RS = undef;
84 3         226 $data = <$fh>;
85 3 50       458 close $fh or $ok = 0;
86             }
87              
88 3         28 return $data;
89             }
90              
91             sub spew_utf8 {
92 1     1 1 4 my ($filename, $string) = @_;
93              
94 1     1   60 my $ok = open my $fh, '>:encoding(UTF-8)', $filename;
  1         876  
  1         20  
  1         7  
95 1 50       1438 if ($ok) {
96 1         3 $ok = print {$fh} $string;
  1         6  
97 1 50       131 close $fh or $ok = 0;
98             }
99              
100 1         14 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 9 my $path = shift;
152              
153 6         15 (undef, $path) = splitpath($path, 1);
154 6         35 $path = File::Spec::Unix->catfile(splitdir($path));
155              
156 6         44 return $path;
157             }
158              
159             sub filetype {
160 7     7 1 25 my $filename = shift;
161              
162 7         198 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         139 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         25 my $type = 'data';
189              
190 7 50       182 if ($filename =~ m{[.]([^.]+) \z}xms) {
191 7         45 my $suffix = lc $1;
192 7 50       36 if (exists $type_for{$suffix}) {
193 7         21 $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         215 return $type;
213             }
214              
215             sub find_most_recent_mtime {
216 6     6 1 43 my $sourcedir = shift;
217              
218 6         35 my $most_recent_mtime = 0;
219              
220             my $find = sub {
221 236     236   541 my $dir = shift;
222              
223 236 50       8252 opendir my $dh, $dir or croak "Could not traverse '$dir': $OS_ERROR";
224             ENTRY:
225 236         4963 while (defined(my $entry = readdir $dh)) {
226 1666 100 100     10040 next ENTRY if $entry eq q{.} || $entry eq q{..};
227              
228 1194         6446 my $path = catfile($dir, $entry);
229              
230             # Skip symbolic links.
231 1194 50       20224 next ENTRY if -l $path;
232              
233 1194 100       12311 if (-d $path) {
234 230         813 __SUB__->($path);
235             }
236             else {
237 964         10000 my @stat = stat $path;
238 964 50       2308 if (@stat) {
239 964         1646 my $mtime = $stat[9];
240 964 100       7218 if ($most_recent_mtime < $mtime) {
241 62         322 $most_recent_mtime = $mtime;
242             }
243             }
244             }
245             }
246 236         3059 closedir $dh;
247              
248 236         2202 return;
249 6         153 };
250 6         53 $find->($sourcedir);
251              
252 6         2504 return $most_recent_mtime;
253             }
254              
255             sub find_shared_objects {
256 3     3 1 12 my $stagingdir = shift;
257              
258 3         9 my @shared_objects;
259              
260             my $find = sub {
261 18     18   34 my $dir = shift;
262              
263 18 50       642 opendir my $dh, $dir
264             or croak "Could not traverse '$dir': $OS_ERROR";
265             ENTRY:
266 18         294 while (defined(my $entry = readdir $dh)) {
267 57 100 100     262 next ENTRY if $entry eq q{.} || $entry eq q{..};
268              
269 21         150 my $path = catfile($dir, $entry);
270              
271             # Skip symbolic links.
272 21 50       370 next ENTRY if -l $path;
273              
274 21 100       274 if (-d $path) {
275 15         73 __SUB__->($path);
276             }
277             else {
278 6 50       43 if (filetype($path) eq 'executable') {
279 0         0 push @shared_objects, $path;
280             }
281             }
282             }
283 18         137 closedir $dh;
284              
285 18         165 return;
286 3         35 };
287 3         16 $find->($stagingdir);
288              
289 3         37 return \@shared_objects;
290             }
291              
292             sub is_testing {
293 2   33 2 1 78 return $ENV{AUTOMATED_TESTING} || $ENV{RELEASE_TESTING};
294             }
295              
296             1;
297             __END__