File Coverage

lib/ChordPro/Output/Common.pm
Criterion Covered Total %
statement 129 145 88.9
branch 43 68 63.2
condition 23 35 65.7
subroutine 12 14 85.7
pod 0 6 0.0
total 207 268 77.2


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             package main;
4              
5             our $config;
6             our $options;
7              
8             package ChordPro::Output::Common;
9              
10 81     81   676 use strict;
  81         203  
  81         2783  
11 81     81   485 use warnings;
  81         200  
  81         2295  
12 81     81   450 use ChordPro::Chords;
  81         189  
  81         1992  
13 81     81   512 use ChordPro::Utils qw( demarkup );
  81         186  
  81         3977  
14 81     81   566 use String::Interpolate::Named;
  81         232  
  81         3787  
15 81     81   573 use utf8;
  81         228  
  81         510  
16 81     81   3491 use POSIX qw(setlocale LC_TIME strftime);
  81         7403  
  81         1260  
17              
18 81     81   143739 use parent qw(Exporter);
  81         191  
  81         503  
19             our @EXPORT;
20             our @EXPORT_OK;
21              
22             sub fmt_subst {
23 2139     2139 0 56636 my ( $s, $t ) = @_;
24 2139         3791 my $res = "";
25 2139 50       3203 my $m = { %{$s->{meta} || {} } };
  2139         12320  
26              
27             # Derived item(s).
28 2139 100       6716 $m->{_key} = $m->{key} if exists $m->{key};
29 2139 100 100     7314 if ( $m->{key} && $m->{capo} && (my $capo = $m->{capo}->[-1]) ) {
      66        
30             ####CHECK
31             $m->{_key} =
32 123         380 [ map { ChordPro::Chords::transpose( $_, $capo ) }
33 115         204 @{$m->{key}} ];
  115         290  
34             }
35 2139   100     7771 $m->{key_actual} //= $m->{key};
36 2139   50     10098 $m->{tuning} //= [ join(" ", ChordPro::Chords::get_tuning) ];
37             # If config->{instrument} is missing, or null, the program abends with
38             # Modification of a read-only value attempted.
39 2139 50       6593 if ( $config->{instrument} ) {
40 2139         7692 $m->{instrument} = [ $config->{instrument}->{type} ];
41 2139         5688 $m->{"instrument.type"} = [ $config->{instrument}->{type} ];
42 2139         5768 $m->{"instrument.description"} = [ $config->{instrument}->{description} ];
43             }
44             # Same here.
45 2139 50       5270 if ( $config->{user} ) {
46 2139         5746 $m->{user} = [ $config->{user}->{name} ];
47 2139         5217 $m->{"user.name"} = [ $config->{user}->{name} ];
48 2139         5643 $m->{"user.fullname"} = [ $config->{user}->{fullname} ];
49             }
50 2139         15977 setlocale( LC_TIME, "" );
51             $m->{today} //= [ strftime( $config->{dates}->{today}->{format},
52 2139   50     120897 localtime(time) ) ];
53              
54 2139         6001 for ( keys %{ $config->{settings} } ) {
  2139         14359  
55 40641         68222 my $v = $config->{settings}->{$_};
56 40641 100       93102 $v = '' if $v =~ /^(0|false|off)$/i;
57 40641 50       215338 $v = 1 if $v=~ /^(true|on)$/i;
58 40641         142827 $m->{"settings.$_"} = $v;
59             }
60             interpolate( { %$s, args => $m,
61             separator => $config->{metadata}->{separator} },
62 2139         23097 $t );
63             }
64             push( @EXPORT, 'fmt_subst' );
65              
66             # Roman - functions for converting between Roman and Arabic numerals
67             #
68             # Stolen from Roman Version 1.24 by OZAWA Sakuro
69             # 1995-1997 and Alexandr Ciornii, C<< >> 2007
70             #
71             # Copyright (c) 1995 OZAWA Sakuro. All rights reserved. This program
72             # is free software; you can redistribute it and/or modify it under the
73             # same terms as Perl itself.
74              
75             our %roman2arabic = qw(I 1 V 5 X 10 L 50 C 100 D 500 M 1000);
76             my %roman_digit = qw(1 IV 10 XL 100 CD 1000 MMMMMM);
77             my @figure = reverse sort keys %roman_digit;
78             #my %roman_digit;
79             $roman_digit{$_} = [split(//, $roman_digit{$_}, 2)] foreach @figure;
80              
81             sub isroman($) {
82 0     0 0 0 my $arg = shift;
83 0 0       0 $arg ne '' and
84             $arg =~ /^(?: M{0,3})
85             (?: D?C{0,3} | C[DM])
86             (?: L?X{0,3} | X[LC])
87             (?: V?I{0,3} | I[VX])$/ix;
88             }
89             push( @EXPORT_OK, 'isroman' );
90              
91             sub arabic($) {
92 0     0 0 0 my $arg = shift;
93 0 0       0 isroman $arg or return undef;
94 0         0 my($last_digit) = 1000;
95 0         0 my($arabic);
96 0         0 foreach (split(//, uc $arg)) {
97 0         0 my($digit) = $roman2arabic{$_};
98 0 0       0 $arabic -= 2 * $last_digit if $last_digit < $digit;
99 0         0 $arabic += ($last_digit = $digit);
100             }
101 0         0 $arabic;
102             }
103             push( @EXPORT_OK, 'arabic' );
104              
105             sub Roman($) {
106 32     32 0 75 my $arg = shift;
107 32 50 33     186 0 < $arg and $arg < 4000 or return undef;
108 32         82 my($x, $roman);
109 32         99 foreach (@figure) {
110 128         272 my($digit, $i, $v) = (int($arg / $_), @{$roman_digit{$_}});
  128         351  
111 128 100 66     633 if (1 <= $digit and $digit <= 3) {
    50 33        
    50          
    50          
    50          
112 32         98 $roman .= $i x $digit;
113             } elsif ($digit == 4) {
114 0         0 $roman .= "$i$v";
115             } elsif ($digit == 5) {
116 0         0 $roman .= $v;
117             } elsif (6 <= $digit and $digit <= 8) {
118 0         0 $roman .= $v . $i x ($digit - 5);
119             } elsif ($digit == 9) {
120 0         0 $roman .= "$i$x";
121             }
122 128         199 $arg -= $digit * $_;
123 128         243 $x = $i;
124             }
125 32         207 $roman;
126             }
127             push( @EXPORT_OK, 'Roman' );
128              
129             sub roman($) {
130 32     32 0 140 lc Roman shift;
131             }
132             push( @EXPORT_OK, 'roman' );
133              
134             # Prepare outlines.
135             # This mainly untangles alternative names when being sorted on.
136             # Returns a book array where each element consists of the sort items,
137             # and the song.
138              
139             #sub PODBG() { $config->{debug}->{x1} }
140             sub PODBG() { 0 }
141              
142             sub prep_outlines {
143 37     37 0 5585 my ( $book, $ctl ) = @_;
144 37 50 33     287 return [] unless $book && @$book; # unlikely
145 37 50       154 return [] if $ctl->{omit};
146              
147 37         194 warn("FLD: ", join(" ", @{$ctl->{fields}}), "\n") if PODBG;
148              
149 37 50       85 my @fields = map { /^[-+]*(.*)/ ? $1 : $_ } @{$ctl->{fields}};
  64         489  
  37         135  
150 37 100 100     261 if ( @fields == 1 && $fields[0] eq "songindex" ) {
151             # Return in book order.
152 8         42 return [ map { [ $_->{meta}->{songindex}, $_ ] } @$book ];
  24         99  
153             }
154 29 50       111 return $book unless @fields; # ?
155              
156 29         61 my @book;
157 29         94 foreach my $song ( @$book ) {
158 87         220 my $meta = $song->{meta};
159              
160 87         142 my @split;
161              
162 87         170 foreach my $item ( @fields ) {
163 168         487 ( my $coreitem = $item ) =~ s/^sort//;
164 168 100       467 push( @split, [ $coreitem, [""] ] ), next unless $meta->{$coreitem};
165              
166 178         578 my @s = map { [ $_ ] }
167 167         246 @{ UNIVERSAL::isa( $meta->{$coreitem}, 'ARRAY' )
168             ? $meta->{$coreitem}
169 167 50       627 : [ $meta->{$coreitem} ]
170             };
171              
172 167 100       540 if ( $meta->{"sort$coreitem"} ) {
173 9 100       26 if ( $coreitem eq $item ) {
174 6         12 for ( my $i = 0; $i < @{$meta->{"sort$coreitem"}}; $i++ ) {
  14         42  
175 8 50       20 next unless defined $s[$i]->[0];
176 8         21 $s[$i]->[1] = $meta->{"sort$coreitem"}->[$i];
177             }
178             }
179             else {
180 3         7 for ( my $i = 0; $i < @{$meta->{$item}}; $i++ ) {
  7         17  
181 4 50       10 next unless defined $s[$i]->[0];
182 4         11 $s[$i]->[1] = $meta->{$item}->[$i];
183             }
184             }
185             }
186 167         443 push( @split, [ $coreitem, @s ] );
187             }
188              
189             # Merge with (unique) copies of the song.
190 87 50       261 if ( @split == 0 ) {
191 0         0 push( @book, $song );
192             }
193             # elsif ( @split == 1 ) {
194             # my $f1 = shift(@{$split[0]});
195             # my $addsort1 = $f1 =~ /^(title|artist)$/;
196             # for my $s1 ( @{$split[0]} ) {
197             # push( @book,
198             # { %$song,
199             # meta =>
200             # { %$meta,
201             # $f1 => [ $s1->[0] ],
202             # $addsort1
203             # ? ( "sort$f1" => [ $s1->[1] // $s1->[0] ] )
204             # : (),
205             # }
206             # }
207             # );
208             # }
209             # }
210             # elsif ( @split == 200 ) {
211             # my $f1 = shift(@{$split[0]}) // "";
212             # my $f2 = shift(@{$split[1]}) // "";
213             # my $addsort1 = $f1 =~ /^(title|artist)$/;
214             # my $addsort2 = $f2 =~ /^(title|artist)$/;
215             # for my $s1 ( @{$split[0]} ) {
216             # for my $s2 ( @{$split[1]} ) {
217             # push( @book,
218             # { %$song,
219             # meta =>
220             # { %$meta,
221             # $f1 => [ $s1->[0] ],
222             # $addsort1
223             # ? ( "sort$f1" => [ $s1->[1] // $s1->[0] ] )
224             # : (),
225             # $f2 => [ $s2->[0] ],
226             # $addsort2
227             # ? ( "sort$f2" => [ $s2->[1] // $s2->[0] ] )
228             # : (),
229             # }
230             # }
231             # );
232             # }
233             # }
234             # }
235             else {
236 87         146 my @mm;
237 87         169 for my $split ( @split ) {
238 168   50     401 my $f = shift(@$split) // "";
239 168         249 warn("F: $f\n") if PODBG;
240 168         954 my $addsort = $f =~ /^(title|artist)$/;
241 168         289 my @x;
242 168         235 for my $s ( @{$split} ) {
  168         332  
243 179         230 warn("V: $s->[0]\n") if PODBG;
244 179         498 my %x = ( $f => [ $s->[0] ] );
245 179 100 100     809 $x{"sort$f"} = [ $s->[1] // $s->[0] ] if $addsort;
246 179 100       410 if ( @mm ) {
247 87         476 push( @x, { %x, %$_ } ) for @mm;
248             }
249             else {
250 92         200 push( @x, \%x );
251             }
252 179         380 warn("X: ", scalar(@x), " items\n") if PODBG;
253             }
254 168         420 @mm = @x;
255             }
256 87         1499 push( @book, { %$song, meta => { %$meta, %$_ } } ) for @mm;
257             }
258             }
259              
260             # Sort.
261 29         113 my $i = -1;
262             my $srt =
263             "sub { " .
264             join( " or ",
265 56         104 map { $i++;
266 56         256 my ( $rev, $f ) = /^([-+]*)(.*)/;
267 56         139 my $num = $rev =~ s/\+//g;
268 56         83 warn("F: $f, N: $num, R: $rev\n") if PODBG;
269 56 100       466 "\$" . ( $rev =~ /-/ ? "b" : "a" ) . "->[$i] " .
    50          
    100          
270             ($num ? '<=>' : 'cmp') .
271             " \$" . ( $rev =~ /-/ ? "a" : "b" ) . "->[$i]" }
272 29         85 @{$ctl->{fields}} ) .
  29         89  
273             " }";
274 29         77 warn("SRT; $srt\n") if PODBG;
275 29 50       5260 $srt = eval $srt or die($@);
276             @book =
277             sort $srt
278 29         131 map { my $t = $_;
  101         187  
279 101   50     167 [ ( map { demarkup(lc($t->{meta}->{$_}->[0] // "")) }
  194         864  
280             @fields ),
281             $_ ] }
282             @book;
283              
284 29         433 return \@book;
285             }
286             push( @EXPORT_OK, 'prep_outlines' );
287              
288             1;