| 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 { ! /^\ } splitmarkup($t) ); | 
|  | 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;(?(?:[-\w]+|span\s.*?)>);, $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; |