File Coverage

blib/lib/ChordPro/A2Crd.pm
Criterion Covered Total %
statement 308 426 72.3
branch 153 266 57.5
condition 24 62 38.7
subroutine 22 27 81.4
pod 0 14 0.0
total 507 795 63.7


line stmt bran cond sub pod time code
1             #! perl
2              
3 1     1   705 use v5.26;
  1         3  
4              
5             package ChordPro::A2Crd;
6              
7 1     1   8 use App::Packager;
  1         2  
  1         16  
8              
9 1     1   132 use ChordPro::Version;
  1         5  
  1         26  
10 1     1   19 use ChordPro::Chords;
  1         3  
  1         56  
11              
12             our $VERSION = $ChordPro::Version::VERSION;
13              
14             =head1 NAME
15              
16             ChordPro::A2Crd - convert lyrics and chords to ChordPro
17              
18             =head1 SYNOPSIS
19              
20             perl -MA2Crd -e run -- [ options ] [ file ... ]
21              
22             (But noone does that.)
23              
24             When the associated B program has been installed correctly:
25              
26             chordpro --a2crd [ options ] [ file ... ]
27              
28             =head1 DESCRIPTION
29              
30             B, referred to as B, will read a text file
31             containing the lyrics of one or many songs with chord information
32             written visually above the lyrics. This is often referred to as I
33             data. B will then generate equivalent ChordPro output.
34              
35             Typical a2crd input:
36              
37             Title: Swing Low Sweet Chariot
38              
39             D G D
40             Swing low, sweet chariot,
41             A7
42             Comin’ for to carry me home.
43             D7 G D
44             Swing low, sweet chariot,
45             A7 D
46             Comin’ for to carry me home.
47              
48             D G D
49             I looked over Jordan, and what did I see,
50             A7
51             Comin’ for to carry me home.
52             D G D
53             A band of angels comin’ after me,
54             A7 D
55             Comin’ for to carry me home.
56              
57             Note that the output from the conversion will generally need some
58             additional editing to be useful as input to ChordPro.
59              
60             B is a wrapper around L, which
61             does all of the work.
62              
63             B will read one or more text files containing the lyrics of
64             one or many songs plus chord information. B will then
65             generate a photo-ready, professional looking, impress-your-friends
66             sheet-music suitable for printing on your nearest printer.
67              
68             B is a rewrite of the Chordii program.
69              
70             For more information about the ChordPro file format, see
71             L.
72              
73             =cut
74              
75             ################ Common stuff ################
76              
77 1     1   5 use strict;
  1         3  
  1         35  
78 1     1   7 use warnings;
  1         1  
  1         31  
79 1     1   6 use utf8;
  1         4  
  1         5  
80 1     1   37 use Carp;
  1         2  
  1         111  
81              
82             ################ The Process ################
83              
84             package main;
85              
86             our $options;
87             our $config;
88              
89             package ChordPro::A2Crd;
90              
91 1     1   10 use ChordPro::Config;
  1         2  
  1         64  
92              
93 1     1   8 use File::LoadLines;
  1         2  
  1         56  
94 1     1   7 use Encode qw(decode decode_utf8 encode_utf8);
  1         2  
  1         4666  
95              
96             # API: Main entry point.
97             sub a2crd {
98 19     19 0 68 my ($opts) = @_;
99 19 50       64 $options = { %$options, %$opts } if $opts;
100              
101             # One configurator to bind them all.
102 19         94 $config = ChordPro::Config::configurator({});
103              
104             # Process input.
105             my $lines = $opts->{lines}
106             ? delete($opts->{lines})
107 19 50       306 : loadlines( @ARGV ? $ARGV[0] : \*STDIN);
    50          
108              
109 19         13077 return [ a2cho($lines) ];
110             }
111              
112             ################ Subroutines ################
113              
114             # Replace tabs with blanks, retaining layout.
115             my $tabstop;
116             sub expand {
117 31     31 0 62 my ( $line ) = @_;
118 31 50       78 return $line unless $line;
119 31   66     67 $tabstop //= $::config->{a2crd}->{tabstop};
120 31 50       66 return $line unless $tabstop > 0;
121              
122 31         99 my ( @l ) = split( /\t/, $line, -1 );
123 31 50       84 return $l[0] if @l == 1;
124              
125 31         56 $line = shift(@l);
126 31         198 $line .= " " x ($tabstop-length($line)%$tabstop) . shift(@l) while @l;
127              
128 31         75 return $line;
129             }
130              
131             # API: Produce ChordPro data from AsciiCRD lines.
132             sub a2cho {
133 19     19 0 74 my ( $lines ) = @_;
134 19         61 my $map = "";
135 19         57 my @lines_with_tabs_replaced ;
136 19         71 foreach ( @$lines ) {
137 757 100       1941 if(/\t/) {
138 31         76 $_ = expand($_) ;
139             }
140              
141             #s/=20/ /g ; # replace HTML coded space with ascii space, no, MUST LEAVE IN because it can mess up fingering diagrams like A/F#=202220
142 757         1375 s/=3D/=/g ; # replace HTML coded equal with ascii =
143             # s/\s*$// ; # remove all trailing whitespace -- no, MUST LEAVE IN so chords indicated above trailing whitespace will be properly formatted
144              
145 757         1084 my $n_ch_chords=0 ;
146              
147             #An odd format for chords, [ch]Chordname[\ch], possibly from reformated webpage
148             # need to strip out and consider it to be a chord line
149 757         1781 while(s/\[ch\](.*?)\[\/ch\]/$1/) {
150 16         83 $n_ch_chords++ ;
151             }
152              
153 757         1378 push @lines_with_tabs_replaced, $_ ;
154              
155 757 100       1421 if($n_ch_chords < 1) {
156 754         1338 $map .= classify($_);
157             } else {
158 3         8 $map .= "c" ;
159             }
160             }
161 19         229 maplines( $map, \@lines_with_tabs_replaced );
162              
163             }
164              
165             # Classify the line and return a single-char token.
166             my $classify;
167             sub classify {
168 754     754 0 1420 my ( $line ) = @_;
169 754 100       2412 return '_' if $line =~ /^\s*$/; # empty line
170 611 100       1241 return '{' if $line =~ /^\{.+/; # directive
171 601 100       1099 unless ( defined $classify ) {
172 1         4 my $classifier = $::config->{a2crd}->{classifier};
173 1         17 $classify = __PACKAGE__->can("classify_".$classifier);
174 1 50       4 unless ( $classify ) {
175 0         0 warn("No such classifier: $classifier, using classic\n");
176 0         0 $classify = \&classify_classic;
177             }
178              
179             }
180 601         1109 $classify->($line);
181             }
182              
183             sub classify_classic {
184 0     0 0 0 my ( $line ) = @_;
185             # Lyrics or Chords heuristic.
186 0         0 my @words = split ( /\s+/, $line );
187 0         0 my $len = length($line);
188 0         0 $line =~ s/\s+//g;
189 0 0       0 my $type = ( $len / length($line) - 1 ) < 1 ? 'l' : 'c';
190 0         0 my $p = ChordPro::Chords::Parser->default;
191 0 0       0 if ( $type eq 'l') {
192 0         0 foreach (@words) {
193 0 0       0 if (length $_ > 0) {
194 0 0       0 if (!ChordPro::Chords::parse_chord($_)) {
195 0         0 return 'l';
196             }
197             }
198             }
199 0         0 return 'c';
200             }
201 0         0 return $type;
202             }
203              
204             # JJW -- attempts at using "relaxed" in the standard chordnames parser were too relaxed
205             # so I made this to try to parse unspecified chords that still have well defined "parts" in the chordname
206             # these chords probably are understandable by a human, but too out of spec for the chordpro parser to interpret
207             # my use of regex is probably not optimal -- I haven't had a lot of regex experience.
208             # this currently only works for the roman chord notation
209             sub generic_parse_chord
210             {
211 890     890 0 1353 my $word = shift ;
212              
213 890         1290 my ($chord,$bass) ;
214 890 100       1911 if ( $word =~ m;^(.*)/(.*); ) {
215 15         41 $chord = $1;
216 15         46 $bass = $2;
217             } else {
218 875         1301 $chord=$word ;
219             }
220              
221 890 100       1502 if($bass) {
222              
223             # this was the first attempt, but found it to be to restrictive
224             #return 0 if(! ($bass =~ /^($roots)$/) ) ;
225              
226             # now allow anything after the "/"
227             }
228              
229             # in anticipation of nashville and solfege ;
230 890         1278 my $roots = "^[A-G]" ;
231 890         1237 my $found_chord_base="" ;
232              
233             # first part of chord needs to be [A-G]
234 890 100       4350 return 0 if(! ($chord =~ s/($roots)//)) ;
235              
236 48         196 $found_chord_base .= $1 ;
237              
238 48         121 $chord = lc($chord) ; # simplify to lowercase for further parsing
239              
240 48 100       170 if($chord =~ s/^([b#]|flat|sharp)//) {
241 3         6 $found_chord_base .= $1 ;
242             }
243              
244 48 50       150 if($chord =~ s/^(minor|major)//) {
245 0         0 $found_chord_base .= $1 ;
246             }
247              
248 48 50       135 if($chord =~ s/^(min|maj)//) {
249 0         0 $found_chord_base .= $1 ;
250             }
251              
252 48 100       157 if($chord =~ s/^(m|dim|0|o|aug|\+)//) {
253 9         21 $found_chord_base .= $1 ;
254             }
255              
256 48         166 $chord =~ s/^[\d]*// ; # to get the 7 in "A7", etc
257              
258             # all that should remain are note numbers and note modifiers b, #, "sus", "add", "flat", "sharp", -, +
259             # strip those possible combinations one at a time
260              
261 48         207 while( $chord =~ s/^(b|#|\+|\-|flat|sharp|sus|add)*?\d// ) {} ;
262              
263             # if all that remains are digits and "#b-", it's probably a chord
264 48         100 my $n_ok = ($chord =~ tr/0123456789#b-//) ;
265              
266 48 100       235 return 1 if $n_ok == length $chord ;
267             }
268              
269             # determine if the input line is a fingering definition for a chord
270             sub decode_fingering
271             {
272 787     787 0 1587 my ($line,$return_chordpro_fingering) = @_ ;
273 787         973 my $is_fingering=0 ;
274 787         1219 my $input_line = $line ;
275 787         1042 my $any_chord_ok=1 ; # allows any text for the chord preceding a fingering pattern to be valid
276              
277             # since more than one chord can be defined on a single input text line,
278             # hold all results in these two arrays
279 787         1120 my (@chords,@fingerss) ;
280              
281             # THIS ONLY WORKS FOR FRETS <=9 right now
282              
283             # is it a fingering notation?
284              
285 787         1185 my $pre = "^.*?\\s*?" ; # the pattern to match just before a chord name
286 787         1055 my $valid = "[A-G]{1}\\S*?" ; # a valid chordname
287              
288             # ("chord:") followed by "|x2344x|" or "x2344x"
289 787         3572 while($line =~ /$pre($valid)\:+?\s*?(\|?[xX0-9]{3,7}\|?)/) {
290 38         96 my $cname=$1 ;
291 38         86 my $fingers_this=$2 ;
292 38         57 my $nobar_fingers=$fingers_this ;
293 38         117 $nobar_fingers =~ s/\|//g ;
294              
295 38 50 33     108 if($any_chord_ok || generic_parse_chord($cname)) {
296 38         68 push @chords,$cname ;
297 38         56 push @fingerss,$nobar_fingers ;
298 38         54 $is_fingering=1 ;
299             }
300              
301 38         604 $line =~ s/.*?$nobar_fingers// ;
302             }
303              
304              
305             # ("chord") followed by "|x2344x|" "x2344x"
306 787         10868 while($line =~ /$pre($valid)\s+?(\|?[xX0-9]{3,7}\|?)/) {
307 32         82 my $cname=$1 ;
308 32         62 my $fingers_this=$2 ;
309 32         58 my $nobar_fingers=$fingers_this ;
310 32         56 $nobar_fingers =~ s/\|//g ;
311              
312 32 50 33     76 if($any_chord_ok || generic_parse_chord($1)) {
313 32         70 push @chords,$cname ;
314 32         52 push @fingerss,$nobar_fingers ;
315 32         51 $is_fingering=1 ;
316             }
317              
318 32         493 $line =~ s/.*?$nobar_fingers// ;
319             }
320              
321             # "(chord) = (fingering)" format
322 787         3750 while($line =~ /$pre($valid)\s*?\=\s*?([xX0123456789]{3,7})/) {
323 384         922 my $cname=$1 ;
324 384         580 my $fingers_this=$2 ;
325 384         535 my $nobar_fingers=$fingers_this ;
326 384         723 $nobar_fingers =~ s/\|//g ;
327              
328 384 50 33     866 if($any_chord_ok || generic_parse_chord($1)) {
329 384         685 push @chords,$cname ;
330 384         507 push @fingerss,$nobar_fingers ;
331 384         516 $is_fingering=1 ;
332             }
333              
334 384         5526 $line =~ s/.*?$nobar_fingers// ;
335             }
336              
337 787 100       1770 if($is_fingering) {
338 402 100       1320 return 1 if ! $return_chordpro_fingering ;
339              
340             # handle situation where more than one chord is defined on an input text line
341 201         275 my @output_lines ;
342              
343             #push @output_lines, $input_line if 1 ; # only for debugging
344              
345 201         390 foreach my $chord (@chords) {
346 227         363 my $fingers = shift @fingerss ;
347 227         350 my $min_fret=100 ;
348 227         319 my $max_fret=0 ;
349 227         293 my @frets ;
350              
351 227         818 while($fingers =~ s/(.)//) {
352 1359         2757 my $fret=$1 ;
353 1359         2178 push @frets, $fret ;
354              
355 1359 100       3349 if($fret =~ /[0-9]/) {
356 1213 100       2321 $min_fret = $fret if $min_fret > $fret ;
357 1213 100       3868 $max_fret = $fret if $max_fret < $fret ;
358             }
359             }
360              
361             # now convert the requested fingering to chordpro format
362 227         351 my $bf=$min_fret ;
363              
364 227         557 my $chordpro = "{define $chord base-fret $bf frets" ;
365 227 100       500 $bf-- if $bf > 0 ;
366              
367 227         404 foreach my $fret (@frets) {
368 1359         2104 $chordpro = $chordpro . " " ;
369              
370 1359 100       3037 if($fret =~ /[0-9]/) {
371 1213         1848 my $rf = $fret-$bf ;
372              
373 1213         2303 $chordpro .= "$rf" ;
374             } else {
375 146         265 $chordpro .= '-' ;
376             }
377             }
378              
379 227         357 $chordpro .= "}" ;
380 227         585 push @output_lines, $chordpro ;
381             }
382              
383 201         585 return @output_lines ;
384             }
385              
386 385         953 return 0 ;
387             }
388              
389             # classification characters are:
390             # 'l' = normal text line, usually lyrics but may be other plain text as well
391             # 'C' = a comment
392             # 'f' = a chord fingering request
393             # 't' = tablature
394             # 'c' = chords, usually to be output inline with a subsequent 'l' line
395             # '{' = an embedded chordpro directive found in the input file, to be output with no changes
396             # '_' = a blank line, i.e. it contains only whitespace
397              
398             # Alternative classifier by Jeff Welty.
399             # Strategy: Percentage of recognzied chords.
400             sub classify_pct_chords {
401 601     601 0 1046 my ( $line ) = @_;
402 601         1471 my $lc_line = lc($line) ;
403 601         833 my $local_debug=0 ;
404              
405 601 100       1318 return 'C' if $line =~ /^\s*\[.+?\]/; # comment
406 595 100       1247 return 'C' if $line =~ /^\s*\#.+?/; # comment
407 592 100       1167 return 'C' if $lc_line =~ /(from|email|e\-mail)\:?.+?@+/ ; # email is treated as a comment
408 590 100       1159 return 'C' if $lc_line =~ /(from|email|e\-mail)\:.+?/ ; # same as above, but there MUST be a colon, and no @ is necessary
409 588 100       1258 return 'C' if $lc_line =~ /(date|subject)\:.+?/ ; # most likely part of email lines is treated as a comment
410              
411             # check for a chord fingering specification, i.e. A=x02220
412 586 100       1079 return 'f' if decode_fingering($line,0) ;
413              
414 385         556 if(0) {
415             #Oct 31 and before
416             return 't' if $line =~ /^\s*?[A-G|a-g]\s*\|.*?\-.*\|/; # tablature
417             return 't' if $line =~ /^\s*?[A-G|a-g]\s*\-.*?\-.*\|*/; # tablature
418             } else {
419             # try to accomodate tablature lines with text after the tab
420              
421 385         547 my $longest_tablature_string=0 ;
422 385         610 my $tmpline = $line ;
423              
424             # REGEX components:
425              
426             # start with any amount of whitespace
427             # ^\s*?
428             # must be one string note
429             # [A-G|a-g]
430             # one or more of : or |
431             # [:\|]+
432             # in the tablature itself, separators of : or |, modifiers of b=bend,p=pull off,h=hammer on,x=muted,0-9 fret positionsj,\/=slides,() for two digit fret positions
433             # [\-:\|bphxBPHX0-9\/\\\(\)]*?
434             # one or more of : or |
435             # [:\|]+
436              
437 385         1445 while($tmpline =~ s/^(\s*?[A-G|a-g][:\|]+[\-:\|bphxBPHX0-9\/\\\(\)]*?[:\|]+)//) {
438 55 50       316 $longest_tablature_string = length($1) if $longest_tablature_string < length($1) ;
439             }
440              
441 385 100       887 return 't' if $longest_tablature_string > 8 ;
442             }
443              
444              
445             # count number of specific characters to help identify tablature lines
446 330         848 my $n_v = ($line =~ tr/v//) ;
447 330         643 my $n_dash = ($line =~ tr/-//) ;
448 330         638 my $n_equal = ($line =~ tr/=//) ;
449 330         616 my $n_bar = ($line =~ tr/|//) ;
450 330         652 my $n_c_accent = ($line =~ tr/^//) ;
451 330         597 my $n_period = ($line =~ tr/.//) ;
452 330         615 my $n_space = ($line =~ tr/ //) ;
453 330         613 my $n_slash = ($line =~ tr/\///) ;
454 330         601 my $n_underscore = ($line =~ tr/_//) ;
455 330         619 my $n_digit = ($line =~ tr/0123456789//) ;
456              
457             # some inputs are of the form "| / / / _ / | / / / / / |", to indicate strumming patterns
458             # need to recognize this as tablature for nice formatting, and if chords are in the line
459             # preceding they will be included in the tablature by maplines() to ensure correct formatting
460 330         443 my $longest_strumming_string=0 ;
461 330         480 my $cntline = $line ;
462              
463 330         1480 while( $cntline =~ s/([\|\/ _]+?)//) {
464 3100 100       14420 $longest_strumming_string = length($1) if $longest_strumming_string < length($1) ;
465             }
466              
467 330 50       692 return 't' if ($longest_strumming_string >= 6) ;
468              
469              
470              
471             # Lyrics or Chords heuristic.
472 330         1547 my @words = split ( /\s+/, $line );
473              
474 330         704 my $n_tot_chars = length($line) ;
475 330         1488 $line =~ s/\s+//g ;
476 330         706 my $n_nonblank_chars = length($line) ;
477              
478             # have to wait until $n_nonblank_chars is computed to do these tests
479 330 100 100     1222 return 'l' if ($n_dash == $n_nonblank_chars || $n_equal == $n_nonblank_chars) ; # only "-" or "=", meant to be a textual underline indication of the previous line
480 322 100       1038 return 't' if (($n_period + $n_dash + $n_bar + $n_c_accent + $n_v + $n_digit)/$n_nonblank_chars > 0.8) ; # mostly characters used in standard tablature
481 218 100       558 return 't' if (($n_bar + $n_slash + $n_underscore)/$n_nonblank_chars >= 0.5) ; # mostly characters used in strumming tablature
482              
483              
484 216         294 my $n_chords=0 ;
485 216         299 my $n_words=0 ;
486              
487             #print("CL:") ; # JJW, uncomment for debugging
488              
489 216         490 foreach (@words) {
490 1157 100       2752 if (length $_ > 0) {
491 1083         1348 $n_words++ ;
492              
493              
494 1083 100       2564 my $is_chord = ChordPro::Chords::parse_chord($_) ? 1 : 0 ;
495 1083 100       2646 if(! $is_chord) {
496 890 100       1629 if(generic_parse_chord($_)) {
497 9 50       27 print STDERR "$_ detected by generic, not internal parse_chord\n" if $local_debug ;
498 9         17 $is_chord=1 ;
499             }
500             }
501              
502 1083 100       2290 $n_chords++ if $is_chord ;
503 1083 50       2360 print STDERR " ($is_chord:$_)" if $local_debug ;
504              
505             #print(" \'$is_chord:$_\'") ; # JJW, uncomment for debugging
506             }
507             }
508 216 50       429 print STDERR "\n" if $local_debug ;
509              
510 216 50       455 return '_' if $n_words == 0 ; # blank line, redundant logic with sub classify(), but makes this more robust to changes in classify() ;
511              
512 216 100       567 my $type = $n_chords/$n_words > 0.4 ? 'c' : 'l' ;
513              
514 216 100       471 if($type eq 'l') {
515             # is it likely the line had a lot of unknown chords, check
516             # the ratio of total chars to nonblank chars , if it is large then
517              
518             # it's probably a chord line
519             # $type = 'c' if $n_words > 1 && $n_tot_chars/$n_nonblank_chars > 2. ;
520             }
521              
522             #print(" --- ($n_chords/$n_words) = $type\n") ; # JJW, uncomment for debugging
523              
524 216         928 return $type ;
525             }
526              
527             # reformat an input line classified as a comment for the chordpro format
528             sub format_comment_line
529             {
530 69     69 0 130 my $line = $_[0] ;
531             # remove [] from original comment
532 69         146 $line =~ s/\[// ;
533 69         136 $line =~ s/\]// ;
534 69 50       157 return '' if $line eq '' ;
535 69         219 return "{comment:" . $line . "}" ;
536             }
537              
538             # Process the lines via the map.
539             my $infer_titles;
540             sub maplines {
541 19     19 0 118 my ( $map, $lines ) = @_;
542 19         36 my @out;
543 19         34 my $local_debug=0 ;
544 19   66     76 $infer_titles //= $::config->{a2crd}->{'infer-titles'};
545              
546             # Preamble.
547             # Pass empty lines.
548              
549 19 50       186 print STDERR "====== _C =====\n" if $local_debug ;
550 19 50       53 print STDERR "MAP: \'$map\' \n" if $local_debug ;
551              
552 19         136 while ( $map =~ s/^([_C])// ) {
553 13 50       36 print STDERR "$1 == @{$lines}[0]\n" if $local_debug ;
  0         0  
554             # simply output blank or comment lines at the start of the file
555             # but don't count the line as possible title
556 13 100       44 my $pre = ($1 eq "C" ? "{comment:" : "" ) ;
557 13 100       38 my $post = ($1 eq "C" ? "}" : "" ) ;
558 13         85 push( @out, $pre . shift( @$lines ) . $post );
559             }
560              
561 19 50       56 print STDERR "====== infer title =====\n" if $local_debug ;
562             # Infer title/subtitle.
563 19 100 66     55 if ( $infer_titles && $map =~ s/^l// ) {
564 18         338 push( @out, "{title: " . shift( @$lines ) . "}");
565 18 100       87 if ( $map =~ s/^l// ) {
566 8         35 push( @out, "{subtitle: " . shift( @$lines ) . "}");
567             }
568             }
569              
570 19 50       76 print STDERR "====== UNTIL chords or tablature =====\n" if $local_debug ;
571             # Pass lines until we have chords or tablature
572              
573 19         96 while ($map =~ /^(.)(.)(.)/) {
574 331 50       669 push @out, "ULC $map" if $local_debug ;
575             # some unusual situations to handle,
576              
577             # cl. => exit this loop for normal cl processing
578             # .t => exit the loop
579             # l.t or c.t => output the l or c as comment, then exit the loop
580             # [_f{C].. => output the blank, fingering,directive or comment, and continue the loop
581              
582             # we have to stop one line before tablature, in case the line before the tablature needs to be included in the
583             # tablature itself
584 331 50       563 print STDERR "$1 == @{$lines}[0]\n" if $local_debug ;
  0         0  
585              
586 331 100 100     925 last if($1 eq "c" && $2 eq "l") ;
587 326 100       673 last if($2 eq "t" ) ;
588              
589 322 100 100     1199 if(($1 eq "c" || $1 eq "l") && $3 eq "t") {
      100        
590 6         29 push @out, format_comment_line(shift(@$lines)) ;
591 6         38 $map =~ s/.// ;
592 6         22 last ;
593             }
594              
595             # in the remaining cases, output the line (properly handled), and continue the loop
596 316 100 100     1119 if ( $1 eq "l" or $1 eq "C") {
    100          
    100          
597 59         152 push @out, format_comment_line(shift(@$lines)) ;
598             }
599             elsif ( $1 eq "f" ) {
600 191         460 foreach my $fchart (decode_fingering(shift( @$lines ),1) ) {
601 217         414 push( @out, $fchart);
602             }
603             }
604             elsif ( $1 eq "{" ) {
605 8         17 my $line = shift @$lines ;
606 8         16 push( @out, $line);
607              
608 8 100       26 if($line =~ /{sot}/) {
609             # output all subsequent lines until {eot} is found
610 1         3 while(1) {
611 8         12 $line = shift @$lines ;
612 8 50       16 die "Malformed input, {sot} has no matching {eot}" if ! $line ;
613 8         17 $map = s/.// ;
614 8         16 push( @out, $line);
615 8 100       21 last if $line =~ /{eot}/ ;
616             }
617              
618             }
619             }
620             else {
621 58         132 push( @out, shift( @$lines ) );
622             }
623 316         1344 $map =~ s/.// ;
624             }
625              
626 19 50       75 push @out, "====== FINAL LOOP =====" if $local_debug ;
627             # Process the lines using the map.
628 19         62 while ( $map ) {
629             # warn($map);
630 187 50       310 push @out, "FL $map" if $local_debug ;
631 187         371 $map =~ /(.)/ ;
632 187 50       348 print STDERR "$1 == @{$lines}[0]\n" if $local_debug ;
  0         0  
633              
634             #a fingering line, simply output the directive and continue
635 187 100       408 if ( $map =~ s/^f// ) {
636 10         34 foreach my $fchart (decode_fingering(shift( @$lines ),1) ) {
637 10         26 push( @out, $fchart);
638             }
639 10         32 next ;
640             }
641              
642             # Blank line - output the blank line and continue
643 177 100       431 if ( $map =~ s/^_// ) {
644 78         149 push( @out, '');
645 78         133 shift(@$lines);
646 78         161 next ;
647             }
648              
649             # A comment line, output and continue
650 99 100       230 if ( $map =~ s/^C// ) {
651 4         41 push @out, format_comment_line(shift(@$lines)) ;
652 4         10 next ;
653             }
654              
655             # Tablature
656 95         136 my $in_tablature=0 ;
657              
658             # special case: chords or lyrics before tabs, keep the chords or lyrics in {sot}, which is probably
659             # what the original text intended for alignment with the tablature
660 95 100       237 if ( $map =~ s/^[cl]t/t/ ) {
661 17 50       52 if(! $in_tablature) {
662 17         47 push( @out, "{sot}") ;
663 17         32 $in_tablature=1 ;
664             }
665 17         38 push( @out, shift(@$lines));
666             }
667              
668 95         245 while( $map =~ s/^t// ) {
669 155 100       294 if(! $in_tablature) {
670 12         21 push( @out, "{sot}") ;
671 12         21 $in_tablature=1 ;
672             }
673 155         484 push( @out, shift(@$lines));
674             # and Fall through.
675             }
676              
677 95 100       220 if($in_tablature) {
678             # Text line OR chord line with following blank line or EOF -- make part of tablature
679 29 100       121 if ( $map =~ s/^[cl](_|$)// ) {
680 9         20 push( @out, shift(@$lines));
681 9         16 push( @out, '');
682 9         13 shift(@$lines);
683             }
684              
685 29         67 push( @out, "{eot}") ;
686 29         46 $in_tablature=0 ;
687 29         73 next ;
688             }
689              
690             # Blank line preceding lyrics: pass.
691 66 50       189 if ( $map =~ s/^_l/l/ ) {
692 0         0 push( @out, '');
693 0         0 shift(@$lines);
694             }
695              
696             # The normal case: chords + lyrics.
697 66 100       333 if ( $map =~ s/^cl// ) {
    50          
    50          
    100          
    100          
    50          
    0          
698 39         131 push( @out, combine( shift(@$lines), shift(@$lines), "cl" ) );
699             }
700              
701             # Empty line preceding a chordless lyrics line.
702             elsif ( $map =~ s/^__l// ) {
703 0         0 push( @out, '' );
704 0         0 shift( @$lines );
705 0         0 push( @out, combine( shift(@$lines), shift(@$lines), "__l" ) );
706             }
707              
708             # Chordless lyrics line.
709             elsif ( $map =~ s/^_l// ) {
710 0         0 push( @out, combine( shift(@$lines), shift(@$lines), "_l" ) );
711             }
712              
713             # Lone directives.
714             elsif ( $map =~ s/^{// ) {
715 1         4 my $line = shift @$lines ;
716 1         3 push( @out, $line);
717              
718 1 50       6 if($line =~ /{sot}/) {
719             # output all subsequent lines until {eot} is found
720 0         0 while(1) {
721 0         0 $line = shift @$lines ;
722 0 0       0 die "Malformed input, {sot} has no matching {eot}" if ! $line ;
723 0         0 $map = s/.// ;
724 0         0 push( @out, $line);
725 0 0       0 last if $line =~ /{eot}/ ;
726             }
727              
728             }
729             }
730              
731             # Lone lyrics.
732             elsif ( $map =~ s/^l// ) {
733 22         67 push( @out, shift( @$lines ) );
734             }
735              
736             # Lone chords.
737             elsif ( $map =~ s/^c// ) {
738 4         18 push( @out, combine( shift(@$lines), '', "c" ) );
739             }
740              
741             # Empty line.
742             elsif ( $map =~ s/^_// ) {
743 0         0 push( @out, '' );
744 0         0 shift( @$lines );
745             }
746              
747             # Can't happen.
748             else {
749 0         0 croak("MAP: $map");
750             }
751             }
752 19 50       553 return wantarray ? @out : \@out;
753             }
754              
755             # Combine two lines (chords + lyrics) into lyrics with [chords].
756             sub combine {
757 43     43 0 105 my ( $l1, $l2 ) = @_;
758 43         73 my $res = "";
759 43         176 while ( $l1 =~ /^(\s*)(\S+)(.*)/ ) {
760 130         699 $res .= join( '',
761             substr( $l2, 0, length($1), '' ),
762             '[' . $2 . ']',
763             substr( $l2, 0, length($2), '' ) );
764 130         491 $l1 = $3;
765             }
766 43         219 return $res.$l2;
767             }
768              
769             ################ Options and Configuration ################
770              
771             =head1 COMMAND LINE OPTIONS
772              
773             =over 4
774              
775             =item B<--output=>I (short: B<-o>)
776              
777             Designates the name of the output file where the results are written
778             to. Default is standard output.
779              
780             =item B<--version> (short: B<-V>)
781              
782             Prints the program version and exits.
783              
784             =item B<--help> (short: -h)
785              
786             Prints a help message. No other output is produced.
787              
788             =item B<--manual>
789              
790             Prints the manual page. No other output is produced.
791              
792             =item B<--ident>
793              
794             Shows the program name and version.
795              
796             =item B<--verbose>
797              
798             Provides more verbose information of what is going on.
799              
800             =back
801              
802             =cut
803              
804 1     1   10 use Getopt::Long 2.13;
  1         12  
  1         30  
805              
806             # Package name.
807             my $my_package;
808             # Program name and version.
809             my ($my_name, $my_version);
810             my %configs;
811              
812             sub app_setup {
813 0     0 0   goto &ChordPro::app_setup;
814 0           my ($appname, $appversion, %args) = @_;
815 0           my $help = 0; # handled locally
816 0           my $manual = 0; # handled locally
817 0           my $ident = 0; # handled locally
818 0           my $version = 0; # handled locally
819 0           my $defcfg = 0; # handled locally
820 0           my $fincfg = 0; # handled locally
821              
822             # Package name.
823 0           $my_package = $args{package};
824             # Program name and version.
825 0 0         if ( defined $appname ) {
826 0           ($my_name, $my_version) = ($appname, $appversion);
827             }
828             else {
829 0           ($my_name, $my_version) = qw( MyProg 0.01 );
830             }
831              
832             # Config files.
833 0           my $app_lc = lc("ChordPro"); # common config
834 0 0         if ( -d "/etc" ) { # some *ux
835             $configs{sysconfig} =
836 0           File::Spec->catfile( "/", "etc", "$app_lc.json" );
837             }
838              
839 0   0       my $e = $ENV{CHORDIIRC} || $ENV{CHORDRC};
840 0 0 0       if ( $ENV{HOME} && -d $ENV{HOME} ) {
841 0 0         if ( -d File::Spec->catfile( $ENV{HOME}, ".config" ) ) {
842             $configs{userconfig} =
843 0           File::Spec->catfile( $ENV{HOME}, ".config", $app_lc, "$app_lc.json" );
844             }
845             else {
846             $configs{userconfig} =
847 0           File::Spec->catfile( $ENV{HOME}, ".$app_lc", "$app_lc.json" );
848             }
849 0   0       $e ||= File::Spec->catfile( $ENV{HOME}, ".chordrc" );
850             }
851 0   0       $e ||= "/chordrc"; # Windows, most likely
852 0 0 0       $configs{legacyconfig} = $e if -s $e && -r _;
853              
854 0 0         if ( -s ".$app_lc.json" ) {
855 0           $configs{config} = ".$app_lc.json";
856             }
857             else {
858 0           $configs{config} = "$app_lc.json";
859             }
860              
861 0           my $options =
862             {
863             verbose => 0, # verbose processing
864              
865             # Development options (not shown with -help).
866             debug => 0, # debugging
867             trace => 0, # trace (show process)
868              
869             # Service.
870             _package => $my_package,
871             _name => $my_name,
872             _version => $my_version,
873             _stdin => \*STDIN,
874             _stdout => \*STDOUT,
875             _stderr => \*STDERR,
876             _argv => [ @ARGV ],
877             };
878              
879             # Colled command line options in a hash, for they will be needed
880             # later.
881 0           my $clo = {};
882              
883             # Sorry, layout is a bit ugly...
884 0 0         if ( !GetOptions
885             ($clo,
886             "output|o=s", # Saves the output to FILE
887              
888             ### Configuration handling ###
889              
890             'config|cfg=s@',
891             'noconfig|no-config',
892             'sysconfig=s',
893             'nosysconfig|no-sysconfig',
894             'userconfig=s',
895             'nouserconfig|no-userconfig',
896             'nodefaultconfigs|no-default-configs|X',
897             'define=s%',
898             'print-default-config' => \$defcfg,
899             'print-final-config' => \$fincfg,
900              
901             ### Standard options ###
902              
903             "version|V" => \$version, # Prints version and exits
904             'ident' => \$ident,
905             'help|h|?' => \$help,
906             'manual' => \$manual,
907             'verbose|v+',
908             'trace',
909             'debug+',
910             ) )
911             {
912             # GNU convention: message to STDERR upon failure.
913 0           app_usage(\*STDERR, 2);
914             }
915              
916             my $pod2usage = sub {
917             # Load Pod::Usage only if needed.
918 0     0     require Pod::Usage;
919 0           Pod::Usage->import;
920 0           my $f = "pod/A2Crd.pod";
921 0           unshift( @_, -input => getresource($f) );
922 0           &pod2usage;
923 0           };
924              
925             # GNU convention: message to STDOUT upon request.
926 0 0 0       app_ident(\*STDOUT) if $ident || $help || $manual;
      0        
927 0 0 0       if ( $manual or $help ) {
928 0 0         app_usage(\*STDOUT, 0) if $help;
929 0 0         $pod2usage->(VERBOSE => 2) if $manual;
930             }
931 0 0         app_ident(\*STDOUT, 0) if $version;
932              
933             # If the user specified a config, it must exist.
934             # Otherwise, set to a default.
935 0           for my $config ( qw(sysconfig userconfig) ) {
936 0           for ( $clo->{$config} ) {
937 0 0         if ( defined($_) ) {
938 0 0         die("$_: $!\n") unless -r $_;
939 0           next;
940             }
941             # Use default.
942 0 0         next if $clo->{nodefaultconfigs};
943 0 0         next unless $configs{$config};
944 0           $_ = $configs{$config};
945 0 0         undef($_) unless -r $_;
946             }
947             }
948 0           for my $config ( qw(config) ) {
949 0           for ( $clo->{$config} ) {
950 0 0         if ( defined($_) ) {
951 0           foreach my $c ( @$_ ) {
952             # Check for resource names.
953 0 0 0       if ( ! -r $c && $c !~ m;[/.]; ) {
954 0           $c = ::rsc_or_file( $c, "config" );
955             }
956 0 0         die("$c: $!\n") unless -r $c;
957             }
958 0           next;
959             }
960             # Use default.
961 0 0         next if $clo->{nodefaultconfigs};
962 0 0         next unless $configs{$config};
963 0           $_ = [ $configs{$config} ];
964 0 0         undef($_) unless -r $_->[0];
965             }
966             }
967             # If no config was specified, and no default is available, force no.
968 0           for my $config ( qw(sysconfig userconfig config) ) {
969 0 0         $clo->{"no$config"} = 1 unless $clo->{$config};
970             }
971              
972             ####TODO: Should decode all, and remove filename exception.
973 0           for ( keys %{ $clo->{define} } ) {
  0            
974 0           $clo->{define}->{$_} = decode_utf8($clo->{define}->{$_});
975             }
976              
977             # Plug in command-line options.
978 0           @{$options}{keys %$clo} = values %$clo;
  0            
979             # warn(Dumper($options), "\n") if $options->{debug};
980              
981 0 0 0       if ( $defcfg || $fincfg ) {
982 0 0         print ChordPro::Config::config_default()
983             if $defcfg;
984 0 0         print ChordPro::Config::config_final()
985             if $fincfg;
986 0           exit 0;
987             }
988              
989             # Return result.
990 0           $options;
991             }
992              
993             sub app_ident {
994 0     0 0   my ($fh, $exit) = @_;
995 0 0         print {$fh} ("This is ",
  0            
996             $my_package
997             ? "$my_package [$my_name $my_version]"
998             : "$my_name version $my_version",
999             "\n");
1000 0 0         exit $exit if defined $exit;
1001             }
1002              
1003             sub app_usage {
1004 0     0 0   my ($fh, $exit) = @_;
1005 0           my $cmd = $0;
1006 0 0         $cmd .= " --a2crd" if $cmd !~ m;(?:^|\/|\\)a2crd(?:\.\w+)$;;
1007 0           print ${fh} <
1008             Usage: $cmd [ options ] [ file ... ]
1009              
1010             Options:
1011             --output=FILE -o Saves the output to FILE
1012             --version -V Prints version and exits
1013             --help -h This message
1014             --manual The full manual
1015             --ident Show identification
1016             --verbose Verbose information
1017             EndOfUsage
1018 0 0         exit $exit if defined $exit;
1019             }
1020              
1021             =head1 AUTHOR
1022              
1023             Johan Vromans C<< >>
1024              
1025             =head1 SUPPORT
1026              
1027             A2Crd is part of ChordPro (the program). Development is hosted on
1028             GitHub, repository L.
1029              
1030             Please report any bugs or feature requests to the GitHub issue tracker,
1031             L.
1032              
1033             A user community discussing ChordPro can be found at
1034             L.
1035              
1036             =head1 LICENSE
1037              
1038             Copyright (C) 2010,2018 Johan Vromans,
1039              
1040             This program is free software. You can redistribute it and/or
1041             modify it under the terms of the Artistic License 2.0.
1042              
1043             This program is distributed in the hope that it will be useful,
1044             but without any warranty; without even the implied warranty of
1045             merchantability or fitness for a particular purpose.
1046              
1047             =cut
1048              
1049             1;