File Coverage

blib/lib/ChordPro/Utils.pm
Criterion Covered Total %
statement 123 176 69.8
branch 19 50 38.0
condition 8 22 36.3
subroutine 23 27 85.1
pod 0 15 0.0
total 173 290 59.6


line stmt bran cond sub pod time code
1             #! perl
2              
3             package ChordPro::Utils;
4              
5 79     79   981 use v5.26;
  79         342  
6 79     79   1443 use utf8;
  79         280  
  79         454  
7 79     79   1804 use Carp;
  79         188  
  79         4691  
8 79     79   594 use feature qw( signatures );
  79         264  
  79         7422  
9 79     79   657 no warnings "experimental::signatures";
  79         285  
  79         4096  
10 79     79   574 use parent qw(Exporter);
  79         282  
  79         2236  
11              
12             our @EXPORT;
13              
14             ################ Platforms ################
15              
16 79 50   79   11935 use constant MSWIN => $^O =~ /MSWin|Windows_NT/i ? 1 : 0;
  79         226  
  79         19957  
17              
18 0     0 0 0 sub is_msw () { MSWIN }
  0         0  
  0         0  
19 0     0 0 0 sub is_macos () { $^O =~ /darwin/ }
  0         0  
  0         0  
20              
21             push( @EXPORT, 'is_msw', 'is_macos' );
22              
23             ################ Filenames ################
24              
25 79     79   681 use File::Glob ( ":bsd_glob" );
  79         259  
  79         20406  
26 79     79   663 use File::Spec;
  79         205  
  79         9287  
27              
28             # Derived from Path::ExpandTilde.
29              
30 79 50       7048 use constant BSD_GLOB_FLAGS => GLOB_NOCHECK | GLOB_QUOTE | GLOB_TILDE | GLOB_ERR
31             # add GLOB_NOCASE as in File::Glob
32 79     79   610 | ($^O =~ m/\A(?:MSWin32|VMS|os2|dos|riscos)\z/ ? GLOB_NOCASE : 0);
  79         221  
33              
34             # File::Glob did not try %USERPROFILE% (set in Windows NT derivatives) for ~ before 5.16
35 79     79   641 use constant WINDOWS_USERPROFILE => MSWIN && $] < 5.016;
  79         240  
  79         106795  
36              
37 368     368 0 739 sub expand_tilde ( $dir ) {
  368         854  
  368         659  
38              
39 368 50       1040 return undef unless defined $dir;
40 368 50       3442 return File::Spec->canonpath($dir) unless $dir =~ m/^~/;
41              
42             # Parse path into segments.
43 0         0 my ( $volume, $directories, $file ) = File::Spec->splitpath( $dir, 1 );
44 0         0 my @parts = File::Spec->splitdir($directories);
45 0         0 my $first = shift( @parts );
46 0 0       0 return File::Spec->canonpath($dir) unless defined $first;
47              
48             # Expand first segment.
49 0         0 my $expanded;
50 0         0 if ( WINDOWS_USERPROFILE and $first eq '~' ) {
51             $expanded = $ENV{HOME} || $ENV{USERPROFILE};
52             }
53             else {
54 0         0 ( my $pattern = $first ) =~ s/([\\*?{[])/\\$1/g;
55 0         0 ($expanded) = bsd_glob( $pattern, BSD_GLOB_FLAGS );
56 0 0       0 croak( "Failed to expand $first: $!") if GLOB_ERROR;
57             }
58 0 0 0     0 return File::Spec->canonpath($dir)
59             if !defined $expanded or $expanded eq $first;
60              
61             # Replace first segment with new path.
62 0         0 ( $volume, $directories ) = File::Spec->splitpath( $expanded, 1 );
63 0         0 $directories = File::Spec->catdir( $directories, @parts );
64 0         0 return File::Spec->catpath($volume, $directories, $file);
65             }
66              
67             push( @EXPORT, 'expand_tilde' );
68              
69 0     0 0 0 sub findexe ( $prog ) {
  0         0  
  0         0  
70 0         0 my @path;
71 0         0 if ( MSWIN ) {
72             $prog .= ".exe" unless $prog =~ /\.\w+$/;
73             @path = split( ';', $ENV{PATH} );
74             unshift( @path, '.' );
75             }
76             else {
77 0         0 @path = split( ':', $ENV{PATH} );
78             }
79 0         0 foreach ( @path ) {
80 0         0 my $try = "$_/$prog";
81 0 0       0 if ( -f -x $try ) {
82             #warn("Found $prog in $_\n");
83 0         0 return $try;
84             }
85             }
86             warn("Could not find $prog in ",
87 0         0 join(" ", map { qq{"$_"} } @path), "\n");
  0         0  
88 0         0 return;
89             }
90              
91             push( @EXPORT, 'findexe' );
92              
93 0     0 0 0 sub sys ( @cmd ) {
  0         0  
  0         0  
94 0 0       0 warn("+ @cmd\n") if $::options->{trace};
95             # Use outer defined subroutine, depends on Wx or not.
96 0         0 my $res = ::sys(@cmd);
97 0 0       0 warn( sprintf("=%02x=> @cmd", $res), "\n" ) if $res;
98 0         0 return $res;
99             }
100              
101             push( @EXPORT, 'sys' );
102              
103             ################ (Pre)Processing ################
104              
105 169     169 0 347 sub make_preprocessor ( $prp ) {
  169         356  
  169         299  
106 169 50       693 return unless $prp;
107              
108 169         341 my $prep;
109 169         360 foreach my $linetype ( keys %{ $prp } ) {
  169         685  
110 507         757 my @targets;
111 507         817 my $code = "";
112 507         774 foreach ( @{ $prp->{$linetype} } ) {
  507         1113  
113 0   0     0 my $flags = $_->{flags} // "g";
114             $code .= "m\0" . $_->{select} . "\0 && "
115 0 0       0 if $_->{select};
116 0 0       0 if ( $_->{pattern} ) {
117             $code .= "s\0" . $_->{pattern} . "\0"
118 0         0 . $_->{replace} . "\0$flags;\n";
119             }
120             else {
121             $code .= "s\0" . quotemeta($_->{target}) . "\0"
122 0         0 . quotemeta($_->{replace}) . "\0$flags;\n";
123             }
124             }
125 507 50       1307 if ( $code ) {
126 0         0 my $t = "sub { for (\$_[0]) {\n" . $code . "}}";
127 0         0 $prep->{$linetype} = eval $t;
128 0 0       0 die( "CODE : $t\n$@" ) if $@;
129             }
130             }
131 169         628 $prep;
132             }
133              
134             push( @EXPORT, 'make_preprocessor' );
135              
136             ################ Utilities ################
137              
138             # Split (pseudo) command line into key/value pairs.
139              
140 6     6 0 6030 sub parse_kv ( @lines ) {
  6         17  
  6         11  
141              
142 79     79   42506 use Text::ParseWords qw(shellwords);
  79         110323  
  79         115175  
143 6         23 my @words = shellwords(@lines);
144              
145 6         1071 my $res = {};
146 6         17 foreach ( @words ) {
147 22 100       125 if ( /^(.*?)=(.+)/ ) {
    100          
148 14         46 $res->{$1} = $2;
149             }
150             elsif ( /^no[-_]?(.+)/ ) {
151 2         8 $res->{$1} = 0;
152             }
153             else {
154 6         17 $res->{$_}++;
155             }
156             }
157              
158 6         31 return $res;
159             }
160              
161             push( @EXPORT, 'parse_kv' );
162              
163             # Map true/false etc to true / false.
164              
165 449     449 0 757 sub is_true ( $arg ) {
  449         694  
  449         627  
166 449 50 33     1703 return if !defined($arg) || $arg eq '';
167 449 100       1885 return if $arg =~ /^(false|null|no|none|off|\s+|0)$/i;
168 436         1610 return !!$arg;
169             }
170              
171             push( @EXPORT, 'is_true' );
172              
173             # Stricter form of true.
174 9     9 0 21 sub is_ttrue ( $arg ) {
  9         19  
  9         15  
175 9 50       31 return if !defined($arg);
176 9         70 $arg =~ /^(on|true|1)$/i;
177             }
178              
179             push( @EXPORT, 'is_ttrue' );
180              
181             # Fix apos -> quote.
182              
183 1179     1179 0 1619 sub fq ( $arg ) {
  1179         1853  
  1179         1544  
184 1179         2647 $arg =~ s/'/\x{2019}/g;
185 1179         3776 $arg;
186             }
187              
188             push( @EXPORT, 'fq' );
189              
190             # Quote a string if needed unless forced.
191              
192 11     11 0 23 sub qquote ( $arg, $force = 0 ) {
  11         22  
  11         19  
  11         14  
193 11         31 for ( $arg ) {
194 11         32 s/([\\\"])/\\$1/g;
195 11         36 s/([[:^print:]])/sprintf("\\u%04x", ord($1))/ge;
  0         0  
196 11 100 100     65 return $_ unless /[\\\s]/ || $force;
197 9         43 return qq("$_");
198             }
199             }
200              
201             push( @EXPORT, 'qquote' );
202              
203             # Turn foo.bar.blech=blah into { foo => { bar => { blech ==> "blah" } } }.
204              
205 119     119 0 307 sub prp2cfg ( $defs, $cfg ) {
  119         290  
  119         296  
  119         266  
206 119         349 my $ccfg = {};
207 119   50     514 $cfg //= {};
208 119         827 while ( my ($k, $v) = each(%$defs) ) {
209 10         85 my @k = split( /[:.]/, $k );
210 10         25 my $c = \$ccfg; # new
211 10         25 my $o = $cfg; # current
212 10         28 my $lk = pop(@k); # last key
213              
214             # Step through the keys.
215 10         30 foreach ( @k ) {
216 13         38 $c = \($$c->{$_});
217 13         34 $o = $o->{$_};
218             }
219              
220             # Final key. Merge array if so.
221 10 50 33     63 if ( $lk =~ /^\d+$/ && ref($o) eq 'ARRAY' ) {
222 0 0       0 unless ( ref($$c) eq 'ARRAY' ) {
223             # Only copy orig values the first time.
224 0         0 $$c->[$_] = $o->[$_] for 0..scalar(@{$o})-1;
  0         0  
225             }
226 0         0 $$c->[$lk] = $v;
227             }
228             else {
229 10         80 $$c->{$lk} = $v;
230             }
231             }
232 119         448 return $ccfg;
233             }
234              
235             push( @EXPORT, 'prp2cfg' );
236              
237             # Remove markup.
238 2254     2254 0 18170 sub demarkup ( $t ) {
  2254         3474  
  2254         2907  
239 2254         4225 return join( '', grep { ! /^\
  2287         11396  
240             }
241             push( @EXPORT, 'demarkup' );
242              
243             # Split into markup/nonmarkup segments.
244 2276     2276 0 3067 sub splitmarkup ( $t ) {
  2276         3180  
  2276         3332  
245 2276         11431 my @t = split( qr;();, $t );
246 2276         7056 return @t;
247             }
248             push( @EXPORT, 'splitmarkup' );
249              
250             # For conditional filling of hashes.
251 46     46 0 68 sub maybe ( $key, $value, @rest ) {
  46         81  
  46         59  
  46         78  
  46         81  
252 46 50 33     189 if (defined $key and defined $value) {
253 0         0 return ( $key, $value, @rest );
254             }
255             else {
256 46 50 33     344 ( defined($key) || @rest ) ? @rest : ();
257             }
258             }
259             push( @EXPORT, "maybe" );
260              
261             1;