File Coverage

lib/Stable/Module.pm
Criterion Covered Total %
statement 229 334 68.5
branch 49 124 39.5
condition 7 30 23.3
subroutine 30 40 75.0
pod 0 1 0.0
total 315 529 59.5


line stmt bran cond sub pod time code
1             package Stable::Module;
2             ######################################################################
3             #
4             # Stable::Module - frequently used modules on Perl5 application
5             #
6             # http://search.cpan.org/dist/Stable-Module/
7             #
8             # Copyright (c) 2014, 2016, 2017, 2018, 2019, 2023 INABA Hitoshi in a CPAN
9             ######################################################################
10              
11             $VERSION = '0.11';
12             $VERSION = $VERSION;
13              
14 25     25   92400 use 5.00503;
  25         218  
15 25     25   162 use strict;
  25         48  
  25         1450  
16 25 50   25   768 BEGIN { $INC{'warnings.pm'} = '' if $] < 5.006 }; use warnings; $^W=1;
  25     25   172  
  25         57  
  25         1031  
17 25     25   156 use Carp;
  25         52  
  25         1410  
18 25     25   149 use FindBin;
  25         46  
  25         909  
19 25     25   11600 use IO::File;
  25         216537  
  25         3019  
20              
21 25     25   216 use vars qw($re_char $hide_stderr);
  25         54  
  25         5258  
22              
23             #---------------------------------------------------------------------
24             sub VERSION {
25 0     0 0 0 my($self,$version) = @_;
26 0 0       0 if ($version != $Stable::Module::VERSION) {
27 0         0 my($package,$filename,$line) = caller;
28 0         0 die "$filename requires Stable::Module $version, this is version $Stable::Module::VERSION, stopped at $filename line $line.\n";
29             }
30             }
31              
32             #---------------------------------------------------------------------
33             sub BEGIN {
34 25 50 33 25   316 if (($^O eq 'MSWin32') and (defined $ENV{'OS'}) and ($ENV{'OS'} eq 'Windows_NT')) {
      33        
35 0         0 $hide_stderr = '2>NUL';
36             }
37             else {
38 25         9946 $hide_stderr = '';
39             }
40             }
41              
42             #---------------------------------------------------------------------
43             sub import {
44 25     25   263 my($caller,$filename,$line) = caller;
45              
46             # verify that we're called correctly so that strictures will work.
47 25 50       228 if (__FILE__ !~ m{ \b Stable[/\\]Module\.pm \z}x) {
48 0         0 die "Incorrect use of module '${\__PACKAGE__}' at $filename line $line.\n";
  0         0  
49             }
50              
51             # get /./ (dot: one character) regexp
52 25         121 $re_char = qr/[\x00-\xFF]/;
53              
54             # Code Page Identifiers (Microsoft Windows)
55             # Identifier .NET Name Additional information
56 25         332 my %re_char = (
57              
58             # Sjis shift_jis ANSI/OEM Japanese; Japanese (Shift-JIS)
59             '932' => qr{[\x81-\x9F\xE0-\xFC][\x00-\xFF]|[\x00-\xFF]},
60              
61             # GBK gb2312 ANSI/OEM Simplified Chinese (PRC, Singapore); Chinese Simplified (GB2312)
62             '936' => qr{[\x81-\xFE][\x00-\xFF]|[\x00-\xFF]},
63              
64             # UHC ks_c_5601-1987 ANSI/OEM Korean (Unified Hangul Code)
65             '949' => qr{[\x81-\xFE][\x00-\xFF]|[\x00-\xFF]},
66              
67             # Big5Plus big5 ANSI/OEM Traditional Chinese (Taiwan; Hong Kong SAR, PRC); Chinese Traditional (Big5)
68             '950' => qr{[\x81-\xFE][\x00-\xFF]|[\x00-\xFF]},
69              
70             # Big5HKSCS HKSCS support on top of traditional Chinese Windows
71             '951' => qr{[\x81-\xFE][\x00-\xFF]|[\x00-\xFF]},
72              
73             # GB18030 GB18030 Windows XP and later: GB18030 Simplified Chinese (4 byte); Chinese Simplified (GB18030)
74             '54936' => qr{[\x81-\xFE][\x30-\x39][\x81-\xFE][\x30-\x39]|[\x81-\xFE][\x00-\xFF]|[\x00-\xFF]},
75             );
76              
77 25         55 my $codepage = 'File System Safe';
78 25 50       136 if ($^O =~ m{\A (?:MSWin32|NetWare|symbian|dos) \z}oxms) {
79 0         0 $codepage = (qx{chcp} =~ m{ ([0-9]{3,5}) \Z}oxms)[0];
80 0   0     0 $re_char = $re_char{$codepage} || $re_char;
81             }
82              
83 25     25   202 no strict 'refs';
  25         59  
  25         90643  
84              
85             # use Cwd qw(cwd);
86 25         138 require Cwd;
87 25 50       111 if ($^O =~ m{\A (?:MSWin32|NetWare|symbian|dos) \z}oxms) {
88 0     0   0 *{$caller.'::cwd'} = sub { _dos_path(Cwd::cwd) };
  0         0  
  0         0  
89             }
90             else {
91 25         88 *{$caller.'::cwd'} = \&Cwd::cwd;
  25         156  
92             }
93              
94             # use FindBin qw($Bin);
95 25         105 require FindBin;
96 25 50       125 if ($^O =~ m{\A (?:MSWin32|NetWare|symbian|dos) \z}oxms) {
97 0         0 $FindBin::Bin = _dos_path($FindBin::Bin);
98             }
99 25         56 *{$caller.'::Bin'} = \$FindBin::Bin;
  25         92  
100              
101             # use File::Basename qw(fileparse basename dirname);
102             # use internal routines always for compatibility -- for example, perl 5.6 has incompatible File::Basename
103 25         60 if (1 or exists $re_char{$codepage}) {
104 25         313 $INC{'File/Basename.pm'} = join '/', $FindBin::Bin, __FILE__;
105 25         90 *{$caller.'::fileparse'} = \&_fileparse;
  25         130  
106 25         54 *{$caller.'::basename'} = \&_basename;
  25         87  
107 25         52 *{$caller.'::dirname'} = \&_dirname;
  25         89  
108             }
109             else {
110             require File::Basename;
111             *{$caller.'::fileparse'} = \&File::Basename::fileparse;
112             *{$caller.'::basename'} = \&File::Basename::basename;
113             *{$caller.'::dirname'} = \&File::Basename::dirname;
114             }
115              
116             # use File::Path qw(mkpath rmtree);
117 25 50       95 if (exists $re_char{$codepage}) {
118 0         0 $INC{'File/Path.pm'} = join '/', $FindBin::Bin, __FILE__;
119 0         0 *{$caller.'::mkpath'} = \&_mkpath;
  0         0  
120 0         0 *{$caller.'::rmtree'} = \&_rmtree;
  0         0  
121             }
122             else {
123 25         114 require File::Path;
124 25         74 *{$caller.'::mkpath'} = \&File::Path::mkpath;
  25         252  
125 25         109 *{$caller.'::rmtree'} = \&File::Path::rmtree;
  25         217  
126             }
127              
128             # use File::Copy qw(copy move);
129 25 50       100 if (exists $re_char{$codepage}) {
130 0         0 $INC{'File/Copy.pm'} = join '/', $FindBin::Bin, __FILE__;
131 0         0 *{$caller.'::copy'} = \&_copy;
  0         0  
132 0         0 *{$caller.'::move'} = \&_move;
  0         0  
133             }
134             else {
135 25         12554 require File::Copy;
136 25         115471 *{$caller.'::copy'} = \&File::Copy::copy;
  25         692  
137 25         98 *{$caller.'::move'} = \&File::Copy::move;
  25         106  
138             }
139              
140             # use File::Compare qw(compare);
141 25         10477 require File::Compare;
142 25         24143 *{$caller.'::compare'} = \&File::Compare::compare;
  25         162  
143              
144             # use Sys::Hostname qw(hostname);
145 25         10389 require Sys::Hostname;
146 25         34129 *{$caller.'::hostname'} = \&Sys::Hostname::hostname;
  25         1780  
147              
148             # use Time::Local qw(timelocal);
149 25         17221 require Time::Local;
150 25         82124 *{$caller.'::timelocal'} = \&Time::Local::timelocal;
  25         162  
151              
152             # internal List::Util::reduce
153             sub _reduce (&@) {
154 5     5   14 my $coderef = shift @_;
155 5         10 local $a = shift @_;
156 5         15 for $b (@_) {
157 77         143 $a = $coderef->();
158             }
159 5         17 return $a;
160             }
161              
162             # use List::Util qw(reduce first shuffle max maxstr min minstr sum);
163 25         55 *{$caller.'::first'} = \&_first;
  25         151  
164 25         58 *{$caller.'::shuffle'} = \&_shuffle;
  25         94  
165 25 50   9   113 *{$caller.'::max'} = sub { _reduce { $a > $b ? $a : $b } @_ };
  25         93  
  1         13  
  9         17  
166 25 50   1   73 *{$caller.'::maxstr'} = sub { _reduce { $a gt $b ? $a : $b } @_ };
  25         192  
  1         16  
  25         57  
167 25 50   1   82 *{$caller.'::min'} = sub { _reduce { $a < $b ? $a : $b } @_ };
  25         78  
  1         15  
  9         19  
168 25 50   1   65 *{$caller.'::minstr'} = sub { _reduce { $a lt $b ? $a : $b } @_ };
  25         79  
  1         17  
  25         48  
169 25     1   133 *{$caller.'::sum'} = sub { _reduce { $a + $b } @_ };
  25         91  
  1         14  
  9         14  
170              
171             # use List::MoreUtils qw(all any none notall uniq);
172 25 100   4   97 *{$caller.'::all'} = sub (&@) { my $coderef = shift @_; for (@_) { return 0 if not $coderef->($_) } return 1; }; # All arguments are true
  25         89  
  4         169  
  4         10  
  10         29  
  1         6  
173 25 100   4   76 *{$caller.'::any'} = sub (&@) { my $coderef = shift @_; for (@_) { return 1 if $coderef->($_) } return 0; }; # One argument is true
  25         77  
  4         135  
  4         8  
  10         28  
  1         6  
174 25 100   4   66 *{$caller.'::none'} = sub (&@) { my $coderef = shift @_; for (@_) { return 0 if $coderef->($_) } return 1; }; # All arguments are false
  25         72  
  4         132  
  4         8  
  10         27  
  1         5  
175 25 100   4   93 *{$caller.'::notall'} = sub (&@) { my $coderef = shift @_; for (@_) { return 1 if not $coderef->($_) } return 0; }; # One argument is false
  25         77  
  4         141  
  4         10  
  10         29  
  1         6  
176 25         48 *{$caller.'::uniq'} = \&_uniq;
  25         82  
177              
178             # use feature qw(say);
179 25         45 *{$caller.'::say'} = \&_say;
  25         91  
180 25 50       37072 *IO::Handle::say = \&_say if not defined(*IO::Handle::say);
181             }
182              
183             #---------------------------------------------------------------------
184       0     sub unimport {
185             # nothing
186             }
187              
188             #---------------------------------------------------------------------
189             sub _fileparse {
190 8     8   437 my($fullname,@suffixlist) = @_;
191              
192 8 50       20 if (not defined $fullname) {
193 0         0 croak "fileparse(): need a valid pathname";
194             }
195 8         19 my $taint = substr($fullname,0,0); # Is $fullname tainted?
196              
197 8         19 my $dirname = '';
198 8         15 my $subdir = '';
199 8         98 while ($fullname =~ m{\G ($re_char) }oxmsgc) {
200 163         252 my $char = $1;
201 163 100       337 if ($char =~ m{\A [:\\/] \z}oxms) {
202 31         50 $dirname .= $subdir;
203 31         40 $dirname .= $char;
204 31         70 $subdir = '';
205             }
206             else {
207 132         303 $subdir .= $char;
208             }
209             }
210 8         11 my $name = $subdir;
211              
212 8 50 33     57 if (($dirname eq '') or ($dirname =~ m{ : \z}oxms)) {
213 0         0 $dirname .= '.\\';
214             }
215              
216             # ignore case of name
217 8         69 my @char = $name =~ m{\G ($re_char) }oxmsgc;
218 8         27 my $name_lc = join '', map { lcfirst } @char;
  47         97  
219 8         15 my $suffix = '';
220 8 100       20 if (@suffixlist) {
221 3         7 for my $s (@suffixlist) {
222              
223             # ignore case of suffix
224 3         26 my @char = $s =~ m{\G ($re_char) }oxmsgc;
225 3         8 my $s_lc = join '', map { lcfirst } @char;
  11         20  
226 3 100       14 if (substr($name_lc,-length($s_lc),length($s_lc)) eq $s_lc) {
227 2         5 $taint .= substr($s,0,0);
228 2         5 $suffix = substr($name,-length($s_lc),length($s_lc)) . $suffix;
229 2         8 $name = substr($name,0,length($name)-length($s_lc));
230             }
231             }
232             }
233              
234             # Ensure taint is propagated from the path to its pieces.
235 8         17 $name .= $taint;
236 8         13 $dirname .= $taint;
237 8         12 $suffix .= $taint;
238 8 50       13 if (wantarray) {
239 8         58 return ($name,$dirname,$suffix);
240             }
241             else {
242 0         0 return $name;
243             }
244             }
245              
246             #---------------------------------------------------------------------
247             sub _basename {
248 5     5   189 my($fullname,@suffixlist) = @_;
249              
250 5 50       14 if (not defined $fullname) {
251 0         0 croak "basename(): need a valid pathname";
252             }
253 5         13 my $taint = substr($fullname,0,0); # Is $fullname tainted?
254              
255             # From BSD basename(1)
256             # The basename utility deletes any prefix ending with the last slash '/'
257             # character present in string (after first stripping trailing slashes)
258 5         10 $fullname = _strip_trailing_sep($fullname);
259              
260 5         10 my $dirname = '';
261 5         7 my $subdir = '';
262 5         29 while ($fullname =~ m{\G ($re_char) }oxmsgc) {
263 76         118 my $char = $1;
264 76 100       135 if ($char =~ m{\A [:\\/] \z}oxms) {
265 15         22 $dirname .= $subdir;
266 15         19 $dirname .= $char;
267 15         33 $subdir = '';
268             }
269             else {
270 61         137 $subdir .= $char;
271             }
272             }
273 5         8 my $name = $subdir;
274              
275 5 50 33     27 if (($dirname eq '') or ($dirname =~ m{ : \z}oxms)) {
276 0         0 $dirname .= '.\\';
277             }
278              
279             # ignore case of name
280 5         34 my @char = $name =~ m{\G ($re_char) }oxmsgc;
281 5         12 my $name_lc = join '', map { lcfirst } @char;
  27         49  
282 5         10 my $suffix = '';
283 5 100       11 if (@suffixlist) {
284 2         3 for my $s (@suffixlist) {
285              
286             # ignore case of suffix
287 2         23 my @char = $s =~ m{\G ($re_char) }oxmsgc;
288 2         6 my $s_lc = join '', map { lcfirst } @char;
  8         16  
289 2 100       9 if (substr($name_lc,-length($s_lc),length($s_lc)) eq $s_lc) {
290 1         3 $taint .= substr($s,0,0);
291 1         3 $suffix = substr($name,-length($s_lc),length($s_lc)) . $suffix;
292 1         4 $name = substr($name,0,length($name)-length($s_lc));
293             }
294             }
295             }
296              
297             # From BSD basename(1)
298             # The suffix is not stripped if it is identical to the remaining
299             # characters in string.
300 5 50 33     14 if (($name eq '') and ($suffix ne '')) {
301 0         0 $name = $suffix;
302             }
303              
304             # Ensure that basename '/' == '/'
305 5 50       10 if ($name eq '') {
306 0         0 $name = $dirname;
307             }
308              
309             # Ensure taint is propagated from the path to its pieces.
310 5         10 $name .= $taint;
311 5         17 return $name;
312             }
313              
314             #---------------------------------------------------------------------
315             sub _dirname {
316 2     2   87 my($fullname) = @_;
317              
318 2 50       8 if (not defined $fullname) {
319 0         0 croak "dirname(): need a valid pathname";
320             }
321 2         8 my $taint = substr($fullname,0,0); # Is $fullname tainted?
322              
323 2         5 my($basename,$dirname) = _fileparse($fullname);
324 2         5 $dirname = _strip_trailing_sep($dirname);
325              
326 2 50       5 if ($basename eq '') {
327 0         0 ($basename,$dirname) = _fileparse($dirname);
328 0         0 $dirname = _strip_trailing_sep($dirname);
329             }
330              
331             # Ensure taint is propagated from the path to its pieces.
332 2         5 $dirname .= $taint;
333 2         6 return $dirname;
334             }
335              
336             #---------------------------------------------------------------------
337             sub _strip_trailing_sep {
338 7     7   25 my($dirname) = @_;
339              
340 7         133 my @char = $dirname =~ m{\G ([\\\/]+|$re_char) }oxmsgc;
341 7 50       27 if (scalar(@char) >= 2) {
342 7 100 66     36 if (($char[-1] =~ m{\A [\\\/]+ \z}oxms) and ($char[-2] ne ':')) {
343 3         7 pop @char;
344             }
345             }
346              
347 7         32 return join '', @char;
348             }
349              
350             #---------------------------------------------------------------------
351             sub _mkpath {
352 0     0   0 my $path = _dos_path($_[0]);
353              
354 0 0 0     0 if (_is_directory($_[0])) { # *NOT* _is_directory($path)
    0          
355 0         0 return 1;
356             }
357              
358             # cmd.exe on Windows NT, Windows 2000, Windows XP, Windows 2003 or later
359             elsif ((defined $ENV{'OS'}) and ($ENV{'OS'} eq 'Windows_NT')) {
360 0 0       0 if (CORE::system(qq{cmd.exe /E:ON /C mkdir $path >NUL $hide_stderr}) == 0) {
361 0         0 return 1;
362             }
363             }
364              
365             # COMMAND.COM on Windows 95, Windows 98, Windows 98 Second Edition, Windows Millennium Edition
366             else {
367 0         0 my @subdir = ();
368 0         0 my $i = 0;
369 0         0 while ($_[0] =~ m{\G ($re_char) }oxmsgc) {
370 0         0 my $char = $1;
371 0 0       0 if ($char =~ m{\A [\\/] \z}oxms) {
372 0         0 $i++;
373             }
374             else {
375 0         0 $subdir[$i] .= $char;
376             }
377             }
378 0 0       0 if (@subdir >= 2) {
379 0         0 for my $i (0 .. $#subdir-1) {
380 0         0 my $path = _dos_path(join '\\',@subdir[0..$i]);
381 0 0       0 if (not _is_directory($path)) {
382 0         0 CORE::system(qq{mkdir $path >NUL});
383             }
384             }
385             }
386 0         0 my $path = _dos_path(join '\\',@subdir);
387 0 0       0 if (CORE::system(qq{mkdir $path >NUL}) == 0) {
388 0         0 return 1;
389             }
390             }
391              
392 0 0       0 if (exists $INC{'Strict/Perl.pm'}) {
393 0         0 croak "mkpath: $path";
394             }
395             else {
396 0         0 return undef;
397             }
398             }
399              
400             #---------------------------------------------------------------------
401             sub _rmtree {
402 0     0   0 my $root = _dos_path($_[0]);
403              
404 0 0 0     0 if (not _is_directory($_[0])) { # *NOT* not _is_directory($root)
    0          
405 0         0 return 1;
406             }
407              
408             # cmd.exe on Windows NT, Windows 2000, Windows XP, Windows 2003 or later
409             elsif ((defined $ENV{'OS'}) and ($ENV{'OS'} eq 'Windows_NT')) {
410 0 0       0 if (CORE::system(qq{rmdir /S /Q $root >NUL $hide_stderr}) == 0) {
411 0         0 return 1;
412             }
413             }
414              
415             # COMMAND.COM on Windows 95, Windows 98, Windows 98 Second Edition, Windows Millennium Edition
416             else {
417 0         0 my @file = split /\n/, qx{dir /s /b /a-d $root};
418 0         0 for my $file (@file) {
419 0         0 $file = _dos_path($file);
420 0         0 CORE::system(qq{del $file >NUL});
421             }
422 0         0 my @dir = split /\n/, qx{dir /s /b /ad $root};
423 0         0 for my $dir (sort { length($b) <=> length($a) } @dir) {
  0         0  
424 0         0 $dir = _dos_path($dir);
425 0         0 CORE::system(qq{rmdir $dir >NUL});
426             }
427 0 0       0 if (CORE::system(qq{rmdir $root >NUL}) == 0) {
428 0         0 return 1;
429             }
430             }
431              
432 0 0       0 if (exists $INC{'Strict/Perl.pm'}) {
433 0         0 croak "rmdir: $root";
434             }
435             else {
436 0         0 return undef;
437             }
438             }
439              
440             #---------------------------------------------------------------------
441             sub _copy {
442 0     0   0 my $source = _dos_path($_[0]);
443 0         0 my $dest = _dos_path($_[1]);
444              
445 0 0       0 if (CORE::system(qq{copy /Y $source $dest >NUL $hide_stderr}) == 0) {
    0          
446 0         0 return 1;
447             }
448             elsif (exists $INC{'Strict/Perl.pm'}) {
449 0         0 croak "copy: $source $dest";
450             }
451             else {
452 0         0 return undef;
453             }
454             }
455              
456             #---------------------------------------------------------------------
457             sub _move {
458 0     0   0 my $source = _dos_path($_[0]);
459 0         0 my $dest = _dos_path($_[1]);
460              
461 0 0       0 if (CORE::system(qq{move /Y $source $dest >NUL $hide_stderr}) == 0) {
    0          
462 0         0 return 1;
463             }
464             elsif (exists $INC{'Strict/Perl.pm'}) {
465 0         0 croak "move: $source $dest";
466             }
467             else {
468 0         0 return undef;
469             }
470             }
471              
472             #---------------------------------------------------------------------
473             sub _dos_path {
474 0     0   0 my($path) = @_;
475              
476 0         0 my @char = $path =~ m{\G ($re_char) }oxmsg;
477 0 0       0 $path = join '', map {{'/' => '\\'}->{$_} || $_} @char;
  0         0  
478 0 0       0 $path = qq{"$path"} if $path =~ m{ };
479 0         0 return $path;
480             }
481              
482             #---------------------------------------------------------------------
483             sub _is_directory {
484 0     0   0 my($unknown) = @_;
485              
486 0 0       0 if (-e $unknown) {
    0          
487 0         0 return -d _;
488             }
489             elsif (_MSWin32_5Cended_path($unknown)) {
490 0         0 return -d "$unknown/.";
491             }
492 0         0 return undef;
493             }
494              
495             #---------------------------------------------------------------------
496             sub _MSWin32_5Cended_path {
497 0 0 0 0   0 if ((@_ >= 1) and ($_[0] ne '')) {
498 0 0       0 if ($^O =~ /\A (?: MSWin32 | NetWare | symbian | dos ) \z/oxms) {
499 0         0 my @char = $_[0] =~ m{\G ($re_char) }oxmsg;
500 0 0       0 if ($char[-1] =~ m{ \x5C \z}oxms) {
501 0         0 return 1;
502             }
503             }
504             }
505 0         0 return undef;
506             }
507              
508             #---------------------------------------------------------------------
509             sub _first(&@) {
510 1     1   14 my $coderef = shift @_;
511 1         4 for (@_) {
512 4 100       16 if ($coderef->()) {
513 1         6 return $_;
514             }
515             }
516 0         0 return undef;
517             }
518              
519             #---------------------------------------------------------------------
520             sub _shuffle(@) {
521 1     1   17 my @a = \(@_);
522 1         2 my $n;
523 1         2 my $i=@_;
524 1         3 return map { $n = rand($i--); (${$a[$n]}, $a[$n] = $a[$i])[0]; } @_;
  26         67  
  26         32  
  26         56  
525             }
526              
527             #---------------------------------------------------------------------
528             sub _uniq {
529 1     1   10 my %seen = ();
530 1         2 return grep { not $seen{$_}++ } @_;
  10         25  
531             }
532              
533             #---------------------------------------------------------------------
534             sub _say {
535 1     1   56 my $currfh = select();
536 1         3 my $handle;
537             {
538 25     25   238 no strict 'refs';
  25         51  
  25         2844  
  1         2  
539 1 50       8 $handle = defined(fileno($_[0])) ? shift : \*$currfh;
540             }
541 1 50       5 @_ = ($_) unless @_;
542 1         4 return print {$handle} @_, "\n";
  1         79  
543             }
544              
545             1;
546              
547             __END__