File Coverage

blib/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   1037 use ChordPro::Output::Common;
  5         12  
  5         351  
11              
12 5     5   35 use strict;
  5         13  
  5         167  
13 5     5   29 use warnings;
  5         10  
  5         11066  
14              
15             sub generate_songbook {
16 21     21 0 77 my ( $self, $sb ) = @_;
17 21         57 my @book;
18              
19 21         46 foreach my $song ( @{$sb->{songs}} ) {
  21         82  
20 21 50       77 if ( @book ) {
21 0 0       0 push(@book, "") if $options->{'backend-option'}->{tidy};
22 0         0 push(@book, "-- New song");
23             }
24 21         48 push(@book, @{generate_song($song)});
  21         96  
25             }
26              
27 21         58 push( @book, "");
28 21         77 \@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 66 $lyrics_only = $config->{settings}->{'lyrics-only'};
39 24         70 $chords_under = $config->{settings}->{'chords-under'};
40 24         108 $rechorus = $config->{text}->{chorus}->{recall};
41             }
42              
43             sub generate_song {
44 21     21 0 60 my ( $s ) = @_;
45              
46 21         87 my $tidy = $options->{'backend-option'}->{tidy};
47 21         57 $single_space = $options->{'single-space'};
48 21         99 upd_config();
49              
50             $s->structurize
51 21 50 50     156 if ( $options->{'backend-option'}->{structure} // '' ) eq 'structured';
52              
53 21         46 my @s;
54              
55             push(@s, "-- Title: " . $s->{title})
56 21 50       131 if defined $s->{title};
57 21 100       90 if ( defined $s->{subtitle} ) {
58 6         13 push(@s, map { +"-- Subtitle: $_" } @{$s->{subtitle}});
  6         24  
  6         22  
59             }
60              
61 21 50       66 push(@s, "") if $tidy;
62              
63 21         53 my $ctx = "";
64 21         53 my @elts = @{$s->{body}};
  21         92  
65 21         70 while ( @elts ) {
66 309         514 my $elt = shift(@elts);
67              
68 309 100       744 if ( $elt->{context} ne $ctx ) {
69 54 100       169 push(@s, "-- End of $ctx") if $ctx;
70 54 100       186 push(@s, "-- Start of $ctx") if $ctx = $elt->{context};
71             }
72              
73 309 100       1072 if ( $elt->{type} eq "empty" ) {
74             push(@s, "***SHOULD NOT HAPPEN***")
75 81 50       188 if $s->{structure} eq 'structured';
76 81         171 push(@s, "");
77 81         159 next;
78             }
79              
80 228 100       472 if ( $elt->{type} eq "colb" ) {
81 3         10 push(@s, "-- Column break");
82 3         7 next;
83             }
84              
85 225 100       449 if ( $elt->{type} eq "newpage" ) {
86 3         9 push(@s, "-- New page");
87 3         9 next;
88             }
89              
90 222 100       463 if ( $elt->{type} eq "songline" ) {
91 105         267 push(@s, songline( $s, $elt ));
92 105         827 next;
93             }
94              
95 117 100       263 if ( $elt->{type} eq "tabline" ) {
96 24         59 push(@s, $elt->{text});
97 24         51 next;
98             }
99              
100 93 50       229 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       232 if ( $elt->{type} eq "rechorus" ) {
119 15 50 0     56 if ( $rechorus->{quote} ) {
    0          
120 15         23 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       192 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       187 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       315 if ( $elt->{type} =~ /^comment(?:_italic|_box)?$/ ) {
168 42 50       120 push(@s, "") if $tidy;
169 42         97 my $text = $elt->{text};
170 42 100       118 if ( $elt->{chords} ) {
171 12         30 $text = "";
172 12         23 for ( 0..$#{ $elt->{chords} } ) {
  12         59  
173             $text .= "[" . $elt->{chords}->[$_]->key . "]"
174 24 100       137 if $elt->{chords}->[$_] ne "";
175 24         62 $text .= $elt->{phrases}->[$_];
176             }
177             }
178             # $text = fmt_subst( $s, $text );
179 42         155 push(@s, "-- $text");
180 42 50       111 push(@s, "") if $tidy;
181 42         113 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       89 if ( $elt->{type} eq "set" ) {
198 33 50       146 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         9 my $cc = {};
206 3         9 my $c = \$cc;
207 3         10 foreach ( @k ) {
208 12         34 $c = \($$c->{$_});
209             }
210 3         8 $$c = $elt->{value};
211 3         21 $config->augment($cc);
212 3         35 upd_config();
213             }
214 33         92 next;
215             }
216              
217 3 50       16 if ( $elt->{type} eq "control" ) {
218             }
219             }
220 21 100       115 push(@s, "-- End of $ctx") if $ctx;
221              
222 21         190 \@s;
223             }
224              
225             sub songline {
226 105     105 0 219 my ( $song, $elt ) = @_;
227              
228 105         156 my $t_line = "";
229 330         960 my @phrases = map { $layout->set_markup($_); $layout->render }
  330         15609  
230 105         149 @{ $elt->{phrases} };
  105         242  
231              
232 105 100 66     344 if ( $lyrics_only
      100        
      100        
233             or
234             $single_space && ! ( $elt->{chords} && join( "", map { $_?$_->key:"" } @{ $elt->{chords} } ) =~ /\S/ )
235             ) {
236 43         188 $t_line = join( "", @phrases );
237 43         218 $t_line =~ s/\s+$//;
238 43         154 return $t_line;
239             }
240              
241 62 100       474 unless ( $elt->{chords} ) {
242 8         34 return ( "", join( " ", @phrases ) );
243             }
244              
245 54 50       169 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         401 my $c_line = "";
257 54         82 foreach my $c ( 0..$#{$elt->{chords}} ) {
  54         174  
258             $c_line .= chord( $song, $elt->{chords}->[$c] ) . " "
259 204 100       750 if ref $elt->{chords}->[$c];
260 204         499 $t_line .= $phrases[$c];
261 204         485 my $d = length($c_line) - length($t_line);
262 204 100       450 $t_line .= "-" x $d if $d > 0;
263 204 100       770 $c_line .= " " x -$d if $d < 0;
264             }
265 54         602 s/\s+$// for ( $t_line, $c_line );
266 54 50       197 return $chords_under
267             ? ( $t_line, $c_line )
268             : ( $c_line, $t_line )
269             }
270              
271             sub chord {
272 150     150 0 290 my ( $s, $c ) = @_;
273 150 50       415 return "" unless length($c);
274 150         466 $layout->set_markup($c->chord_display);
275 150         9152 my $t = $layout->render;
276 150 50       511 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   65 use parent 'Text::Layout';
  5         12  
  5         59  
284 5     5   63582 use ChordPro::Utils qw( fq );
  5         19  
  5         283  
285              
286             # Eliminate warning when HTML backend is loaded together with Text backend.
287 5     5   34 no warnings 'redefine';
  5         10  
  5         798  
288              
289             sub new {
290 4     4   15 my ( $pkg, @data ) = @_;
291 4         34 my $self = $pkg->SUPER::new;
292 4         66 $self;
293             }
294              
295             sub render {
296 480     480   936 my ( $self ) = @_;
297 480         739 my $res = "";
298 480         660 foreach my $fragment ( @{ $self->{_content} } ) {
  480         1077  
299 474 50       1287 next unless length($fragment->{text});
300 474         1260 $res .= fq($fragment->{text});
301             }
302 480         1266 $res;
303             }
304              
305             1;