File Coverage

lib/ChordPro/Files.pm
Criterion Covered Total %
statement 121 192 63.0
branch 36 90 40.0
condition 11 24 45.8
subroutine 24 30 80.0
pod 0 13 0.0
total 192 349 55.0


line stmt bran cond sub pod time code
1             #! perl
2              
3 104     104   150570 use v5.26;
  104         414  
4 104     104   771 use feature qw( signatures );
  104         228  
  104         39487  
5 104     104   828 no warnings qw( experimental::signatures );
  104         231  
  104         5386  
6 104     104   8194 use utf8;
  104         4502  
  104         1196  
7              
8             package ChordPro::Files;
9              
10             # Generalize some file system operations so they use LongPath on Windows.
11             # This is necessary for long filenames and unicode filenames.
12              
13             # NOTE: FILENAMES SHOULD AT ALL TIMES BE PERL STRINGS!
14              
15             # Where do filenames come from?
16             #
17             # 1. Command line arguments. Decode ASAP.
18             # 2. File (and directory) dialogs: Always perl string.
19             # 3. Preferences, configs, recents: should all be perl strings.
20             # 4. From filelists. We expect these lists to have UTF8 filenames that
21             # get decoded when the list is read.
22              
23 104     104   69590 use Encode qw( decode_utf8 encode_utf8 );
  104         2116384  
  104         16036  
24 104     104   64625 use Ref::Util qw(is_ref);
  104         294061  
  104         11416  
25              
26 104     104   950 use Exporter 'import';
  104         277  
  104         15554  
27             our @EXPORT;
28             our @EXPORT_OK;
29              
30             ################ Platforms ################
31              
32 104 50   104   800 use constant MSWIN => $^O =~ /MSWin|Windows_NT/i ? 1 : 0;
  104         308  
  104         180710  
33              
34 2582     2582 0 8788 sub is_msw () { MSWIN }
  2582         3792  
  2582         166323  
35 2     2 0 6 sub is_macos () { $^O =~ /darwin/ }
  2         3  
  2         12  
36 0     0 0 0 sub is_wx () { defined($Wx::wxVERSION) }
  0         0  
  0         0  
37              
38             push( @EXPORT, qw( is_msw is_macos is_wx ) );
39              
40             if ( is_msw ) {
41             require Win32::LongPath;
42             }
43              
44             ################ ################
45              
46             # General pattern:
47             # If Windows, call Windows specific function.
48             # Otherwise
49             # If the filename contains UTF8 characters, encode.
50             # Call standard perl function.
51              
52 490     490 0 323484 sub fs_open( $name, $mode = '<:utf8' ) {
  490         1039  
  490         1123  
  490         853  
53 490         900 my $fd;
54 490 50       1586 if ( is_msw ) {
55 0 0       0 Win32::LongPath::openL( \$fd, $mode, $name )
56             or die("$name: $^E\n");
57 0         0 return $fd;
58             }
59              
60 490         1179 my $uname = $name;
61 490 100       2761 $uname = encode_utf8($name) if utf8::is_utf8($uname);
62              
63 490 50       67166 open( $fd, $mode, $uname )
64             or die("$name: $!\n");
65 490         2916 return $fd;
66             }
67              
68             push( @EXPORT, qw(fs_open) );
69              
70 1229     1229 0 2346 sub fs_test( $tests, $name ) {
  1229         2394  
  1229         2255  
  1229         1957  
71 1229         2176 my $res = 1;
72 1229         4058 for my $test ( split( //, $tests ) ) {
73 1860         4593 $res = _fs_test( $test, $name );
74 1860 100       8677 return unless $res;
75             }
76 756         17228 $res;
77             }
78              
79 1860     1860   3031 sub _fs_test( $test, $name ) {
  1860         3173  
  1860         5217  
  1860         7971  
80 1860 50       6426 return Win32::LongPath::testL( $test, $name ) if is_msw;
81              
82 1860         4524 my $uname = $name;
83 1860 100       5802 $uname = encode_utf8($name) if utf8::is_utf8($uname);
84              
85 1860 50       13478 if ( $test eq 'b' ) { return -b $uname }
  0 50       0  
    100          
    50          
    100          
    50          
    50          
    50          
    100          
    50          
    50          
    0          
    0          
    0          
    0          
    0          
86 0         0 elsif ( $test eq 'c' ) { return -c $uname }
87 182         4982 elsif ( $test eq 'd' ) { return -d $uname }
88 0         0 elsif ( $test eq 'e' ) { return -e $uname }
89 822         20686 elsif ( $test eq 'f' ) { return -f $uname }
90 0         0 elsif ( $test eq 'l' ) { return -l $uname }
91 0         0 elsif ( $test eq 'o' ) { return -o $uname }
92 0         0 elsif ( $test eq 'O' ) { return -O $uname }
93 6         163 elsif ( $test eq 'r' ) { return -r $uname }
94 0         0 elsif ( $test eq 'R' ) { return -R $uname }
95 850         14818 elsif ( $test eq 's' ) { return -s $uname }
96 0         0 elsif ( $test eq 'w' ) { return -w $uname }
97 0         0 elsif ( $test eq 'W' ) { return -W $uname }
98 0         0 elsif ( $test eq 'x' ) { return -x $uname }
99 0         0 elsif ( $test eq 'X' ) { return -X $uname }
100 0         0 elsif ( $test eq 'z' ) { return -z $uname }
101 0         0 else { die("Invalid test '$test' for $name\n") }
102             }
103              
104             push( @EXPORT, qw(fs_test) );
105              
106 3     3 0 4213 sub fs_unlink( $name ) {
  3         8  
  3         5  
107              
108 3 50       10 return Win32::LongPath::unlinkL($name) if is_msw;
109              
110 3         6 my $uname = $name;
111 3 50       41 $uname = encode_utf8($name) if utf8::is_utf8($uname);
112 3         533 unlink($uname);
113             }
114              
115             push( @EXPORT, qw(fs_unlink) );
116              
117 1     1 0 1916 sub fs_find( $folder, $opts = {} ) {
  1         3  
  1         2  
  1         2  
118              
119 1   33     5 my $filter = $opts->{filter} // qr/[.]/i;
120 1   50     8 my $recurse = $opts->{recurse} // 1;
121 1         5 $opts->{subfolders} = 0;
122              
123 1 50       3 unless ( is_msw ) {
124 1         3 my $ufolder = $folder;
125 1 50       5 $ufolder = encode_utf8($folder) if utf8::is_utf8($folder);
126              
127 104     104   1056 use File::Find qw(find);
  104         222  
  104         136240  
128 1         2 my @files;
129              
130             find sub {
131 268 100 100 268   9405 if ( -d && $File::Find::name ne $folder ) {
    100 66        
132 8         20 $File::Find::prune = !$recurse;
133 8         1387 $opts->{subfolders} = 1;
134             }
135             elsif ( -s _ && $_ =~ $filter ) {
136 3         10 my $i = 0;
137 3         10 my @st = stat(_);
138             push( @files,
139             { name => decode_utf8($File::Find::name =~ s;^\Q$ufolder\E/?;;r),
140 3         78 map { $_ => $st[$i++] }
  39         251  
141             qw{ dev ino mode nlink uid gid rdev size
142             atime mtime ctime blksize blocks }
143             } );
144             }
145 1         122 }, $ufolder;
146              
147 1         17 @files = sort { $a->{name} cmp $b->{name} } @files;
  2         10  
148 1         7 return \@files;
149             }
150              
151 0     0 0 0 sub search_tree( $path, $opts, $folder ) {
  0         0  
  0         0  
  0         0  
  0         0  
152              
153 0   0     0 my $filter = $opts->{filter} // qr/[.]/i;
154 0   0     0 my $recurse = $opts->{recurse} // 1;
155 0         0 my $dir = Win32::LongPath->new;
156 0         0 my @files;
157 0 0       0 $dir->opendirL($path)
158             or die ("$path: $^E\n");
159              
160 0         0 foreach my $file ( $dir->readdirL ) {
161             # Skip parent dir.
162 0 0       0 next if $file eq '..';
163             # Get file stats.
164 0 0       0 my $name = $file eq '.' ? $path : "$path/$file";
165 0 0       0 my $stat = Win32::LongPath::lstatL($name)
166             or die( "stat($name,", Win32::LongPath::getcwdL(), "): $^E\n" );
167              
168             # Recurse if dir.
169 0 0 0     0 if ( ( $file ne '.' )
170             && ( ($stat->{attribs}
171             & ( Win32::LongPath::FILE_ATTRIBUTE_DIRECTORY()
172             | Win32::LongPath::FILE_ATTRIBUTE_REPARSE_POINT() ) )
173             == Win32::LongPath::FILE_ATTRIBUTE_DIRECTORY() ) ) {
174 0 0       0 push( @files, @{ search_tree( $name, $opts, $folder ) } )
  0         0  
175             if $recurse;
176 0         0 $opts->{subfolders} = 1;
177 0         0 next;
178             }
179 0         0 $name =~ s;^\Q$folder\E/?;;;
180 0 0       0 push( @files, { #%$stat,
181             name => $name,
182             full => Win32::LongPath::abspathL($name) } )
183             if $file =~ $filter;
184             }
185              
186 0         0 $dir->closedirL;
187 0         0 return \@files;
188             }
189              
190 0         0 return [ sort { $a->{name} cmp $b->{name} }
191 0         0 @{ search_tree( $folder, $opts, $folder ) } ];
  0         0  
192              
193             }
194              
195             push( @EXPORT, qw(fs_find) );
196              
197 0     0 0 0 sub fs_copy( $from, $to ) {
  0         0  
  0         0  
  0         0  
198 0 0       0 return Win32::LongPath::copyL( $from, $to ) if is_msw;
199              
200 0 0       0 $to = encode_utf8($to) if utf8::is_utf8($to);
201 0 0       0 $from = encode_utf8($from) if utf8::is_utf8($from);
202              
203 104     104   67335 use File::Copy;
  104         710984  
  104         19358  
204 0         0 copy( $from, $to );
205             }
206              
207             push( @EXPORT, qw(fs_copy) );
208              
209             # Wrapper for File::LoadLines.
210              
211 479     479 0 1209 sub fs_load( $name, $opts = {} ) {
  479         1148  
  479         1104  
  479         899  
212              
213 104     104   62983 use File::LoadLines;
  104         316183  
  104         55244  
214              
215 479   100     2430 $opts->{fail} //= "soft";
216              
217 479         1878 my $ret;
218 479         1578 eval {
219 479 100 66     4967 if ( is_ref($name) || $name =~ m;^\w\w+:; ) {
220 82         505 $ret = loadlines( $name, $opts );
221             }
222             else {
223 397 50       2248 my $fd = $name eq '-' ? \*STDIN : fs_open($name);
224 397         2441 $ret = loadlines( $fd, $opts );
225 397         478599 $opts->{_filesource} = $name;
226             }
227             };
228 479 50       13162 return $ret unless $@;
229              
230 0         0 my $msg = $@;
231 0 0       0 die( "$msg\n" ) unless $opts->{fail} eq "soft";
232 0 0       0 $msg = $1 if $msg =~ /^\Q$name\E: (.*)$/;
233 0         0 $opts->{error} = $msg;
234 0         0 return;
235             }
236              
237 0     0 0 0 sub fs_blob( $name, $opts = {} ) {
  0         0  
  0         0  
  0         0  
238 0         0 fs_load( $name, { blob => 1, %$opts } );
239             }
240              
241             push( @EXPORT, qw(fs_load fs_blob) );
242              
243             ################ File::Spec functions ################
244              
245             # Adapted from File::Spec::Functions 3.75.
246              
247             # Function fn_catfile = File::Spec->catfile, etc.
248              
249 104     104   1099 use File::Spec;
  104         219  
  104         24430  
250             require File::Spec::Unix;
251              
252             my @funcs =
253             qw( canonpath
254             catdir
255             catfile
256             curdir
257             rootdir
258             updir
259             is_absolute
260             splitpath
261             catpath
262             path
263             devnull
264             tmpdir
265             splitdir
266             abs2rel
267             rel2abs
268             case_tolerant
269             );
270             push( @EXPORT, map { "fn_$_" } @funcs );
271              
272             my %udeps = ( canonpath => [],
273             catdir => [ qw(canonpath) ],
274             catfile => [ qw(canonpath catdir) ],
275             case_tolerant => [],
276             curdir => [],
277             devnull => [],
278             rootdir => [],
279             updir => [],
280             );
281              
282             foreach my $meth ( @funcs ) {
283             $meth = 'file_name_is_absolute' if $meth eq 'is_absolute';
284             my $sub = File::Spec->can($meth);
285 104     104   933 no strict 'refs';
  104         291  
  104         47341  
286             if ( exists( $udeps{$meth} )
287             && $sub == File::Spec::Unix->can($meth)
288             && !( grep { File::Spec->can($_) != File::Spec::Unix->can($_) }
289             @{$udeps{$meth} } )
290             && defined( &{"File::Spec::Unix::_fn_$meth"} ) ) {
291             *{"fn_$meth"} = \&{"File::Spec::Unix::_fn_$meth"};
292             }
293             else {
294             $meth = 'is_absolute' if $meth eq 'file_name_is_absolute';
295 313     313   7829 *{"fn_$meth"} = sub { &$sub( 'File::Spec', @_) };
296             }
297             }
298              
299             ################ File::Basename functions ################
300              
301             # For now, use File:Basename functions.
302             # Refine to File::Spec functions later.
303              
304 104     104   927 use File::Basename ();
  104         218  
  104         25204  
305              
306 0     0 0   sub fn_basename( $fullname, @suffixlist ) {
  0            
  0            
  0            
307 0           File::Basename::basename( $fullname, @suffixlist );
308             }
309              
310             push( @EXPORT, "fn_basename" );
311              
312 0     0 0   sub fn_dirname( $fullname ) {
  0            
  0            
313 0           File::Basename::dirname( $fullname );
314             }
315              
316             push( @EXPORT, "fn_dirname" );
317              
318             1;