File Coverage

lib/ChordPro/Output/Text.pm
Criterion Covered Total %
statement 138 199 69.3
branch 59 118 50.0
condition 9 16 56.2
subroutine 13 13 100.0
pod 0 5 0.0
total 219 351 62.3


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             package main;
4              
5             our $options;
6             our $config;
7              
8             package ChordPro::Output::Text;
9              
10 5     5   1608 use ChordPro::Output::Common;
  5         10  
  5         421  
11              
12 5     5   59 use strict;
  5         10  
  5         162  
13 5     5   21 use warnings;
  5         10  
  5         19110  
14              
15             sub generate_songbook {
16 21     21 0 73 my ( $self, $sb ) = @_;
17 21         48 my @book;
18              
19 21         42 foreach my $song ( @{$sb->{songs}} ) {
  21         92  
20 21 50       75 if ( @book ) {
21 0 0       0 push(@book, "") if $options->{'backend-option'}->{tidy};
22 0         0 push(@book, "-- New song");
23             }
24 21         39 push(@book, @{generate_song($song)});
  21         100  
25             }
26              
27 21         73 push( @book, "");
28 21         101 \@book;
29             }
30              
31             my $single_space = 0; # suppress chords line when empty
32             my $lyrics_only = 0; # suppress all chords lines
33             my $chords_under = 0; # chords under lyrics
34             my $layout = Text::Layout::Text->new;
35             my $rechorus;
36              
37             sub upd_config {
38 24     24 0 84 $lyrics_only = $config->{settings}->{'lyrics-only'};
39 24         68 $chords_under = $config->{settings}->{'chords-under'};
40 24         193 $rechorus = $config->{text}->{chorus}->{recall};
41             }
42              
43             sub generate_song {
44 21     21 0 58 my ( $s ) = @_;
45              
46 21         136 my $tidy = $options->{'backend-option'}->{tidy};
47 21         60 $single_space = $options->{'single-space'};
48 21         86 upd_config();
49              
50             $s->structurize
51 21 50 50     165 if ( $options->{'backend-option'}->{structure} // '' ) eq 'structured';
52              
53 21         41 my @s;
54              
55             push(@s, "-- Title: " . $s->{title})
56 21 50       181 if defined $s->{title};
57 21 100       80 if ( defined $s->{subtitle} ) {
58 6         16 push(@s, map { +"-- Subtitle: $_" } @{$s->{subtitle}});
  6         31  
  6         20  
59             }
60              
61 21 50       81 push(@s, "") if $tidy;
62              
63 21         55 my $ctx = "";
64 21         42 my @elts = @{$s->{body}};
  21         115  
65 21         63 while ( @elts ) {
66 306         567 my $elt = shift(@elts);
67              
68 306 100       1018 if ( $elt->{context} ne $ctx ) {
69 54 100       161 push(@s, "-- End of $ctx") if $ctx;
70 54 100       209 push(@s, "-- Start of $ctx") if $ctx = $elt->{context};
71             }
72              
73 306 100       823 if ( $elt->{type} eq "empty" ) {
74             push(@s, "***SHOULD NOT HAPPEN***")
75 81 50       260 if $s->{structure} eq 'structured';
76 81         200 push(@s, "");
77 81         164 next;
78             }
79              
80 225 100       498 if ( $elt->{type} eq "colb" ) {
81 3         11 push(@s, "-- Column break");
82 3         8 next;
83             }
84              
85 222 100       495 if ( $elt->{type} eq "newpage" ) {
86 3         12 push(@s, "-- New page");
87 3         10 next;
88             }
89              
90 219 100       515 if ( $elt->{type} eq "songline" ) {
91 105         273 push(@s, songline( $s, $elt ));
92 105         424 next;
93             }
94              
95 114 100       362 if ( $elt->{type} eq "tabline" ) {
96 24         82 push(@s, $elt->{text});
97 24         52 next;
98             }
99              
100 90 50       256 if ( $elt->{type} eq "chorus" ) {
101 0 0       0 push(@s, "") if $tidy;
102 0         0 push(@s, "-- Start of chorus*");
103 0         0 foreach my $e ( @{$elt->{body}} ) {
  0         0  
104 0 0       0 if ( $e->{type} eq "empty" ) {
105 0         0 push(@s, "");
106 0         0 next;
107             }
108 0 0       0 if ( $e->{type} eq "songline" ) {
109 0         0 push(@s, songline( $s, $e ));
110 0         0 next;
111             }
112             }
113 0         0 push(@s, "-- End of chorus*");
114 0 0       0 push(@s, "") if $tidy;
115 0         0 next;
116             }
117              
118 90 100       241 if ( $elt->{type} eq "rechorus" ) {
119 15 50 0     63 if ( $rechorus->{quote} ) {
    0          
120 15         32 unshift( @elts, @{ $elt->{chorus} } );
  15         50  
121             }
122             elsif ( $rechorus->{type} && $rechorus->{tag} ) {
123 0         0 push( @s, "{".$rechorus->{type}.": ".$rechorus->{tag}."}" );
124             }
125             else {
126 0         0 push( @s, "{chorus}" );
127             }
128 15         29 next;
129             }
130              
131 75 50       289 if ( $elt->{type} eq "tab" ) {
132 0 0       0 push(@s, "") if $tidy;
133 0         0 push(@s, "-- Start of tab");
134 0         0 push(@s, map { $_->{text} } @{$elt->{body}} );
  0         0  
  0         0  
135 0         0 push(@s, "-- End of tab");
136 0 0       0 push(@s, "") if $tidy;
137 0         0 next;
138             }
139              
140 75 50       235 if ( $elt->{type} eq "verse" ) {
141 0 0       0 push(@s, "") if $tidy;
142 0         0 push(@s, "-- Start of verse");
143 0         0 foreach my $e ( @{$elt->{body}} ) {
  0         0  
144 0 0       0 if ( $e->{type} eq "empty" ) {
145             push(@s, "***SHOULD NOT HAPPEN***")
146 0 0       0 if $s->{structure} eq 'structured';
147 0         0 next;
148             }
149 0 0       0 if ( $e->{type} eq "songline" ) {
150 0         0 push(@s, songline( $s, $e ));
151 0         0 next;
152             }
153 0 0       0 if ( $e->{type} eq "comment" ) {
154 0         0 push(@s, "-c- " . $e->{text});
155 0         0 next;
156             }
157 0 0       0 if ( $e->{type} eq "comment_italic" ) {
158 0         0 push(@s, "-i- " . $e->{text});
159 0         0 next;
160             }
161             }
162 0         0 push(@s, "-- End of verse");
163 0 0       0 push(@s, "") if $tidy;
164 0         0 next;
165             }
166              
167 75 100       428 if ( $elt->{type} =~ /^comment(?:_italic|_box)?$/ ) {
168 42 50       139 push(@s, "") if $tidy;
169 42         119 my $text = $elt->{text};
170 42 100       127 if ( $elt->{chords} ) {
171 12         32 $text = "";
172 12         37 for ( 0..$#{ $elt->{chords} } ) {
  12         60  
173             $text .= "[" . $elt->{chords}->[$_]->key . "]"
174 24 100       191 if $elt->{chords}->[$_] ne "";
175 24         82 $text .= $elt->{phrases}->[$_];
176             }
177             }
178             # $text = fmt_subst( $s, $text );
179 42         155 push(@s, "-- $text");
180 42 50       121 push(@s, "") if $tidy;
181 42         111 next;
182             }
183              
184 33 50       96 if ( $elt->{type} eq "image" ) {
185 0   0     0 my @args = ( "image:", $elt->{uri} // "" );
186 0         0 while ( my($k,$v) = each( %{ $elt->{opts} } ) ) {
  0         0  
187 0         0 push( @args, "$k=$v" );
188             }
189 0         0 foreach ( @args ) {
190 0 0       0 next unless /\s/;
191 0         0 $_ = '"' . $_ . '"';
192             }
193 0         0 push( @s, "+ @args" );
194 0         0 next;
195             }
196              
197 33 50       116 if ( $elt->{type} eq "set" ) {
198 33 50       293 if ( $elt->{name} eq "lyrics-only" ) {
    100          
199             $lyrics_only = $elt->{value}
200 0 0       0 unless $lyrics_only > 1;
201             }
202             # Arbitrary config values.
203             elsif ( $elt->{name} =~ /^(text\..+)/ ) {
204 3         26 my @k = split( /[.]/, $1 );
205 3         7 my $cc = {};
206 3         8 my $c = \$cc;
207 3         11 foreach ( @k ) {
208 12         35 $c = \($$c->{$_});
209             }
210 3         12 $$c = $elt->{value};
211 3         30 $config->augment($cc);
212 3         12 upd_config();
213             }
214 33         110 next;
215             }
216              
217 0 0       0 if ( $elt->{type} eq "control" ) {
218             }
219             }
220 21 100       78 push(@s, "-- End of $ctx") if $ctx;
221              
222 21         364 \@s;
223             }
224              
225             sub songline {
226 105     105 0 260 my ( $song, $elt ) = @_;
227              
228 105         204 my $t_line = "";
229 330         1323 my @phrases = map { $layout->set_markup($_); $layout->render }
  330         19790  
230 105         153 @{ $elt->{phrases} };
  105         320  
231              
232 105 100 66     442 if ( $lyrics_only
      100        
      100        
233             or
234             $single_space && ! ( $elt->{chords} && join( "", map { $_?$_->key:"" } @{ $elt->{chords} } ) =~ /\S/ )
235             ) {
236 43         205 $t_line = join( "", @phrases );
237 43         311 $t_line =~ s/\s+$//;
238 43         255 return $t_line;
239             }
240              
241 62 100       222 unless ( $elt->{chords} ) {
242 8         65 return ( "", join( " ", @phrases ) );
243             }
244              
245 54 50       204 if ( my $f = $::config->{settings}->{'inline-chords'} ) {
246 0 0       0 $f = '[%s]' unless $f =~ /^[^%]*\%s[^%]*$/;
247 0         0 $f .= '%s';
248 0         0 foreach ( 0..$#{$elt->{chords}} ) {
  0         0  
249             $t_line .= sprintf( $f,
250 0 0       0 $elt->{chords}->[$_] ? chord( $song, $elt->{chords}->[$_] ) : "",
251             $phrases[$_] );
252             }
253 0         0 return ( $t_line );
254             }
255              
256 54         126 my $c_line = "";
257 54         72 foreach my $c ( 0..$#{$elt->{chords}} ) {
  54         190  
258             $c_line .= chord( $song, $elt->{chords}->[$c] ) . " "
259 204 100       747 if ref $elt->{chords}->[$c];
260 204         489 $t_line .= $phrases[$c];
261 204         460 my $d = length($c_line) - length($t_line);
262 204 100       397 $t_line .= "-" x $d if $d > 0;
263 204 100       637 $c_line .= " " x -$d if $d < 0;
264             }
265 54         650 s/\s+$// for ( $t_line, $c_line );
266 54 50       189 return $chords_under
267             ? ( $t_line, $c_line )
268             : ( $c_line, $t_line )
269             }
270              
271             sub chord {
272 150     150 0 243 my ( $s, $c ) = @_;
273 150 50       380 return "" unless length($c);
274 150         626 $layout->set_markup($c->chord_display);
275 150         21444 my $t = $layout->render;
276 150 50       560 return $c->info->is_annotation ? "*$t" : $t;
277             }
278              
279             # Temporary. Eventually we'll have a decent HTML backend for Text::Layout.
280              
281             package Text::Layout::Text;
282              
283 5     5   69 use parent 'Text::Layout';
  5         12  
  5         47  
284 5     5   130625 use ChordPro::Utils qw( fq );
  5         16  
  5         393  
285              
286             # Eliminate warning when HTML backend is loaded together with Text backend.
287 5     5   34 no warnings 'redefine';
  5         11  
  5         1400  
288              
289             sub new {
290 5     5   21 my ( $pkg, @data ) = @_;
291 5         55 my $self = $pkg->SUPER::new;
292 5         114 $self;
293             }
294              
295             sub render {
296 480     480   892 my ( $self ) = @_;
297 480         775 my $res = "";
298 480         646 foreach my $fragment ( @{ $self->{_content} } ) {
  480         1037  
299 474 50       1092 next unless length($fragment->{text});
300 474         1416 $res .= fq($fragment->{text});
301             }
302 480         1097 $res;
303             }
304              
305             1;