File Coverage

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   784 use v5.26;
  1         5  
4              
5             package ChordPro::A2Crd;
6              
7 1     1   8 use App::Packager;
  1         2  
  1         13  
8              
9 1     1   164 use ChordPro::Version;
  1         2  
  1         43  
10 1     1   7 use ChordPro::Chords;
  1         2  
  1         64  
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   14 use strict;
  1         3  
  1         23  
78 1     1   5 use warnings;
  1         7  
  1         42  
79 1     1   6 use utf8;
  1         2  
  1         5  
80 1     1   27 use Carp;
  1         3  
  1         106  
81              
82             ################ The Process ################
83              
84             package main;
85              
86             our $options;
87             our $config;
88              
89             package ChordPro::A2Crd;
90              
91 1     1   9 use ChordPro::Config;
  1         2  
  1         53  
92              
93 1     1   6 use File::LoadLines;
  1         3  
  1         63  
94 1     1   6 use Encode qw(decode decode_utf8 encode_utf8);
  1         2  
  1         4896  
95              
96             # API: Main entry point.
97             sub a2crd {
98 19     19 0 66 my ($opts) = @_;
99 19 50       63 $options = { %$options, %$opts } if $opts;
100              
101             # One configurator to bind them all.
102 19         91 $config = ChordPro::Config::configurator({});
103              
104             # Process input.
105             my $lines = $opts->{lines}
106             ? delete($opts->{lines})
107 19 50       292 : loadlines( @ARGV ? $ARGV[0] : \*STDIN);
    50          
108              
109 19         13045 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       63 return $line unless $line;
119 31   66     74 $tabstop //= $::config->{a2crd}->{tabstop};
120 31 50       66 return $line unless $tabstop > 0;
121              
122 31         98 my ( @l ) = split( /\t/, $line, -1 );
123 31 50       79 return $l[0] if @l == 1;
124              
125 31         62 $line = shift(@l);
126 31         191 $line .= " " x ($tabstop-length($line)%$tabstop) . shift(@l) while @l;
127              
128 31         73 return $line;
129             }
130              
131             # API: Produce ChordPro data from AsciiCRD lines.
132             sub a2cho {
133 19     19 0 85 my ( $lines ) = @_;
134 19         55 my $map = "";
135 19         50 my @lines_with_tabs_replaced ;
136 19         93 foreach ( @$lines ) {
137 757 100       1871 if(/\t/) {
138 31         74 $_ = 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         1410 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         1119 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         1721 while(s/\[ch\](.*?)\[\/ch\]/$1/) {
150 16         77 $n_ch_chords++ ;
151             }
152              
153 757         1446 push @lines_with_tabs_replaced, $_ ;
154              
155 757 100       1340 if($n_ch_chords < 1) {
156 754         1275 $map .= classify($_);
157             } else {
158 3         15 $map .= "c" ;
159             }
160             }
161 19         207 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 1314 my ( $line ) = @_;
169 754 100       2490 return '_' if $line =~ /^\s*$/; # empty line
170 611 100       1369 return '{' if $line =~ /^\{.+/; # directive
171 601 100       1100 unless ( defined $classify ) {
172 1         3 my $classifier = $::config->{a2crd}->{classifier};
173 1         20 $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         1108 $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 1431 my $word = shift ;
212              
213 890         1737 my ($chord,$bass) ;
214 890 100       1849 if ( $word =~ m;^(.*)/(.*); ) {
215 15         41 $chord = $1;
216 15         49 $bass = $2;
217             } else {
218 875         1219 $chord=$word ;
219             }
220              
221 890 100       1464 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         1253 my $roots = "^[A-G]" ;
231 890         1131 my $found_chord_base="" ;
232              
233             # first part of chord needs to be [A-G]
234 890 100       4308 return 0 if(! ($chord =~ s/($roots)//)) ;
235              
236 48         156 $found_chord_base .= $1 ;
237              
238 48         124 $chord = lc($chord) ; # simplify to lowercase for further parsing
239              
240 48 100       183 if($chord =~ s/^([b#]|flat|sharp)//) {
241 3         8 $found_chord_base .= $1 ;
242             }
243              
244 48 50       139 if($chord =~ s/^(minor|major)//) {
245 0         0 $found_chord_base .= $1 ;
246             }
247              
248 48 50       134 if($chord =~ s/^(min|maj)//) {
249 0         0 $found_chord_base .= $1 ;
250             }
251              
252 48 100       168 if($chord =~ s/^(m|dim|0|o|aug|\+)//) {
253 9         22 $found_chord_base .= $1 ;
254             }
255              
256 48         174 $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         208 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         108 my $n_ok = ($chord =~ tr/0123456789#b-//) ;
265              
266 48 100       256 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 1379 my ($line,$return_chordpro_fingering) = @_ ;
273 787         1115 my $is_fingering=0 ;
274 787         1103 my $input_line = $line ;
275 787         1111 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         1203 my (@chords,@fingerss) ;
280              
281             # THIS ONLY WORKS FOR FRETS <=9 right now
282              
283             # is it a fingering notation?
284              
285 787         1073 my $pre = "^.*?\\s*?" ; # the pattern to match just before a chord name
286 787         1029 my $valid = "[A-G]{1}\\S*?" ; # a valid chordname
287              
288             # ("chord:") followed by "|x2344x|" or "x2344x"
289 787         3448 while($line =~ /$pre($valid)\:+?\s*?(\|?[xX0-9]{3,7}\|?)/) {
290 38         93 my $cname=$1 ;
291 38         70 my $fingers_this=$2 ;
292 38         54 my $nobar_fingers=$fingers_this ;
293 38         120 $nobar_fingers =~ s/\|//g ;
294              
295 38 50 33     98 if($any_chord_ok || generic_parse_chord($cname)) {
296 38         70 push @chords,$cname ;
297 38         60 push @fingerss,$nobar_fingers ;
298 38         58 $is_fingering=1 ;
299             }
300              
301 38         542 $line =~ s/.*?$nobar_fingers// ;
302             }
303              
304              
305             # ("chord") followed by "|x2344x|" "x2344x"
306 787         10760 while($line =~ /$pre($valid)\s+?(\|?[xX0-9]{3,7}\|?)/) {
307 32         78 my $cname=$1 ;
308 32         55 my $fingers_this=$2 ;
309 32         52 my $nobar_fingers=$fingers_this ;
310 32         60 $nobar_fingers =~ s/\|//g ;
311              
312 32 50 33     75 if($any_chord_ok || generic_parse_chord($1)) {
313 32         66 push @chords,$cname ;
314 32         81 push @fingerss,$nobar_fingers ;
315 32         47 $is_fingering=1 ;
316             }
317              
318 32         457 $line =~ s/.*?$nobar_fingers// ;
319             }
320              
321             # "(chord) = (fingering)" format
322 787         3478 while($line =~ /$pre($valid)\s*?\=\s*?([xX0123456789]{3,7})/) {
323 384         885 my $cname=$1 ;
324 384         605 my $fingers_this=$2 ;
325 384         579 my $nobar_fingers=$fingers_this ;
326 384         639 $nobar_fingers =~ s/\|//g ;
327              
328 384 50 33     779 if($any_chord_ok || generic_parse_chord($1)) {
329 384         695 push @chords,$cname ;
330 384         548 push @fingerss,$nobar_fingers ;
331 384         521 $is_fingering=1 ;
332             }
333              
334 384         5039 $line =~ s/.*?$nobar_fingers// ;
335             }
336              
337 787 100       1686 if($is_fingering) {
338 402 100       1376 return 1 if ! $return_chordpro_fingering ;
339              
340             # handle situation where more than one chord is defined on an input text line
341 201         310 my @output_lines ;
342              
343             #push @output_lines, $input_line if 1 ; # only for debugging
344              
345 201         366 foreach my $chord (@chords) {
346 227         381 my $fingers = shift @fingerss ;
347 227         376 my $min_fret=100 ;
348 227         299 my $max_fret=0 ;
349 227         290 my @frets ;
350              
351 227         803 while($fingers =~ s/(.)//) {
352 1359         2625 my $fret=$1 ;
353 1359         2140 push @frets, $fret ;
354              
355 1359 100       3136 if($fret =~ /[0-9]/) {
356 1213 100       2216 $min_fret = $fret if $min_fret > $fret ;
357 1213 100       3856 $max_fret = $fret if $max_fret < $fret ;
358             }
359             }
360              
361             # now convert the requested fingering to chordpro format
362 227         357 my $bf=$min_fret ;
363              
364 227         517 my $chordpro = "{define $chord base-fret $bf frets" ;
365 227 100       474 $bf-- if $bf > 0 ;
366              
367 227         362 foreach my $fret (@frets) {
368 1359         2050 $chordpro = $chordpro . " " ;
369              
370 1359 100       2899 if($fret =~ /[0-9]/) {
371 1213         1799 my $rf = $fret-$bf ;
372              
373 1213         2218 $chordpro .= "$rf" ;
374             } else {
375 146         255 $chordpro .= '-' ;
376             }
377             }
378              
379 227         321 $chordpro .= "}" ;
380 227         566 push @output_lines, $chordpro ;
381             }
382              
383 201         586 return @output_lines ;
384             }
385              
386 385         875 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 1066 my ( $line ) = @_;
402 601         1416 my $lc_line = lc($line) ;
403 601         804 my $local_debug=0 ;
404              
405 601 100       1292 return 'C' if $line =~ /^\s*\[.+?\]/; # comment
406 595 100       1122 return 'C' if $line =~ /^\s*\#.+?/; # comment
407 592 100       1217 return 'C' if $lc_line =~ /(from|email|e\-mail)\:?.+?@+/ ; # email is treated as a comment
408 590 100       1217 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       1146 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       971 return 'f' if decode_fingering($line,0) ;
413              
414 385         599 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         597 my $longest_tablature_string=0 ;
422 385         582 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         1454 while($tmpline =~ s/^(\s*?[A-G|a-g][:\|]+[\-:\|bphxBPHX0-9\/\\\(\)]*?[:\|]+)//) {
438 55 50       262 $longest_tablature_string = length($1) if $longest_tablature_string < length($1) ;
439             }
440              
441 385 100       896 return 't' if $longest_tablature_string > 8 ;
442             }
443              
444              
445             # count number of specific characters to help identify tablature lines
446 330         784 my $n_v = ($line =~ tr/v//) ;
447 330         631 my $n_dash = ($line =~ tr/-//) ;
448 330         639 my $n_equal = ($line =~ tr/=//) ;
449 330         636 my $n_bar = ($line =~ tr/|//) ;
450 330         670 my $n_c_accent = ($line =~ tr/^//) ;
451 330         649 my $n_period = ($line =~ tr/.//) ;
452 330         627 my $n_space = ($line =~ tr/ //) ;
453 330         649 my $n_slash = ($line =~ tr/\///) ;
454 330         640 my $n_underscore = ($line =~ tr/_//) ;
455 330         590 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         469 my $longest_strumming_string=0 ;
461 330         495 my $cntline = $line ;
462              
463 330         1499 while( $cntline =~ s/([\|\/ _]+?)//) {
464 3100 100       13766 $longest_strumming_string = length($1) if $longest_strumming_string < length($1) ;
465             }
466              
467 330 50       661 return 't' if ($longest_strumming_string >= 6) ;
468              
469              
470              
471             # Lyrics or Chords heuristic.
472 330         1489 my @words = split ( /\s+/, $line );
473              
474 330         759 my $n_tot_chars = length($line) ;
475 330         1500 $line =~ s/\s+//g ;
476 330         1069 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     1295 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       1075 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       526 return 't' if (($n_bar + $n_slash + $n_underscore)/$n_nonblank_chars >= 0.5) ; # mostly characters used in strumming tablature
482              
483              
484 216         313 my $n_chords=0 ;
485 216         302 my $n_words=0 ;
486              
487             #print("CL:") ; # JJW, uncomment for debugging
488              
489 216         423 foreach (@words) {
490 1157 100       4354 if (length $_ > 0) {
491 1083         1455 $n_words++ ;
492              
493              
494 1083 100       2321 my $is_chord = ChordPro::Chords::parse_chord($_) ? 1 : 0 ;
495 1083 100       2610 if(! $is_chord) {
496 890 100       1659 if(generic_parse_chord($_)) {
497 9 50       28 print STDERR "$_ detected by generic, not internal parse_chord\n" if $local_debug ;
498 9         16 $is_chord=1 ;
499             }
500             }
501              
502 1083 100       2075 $n_chords++ if $is_chord ;
503 1083 50       2243 print STDERR " ($is_chord:$_)" if $local_debug ;
504              
505             #print(" \'$is_chord:$_\'") ; # JJW, uncomment for debugging
506             }
507             }
508 216 50       466 print STDERR "\n" if $local_debug ;
509              
510 216 50       415 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       512 my $type = $n_chords/$n_words > 0.4 ? 'c' : 'l' ;
513              
514 216 100       495 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         954 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 121 my $line = $_[0] ;
531             # remove [] from original comment
532 69         172 $line =~ s/\[// ;
533 69         133 $line =~ s/\]// ;
534 69 50       152 return '' if $line eq '' ;
535 69         225 return "{comment:" . $line . "}" ;
536             }
537              
538             # Process the lines via the map.
539             my $infer_titles;
540             sub maplines {
541 19     19 0 93 my ( $map, $lines ) = @_;
542 19         47 my @out;
543 19         46 my $local_debug=0 ;
544 19   66     75 $infer_titles //= $::config->{a2crd}->{'infer-titles'};
545              
546             # Preamble.
547             # Pass empty lines.
548              
549 19 50       182 print STDERR "====== _C =====\n" if $local_debug ;
550 19 50       70 print STDERR "MAP: \'$map\' \n" if $local_debug ;
551              
552 19         128 while ( $map =~ s/^([_C])// ) {
553 13 50       34 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       46 my $pre = ($1 eq "C" ? "{comment:" : "" ) ;
557 13 100       36 my $post = ($1 eq "C" ? "}" : "" ) ;
558 13         83 push( @out, $pre . shift( @$lines ) . $post );
559             }
560              
561 19 50       64 print STDERR "====== infer title =====\n" if $local_debug ;
562             # Infer title/subtitle.
563 19 100 66     61 if ( $infer_titles && $map =~ s/^l// ) {
564 18         417 push( @out, "{title: " . shift( @$lines ) . "}");
565 18 100       121 if ( $map =~ s/^l// ) {
566 8         43 push( @out, "{subtitle: " . shift( @$lines ) . "}");
567             }
568             }
569              
570 19 50       108 print STDERR "====== UNTIL chords or tablature =====\n" if $local_debug ;
571             # Pass lines until we have chords or tablature
572              
573 19         107 while ($map =~ /^(.)(.)(.)/) {
574 331 50       610 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       546 print STDERR "$1 == @{$lines}[0]\n" if $local_debug ;
  0         0  
585              
586 331 100 100     845 last if($1 eq "c" && $2 eq "l") ;
587 326 100       638 last if($2 eq "t" ) ;
588              
589 322 100 100     1268 if(($1 eq "c" || $1 eq "l") && $3 eq "t") {
      100        
590 6         40 push @out, format_comment_line(shift(@$lines)) ;
591 6         53 $map =~ s/.// ;
592 6         23 last ;
593             }
594              
595             # in the remaining cases, output the line (properly handled), and continue the loop
596 316 100 100     1183 if ( $1 eq "l" or $1 eq "C") {
    100          
    100          
597 59         136 push @out, format_comment_line(shift(@$lines)) ;
598             }
599             elsif ( $1 eq "f" ) {
600 191         455 foreach my $fchart (decode_fingering(shift( @$lines ),1) ) {
601 217         397 push( @out, $fchart);
602             }
603             }
604             elsif ( $1 eq "{" ) {
605 8         20 my $line = shift @$lines ;
606 8         16 push( @out, $line);
607              
608 8 100       24 if($line =~ /{sot}/) {
609             # output all subsequent lines until {eot} is found
610 1         3 while(1) {
611 8         14 $line = shift @$lines ;
612 8 50       14 die "Malformed input, {sot} has no matching {eot}" if ! $line ;
613 8         21 $map = s/.// ;
614 8         12 push( @out, $line);
615 8 100       21 last if $line =~ /{eot}/ ;
616             }
617              
618             }
619             }
620             else {
621 58         116 push( @out, shift( @$lines ) );
622             }
623 316         1281 $map =~ s/.// ;
624             }
625              
626 19 50       61 push @out, "====== FINAL LOOP =====" if $local_debug ;
627             # Process the lines using the map.
628 19         60 while ( $map ) {
629             # warn($map);
630 187 50       354 push @out, "FL $map" if $local_debug ;
631 187         348 $map =~ /(.)/ ;
632 187 50       312 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       419 if ( $map =~ s/^f// ) {
636 10         32 foreach my $fchart (decode_fingering(shift( @$lines ),1) ) {
637 10         33 push( @out, $fchart);
638             }
639 10         26 next ;
640             }
641              
642             # Blank line - output the blank line and continue
643 177 100       419 if ( $map =~ s/^_// ) {
644 78         160 push( @out, '');
645 78         125 shift(@$lines);
646 78         161 next ;
647             }
648              
649             # A comment line, output and continue
650 99 100       225 if ( $map =~ s/^C// ) {
651 4         31 push @out, format_comment_line(shift(@$lines)) ;
652 4         18 next ;
653             }
654              
655             # Tablature
656 95         132 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       232 if ( $map =~ s/^[cl]t/t/ ) {
661 17 50       53 if(! $in_tablature) {
662 17         40 push( @out, "{sot}") ;
663 17         31 $in_tablature=1 ;
664             }
665 17         36 push( @out, shift(@$lines));
666             }
667              
668 95         264 while( $map =~ s/^t// ) {
669 155 100       298 if(! $in_tablature) {
670 12         37 push( @out, "{sot}") ;
671 12         17 $in_tablature=1 ;
672             }
673 155         509 push( @out, shift(@$lines));
674             # and Fall through.
675             }
676              
677 95 100       232 if($in_tablature) {
678             # Text line OR chord line with following blank line or EOF -- make part of tablature
679 29 100       122 if ( $map =~ s/^[cl](_|$)// ) {
680 9         24 push( @out, shift(@$lines));
681 9         20 push( @out, '');
682 9         13 shift(@$lines);
683             }
684              
685 29         64 push( @out, "{eot}") ;
686 29         42 $in_tablature=0 ;
687 29         67 next ;
688             }
689              
690             # Blank line preceding lyrics: pass.
691 66 50       177 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       340 if ( $map =~ s/^cl// ) {
    50          
    50          
    100          
    100          
    50          
    0          
698 39         159 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       7 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         68 push( @out, shift( @$lines ) );
734             }
735              
736             # Lone chords.
737             elsif ( $map =~ s/^c// ) {
738 4         12 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       584 return wantarray ? @out : \@out;
753             }
754              
755             # Combine two lines (chords + lyrics) into lyrics with [chords].
756             sub combine {
757 43     43 0 125 my ( $l1, $l2 ) = @_;
758 43         79 my $res = "";
759 43         169 while ( $l1 =~ /^(\s*)(\S+)(.*)/ ) {
760 130         682 $res .= join( '',
761             substr( $l2, 0, length($1), '' ),
762             '[' . $2 . ']',
763             substr( $l2, 0, length($2), '' ) );
764 130         477 $l1 = $3;
765             }
766 43         220 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   13 use Getopt::Long 2.13;
  1         17  
  1         35  
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;