File Coverage

lib/ChordPro/A2Crd.pm
Criterion Covered Total %
statement 304 409 74.3
branch 153 256 59.7
condition 23 45 51.1
subroutine 21 26 80.7
pod 0 14 0.0
total 501 750 66.8


line stmt bran cond sub pod time code
1             #! perl
2              
3 1     1   347700 use v5.26;
  1         6  
4              
5             package ChordPro::A2Crd;
6              
7 1     1   7 use ChordPro::Version;
  1         3  
  1         59  
8 1     1   6 use ChordPro::Files;
  1         2  
  1         232  
9 1     1   9 use ChordPro::Paths;
  1         2  
  1         117  
10 1     1   10 use ChordPro::Chords;
  1         2  
  1         90  
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 no one 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   8 use strict;
  1         1  
  1         51  
78 1     1   6 use warnings;
  1         3  
  1         100  
79 1     1   9 use utf8;
  1         2  
  1         10  
80 1     1   40 use Carp;
  1         3  
  1         153  
81              
82             ################ The Process ################
83              
84             package main;
85              
86             our $options;
87             our $config;
88              
89             package ChordPro::A2Crd;
90              
91 1     1   8 use ChordPro::Config;
  1         3  
  1         10823  
92              
93             my $local_debug;
94              
95             # API: Main entry point.
96             sub a2crd {
97 19     19 0 62 my ($opts) = @_;
98 19 50       92 $options = { %$options, %$opts } if $opts;
99              
100             # One configurator to bind them all.
101 19         91 $config = ChordPro::Config::configurator({});
102 19         199 $local_debug = $config->{debug}->{a2crd};
103              
104             # Process input.
105             my $lines = $opts->{lines}
106             ? delete($opts->{lines})
107 19 50       288 : fs_load( @ARGV ? $ARGV[0] : \*STDIN);
    50          
108              
109 19         117 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 63 my ( $line ) = @_;
118 31 50       106 return $line unless $line;
119 31   66     82 $tabstop //= $::config->{a2crd}->{tabstop};
120 31 50       77 return $line unless $tabstop > 0;
121              
122 31         102 my ( @l ) = split( /\t/, $line, -1 );
123 31 50       79 return $l[0] if @l == 1;
124              
125 31         54 $line = shift(@l);
126 31         221 $line .= " " x ($tabstop-length($line)%$tabstop) . shift(@l) while @l;
127              
128 31         117 return $line;
129             }
130              
131             # API: Produce ChordPro data from AsciiCRD lines.
132             sub a2cho {
133 19     19 0 64 my ( $lines ) = @_;
134 19         60 my $map = "";
135 19         58 my @lines_with_tabs_replaced ;
136 19         116 foreach ( @$lines ) {
137 757 100       2380 if(/\t/) {
138 31         97 $_ = 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         1493 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         1224 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         1983 while(s/\[ch\](.*?)\[\/ch\]/$1/) {
150 16         46 $n_ch_chords++ ;
151             }
152              
153 757         1772 push @lines_with_tabs_replaced, $_ ;
154              
155 757 100       1775 if($n_ch_chords < 1) {
156 754         1504 $map .= classify($_);
157             } else {
158 3         5 $map .= "c" ;
159             }
160             }
161 19         102 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 1439 my ( $line ) = @_;
169 754 100       2765 return '_' if $line =~ /^\s*$/; # empty line
170 611 100       1503 return '{' if $line =~ /^\{.+/; # directive
171 601 100       1301 unless ( defined $classify ) {
172 1         4 my $classifier = $::config->{a2crd}->{classifier};
173 1         15 $classify = __PACKAGE__->can("classify_".$classifier);
174 1 50       3 unless ( $classify ) {
175 0         0 warn("No such classifier: $classifier, using classic\n");
176 0         0 $classify = \&classify_classic;
177             }
178              
179             }
180 601         1292 $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 1875 my $word = shift ;
212              
213 890         1800 my ($chord,$bass) ;
214 890 100       2507 if ( $word =~ m;^(.*)/(.*); ) {
215 15         59 $chord = $1;
216 15         54 $bass = $2;
217             } else {
218 875         1540 $chord=$word ;
219             }
220              
221 890 100       2339 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         1692 my $roots = "^[A-G]" ;
231 890         1850 my $found_chord_base="" ;
232              
233             # first part of chord needs to be [A-G]
234 890 100       9059 return 0 if(! ($chord =~ s/($roots)//)) ;
235              
236 48         206 $found_chord_base .= $1 ;
237              
238 48         142 $chord = lc($chord) ; # simplify to lowercase for further parsing
239              
240 48 100       270 if($chord =~ s/^([b#]|flat|sharp)//) {
241 3         10 $found_chord_base .= $1 ;
242             }
243              
244 48 50       214 if($chord =~ s/^(minor|major)//) {
245 0         0 $found_chord_base .= $1 ;
246             }
247              
248 48 50       204 if($chord =~ s/^(min|maj)//) {
249 0         0 $found_chord_base .= $1 ;
250             }
251              
252 48 100       235 if($chord =~ s/^(m|dim|0|o|aug|\+)//) {
253 9         28 $found_chord_base .= $1 ;
254             }
255              
256 48         285 $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         299 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         123 my $n_ok = ($chord =~ tr/0123456789#b-//) ;
265              
266 48 100       344 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 1486 my ($line,$return_chordpro_fingering) = @_ ;
273 787         1041 my $is_fingering=0 ;
274 787         1128 my $input_line = $line ;
275 787         1040 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         1249 my (@chords,@fingerss) ;
280              
281             # THIS ONLY WORKS FOR FRETS <=9 right now
282              
283             # is it a fingering notation?
284              
285 787         1193 my $pre = "^.*?\\s*?" ; # the pattern to match just before a chord name
286 787         1232 my $valid = "[A-G]{1}\\S*?" ; # a valid chordname
287              
288             # ("chord:") followed by "|x2344x|" or "x2344x"
289 787         4530 while($line =~ /$pre($valid)\:+?\s*?(\|?[xX0-9]{3,7}\|?)/) {
290 38         84 my $cname=$1 ;
291 38         54 my $fingers_this=$2 ;
292 38         54 my $nobar_fingers=$fingers_this ;
293 38         104 $nobar_fingers =~ s/\|//g ;
294              
295 38 50 33     93 if($any_chord_ok || generic_parse_chord($cname)) {
296 38         64 push @chords,$cname ;
297 38         50 push @fingerss,$nobar_fingers ;
298 38         45 $is_fingering=1 ;
299             }
300              
301 38         490 $line =~ s/.*?$nobar_fingers// ;
302             }
303              
304              
305             # ("chord") followed by "|x2344x|" "x2344x"
306 787         13912 while($line =~ /$pre($valid)\s+?(\|?[xX0-9]{3,7}\|?)/) {
307 32         75 my $cname=$1 ;
308 32         64 my $fingers_this=$2 ;
309 32         56 my $nobar_fingers=$fingers_this ;
310 32         74 $nobar_fingers =~ s/\|//g ;
311              
312 32 50 33     140 if($any_chord_ok || generic_parse_chord($1)) {
313 32         77 push @chords,$cname ;
314 32         61 push @fingerss,$nobar_fingers ;
315 32         54 $is_fingering=1 ;
316             }
317              
318 32         708 $line =~ s/.*?$nobar_fingers// ;
319             }
320              
321             # "(chord) = (fingering)" format
322 787         4016 while($line =~ /$pre($valid)\s*?\=\s*?([xX0123456789]{3,7})/) {
323 384         574 my $cname=$1 ;
324 384         472 my $fingers_this=$2 ;
325 384         422 my $nobar_fingers=$fingers_this ;
326 384         462 $nobar_fingers =~ s/\|//g ;
327              
328 384 50 33     661 if($any_chord_ok || generic_parse_chord($1)) {
329 384         474 push @chords,$cname ;
330 384         453 push @fingerss,$nobar_fingers ;
331 384         391 $is_fingering=1 ;
332             }
333              
334 384         4245 $line =~ s/.*?$nobar_fingers// ;
335             }
336              
337 787 100       1741 if($is_fingering) {
338 402 100       1031 return 1 if ! $return_chordpro_fingering ;
339              
340             # handle situation where more than one chord is defined on an input text line
341 201         243 my @output_lines ;
342              
343             #push @output_lines, $input_line if 1 ; # only for debugging
344              
345 201         272 foreach my $chord (@chords) {
346 227         355 my $fingers = shift @fingerss ;
347 227         294 my $min_fret=100 ;
348 227         269 my $max_fret=0 ;
349 227         263 my @frets ;
350              
351 227         610 while($fingers =~ s/(.)//) {
352 1359         1956 my $fret=$1 ;
353 1359         1744 push @frets, $fret ;
354              
355 1359 100       2658 if($fret =~ /[0-9]/) {
356 1213 100       1811 $min_fret = $fret if $min_fret > $fret ;
357 1213 100       2832 $max_fret = $fret if $max_fret < $fret ;
358             }
359             }
360              
361             # now convert the requested fingering to chordpro format
362 227         318 my $bf=$min_fret ;
363              
364 227         352 my $chordpro = "{define $chord base-fret $bf frets" ;
365 227 100       422 $bf-- if $bf > 0 ;
366              
367 227         290 foreach my $fret (@frets) {
368 1359         1449 $chordpro = $chordpro . " " ;
369              
370 1359 100       2196 if($fret =~ /[0-9]/) {
371 1213         1334 my $rf = $fret-$bf ;
372              
373 1213         1569 $chordpro .= "$rf" ;
374             } else {
375 146         255 $chordpro .= '-' ;
376             }
377             }
378              
379 227         259 $chordpro .= "}" ;
380 227         467 push @output_lines, $chordpro ;
381             }
382              
383 201         508 return @output_lines ;
384             }
385              
386 385         1357 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 1144 my ( $line ) = @_;
402 601         1666 my $lc_line = lc($line) ;
403              
404 601 100       1506 return 'C' if $line =~ /^\s*\[.+?\]/; # comment
405 595 100       1335 return 'C' if $line =~ /^\s*\#.+?/; # comment
406 592 100       1340 return 'C' if $lc_line =~ /(from|email|e\-mail)\:?.+?@+/ ; # email is treated as a comment
407 590 100       1474 return 'C' if $lc_line =~ /(from|email|e\-mail)\:.+?/ ; # same as above, but there MUST be a colon, and no @ is necessary
408 588 100       1602 return 'C' if $lc_line =~ /(date|subject)\:.+?/ ; # most likely part of email lines is treated as a comment
409              
410             # check for a chord fingering specification, i.e. A=x02220
411 586 100       1294 return 'f' if decode_fingering($line,0) ;
412              
413 385         727 if(0) {
414             #Oct 31 and before
415             return 't' if $line =~ /^\s*?[A-G|a-g]\s*\|.*?\-.*\|/; # tablature
416             return 't' if $line =~ /^\s*?[A-G|a-g]\s*\-.*?\-.*\|*/; # tablature
417             } else {
418             # try to accomodate tablature lines with text after the tab
419              
420 385         886 my $longest_tablature_string=0 ;
421 385         648 my $tmpline = $line ;
422              
423             # REGEX components:
424              
425             # start with any amount of whitespace
426             # ^\s*?
427             # must be one string note
428             # [A-G|a-g]
429             # one or more of : or |
430             # [:\|]+
431             # 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
432             # [\-:\|bphxBPHX0-9\/\\\(\)]*?
433             # one or more of : or |
434             # [:\|]+
435              
436 385         1762 while($tmpline =~ s/^(\s*?[A-G|a-g][:\|]+[\-:\|bphxBPHX0-9\/\\\(\)]*?[:\|]+)//) {
437 55 50       422 $longest_tablature_string = length($1) if $longest_tablature_string < length($1) ;
438             }
439              
440 385 100       1155 return 't' if $longest_tablature_string > 8 ;
441             }
442              
443              
444             # count number of specific characters to help identify tablature lines
445 330         770 my $n_v = ($line =~ tr/v//) ;
446 330         651 my $n_dash = ($line =~ tr/-//) ;
447 330         607 my $n_equal = ($line =~ tr/=//) ;
448 330         576 my $n_bar = ($line =~ tr/|//) ;
449 330         620 my $n_c_accent = ($line =~ tr/^//) ;
450 330         731 my $n_period = ($line =~ tr/.//) ;
451 330         590 my $n_space = ($line =~ tr/ //) ;
452 330         611 my $n_slash = ($line =~ tr/\///) ;
453 330         589 my $n_underscore = ($line =~ tr/_//) ;
454 330         591 my $n_digit = ($line =~ tr/0123456789//) ;
455              
456             # some inputs are of the form "| / / / _ / | / / / / / |", to indicate strumming patterns
457             # need to recognize this as tablature for nice formatting, and if chords are in the line
458             # preceding they will be included in the tablature by maplines() to ensure correct formatting
459 330         560 my $longest_strumming_string=0 ;
460 330         619 my $cntline = $line ;
461              
462 330         2203 while( $cntline =~ s/([\|\/ _]+?)//) {
463 3100 100       15245 $longest_strumming_string = length($1) if $longest_strumming_string < length($1) ;
464             }
465              
466 330 50       942 return 't' if ($longest_strumming_string >= 6) ;
467              
468              
469              
470             # Lyrics or Chords heuristic.
471 330         1411 my @words = split ( /\s+/, $line );
472              
473 330         857 my $n_tot_chars = length($line) ;
474 330         2333 $line =~ s/\s+//g ;
475 330         753 my $n_nonblank_chars = length($line) ;
476              
477             # have to wait until $n_nonblank_chars is computed to do these tests
478 330 100 100     1807 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
479 322 100       1585 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
480 218 100       685 return 't' if (($n_bar + $n_slash + $n_underscore)/$n_nonblank_chars >= 0.5) ; # mostly characters used in strumming tablature
481              
482              
483 216         378 my $n_chords=0 ;
484 216         429 my $n_words=0 ;
485              
486             #print("CL:") ; # JJW, uncomment for debugging
487              
488 216         532 foreach (@words) {
489 1157 100       4061 if (length $_ > 0) {
490 1083         1769 $n_words++ ;
491              
492              
493 1083 100       3391 my $is_chord = ChordPro::Chords::parse_chord($_) ? 1 : 0 ;
494 1083 100       3597 if(! $is_chord) {
495 890 100       2157 if(generic_parse_chord($_)) {
496 9 50       32 print STDERR "$_ detected by generic, not internal parse_chord\n" if $local_debug ;
497 9         24 $is_chord=1 ;
498             }
499             }
500              
501 1083 100       2852 $n_chords++ if $is_chord ;
502 1083 50       3723 print STDERR " ($is_chord:$_)" if $local_debug ;
503              
504             #print(" \'$is_chord:$_\'") ; # JJW, uncomment for debugging
505             }
506             }
507 216 50       523 print STDERR "\n" if $local_debug ;
508              
509 216 50       552 return '_' if $n_words == 0 ; # blank line, redundant logic with sub classify(), but makes this more robust to changes in classify() ;
510              
511 216 100       740 my $type = $n_chords/$n_words > 0.4 ? 'c' : 'l' ;
512              
513 216 100       724 if($type eq 'l') {
514             # is it likely the line had a lot of unknown chords, check
515             # the ratio of total chars to nonblank chars , if it is large then
516              
517             # it's probably a chord line
518             # $type = 'c' if $n_words > 1 && $n_tot_chars/$n_nonblank_chars > 2. ;
519             }
520              
521             #print(" --- ($n_chords/$n_words) = $type\n") ; # JJW, uncomment for debugging
522              
523 216         2020 return $type ;
524             }
525              
526             # reformat an input line classified as a comment for the chordpro format
527             sub format_comment_line
528             {
529 69     69 0 163 my $line = $_[0] ;
530             # remove [] from original comment
531 69         167 $line =~ s/\[// ;
532 69         196 $line =~ s/\]// ;
533 69 50       188 return '' if $line eq '' ;
534 69         233 return "{comment: " . $line . "}" ;
535             }
536              
537             # Process the lines via the map.
538             my $infer_titles;
539             sub maplines {
540 19     19 0 65 my ( $map, $lines ) = @_;
541 19         46 my @out;
542             $infer_titles = $config->{a2crd}->{'infer-titles'}
543 19   33     149 && !$options->{fragment};
544              
545             # Preamble.
546             # Pass empty lines.
547              
548 19 50       79 print STDERR "====== _C =====\n" if $local_debug ;
549 19 50       88 print STDERR "MAP: \'$map\' \n" if $local_debug ;
550              
551 19         125 while ( $map =~ s/^([_C])// ) {
552 13 50       39 print STDERR "$1 == @{$lines}[0]\n" if $local_debug ;
  0         0  
553             # simply output blank or comment lines at the start of the file
554             # but don't count the line as possible title
555 13 100       51 my $pre = ($1 eq "C" ? "{comment:" : "" ) ;
556 13 100       46 my $post = ($1 eq "C" ? "}" : "" ) ;
557 13         77 push( @out, $pre . shift( @$lines ) . $post );
558             }
559              
560 19 50       79 print STDERR "====== infer title =====\n" if $local_debug ;
561             # Infer title/subtitle.
562 19 100 66     202 if ( $infer_titles && $map =~ s/^l// ) {
563 18         76 push( @out, "{title: " . shift( @$lines ) . "}");
564 18 100       95 if ( $map =~ s/^l// ) {
565 8         37 push( @out, "{subtitle: " . shift( @$lines ) . "}");
566             }
567             }
568              
569 19 50       76 print STDERR "====== UNTIL chords or tablature =====\n" if $local_debug ;
570             # Pass lines until we have chords or tablature
571              
572 19         105 while ($map =~ /^(.)(.)(.)/) {
573 332 50       603 push @out, "ULC $map" if $local_debug ;
574             # some unusual situations to handle,
575              
576             # cl. => exit this loop for normal cl processing
577             # .t => exit the loop
578             # l.t or c.t => output the l or c as comment, then exit the loop
579             # [_f{C].. => output the blank, fingering,directive or comment, and continue the loop
580              
581             # we have to stop one line before tablature, in case the line before the tablature needs to be included in the
582             # tablature itself
583 332 50       580 print STDERR "$1 == @{$lines}[0]\n" if $local_debug ;
  0         0  
584              
585 332 100 100     902 last if($1 eq "c" && $2 eq "l") ;
586 327 100       741 last if($2 eq "t" ) ;
587              
588 323 100 100     1356 if(($1 eq "c" || $1 eq "l") && $3 eq "t") {
      100        
589 6         30 push @out, format_comment_line(shift(@$lines)) ;
590 6         35 $map =~ s/.// ;
591 6         19 last ;
592             }
593              
594             # in the remaining cases, output the line (properly handled), and continue the loop
595 317 100 100     1215 if ( $1 eq "l" or $1 eq "C") {
    100          
    100          
596 59         194 push @out, format_comment_line(shift(@$lines)) ;
597             }
598             elsif ( $1 eq "f" ) {
599 191         370 foreach my $fchart (decode_fingering(shift( @$lines ),1) ) {
600 217         350 push( @out, $fchart);
601             }
602             }
603             elsif ( $1 eq "{" ) {
604 8         25 my $line = shift @$lines ;
605 8         17 push( @out, $line);
606              
607 8 100       35 if($line =~ /{sot}/) {
608             # output all subsequent lines until {eot} is found
609 1         3 while(1) {
610 8         16 $line = shift @$lines ;
611 8 50       18 die "Malformed input, {sot} has no matching {eot}" if ! $line ;
612 8         26 $map =~ s/.// ;
613 8         16 push( @out, $line);
614 8 100       25 last if $line =~ /{eot}/ ;
615             }
616              
617             }
618             }
619             else {
620 59         143 push( @out, shift( @$lines ) );
621             }
622 317         1306 $map =~ s/.// ;
623             }
624              
625 19 50       87 push @out, "====== FINAL LOOP =====" if $local_debug ;
626             # Process the lines using the map.
627 19         91 while ( $map ) {
628             # warn($map);
629 188 50       397 push @out, "FL $map" if $local_debug ;
630 188         452 $map =~ /(.)/ ;
631 188 50       417 print STDERR "$1 == @{$lines}[0]\n" if $local_debug ;
  0         0  
632              
633             #a fingering line, simply output the directive and continue
634 188 100       482 if ( $map =~ s/^f// ) {
635 10         34 foreach my $fchart (decode_fingering(shift( @$lines ),1) ) {
636 10         30 push( @out, $fchart);
637             }
638 10         44 next ;
639             }
640              
641             # Blank line - output the blank line and continue
642 178 100       473 if ( $map =~ s/^_// ) {
643 78         170 push( @out, '');
644 78         204 shift(@$lines);
645 78         209 next ;
646             }
647              
648             # A comment line, output and continue
649 100 100       246 if ( $map =~ s/^C// ) {
650 4         21 push @out, format_comment_line(shift(@$lines)) ;
651 4         16 next ;
652             }
653              
654             # Tablature
655 96         190 my $in_tablature=0 ;
656              
657             # special case: chords or lyrics before tabs, keep the chords or lyrics in {sot}, which is probably
658             # what the original text intended for alignment with the tablature
659 96 100       295 if ( $map =~ s/^[cl]t/t/ ) {
660 17 50       79 if(! $in_tablature) {
661 17         42 push( @out, "{sot}") ;
662 17         35 $in_tablature=1 ;
663             }
664 17         45 push( @out, shift(@$lines));
665             }
666              
667 96         338 while( $map =~ s/^t// ) {
668 155 100       330 if(! $in_tablature) {
669 12         30 push( @out, "{sot}") ;
670 12         26 $in_tablature=1 ;
671             }
672 155         537 push( @out, shift(@$lines));
673             # and Fall through.
674             }
675              
676 96 100       218 if($in_tablature) {
677             # Text line OR chord line with following blank line or EOF -- make part of tablature
678 29 100       155 if ( $map =~ s/^[cl](_|$)// ) {
679 9         32 push( @out, shift(@$lines));
680 9         21 push( @out, '');
681 9         16 shift(@$lines);
682             }
683              
684 29         97 push( @out, "{eot}") ;
685 29         53 $in_tablature=0 ;
686 29         107 next ;
687             }
688              
689             # Blank line preceding lyrics: pass.
690 67 50       167 if ( $map =~ s/^_l/l/ ) {
691 0         0 push( @out, '');
692 0         0 shift(@$lines);
693             }
694              
695             # The normal case: chords + lyrics.
696 67 100       371 if ( $map =~ s/^cl// ) {
    50          
    50          
    100          
    100          
    50          
    0          
697 40         181 push( @out, combine( shift(@$lines), shift(@$lines), "cl" ) );
698             }
699              
700             # Empty line preceding a chordless lyrics line.
701             elsif ( $map =~ s/^__l// ) {
702 0         0 push( @out, '' );
703 0         0 shift( @$lines );
704 0         0 push( @out, combine( shift(@$lines), shift(@$lines), "__l" ) );
705             }
706              
707             # Chordless lyrics line.
708             elsif ( $map =~ s/^_l// ) {
709 0         0 push( @out, combine( shift(@$lines), shift(@$lines), "_l" ) );
710             }
711              
712             # Lone directives.
713             elsif ( $map =~ s/^{// ) {
714 1         22 my $line = shift @$lines ;
715 1         5 push( @out, $line);
716              
717 1 50       9 if($line =~ /{sot}/) {
718             # output all subsequent lines until {eot} is found
719 0         0 while(1) {
720 0         0 $line = shift @$lines ;
721 0 0       0 die "Malformed input, {sot} has no matching {eot}" if ! $line ;
722 0         0 $map = s/.// ;
723 0         0 push( @out, $line);
724 0 0       0 last if $line =~ /{eot}/ ;
725             }
726              
727             }
728             }
729              
730             # Lone lyrics.
731             elsif ( $map =~ s/^l// ) {
732 22         82 push( @out, shift( @$lines ) );
733             }
734              
735             # Lone chords.
736             elsif ( $map =~ s/^c// ) {
737 4         19 push( @out, combine( shift(@$lines), '', "c" ) );
738             }
739              
740             # Empty line.
741             elsif ( $map =~ s/^_// ) {
742 0         0 push( @out, '' );
743 0         0 shift( @$lines );
744             }
745              
746             # Can't happen.
747             else {
748 0         0 croak("MAP: $map");
749             }
750             }
751 19 50       1136 return wantarray ? @out : \@out;
752             }
753              
754             # Combine two lines (chords + lyrics) into lyrics with [chords].
755             sub combine {
756 44     44 0 158 my ( $l1, $l2 ) = @_;
757 44         75 my $res = "";
758 44         192 while ( $l1 =~ /^(\s*)(\S+)(.*)/ ) {
759 136         665 $res .= join( '',
760             substr( $l2, 0, length($1), '' ),
761             '[' . $2 . ']',
762             substr( $l2, 0, length($2), '' ) );
763 136         527 $l1 = $3;
764             }
765 44         214 return $res.$l2;
766             }
767              
768             ################ Options and Configuration ################
769              
770             =head1 COMMAND LINE OPTIONS
771              
772             =over 4
773              
774             =item B<--output=>I (short: B<-o>)
775              
776             Designates the name of the output file where the results are written
777             to. Default is standard output.
778              
779             =item B<--version> (short: B<-V>)
780              
781             Prints the program version and exits.
782              
783             =item B<--help> (short: -h)
784              
785             Prints a help message. No other output is produced.
786              
787             =item B<--manual>
788              
789             Prints the manual page. No other output is produced.
790              
791             =item B<--ident>
792              
793             Shows the program name and version.
794              
795             =item B<--verbose>
796              
797             Provides more verbose information of what is going on.
798              
799             =back
800              
801             =cut
802              
803 1     1   13 use Getopt::Long 2.13;
  1         14  
  1         43  
804              
805             # Package name.
806             my $my_package;
807             # Program name and version.
808             my ($my_name, $my_version);
809             my %configs;
810              
811             sub app_setup {
812 0     0 0   goto &ChordPro::app_setup;
813 0           my ($appname, $appversion, %args) = @_;
814 0           my $help = 0; # handled locally
815 0           my $manual = 0; # handled locally
816 0           my $ident = 0; # handled locally
817 0           my $version = 0; # handled locally
818 0           my $defcfg = 0; # handled locally
819 0           my $fincfg = 0; # handled locally
820              
821             # Package name.
822 0           $my_package = $args{package};
823             # Program name and version.
824 0 0         if ( defined $appname ) {
825 0           ($my_name, $my_version) = ($appname, $appversion);
826             }
827             else {
828 0           ($my_name, $my_version) = qw( MyProg 0.01 );
829             }
830              
831             # Config files.
832 0           %configs = %{ CP->configs };
  0            
833              
834 0           my $app_lc = lc("ChordPro"); # common config
835 0           my $options =
836             {
837             verbose => 0, # verbose processing
838              
839             # Development options (not shown with -help).
840             debug => 0, # debugging
841             trace => 0, # trace (show process)
842              
843             # Service.
844             _package => $my_package,
845             _name => $my_name,
846             _version => $my_version,
847             _stdin => \*STDIN,
848             _stdout => \*STDOUT,
849             _stderr => \*STDERR,
850             _argv => [ @ARGV ],
851             };
852              
853             # Colled command line options in a hash, for they will be needed
854             # later.
855 0           my $clo = {};
856              
857             # Sorry, layout is a bit ugly...
858 0 0         if ( !GetOptions
859             ($clo,
860             "output|o=s", # Saves the output to FILE
861              
862             ### Configuration handling ###
863              
864             'config|cfg=s@',
865             'noconfig|no-config',
866             'sysconfig=s',
867             'nosysconfig|no-sysconfig',
868             'userconfig=s',
869             'nouserconfig|no-userconfig',
870             'nodefaultconfigs|no-default-configs|X',
871             'define=s%',
872             'print-default-config' => \$defcfg,
873             'print-final-config' => \$fincfg,
874              
875             ### Standard options ###
876              
877             "version|V" => \$version, # Prints version and exits
878             'ident' => \$ident,
879             'help|h|?' => \$help,
880             'manual' => \$manual,
881             'verbose|v+',
882             'trace',
883             'debug+',
884             ) )
885             {
886             # GNU convention: message to STDERR upon failure.
887 0           app_usage(\*STDERR, 2);
888             }
889              
890             my $pod2usage = sub {
891             # Load Pod::Usage only if needed.
892 0     0     require Pod::Usage;
893 0           Pod::Usage->import;
894 0           my $f = "pod/A2Crd.pod";
895 0           unshift( @_, -input => CP->findres($f) );
896 0           &pod2usage;
897 0           };
898              
899             # GNU convention: message to STDOUT upon request.
900 0 0 0       app_ident(\*STDOUT) if $ident || $help || $manual;
      0        
901 0 0 0       if ( $manual or $help ) {
902 0 0         app_usage(\*STDOUT, 0) if $help;
903 0 0         $pod2usage->(VERBOSE => 2) if $manual;
904             }
905 0 0         app_ident(\*STDOUT, 0) if $version;
906              
907             # If the user specified a config, it must exist.
908             # Otherwise, set to a default.
909 0           for my $config ( qw(sysconfig userconfig) ) {
910 0           for ( $clo->{$config} ) {
911 0 0         if ( defined($_) ) {
912 0 0         die("$_: $!\n") unless -r $_;
913 0           next;
914             }
915             # Use default.
916 0 0         next if $clo->{nodefaultconfigs};
917 0 0         next unless $configs{$config};
918 0           $_ = $configs{$config};
919 0 0         undef($_) unless -r $_;
920             }
921             }
922 0           for my $config ( qw(config) ) {
923 0           for ( $clo->{$config} ) {
924 0 0         if ( defined($_) ) {
925 0           foreach my $c ( @$_ ) {
926 0           my $try = $c;
927             # Check for resource names.
928 0 0         if ( ! -r $try ) {
929 0           $try = CP->findcfg($c);
930             }
931 0 0         die("$c: $!\n") unless -r $try;
932             }
933 0           next;
934             }
935             # Use default.
936 0 0         next if $clo->{nodefaultconfigs};
937 0 0         next unless $configs{$config};
938 0           $_ = [ $configs{$config} ];
939 0 0         undef($_) unless -r $_->[0];
940             }
941             }
942             # If no config was specified, and no default is available, force no.
943 0           for my $config ( qw(sysconfig userconfig config) ) {
944 0 0         $clo->{"no$config"} = 1 unless $clo->{$config};
945             }
946              
947             # Plug in command-line options.
948 0           @{$options}{keys %$clo} = values %$clo;
  0            
949             # warn(Dumper($options), "\n") if $options->{debug};
950              
951 0 0 0       if ( $defcfg || $fincfg ) {
952 0 0         print ChordPro::Config::config_default()
953             if $defcfg;
954 0 0         print ChordPro::Config::config_final()
955             if $fincfg;
956 0           exit 0;
957             }
958              
959             # Return result.
960 0           $options;
961             }
962              
963             sub app_ident {
964 0     0 0   my ($fh, $exit) = @_;
965 0 0         print {$fh} ("This is ",
  0            
966             $my_package
967             ? "$my_package [$my_name $my_version]"
968             : "$my_name version $my_version",
969             "\n");
970 0 0         exit $exit if defined $exit;
971             }
972              
973             sub app_usage {
974 0     0 0   my ($fh, $exit) = @_;
975 0           my $cmd = $0;
976 0 0         $cmd .= " --a2crd" if $cmd !~ m;(?:^|\/|\\)a2crd(?:\.\w+)$;;
977 0           print ${fh} <
978             Usage: $cmd [ options ] [ file ... ]
979              
980             Options:
981             --output=FILE -o Saves the output to FILE
982             --version -V Prints version and exits
983             --help -h This message
984             --manual The full manual
985             --ident Show identification
986             --verbose Verbose information
987             EndOfUsage
988 0 0         exit $exit if defined $exit;
989             }
990              
991             =head1 AUTHOR
992              
993             Johan Vromans C<< >>
994              
995             =head1 SUPPORT
996              
997             A2Crd is part of ChordPro (the program). Development is hosted on
998             GitHub, repository L.
999              
1000             Please report any bugs or feature requests to the GitHub issue tracker,
1001             L.
1002              
1003             A user community discussing ChordPro can be found at
1004             L.
1005              
1006             =head1 LICENSE
1007              
1008             Copyright (C) 2010,2018 Johan Vromans,
1009              
1010             This program is free software. You can redistribute it and/or
1011             modify it under the terms of the Artistic License 2.0.
1012              
1013             This program is distributed in the hope that it will be useful,
1014             but without any warranty; without even the implied warranty of
1015             merchantability or fitness for a particular purpose.
1016              
1017             =cut
1018              
1019             1;