File Coverage

lib/ChordPro/Output/Common.pm
Criterion Covered Total %
statement 148 164 90.2
branch 48 74 64.8
condition 29 42 69.0
subroutine 15 17 88.2
pod 0 6 0.0
total 240 303 79.2


line stmt bran cond sub pod time code
1             #! perl
2              
3             package main;
4              
5             our $config;
6             our $options;
7              
8             package ChordPro::Output::Common;
9              
10 90     90   710 use strict;
  90         213  
  90         4072  
11 90     90   497 use warnings;
  90         207  
  90         6436  
12 90     90   555 use ChordPro::Chords;
  90         202  
  90         2744  
13 90     90   514 use ChordPro::Utils qw( demarkup is_true );
  90         192  
  90         6790  
14 90     90   965 use String::Interpolate::Named;
  90         221  
  90         8235  
15 90     90   639 use utf8;
  90         229  
  90         1080  
16 90     90   59725 use POSIX qw(setlocale LC_TIME strftime);
  90         755717  
  90         858  
17 90     90   175655 use Ref::Util qw( is_arrayref );
  90         252  
  90         5822  
18              
19 90     90   580 use Exporter 'import';
  90         179  
  90         245260  
20             our @EXPORT;
21             our @EXPORT_OK;
22              
23             sub fmt_subst {
24 2406     2406 0 273381 my ( $s, $t ) = @_;
25 2406         5089 my $res = "";
26 2406 50       4101 my $m = { %{$s->{meta} || {} } };
  2406         25089  
27              
28             # Derived item(s).
29 2406 100       10144 $m->{_key} = $m->{key} if exists $m->{key};
30 2406 100 100     9705 if ( $m->{key} && $m->{capo} && (my $capo = $m->{capo}->[-1]) ) {
      100        
31             ####CHECK
32             $m->{_key} =
33 123         555 [ map { ChordPro::Chords::transpose( $_, $capo ) }
34 115         272 @{$m->{key}} ];
  115         425  
35             }
36 2406 100       13792 $m->{capo} = [] if $config->{settings}->{decapo};
37 2406   100     11335 $m->{key_actual} //= $m->{key};
38 2406   50     15921 $m->{tuning} //= [ join(" ", ChordPro::Chords::get_tuning) ];
39             # If config->{instrument} is missing, or null, the program abends with
40             # Modification of a read-only value attempted.
41 2406 50       8705 if ( $config->{instrument} ) {
42 2406         9958 $m->{instrument} = [ $config->{instrument}->{type} ];
43 2406         7570 $m->{"instrument.type"} = [ $config->{instrument}->{type} ];
44 2406         9384 $m->{"instrument.description"} = [ $config->{instrument}->{description} ];
45             }
46             # Same here.
47 2406 50       8202 if ( $config->{user} ) {
48 2406         8156 $m->{user} = [ $config->{user}->{name} ];
49 2406         7388 $m->{"user.name"} = [ $config->{user}->{name} ];
50 2406         8217 $m->{"user.fullname"} = [ $config->{user}->{fullname} ];
51             }
52 2406         22508 setlocale( LC_TIME, "" );
53             $m->{today} //= strftime( $config->{dates}->{today}->{format},
54 2406   33     112283 localtime(time) );
55 2406         8652 $m->{chordpro} = "ChordPro";
56 2406         7282 $m->{"chordpro.version"} = $ChordPro::VERSION;
57 2406         4260 for ( keys %{ $config->{settings} } ) {
  2406         24418  
58 55338         103882 my $v = $config->{settings}->{$_};
59 55338 100       143658 $v = '' if $v =~ /^(0|false|off)$/i;
60 55338 100       113196 $v = 1 if $v=~ /^(true|on)$/i;
61 55338         157606 $m->{"settings.$_"} = $v;
62             }
63              
64             interpolate( { %$s, args => $m,
65             separator => $config->{metadata}->{separator} },
66 2406         43000 $t );
67             }
68             push( @EXPORT, 'fmt_subst' );
69              
70             # Roman - functions for converting between Roman and Arabic numerals
71             #
72             # Stolen from Roman Version 1.24 by OZAWA Sakuro
73             # 1995-1997 and Alexandr Ciornii, C<< >> 2007
74             #
75             # Copyright (c) 1995 OZAWA Sakuro. All rights reserved. This program
76             # is free software; you can redistribute it and/or modify it under the
77             # same terms as Perl itself.
78              
79             our %roman2arabic = qw(I 1 V 5 X 10 L 50 C 100 D 500 M 1000);
80             my %roman_digit = qw(1 IV 10 XL 100 CD 1000 MMMMMM);
81             my @figure = reverse sort keys %roman_digit;
82             #my %roman_digit;
83             $roman_digit{$_} = [split(//, $roman_digit{$_}, 2)] foreach @figure;
84              
85             sub isroman($) {
86 0     0 0 0 my $arg = shift;
87 0 0       0 $arg ne '' and
88             $arg =~ /^(?: M{0,3})
89             (?: D?C{0,3} | C[DM])
90             (?: L?X{0,3} | X[LC])
91             (?: V?I{0,3} | I[VX])$/ix;
92             }
93             push( @EXPORT_OK, 'isroman' );
94              
95             sub arabic($) {
96 0     0 0 0 my $arg = shift;
97 0 0       0 isroman $arg or return undef;
98 0         0 my($last_digit) = 1000;
99 0         0 my($arabic);
100 0         0 foreach (split(//, uc $arg)) {
101 0         0 my($digit) = $roman2arabic{$_};
102 0 0       0 $arabic -= 2 * $last_digit if $last_digit < $digit;
103 0         0 $arabic += ($last_digit = $digit);
104             }
105 0         0 $arabic;
106             }
107             push( @EXPORT_OK, 'arabic' );
108              
109             sub Roman($) {
110 32     32 0 104 my $arg = shift;
111 32 50 33     267 0 < $arg and $arg < 4000 or return undef;
112 32         103 my($x, $roman);
113 32         116 foreach (@figure) {
114 128         326 my($digit, $i, $v) = (int($arg / $_), @{$roman_digit{$_}});
  128         560  
115 128 100 66     908 if (1 <= $digit and $digit <= 3) {
    50 33        
    50          
    50          
    50          
116 32         126 $roman .= $i x $digit;
117             } elsif ($digit == 4) {
118 0         0 $roman .= "$i$v";
119             } elsif ($digit == 5) {
120 0         0 $roman .= $v;
121             } elsif (6 <= $digit and $digit <= 8) {
122 0         0 $roman .= $v . $i x ($digit - 5);
123             } elsif ($digit == 9) {
124 0         0 $roman .= "$i$x";
125             }
126 128         308 $arg -= $digit * $_;
127 128         311 $x = $i;
128             }
129 32         314 $roman;
130             }
131             push( @EXPORT_OK, 'Roman' );
132              
133             sub roman($) {
134 32     32 0 164 lc( Roman(shift) );
135             }
136             push( @EXPORT_OK, 'roman' );
137              
138             # Prepare outlines.
139             # This mainly untangles alternative names when being sorted on.
140             # Returns a book array where each element consists of the sort items,
141             # and the song.
142              
143             #sub PODBG() { $config->{debug}->{x1} }
144             sub PODBG() { 0 }
145              
146             # Suppress toc entry.
147             sub _suppresstoc {
148 111     111   290 my ( $meta ) = @_;
149 111 50       357 return !is_true($meta->{_TOC}->[0]) if exists($meta->{_TOC});
150             # return unless exists($meta->{sorttitle});
151             # my $st = $meta->{sorttitle};
152             # defined($st) && ( $st->[0] eq "" || $st->[0] eq '""' );
153 111         319 return;
154             }
155              
156             sub prep_outlines {
157 37     37 0 8116 my ( $book, $ctl ) = @_;
158 37 50 33     276 return [] unless $book && @$book; # unlikely
159 37 50       165 return [] if $ctl->{omit};
160              
161 37         85 warn("FLD: ", join(" ", @{$ctl->{fields}}), "\n") if PODBG;
162              
163 37 50       91 my @fields = map { /^[-+]*(.*)/ ? $1 : $_ } @{$ctl->{fields}};
  64         506  
  37         139  
164 37 100 100     246 if ( @fields == 1 && $fields[0] eq "songindex" ) {
165             # Return in book order.
166 24         128 return [ map { [ $_->{meta}->{songindex}, $_ ] }
167 8         31 grep { !_suppresstoc($_->{meta}) } @$book ];
  24         94  
168             }
169 29 50       132 return $book unless @fields; # ?
170              
171 29         87 my @book;
172 29         100 foreach my $song ( @$book ) {
173 87         180 my $meta = { %{$song->{meta}} };
  87         1385  
174 87 50       379 next if _suppresstoc($meta);
175              
176 87         182 my @split;
177              
178 87         216 foreach my $item ( @fields ) {
179 168         520 ( my $coreitem = $item ) =~ s/^sort//;
180 168 100       448 push( @split, [ $coreitem, [""] ] ), next unless $meta->{$coreitem};
181              
182 178         588 my @s = map { [ $_ ] }
183 167         254 @{ is_arrayref( $meta->{$coreitem} )
184             ? $meta->{$coreitem}
185 167 50       607 : [ $meta->{$coreitem} ]
186             };
187              
188 167 100       495 if ( $meta->{"sort$coreitem"} ) {
189 9 100       25 if ( $coreitem eq $item ) {
190 6         11 for ( my $i = 0; $i < @{$meta->{"sort$coreitem"}}; $i++ ) {
  14         58  
191 8 50       23 next unless defined $s[$i]->[0];
192 8         23 $s[$i]->[1] = $meta->{"sort$coreitem"}->[$i];
193             }
194             }
195             else {
196 3         7 for ( my $i = 0; $i < @{$meta->{$item}}; $i++ ) {
  7         32  
197 4 50       13 next unless defined $s[$i]->[0];
198 4         12 $s[$i]->[1] = $meta->{$item}->[$i];
199             }
200             }
201             }
202 167         504 push( @split, [ $coreitem, @s ] );
203             }
204              
205             # Merge with (unique) copies of the song.
206 87 50       229 if ( @split == 0 ) {
207 0         0 push( @book, $song );
208             }
209             # elsif ( @split == 1 ) {
210             # my $f1 = shift(@{$split[0]});
211             # my $addsort1 = $f1 =~ /^(title|artist)$/;
212             # for my $s1 ( @{$split[0]} ) {
213             # push( @book,
214             # { %$song,
215             # meta =>
216             # { %$meta,
217             # $f1 => [ $s1->[0] ],
218             # $addsort1
219             # ? ( "sort$f1" => [ $s1->[1] // $s1->[0] ] )
220             # : (),
221             # }
222             # }
223             # );
224             # }
225             # }
226             # elsif ( @split == 200 ) {
227             # my $f1 = shift(@{$split[0]}) // "";
228             # my $f2 = shift(@{$split[1]}) // "";
229             # my $addsort1 = $f1 =~ /^(title|artist)$/;
230             # my $addsort2 = $f2 =~ /^(title|artist)$/;
231             # for my $s1 ( @{$split[0]} ) {
232             # for my $s2 ( @{$split[1]} ) {
233             # push( @book,
234             # { %$song,
235             # meta =>
236             # { %$meta,
237             # $f1 => [ $s1->[0] ],
238             # $addsort1
239             # ? ( "sort$f1" => [ $s1->[1] // $s1->[0] ] )
240             # : (),
241             # $f2 => [ $s2->[0] ],
242             # $addsort2
243             # ? ( "sort$f2" => [ $s2->[1] // $s2->[0] ] )
244             # : (),
245             # }
246             # }
247             # );
248             # }
249             # }
250             # }
251             else {
252 87         170 my @mm;
253 87         166 for my $split ( @split ) {
254 168   50     478 my $f = shift(@$split) // "";
255 168         263 warn("F: $f\n") if PODBG;
256 168         688 my $addsort = $f =~ /^(title|artist)$/;
257 168         291 my @x;
258 168         268 for my $s ( @{$split} ) {
  168         318  
259 179         251 warn("V: $s->[0]\n") if PODBG;
260 179         538 my %x = ( $f => [ $s->[0] ] );
261 179 100 100     925 $x{"sort$f"} = [ $s->[1] // $s->[0] ] if $addsort;
262 179 100       410 if ( @mm ) {
263 87         526 push( @x, { %x, %$_ } ) for @mm;
264             }
265             else {
266 92         193 push( @x, \%x );
267             }
268 179         403 warn("X: ", scalar(@x), " items\n") if PODBG;
269             }
270 168         536 @mm = @x;
271             }
272 87         2520 push( @book, { %$song, meta => { %$meta, %$_ } } ) for @mm;
273             }
274             }
275              
276             # Sort.
277 29         90 my $i = -1;
278 90     90   78808 use Unicode::Collate;
  90         1063056  
  90         74792  
279 29         532 my $collator = Unicode::Collate->new;
280             my $srt =
281             "sub { " .
282             join( " or ",
283 56         145 map { $i++;
284 56         404 my ( $rev, $f ) = /^([-+]*)(.*)/;
285 56         177 my $num = $rev =~ s/\+//g;
286 56 100       269 my ( $a, $b ) = $rev =~ /-/ ? qw( b a ) : qw( a b );
287 56         287 my $l = "\$$a"."->[$i]";
288 56         162 my $r = "\$$b"."->[$i]";
289 56         90 warn("F: $f, N: $num, R: $rev\n") if PODBG;
290 56 50       367 $num ? "$l <=> $r" : "\$collator->cmp( $l, $r )"
291             }
292 29         1598530 @{$ctl->{fields}} ) .
  29         210  
293             " }";
294 29         77 warn("SRT; $srt\n") if PODBG;
295 29 50       6661 $srt = eval $srt or die($@);
296             @book =
297             sort $srt
298 29         137 map { my $t = $_;
  101         191  
299 101         215 [ ( map { demarkup(lc( ( index($_,"sort") && is_arrayref($t->{meta}->{"sort$_"})
300             ? $t->{meta}->{"sort$_"}->[0]
301             : undef ) //
302 194 100 100     2030 $t->{meta}->{$_}->[0] //
      66        
      50        
303             "")) }
304             @fields ),
305             $_ ] }
306             @book;
307              
308 29         41004 return \@book;
309             }
310             push( @EXPORT_OK, 'prep_outlines' );
311              
312             1;