File Coverage

lib/ChordPro/Output/Text.pm
Criterion Covered Total %
statement 139 199 69.8
branch 61 118 51.6
condition 9 14 64.2
subroutine 13 13 100.0
pod 0 5 0.0
total 222 349 63.6


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   1148 use ChordPro::Output::Common;
  5         14  
  5         386  
11              
12 5     5   52 use strict;
  5         11  
  5         153  
13 5     5   38 use warnings;
  5         10  
  5         11529  
14              
15             sub generate_songbook {
16 21     21 0 88 my ( $self, $sb ) = @_;
17 21         41 my @book;
18              
19 21         54 foreach my $song ( @{$sb->{songs}} ) {
  21         84  
20 21 50       79 if ( @book ) {
21 0 0       0 push(@book, "") if $options->{'backend-option'}->{tidy};
22 0         0 push(@book, "-- New song");
23             }
24 21         53 push(@book, @{generate_song($song)});
  21         93  
25             }
26              
27 21         79 push( @book, "");
28 21         67 \@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 63 $lyrics_only = $config->{settings}->{'lyrics-only'};
39 24         55 $chords_under = $config->{settings}->{'chords-under'};
40 24         115 $rechorus = $config->{text}->{chorus}->{recall};
41             }
42              
43             sub generate_song {
44 21     21 0 62 my ( $s ) = @_;
45              
46 21         100 my $tidy = $options->{'backend-option'}->{tidy};
47 21         59 $single_space = $options->{'single-space'};
48 21         85 upd_config();
49              
50             $s->structurize
51 21 50 50     172 if ( $options->{'backend-option'}->{structure} // '' ) eq 'structured';
52              
53 21         48 my @s;
54              
55             push(@s, "-- Title: " . $s->{title})
56 21 50       134 if defined $s->{title};
57 21 100       81 if ( defined $s->{subtitle} ) {
58 6         14 push(@s, map { +"-- Subtitle: $_" } @{$s->{subtitle}});
  6         23  
  6         18  
59             }
60              
61 21 50       79 push(@s, "") if $tidy;
62              
63 21         52 my $ctx = "";
64 21         41 my @elts = @{$s->{body}};
  21         96  
65 21         76 while ( @elts ) {
66 309         581 my $elt = shift(@elts);
67              
68 309 100       804 if ( $elt->{context} ne $ctx ) {
69 54 100       156 push(@s, "-- End of $ctx") if $ctx;
70 54 100       176 push(@s, "-- Start of $ctx") if $ctx = $elt->{context};
71             }
72              
73 309 100       656 if ( $elt->{type} eq "empty" ) {
74             push(@s, "***SHOULD NOT HAPPEN***")
75 81 50       187 if $s->{structure} eq 'structured';
76 81         141 push(@s, "");
77 81         147 next;
78             }
79              
80 228 100       455 if ( $elt->{type} eq "colb" ) {
81 3         9 push(@s, "-- Column break");
82 3         20 next;
83             }
84              
85 225 100       440 if ( $elt->{type} eq "newpage" ) {
86 3         16 push(@s, "-- New page");
87 3         8 next;
88             }
89              
90 222 100       451 if ( $elt->{type} eq "songline" ) {
91 105         217 push(@s, songline( $s, $elt ));
92 105         778 next;
93             }
94              
95 117 100       276 if ( $elt->{type} eq "tabline" ) {
96 24         53 push(@s, $elt->{text});
97 24         45 next;
98             }
99              
100 93 50       213 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 93 100       212 if ( $elt->{type} eq "rechorus" ) {
119 15 50 0     49 if ( $rechorus->{quote} ) {
    0          
120 15         24 unshift( @elts, @{ $elt->{chorus} } );
  15         39  
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         31 next;
129             }
130              
131 78 50       182 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 78 50       226 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 78 100       376 if ( $elt->{type} =~ /^comment(?:_italic|_box)?$/ ) {
168 42 50       140 push(@s, "") if $tidy;
169 42         104 my $text = $elt->{text};
170 42 100       118 if ( $elt->{chords} ) {
171 12         27 $text = "";
172 12         28 for ( 0..$#{ $elt->{chords} } ) {
  12         62  
173             $text .= "[" . $elt->{chords}->[$_]->key . "]"
174 24 100       137 if $elt->{chords}->[$_] ne "";
175 24         74 $text .= $elt->{phrases}->[$_];
176             }
177             }
178             # $text = fmt_subst( $s, $text );
179 42         160 push(@s, "-- $text");
180 42 50       104 push(@s, "") if $tidy;
181 42         128 next;
182             }
183              
184 36 50       91 if ( $elt->{type} eq "image" ) {
185 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 36 100       98 if ( $elt->{type} eq "set" ) {
198 33 50       143 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         22 my @k = split( /[.]/, $1 );
205 3         11 my $cc = {};
206 3         7 my $c = \$cc;
207 3         18 foreach ( @k ) {
208 12         51 $c = \($$c->{$_});
209             }
210 3         13 $$c = $elt->{value};
211 3         35 $config->augment($cc);
212 3         10 upd_config();
213             }
214 33         85 next;
215             }
216              
217 3 50       28 if ( $elt->{type} eq "control" ) {
218             }
219             }
220 21 100       85 push(@s, "-- End of $ctx") if $ctx;
221              
222 21         203 \@s;
223             }
224              
225             sub songline {
226 105     105 0 205 my ( $song, $elt ) = @_;
227              
228 105         160 my $t_line = "";
229 330         936 my @phrases = map { $layout->set_markup($_); $layout->render }
  330         15294  
230 105         150 @{ $elt->{phrases} };
  105         229  
231              
232 105 100 66     370 if ( $lyrics_only
      100        
      100        
233             or
234             $single_space && ! ( $elt->{chords} && join( "", map { $_?$_->key:"" } @{ $elt->{chords} } ) =~ /\S/ )
235             ) {
236 43         196 $t_line = join( "", @phrases );
237 43         210 $t_line =~ s/\s+$//;
238 43         152 return $t_line;
239             }
240              
241 62 100       467 unless ( $elt->{chords} ) {
242 8         38 return ( "", join( " ", @phrases ) );
243             }
244              
245 54 50       171 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         404 my $c_line = "";
257 54         86 foreach my $c ( 0..$#{$elt->{chords}} ) {
  54         182  
258             $c_line .= chord( $song, $elt->{chords}->[$c] ) . " "
259 204 100       713 if ref $elt->{chords}->[$c];
260 204         505 $t_line .= $phrases[$c];
261 204         478 my $d = length($c_line) - length($t_line);
262 204 100       438 $t_line .= "-" x $d if $d > 0;
263 204 100       922 $c_line .= " " x -$d if $d < 0;
264             }
265 54         639 s/\s+$// for ( $t_line, $c_line );
266 54 50       194 return $chords_under
267             ? ( $t_line, $c_line )
268             : ( $c_line, $t_line )
269             }
270              
271             sub chord {
272 150     150 0 283 my ( $s, $c ) = @_;
273 150 50       588 return "" unless length($c);
274 150         430 $layout->set_markup($c->chord_display);
275 150         9301 my $t = $layout->render;
276 150 50       506 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   48 use parent 'Text::Layout';
  5         12  
  5         69  
284 5     5   68206 use ChordPro::Utils qw( fq );
  5         12  
  5         320  
285              
286             # Eliminate warning when HTML backend is loaded together with Text backend.
287 5     5   31 no warnings 'redefine';
  5         18  
  5         901  
288              
289             sub new {
290 4     4   14 my ( $pkg, @data ) = @_;
291 4         37 my $self = $pkg->SUPER::new;
292 4         70 $self;
293             }
294              
295             sub render {
296 480     480   915 my ( $self ) = @_;
297 480         759 my $res = "";
298 480         647 foreach my $fragment ( @{ $self->{_content} } ) {
  480         989  
299 474 50       1291 next unless length($fragment->{text});
300 474         1211 $res .= fq($fragment->{text});
301             }
302 480         1200 $res;
303             }
304              
305             1;