File Coverage

blib/lib/ChordPro/Utils.pm
Criterion Covered Total %
statement 91 136 66.9
branch 20 52 38.4
condition 4 16 25.0
subroutine 21 25 84.0
pod 0 15 0.0
total 136 244 55.7


line stmt bran cond sub pod time code
1             #! perl
2              
3             package ChordPro::Utils;
4              
5 79     79   634 use strict;
  79         182  
  79         2407  
6 79     79   475 use warnings;
  79         199  
  79         2142  
7 79     79   1087 use utf8;
  79         219  
  79         489  
8 79     79   2339 use parent qw(Exporter);
  79         316  
  79         484  
9              
10             our @EXPORT;
11              
12             ################ Platforms ################
13              
14 79 50   79   13841 use constant MSWIN => $^O =~ /MSWin|Windows_NT/i ? 1 : 0;
  79         264  
  79         16223  
15              
16 0     0 0 0 sub is_msw { MSWIN }
17 0     0 0 0 sub is_macos { $^O =~ /darwin/ }
18              
19             push( @EXPORT, 'is_msw', 'is_macos' );
20              
21             ################ Filenames ################
22              
23 79 50   79   656 use File::Glob ( $] >= 5.016 ? ":bsd_glob" : ":glob" );
  79         199  
  79         21203  
24 79     79   635 use File::Spec;
  79         150  
  79         8952  
25              
26             # Derived from Path::ExpandTilde.
27              
28 79 50       6152 use constant BSD_GLOB_FLAGS => GLOB_NOCHECK | GLOB_QUOTE | GLOB_TILDE | GLOB_ERR
29             # add GLOB_NOCASE as in File::Glob
30 79     79   605 | ($^O =~ m/\A(?:MSWin32|VMS|os2|dos|riscos)\z/ ? GLOB_NOCASE : 0);
  79         177  
31              
32             # File::Glob did not try %USERPROFILE% (set in Windows NT derivatives) for ~ before 5.16
33 79     79   559 use constant WINDOWS_USERPROFILE => MSWIN && $] < 5.016;
  79         217  
  79         87021  
34              
35             sub expand_tilde {
36 368     368 0 1072 my ( $dir ) = @_;
37              
38 368 50       1170 return undef unless defined $dir;
39 368 50       3571 return File::Spec->canonpath($dir) unless $dir =~ m/^~/;
40              
41             # Parse path into segments.
42 0         0 my ( $volume, $directories, $file ) = File::Spec->splitpath( $dir, 1 );
43 0         0 my @parts = File::Spec->splitdir($directories);
44 0         0 my $first = shift( @parts );
45 0 0       0 return File::Spec->canonpath($dir) unless defined $first;
46              
47             # Expand first segment.
48 0         0 my $expanded;
49 0         0 if ( WINDOWS_USERPROFILE and $first eq '~' ) {
50             $expanded = $ENV{HOME} || $ENV{USERPROFILE};
51             }
52             else {
53 0         0 ( my $pattern = $first ) =~ s/([\\*?{[])/\\$1/g;
54 0         0 ($expanded) = bsd_glob( $pattern, BSD_GLOB_FLAGS );
55 0 0       0 croak( "Failed to expand $first: $!") if GLOB_ERROR;
56             }
57 0 0 0     0 return File::Spec->canonpath($dir)
58             if !defined $expanded or $expanded eq $first;
59              
60             # Replace first segment with new path.
61 0         0 ( $volume, $directories ) = File::Spec->splitpath( $expanded, 1 );
62 0         0 $directories = File::Spec->catdir( $directories, @parts );
63 0         0 return File::Spec->catpath($volume, $directories, $file);
64             }
65              
66             push( @EXPORT, 'expand_tilde' );
67              
68             sub findexe {
69 0     0 0 0 my ( $prog ) = @_;
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             sub sys {
94 0     0 0 0 my ( @cmd ) = @_;
95 0 0       0 warn("+ @cmd\n") if $::options->{trace};
96             # Use outer defined subroutine, depends on Wx or not.
97 0         0 my $res = ::sys(@cmd);
98 0 0       0 warn( sprintf("=%02x=> @cmd", $res), "\n" ) if $res;
99 0         0 return $res;
100             }
101              
102             push( @EXPORT, 'sys' );
103              
104             ################ (Pre)Processing ################
105              
106             sub make_preprocessor {
107 169     169 0 499 my ( $prp ) = @_;
108 169 50       541 return unless $prp;
109              
110 169         389 my $prep;
111 169         394 foreach my $linetype ( keys %{ $prp } ) {
  169         684  
112 507         776 my @targets;
113 507         876 my $code = "";
114 507         758 foreach ( @{ $prp->{$linetype} } ) {
  507         1118  
115 0   0     0 my $flags = $_->{flags} // "g";
116             $code .= "m\0" . $_->{select} . "\0 && "
117 0 0       0 if $_->{select};
118 0 0       0 if ( $_->{pattern} ) {
119             $code .= "s\0" . $_->{pattern} . "\0"
120 0         0 . $_->{replace} . "\0$flags;\n";
121             }
122             else {
123             $code .= "s\0" . quotemeta($_->{target}) . "\0"
124 0         0 . quotemeta($_->{replace}) . "\0$flags;\n";
125             }
126             }
127 507 50       1470 if ( $code ) {
128 0         0 my $t = "sub { for (\$_[0]) {\n" . $code . "}}";
129 0         0 $prep->{$linetype} = eval $t;
130 0 0       0 die( "CODE : $t\n$@" ) if $@;
131             }
132             }
133 169         750 $prep;
134             }
135              
136             push( @EXPORT, 'make_preprocessor' );
137              
138             ################ Utilities ################
139              
140             # Split (pseudo) command line into key/value pairs.
141              
142             sub parse_kv {
143 6     6 0 1326 my ( @lines ) = @_;
144              
145 79     79   41731 use Text::ParseWords qw(shellwords);
  79         111081  
  79         93222  
146 6         26 my @words = shellwords(@lines);
147              
148 6         1081 my $res = {};
149 6         20 foreach ( @words ) {
150 22 100       100 if ( /^(.*?)=(.+)/ ) {
    100          
151 14         62 $res->{$1} = $2;
152             }
153             elsif ( /^no[-_]?(.+)/ ) {
154 2         8 $res->{$1} = 0;
155             }
156             else {
157 6         18 $res->{$_}++;
158             }
159             }
160              
161 6         46 return $res;
162             }
163              
164             push( @EXPORT, 'parse_kv' );
165              
166             # Map true/false etc to true / false.
167              
168             sub is_true {
169 449     449 0 996 my ( $arg ) = @_;
170 449 50 33     1704 return if !defined($arg) || $arg eq '';
171 449 100       1845 return if $arg =~ /^(false|null|no|none|off|\s+|0)$/i;
172 436         1677 return !!$arg;
173             }
174              
175             push( @EXPORT, 'is_true' );
176              
177             # Stricter form of true.
178             sub is_ttrue {
179 9     9 0 30 my ( $arg ) = @_;
180 9 50       30 return if !defined($arg);
181 9         65 $arg =~ /^(on|true|1)$/i;
182             }
183              
184             push( @EXPORT, 'is_ttrue' );
185              
186             # Fix apos -> quote.
187              
188             sub fq {
189 1179     1179 0 2243 my ( $arg ) = @_;
190 1179         2375 $arg =~ s/'/\x{2019}/g;
191 1179         3801 $arg;
192             }
193              
194             push( @EXPORT, 'fq' );
195              
196             # Quote a string.
197              
198             sub qquote {
199 11     11 0 43 my ( $arg, $force ) = @_;
200 11         34 for ( $arg ) {
201 11         31 s/([\\\"])/\\$1/g;
202 11         29 s/([[:^print:]])/sprintf("\\u%04x", ord($1))/ge;
  0         0  
203 11 100       56 return $_ unless /[\\\s]/;
204 3         15 return qq("$_");
205             }
206             }
207              
208             push( @EXPORT, 'qquote' );
209              
210             # Turn foo.bar.blech=blah into { foo => { bar => { blech ==> "blah" } } }.
211              
212             sub prp2cfg {
213 119     119 0 503 my ( $defs, $cfg ) = @_;
214 119         335 my $ccfg = {};
215 119   50     525 $cfg //= {};
216 119         799 while ( my ($k, $v) = each(%$defs) ) {
217 10         81 my @k = split( /[:.]/, $k );
218 10         29 my $c = \$ccfg; # new
219 10         25 my $o = $cfg; # current
220 10         29 my $lk = pop(@k); # last key
221              
222             # Step through the keys.
223 10         29 foreach ( @k ) {
224 13         32 $c = \($$c->{$_});
225 13         34 $o = $o->{$_};
226             }
227              
228             # Final key. Merge array if so.
229 10 50 33     64 if ( $lk =~ /^\d+$/ && ref($o) eq 'ARRAY' ) {
230 0 0       0 unless ( ref($$c) eq 'ARRAY' ) {
231             # Only copy orig values the first time.
232 0         0 $$c->[$_] = $o->[$_] for 0..scalar(@{$o})-1;
  0         0  
233             }
234 0         0 $$c->[$lk] = $v;
235             }
236             else {
237 10         73 $$c->{$lk} = $v;
238             }
239             }
240 119         507 return $ccfg;
241             }
242              
243             push( @EXPORT, 'prp2cfg' );
244              
245             # Remove markup.
246             sub demarkup {
247 2254     2254 0 19454 my ( $t ) = @_;
248 2254         4318 return join( '', grep { ! /^\
  2287         10530  
249             }
250             push( @EXPORT, 'demarkup' );
251              
252             # Split into markup/nonmarkup segments.
253             sub splitmarkup {
254 2276     2276 0 3746 my ( $t ) = @_;
255 2276         11235 my @t = split( qr;();, $t );
256 2276         7172 return @t;
257             }
258             push( @EXPORT, 'splitmarkup' );
259              
260             # For conditional filling of hashes.
261             sub maybe($$@) {
262 46 50 33 46 0 217 if (defined $_[0] and defined $_[1]) {
263 0         0 @_;
264             }
265             else {
266 46 50       372 ( scalar @_ > 1 ) ? @_[2 .. $#_] : ();
267             }
268             }
269             push( @EXPORT, "maybe" );
270              
271             1;