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   717 use v5.26;
  1         4  
4              
5             package ChordPro::A2Crd;
6              
7 1     1   6 use App::Packager;
  1         1  
  1         11  
8              
9 1     1   144 use ChordPro::Version;
  1         4  
  1         23  
10 1     1   8 use ChordPro::Chords;
  1         2  
  1         57  
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   6 use strict;
  1         4  
  1         31  
78 1     1   6 use warnings;
  1         2  
  1         28  
79 1     1   7 use utf8;
  1         3  
  1         5  
80 1     1   36 use Carp;
  1         2  
  1         94  
81              
82             ################ The Process ################
83              
84             package main;
85              
86             our $options;
87             our $config;
88              
89             package ChordPro::A2Crd;
90              
91 1     1   7 use ChordPro::Config;
  1         2  
  1         49  
92              
93 1     1   7 use File::LoadLines;
  1         2  
  1         66  
94 1     1   7 use Encode qw(decode decode_utf8 encode_utf8);
  1         3  
  1         4614  
95              
96             # API: Main entry point.
97             sub a2crd {
98 19     19 0 62 my ($opts) = @_;
99 19 50       62 $options = { %$options, %$opts } if $opts;
100              
101             # One configurator to bind them all.
102 19         95 $config = ChordPro::Config::configurator({});
103              
104             # Process input.
105             my $lines = $opts->{lines}
106             ? delete($opts->{lines})
107 19 50       302 : loadlines( @ARGV ? $ARGV[0] : \*STDIN);
    50          
108              
109 19         12696 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 66 my ( $line ) = @_;
118 31 50       67 return $line unless $line;
119 31   66     68 $tabstop //= $::config->{a2crd}->{tabstop};
120 31 50       62 return $line unless $tabstop > 0;
121              
122 31         96 my ( @l ) = split( /\t/, $line, -1 );
123 31 50       79 return $l[0] if @l == 1;
124              
125 31         69 $line = shift(@l);
126 31         194 $line .= " " x ($tabstop-length($line)%$tabstop) . shift(@l) while @l;
127              
128 31         76 return $line;
129             }
130              
131             # API: Produce ChordPro data from AsciiCRD lines.
132             sub a2cho {
133 19     19 0 66 my ( $lines ) = @_;
134 19         47 my $map = "";
135 19         44 my @lines_with_tabs_replaced ;
136 19         68 foreach ( @$lines ) {
137 757 100       1895 if(/\t/) {
138 31         79 $_ = 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         1722 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         1126 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         1801 while(s/\[ch\](.*?)\[\/ch\]/$1/) {
150 16         82 $n_ch_chords++ ;
151             }
152              
153 757         1419 push @lines_with_tabs_replaced, $_ ;
154              
155 757 100       1416 if($n_ch_chords < 1) {
156 754         1363 $map .= classify($_);
157             } else {
158 3         7 $map .= "c" ;
159             }
160             }
161 19         214 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 1398 my ( $line ) = @_;
169 754 100       2348 return '_' if $line =~ /^\s*$/; # empty line
170 611 100       1309 return '{' if $line =~ /^\{.+/; # directive
171 601 100       1239 unless ( defined $classify ) {
172 1         4 my $classifier = $::config->{a2crd}->{classifier};
173 1         19 $classify = __PACKAGE__->can("classify_".$classifier);
174 1 50       6 unless ( $classify ) {
175 0         0 warn("No such classifier: $classifier, using classic\n");
176 0         0 $classify = \&classify_classic;
177             }
178              
179             }
180 601         1159 $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 885     885 0 1541 my $word = shift ;
212              
213 885         1298 my ($chord,$bass) ;
214 885 100       1821 if ( $word =~ m;^(.*)/(.*); ) {
215 10         31 $chord = $1;
216 10         21 $bass = $2;
217             } else {
218 875         1286 $chord=$word ;
219             }
220              
221 885 100       1735 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 885         1356 my $roots = "^[A-G]" ;
231 885         1196 my $found_chord_base="" ;
232              
233             # first part of chord needs to be [A-G]
234 885 100       4174 return 0 if(! ($chord =~ s/($roots)//)) ;
235              
236 43         138 $found_chord_base .= $1 ;
237              
238 43         99 $chord = lc($chord) ; # simplify to lowercase for further parsing
239              
240 43 100       169 if($chord =~ s/^([b#]|flat|sharp)//) {
241 3         10 $found_chord_base .= $1 ;
242             }
243              
244 43 50       197 if($chord =~ s/^(minor|major)//) {
245 0         0 $found_chord_base .= $1 ;
246             }
247              
248 43 50       125 if($chord =~ s/^(min|maj)//) {
249 0         0 $found_chord_base .= $1 ;
250             }
251              
252 43 100       155 if($chord =~ s/^(m|dim|0|o|aug|\+)//) {
253 8         23 $found_chord_base .= $1 ;
254             }
255              
256 43         156 $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 43         183 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 43         103 my $n_ok = ($chord =~ tr/0123456789#b-//) ;
265              
266 43 100       211 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 1487 my ($line,$return_chordpro_fingering) = @_ ;
273 787         1035 my $is_fingering=0 ;
274 787         1177 my $input_line = $line ;
275 787         1056 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         1074 my (@chords,@fingerss) ;
280              
281             # THIS ONLY WORKS FOR FRETS <=9 right now
282              
283             # is it a fingering notation?
284              
285 787         1142 my $pre = "^.*?\\s*?" ; # the pattern to match just before a chord name
286 787         1462 my $valid = "[A-G]{1}\\S*?" ; # a valid chordname
287              
288             # ("chord:") followed by "|x2344x|" or "x2344x"
289 787         3579 while($line =~ /$pre($valid)\:+?\s*?(\|?[xX0-9]{3,7}\|?)/) {
290 38         92 my $cname=$1 ;
291 38         60 my $fingers_this=$2 ;
292 38         59 my $nobar_fingers=$fingers_this ;
293 38         116 $nobar_fingers =~ s/\|//g ;
294              
295 38 50 33     150 if($any_chord_ok || generic_parse_chord($cname)) {
296 38         64 push @chords,$cname ;
297 38         66 push @fingerss,$nobar_fingers ;
298 38         53 $is_fingering=1 ;
299             }
300              
301 38         538 $line =~ s/.*?$nobar_fingers// ;
302             }
303              
304              
305             # ("chord") followed by "|x2344x|" "x2344x"
306 787         10839 while($line =~ /$pre($valid)\s+?(\|?[xX0-9]{3,7}\|?)/) {
307 32         85 my $cname=$1 ;
308 32         59 my $fingers_this=$2 ;
309 32         43 my $nobar_fingers=$fingers_this ;
310 32         59 $nobar_fingers =~ s/\|//g ;
311              
312 32 50 33     77 if($any_chord_ok || generic_parse_chord($1)) {
313 32         66 push @chords,$cname ;
314 32         49 push @fingerss,$nobar_fingers ;
315 32         52 $is_fingering=1 ;
316             }
317              
318 32         490 $line =~ s/.*?$nobar_fingers// ;
319             }
320              
321             # "(chord) = (fingering)" format
322 787         3615 while($line =~ /$pre($valid)\s*?\=\s*?([xX0123456789]{3,7})/) {
323 384         855 my $cname=$1 ;
324 384         582 my $fingers_this=$2 ;
325 384         552 my $nobar_fingers=$fingers_this ;
326 384         639 $nobar_fingers =~ s/\|//g ;
327              
328 384 50 33     873 if($any_chord_ok || generic_parse_chord($1)) {
329 384         733 push @chords,$cname ;
330 384         540 push @fingerss,$nobar_fingers ;
331 384         543 $is_fingering=1 ;
332             }
333              
334 384         5257 $line =~ s/.*?$nobar_fingers// ;
335             }
336              
337 787 100       1667 if($is_fingering) {
338 402 100       1356 return 1 if ! $return_chordpro_fingering ;
339              
340             # handle situation where more than one chord is defined on an input text line
341 201         281 my @output_lines ;
342              
343             #push @output_lines, $input_line if 1 ; # only for debugging
344              
345 201         393 foreach my $chord (@chords) {
346 227         383 my $fingers = shift @fingerss ;
347 227         399 my $min_fret=100 ;
348 227         293 my $max_fret=0 ;
349 227         293 my @frets ;
350              
351 227         788 while($fingers =~ s/(.)//) {
352 1359         2681 my $fret=$1 ;
353 1359         2275 push @frets, $fret ;
354              
355 1359 100       3478 if($fret =~ /[0-9]/) {
356 1213 100       2282 $min_fret = $fret if $min_fret > $fret ;
357 1213 100       4352 $max_fret = $fret if $max_fret < $fret ;
358             }
359             }
360              
361             # now convert the requested fingering to chordpro format
362 227         336 my $bf=$min_fret ;
363              
364 227         560 my $chordpro = "{define $chord base-fret $bf frets" ;
365 227 100       505 $bf-- if $bf > 0 ;
366              
367 227         398 foreach my $fret (@frets) {
368 1359         2220 $chordpro = $chordpro . " " ;
369              
370 1359 100       3011 if($fret =~ /[0-9]/) {
371 1213         1796 my $rf = $fret-$bf ;
372              
373 1213         2468 $chordpro .= "$rf" ;
374             } else {
375 146         288 $chordpro .= '-' ;
376             }
377             }
378              
379 227         415 $chordpro .= "}" ;
380 227         570 push @output_lines, $chordpro ;
381             }
382              
383 201         584 return @output_lines ;
384             }
385              
386 385         948 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 1003 my ( $line ) = @_;
402 601         1435 my $lc_line = lc($line) ;
403 601         870 my $local_debug=0 ;
404              
405 601 100       1317 return 'C' if $line =~ /^\s*\[.+?\]/; # comment
406 595 100       1247 return 'C' if $line =~ /^\s*\#.+?/; # comment
407 592 100       1154 return 'C' if $lc_line =~ /(from|email|e\-mail)\:?.+?@+/ ; # email is treated as a comment
408 590 100       1171 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       1154 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       1204 return 'f' if decode_fingering($line,0) ;
413              
414 385         557 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         536 my $longest_tablature_string=0 ;
422 385         592 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         1407 while($tmpline =~ s/^(\s*?[A-G|a-g][:\|]+[\-:\|bphxBPHX0-9\/\\\(\)]*?[:\|]+)//) {
438 55 50       242 $longest_tablature_string = length($1) if $longest_tablature_string < length($1) ;
439             }
440              
441 385 100       861 return 't' if $longest_tablature_string > 8 ;
442             }
443              
444              
445             # count number of specific characters to help identify tablature lines
446 330         786 my $n_v = ($line =~ tr/v//) ;
447 330         671 my $n_dash = ($line =~ tr/-//) ;
448 330         730 my $n_equal = ($line =~ tr/=//) ;
449 330         675 my $n_bar = ($line =~ tr/|//) ;
450 330         634 my $n_c_accent = ($line =~ tr/^//) ;
451 330         690 my $n_period = ($line =~ tr/.//) ;
452 330         634 my $n_space = ($line =~ tr/ //) ;
453 330         618 my $n_slash = ($line =~ tr/\///) ;
454 330         663 my $n_underscore = ($line =~ tr/_//) ;
455 330         639 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         522 my $longest_strumming_string=0 ;
461 330         524 my $cntline = $line ;
462              
463 330         1620 while( $cntline =~ s/([\|\/ _]+?)//) {
464 3100 100       14801 $longest_strumming_string = length($1) if $longest_strumming_string < length($1) ;
465             }
466              
467 330 50       680 return 't' if ($longest_strumming_string >= 6) ;
468              
469              
470              
471             # Lyrics or Chords heuristic.
472 330         1612 my @words = split ( /\s+/, $line );
473              
474 330         752 my $n_tot_chars = length($line) ;
475 330         1515 $line =~ s/\s+//g ;
476 330         686 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     1300 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       1141 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       487 return 't' if (($n_bar + $n_slash + $n_underscore)/$n_nonblank_chars >= 0.5) ; # mostly characters used in strumming tablature
482              
483              
484 216         310 my $n_chords=0 ;
485 216         293 my $n_words=0 ;
486              
487             #print("CL:") ; # JJW, uncomment for debugging
488              
489 216         498 foreach (@words) {
490 1157 100       2848 if (length $_ > 0) {
491 1083         1555 $n_words++ ;
492              
493              
494 1083 100       2436 my $is_chord = ChordPro::Chords::parse_chord($_) ? 1 : 0 ;
495 1083 100       2648 if(! $is_chord) {
496 885 100       1615 if(generic_parse_chord($_)) {
497 4 50       26 print STDERR "$_ detected by generic, not internal parse_chord\n" if $local_debug ;
498 4         9 $is_chord=1 ;
499             }
500             }
501              
502 1083 100       2208 $n_chords++ if $is_chord ;
503 1083 50       2287 print STDERR " ($is_chord:$_)" if $local_debug ;
504              
505             #print(" \'$is_chord:$_\'") ; # JJW, uncomment for debugging
506             }
507             }
508 216 50       455 print STDERR "\n" if $local_debug ;
509              
510 216 50       511 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       583 my $type = $n_chords/$n_words > 0.4 ? 'c' : 'l' ;
513              
514 216 100       481 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         989 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 120 my $line = $_[0] ;
531             # remove [] from original comment
532 69         158 $line =~ s/\[// ;
533 69         129 $line =~ s/\]// ;
534 69 50       149 return '' if $line eq '' ;
535 69         238 return "{comment:" . $line . "}" ;
536             }
537              
538             # Process the lines via the map.
539             my $infer_titles;
540             sub maplines {
541 19     19 0 115 my ( $map, $lines ) = @_;
542 19         45 my @out;
543 19         39 my $local_debug=0 ;
544 19   66     64 $infer_titles //= $::config->{a2crd}->{'infer-titles'};
545              
546             # Preamble.
547             # Pass empty lines.
548              
549 19 50       160 print STDERR "====== _C =====\n" if $local_debug ;
550 19 50       63 print STDERR "MAP: \'$map\' \n" if $local_debug ;
551              
552 19         114 while ( $map =~ s/^([_C])// ) {
553 13 50       38 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       48 my $pre = ($1 eq "C" ? "{comment:" : "" ) ;
557 13 100       36 my $post = ($1 eq "C" ? "}" : "" ) ;
558 13         69 push( @out, $pre . shift( @$lines ) . $post );
559             }
560              
561 19 50       62 print STDERR "====== infer title =====\n" if $local_debug ;
562             # Infer title/subtitle.
563 19 100 66     58 if ( $infer_titles && $map =~ s/^l// ) {
564 18         327 push( @out, "{title: " . shift( @$lines ) . "}");
565 18 100       85 if ( $map =~ s/^l// ) {
566 8         56 push( @out, "{subtitle: " . shift( @$lines ) . "}");
567             }
568             }
569              
570 19 50       88 print STDERR "====== UNTIL chords or tablature =====\n" if $local_debug ;
571             # Pass lines until we have chords or tablature
572              
573 19         128 while ($map =~ /^(.)(.)(.)/) {
574 331 50       991 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       567 print STDERR "$1 == @{$lines}[0]\n" if $local_debug ;
  0         0  
585              
586 331 100 100     970 last if($1 eq "c" && $2 eq "l") ;
587 326 100       637 last if($2 eq "t" ) ;
588              
589 322 100 100     1283 if(($1 eq "c" || $1 eq "l") && $3 eq "t") {
      100        
590 6         22 push @out, format_comment_line(shift(@$lines)) ;
591 6         30 $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     1127 if ( $1 eq "l" or $1 eq "C") {
    100          
    100          
597 59         146 push @out, format_comment_line(shift(@$lines)) ;
598             }
599             elsif ( $1 eq "f" ) {
600 191         440 foreach my $fchart (decode_fingering(shift( @$lines ),1) ) {
601 217         415 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       33 if($line =~ /{sot}/) {
609             # output all subsequent lines until {eot} is found
610 1         3 while(1) {
611 8         15 $line = shift @$lines ;
612 8 50       17 die "Malformed input, {sot} has no matching {eot}" if ! $line ;
613 8         19 $map = s/.// ;
614 8         13 push( @out, $line);
615 8 100       32 last if $line =~ /{eot}/ ;
616             }
617              
618             }
619             }
620             else {
621 58         129 push( @out, shift( @$lines ) );
622             }
623 316         1316 $map =~ s/.// ;
624             }
625              
626 19 50       90 push @out, "====== FINAL LOOP =====" if $local_debug ;
627             # Process the lines using the map.
628 19         71 while ( $map ) {
629             # warn($map);
630 187 50       328 push @out, "FL $map" if $local_debug ;
631 187         366 $map =~ /(.)/ ;
632 187 50       320 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       394 if ( $map =~ s/^f// ) {
636 10         26 foreach my $fchart (decode_fingering(shift( @$lines ),1) ) {
637 10         27 push( @out, $fchart);
638             }
639 10         31 next ;
640             }
641              
642             # Blank line - output the blank line and continue
643 177 100       444 if ( $map =~ s/^_// ) {
644 78         152 push( @out, '');
645 78         116 shift(@$lines);
646 78         157 next ;
647             }
648              
649             # A comment line, output and continue
650 99 100       227 if ( $map =~ s/^C// ) {
651 4         14 push @out, format_comment_line(shift(@$lines)) ;
652 4         10 next ;
653             }
654              
655             # Tablature
656 95         139 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       267 if ( $map =~ s/^[cl]t/t/ ) {
661 17 50       51 if(! $in_tablature) {
662 17         37 push( @out, "{sot}") ;
663 17         32 $in_tablature=1 ;
664             }
665 17         35 push( @out, shift(@$lines));
666             }
667              
668 95         244 while( $map =~ s/^t// ) {
669 155 100       309 if(! $in_tablature) {
670 12         26 push( @out, "{sot}") ;
671 12         21 $in_tablature=1 ;
672             }
673 155         482 push( @out, shift(@$lines));
674             # and Fall through.
675             }
676              
677 95 100       204 if($in_tablature) {
678             # Text line OR chord line with following blank line or EOF -- make part of tablature
679 29 100       107 if ( $map =~ s/^[cl](_|$)// ) {
680 9         21 push( @out, shift(@$lines));
681 9         15 push( @out, '');
682 9         14 shift(@$lines);
683             }
684              
685 29         65 push( @out, "{eot}") ;
686 29         42 $in_tablature=0 ;
687 29         71 next ;
688             }
689              
690             # Blank line preceding lyrics: pass.
691 66 50       183 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       411 if ( $map =~ s/^cl// ) {
    50          
    50          
    100          
    100          
    50          
    0          
698 39         128 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       8 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         13 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       587 return wantarray ? @out : \@out;
753             }
754              
755             # Combine two lines (chords + lyrics) into lyrics with [chords].
756             sub combine {
757 43     43 0 114 my ( $l1, $l2 ) = @_;
758 43         67 my $res = "";
759 43         172 while ( $l1 =~ /^(\s*)(\S+)(.*)/ ) {
760 130         708 $res .= join( '',
761             substr( $l2, 0, length($1), '' ),
762             '[' . $2 . ']',
763             substr( $l2, 0, length($2), '' ) );
764 130         501 $l1 = $3;
765             }
766 43         209 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         17  
  1         29  
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;