File Coverage

lib/ChordPro/Song.pm
Criterion Covered Total %
statement 807 1120 72.0
branch 459 750 61.2
condition 186 352 52.8
subroutine 41 42 97.6
pod 0 21 0.0
total 1493 2285 65.3


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             package main;
4              
5             our $options;
6             our $config;
7              
8             package ChordPro::Song;
9              
10 81     81   565 use strict;
  81         170  
  81         2512  
11 81     81   411 use warnings;
  81         209  
  81         1960  
12              
13 81     81   448 use ChordPro;
  81         169  
  81         1603  
14 81     81   474 use ChordPro::Chords;
  81         202  
  81         1942  
15 81     81   34137 use ChordPro::Chords::Appearance;
  81         227  
  81         2896  
16 81     81   633 use ChordPro::Chords::Parser;
  81         221  
  81         1920  
17 81     81   408 use ChordPro::Output::Common;
  81         192  
  81         3992  
18 81     81   527 use ChordPro::Utils;
  81         168  
  81         7580  
19              
20 81     81   543 use Carp;
  81         227  
  81         3999  
21 81     81   549 use List::Util qw(any);
  81         168  
  81         5001  
22 81     81   40796 use File::LoadLines;
  81         1073689  
  81         5367  
23 81     81   696 use Storable qw(dclone);
  81         201  
  81         3633  
24 81     81   559 use feature 'state';
  81         225  
  81         6086  
25 81     81   581 use Text::ParseWords qw(quotewords);
  81         207  
  81         4741  
26 81     81   567 use File::Basename qw(basename);
  81         210  
  81         916705  
27              
28             # Parser context.
29             my $def_context = "";
30             my $in_context = $def_context;
31             my $skip_context = 0;
32             my $grid_arg;
33             my $grid_cells;
34              
35             # Local transposition.
36             my $xpose = 0;
37             my $xpose_dir;
38             my $capo;
39              
40             # Used chords, in order of appearance.
41             my @used_chords;
42              
43             # Chorus lines, if any.
44             my @chorus;
45             my $chorus_xpose = 0;
46             my $chorus_xpose_dir = 0;
47              
48             # Memorized chords.
49             my %memchords; # all sections
50             my $memchords; # current section
51             my $memcrdinx; # chords tally
52             my $memorizing; # if memorizing (a.o.t. recalling)
53              
54             # Keep track of unknown chords, to avoid dup warnings.
55             my %warned_chords;
56              
57             my $re_chords; # for chords
58             my $intervals; # number of note intervals
59             my @labels; # labels used
60              
61             # Normally, transposition and subtitutions are handled by the parser.
62             my $decapo;
63             my $no_transpose; # NYI
64             my $xcmov; # transcode to movable system
65             my $no_substitute;
66              
67             # Stack for properties like textsize.
68             my %propstack;
69              
70             my $diag; # for diagnostics
71             my $lineinfo; # keep lineinfo
72              
73             # Constructor.
74              
75             sub new {
76 172     172 0 2922 my ( $pkg, $filesource ) = @_;
77              
78 172         406 $xpose = 0;
79 172         631 $grid_arg = [ 4, 4, 1, 1 ]; # 1+4x4+1
80 172         452 $in_context = $def_context;
81 172         451 @used_chords = ();
82 172         400 %warned_chords = ();
83 172         411 %memchords = ();
84 172         397 %propstack = ();
85 172         888 ChordPro::Chords::reset_song_chords();
86 172         393 @labels = ();
87 172         1580 @chorus = ();
88 172         346 $capo = undef;
89 172         335 $xcmov = undef;
90 172         696 upd_config();
91              
92 172         627 $diag->{format} = $config->{diagnostics}->{format};
93 172         469 $diag->{file} = $filesource;
94 172         455 $diag->{line} = 0;
95 172         465 $diag->{orig} = "(at start of song)";
96              
97 172         1877 bless { chordsinfo => {},
98             meta => {},
99             structure => "linear",
100             } => $pkg;
101             }
102              
103             sub upd_config {
104 353     353 0 1207 $decapo = $config->{settings}->{decapo};
105 353         764 $lineinfo = $config->{settings}->{lineinfo};
106 353         575 $intervals = @{ $config->{notes}->{sharp} };
  353         919  
107             }
108              
109       170     sub ::break() {}
110              
111             sub parse_song {
112 170     170 0 642 my ( $self, $lines, $linecnt, $meta, $defs ) = @_;
113 170 50       716 die("OOPS! Wrong meta") unless ref($meta) eq 'HASH';
114 170         185003 local $config = dclone($config);
115              
116 170 50       1323 warn("Processing song ", $diag->{file}, "...\n") if $options->{verbose};
117 170         779 ::break();
118 170         361 my @configs;
119             #
120 170 50       1030 if ( $lines->[0] =~ /^##config:\s*json/ ) {
121 0         0 my $cf = "";
122 0         0 shift(@$lines);
123 0         0 $$linecnt++;
124 0         0 while ( @$lines ) {
125 0 0       0 if ( $lines->[0] =~ /^# (.*)/ ) {
126 0         0 $cf .= $1 . "\n";
127 0         0 shift(@$lines);
128 0         0 $$linecnt++;
129             }
130             else {
131 0         0 last;
132             }
133             }
134 0 0       0 if ( $cf ) {
135 0         0 my $pp = JSON::PP->new->relaxed;
136 0         0 my $precfg = $pp->decode($cf);
137 0         0 my $prename = "__PRECFG__";
138 0         0 ChordPro::Config::precheck( $precfg, $prename );
139 0         0 push( @configs, ChordPro::Config::prep_configs( $precfg, $prename) );
140             }
141             }
142             # Load song-specific config, if any.
143 170 50 66     998 if ( !$options->{nosongconfig} && $diag->{file} ) {
144 79 50       286 if ( $options->{verbose} ) {
145 0         0 my $this = ChordPro::Chords::get_parser();
146 0 0       0 $this = defined($this) ? $this->{system} : "";
147 0         0 print STDERR ("Parsers at start of ", $diag->{file}, ":");
148             print STDERR ( $this eq $_ ? " *" : " ", "$_")
149 0 0       0 for keys %{ ChordPro::Chords::Parser->parsers };
  0         0  
150 0         0 print STDERR ("\n");
151             }
152 79 50 33     482 if ( $meta && $meta->{__config} ) {
153 0         0 my $cf = delete($meta->{__config})->[0];
154 0 0       0 die("Missing config: $cf\n") unless -s $cf;
155 0 0       0 warn("Config[song]: $cf\n") if $options->{verbose};
156 0         0 my $have = ChordPro::Config::get_config($cf);
157 0         0 push( @configs, ChordPro::Config::prep_configs( $have, $cf) );
158             }
159             else {
160 79         277 for ( "prp", "json" ) {
161 158         712 ( my $cf = $diag->{file} ) =~ s/\.\w+$/.$_/;
162 158 100       664 $cf .= ".$_" if $cf eq $diag->{file};
163 158 50       2479 next unless -s $cf;
164 0 0       0 warn("Config[song]: $cf\n") if $options->{verbose};
165 0         0 my $have = ChordPro::Config::get_config($cf);
166 0         0 push( @configs, ChordPro::Config::prep_configs( $have, $cf) );
167 0         0 last;
168             }
169             }
170             }
171 170         578 my $tuncheck = join("|",@{$config->{tuning}});
  170         841  
172 170         676 foreach my $have ( @configs ) {
173 0 0       0 warn("Config[song*]: ", $have->{_src}, "\n") if $options->{verbose};
174 0         0 my $chords = $have->{chords};
175 0         0 $config->augment($have);
176 0 0       0 if ( $tuncheck ne join("|",@{$config->{tuning}}) ) {
  0         0  
177 0         0 my $res =
178             ChordPro::Chords::set_tuning($config);
179 0 0       0 warn( "Invalid tuning in config: ", $res, "\n" ) if $res;
180             }
181 0         0 ChordPro::Chords::reset_parser();
182 0         0 ChordPro::Chords::Parser->reset_parsers;
183 0 0       0 if ( $chords ) {
184 0         0 my $c = $chords;
185 0 0 0     0 if ( @$c && $c->[0] eq "append" ) {
186 0         0 shift(@$c);
187             }
188 0         0 foreach ( @$c ) {
189 0         0 my $res =
190             ChordPro::Chords::add_config_chord($_);
191             warn( "Invalid chord in config: ",
192 0 0       0 $_->{name}, ": ", $res, "\n" ) if $res;
193             }
194             }
195 0 0       0 if ( $options->{verbose} > 1 ) {
196 0 0       0 warn( "Processed ", scalar(@$chords), " chord entries\n")
197             if $chords;
198 0         0 warn( "Totals: ",
199             ChordPro::Chords::chord_stats(), "\n" );
200             }
201 0         0 if ( 0 && $options->{verbose} ) {
202             my $this = ChordPro::Chords::get_parser()->{system};
203             print STDERR ("Parsers after local config:");
204             print STDERR ( $this eq $_ ? " *" : " ", "$_")
205             for keys %{ ChordPro::Chords::Parser->parsers };
206             print STDERR ("\n");
207             }
208             }
209              
210 170         1218 $config->unlock;
211              
212 170 50       292990 if ( %$defs ) {
213 0         0 my $c = $config->hmerge( prp2cfg( $defs, $config ) );
214 0         0 bless $c => ref($config);
215 0         0 $config = $c;
216             }
217              
218 170         658 for ( qw( transpose transcode decapo lyrics-only ) ) {
219 680 100       2030 next unless defined $options->{$_};
220 18         79 $config->{settings}->{$_} = $options->{$_};
221             }
222             # Catch common error.
223 170 50       1180 unless ( UNIVERSAL::isa( $config->{instrument}, 'HASH' ) ) {
224 0   0     0 $config->{instrument} //= "guitar";
225             $config->{instrument} =
226             { type => $config->{instrument},
227 0         0 description => ucfirst $config->{instrument} };
228             do_warn( "Missing or invalid instrument - set to ",
229 0         0 $config->{instrument}->{type}, "\n" );
230             }
231 170         915 $config->lock;
232 170         314899 for ( keys %{ $config->{meta} } ) {
  170         1005  
233 0   0     0 $meta->{$_} //= [];
234 0 0       0 if ( UNIVERSAL::isa($config->{meta}->{$_}, 'ARRAY') ) {
235 0         0 push( @{ $meta->{$_} }, @{ $config->{meta}->{$_} } );
  0         0  
  0         0  
236             }
237             else {
238 0         0 push( @{ $meta->{$_} }, $config->{meta}->{$_} );
  0         0  
239             }
240             }
241              
242 170         561 $no_transpose = $options->{'no-transpose'};
243 170         419 $no_substitute = $options->{'no-substitute'};
244 170         421 my $fragment = $options->{fragment};
245 170         497 my $target = $config->{settings}->{transcode};
246 170 100       585 if ( $target ) {
247 2 50       13 unless ( ChordPro::Chords::Parser->have_parser($target) ) {
248 2 50       19 if ( my $file = ::rsc_or_file("config/notes/$target.json") ) {
249 2         11 for ( ChordPro::Config::get_config($file) ) {
250 2         15 my $new = $config->hmerge($_);
251 2         12 local $config = $new;
252 2         25 ChordPro::Chords::Parser->new($new);
253             }
254             }
255             }
256 2 50       22 unless ( ChordPro::Chords::Parser->have_parser($target) ) {
257 0         0 die("No transcoder for ", $target, "\n");
258             }
259 2 50       13 warn("Got transcoder for $target\n") if $::options->{verbose};
260 2         14 ChordPro::Chords::set_parser($target);
261 2         9 my $p = ChordPro::Chords::get_parser;
262 2         11 $xcmov = $p->movable;
263 2 50       10 if ( $target ne $p->{system} ) {
264 0         0 ::dump(ChordPro::Chords::Parser->parsers);
265             warn("OOPS parser mixup, $target <> ",
266             ChordPro::Chords::get_parser->{system})
267 0         0 }
268 2         19 ChordPro::Chords::set_parser($self->{system});
269             }
270             else {
271 168         704 $target = $self->{system};
272             }
273              
274 170         588 upd_config();
275 170         1293 $self->{source} = { file => $diag->{file}, line => 1 + $$linecnt };
276 170         609 $self->{system} = $config->{notes}->{system};
277 170         481 $self->{config} = $config;
278 170 50       781 $self->{meta} = $meta if $meta;
279 170         471 $self->{chordsinfo} = {};
280 170   66     1067 $target //= $self->{system};
281              
282             # Preprocessor.
283 170         1020 my $prep = make_preprocessor( $config->{parser}->{preprocess} );
284              
285             # Pre-fill meta data, if any. TODO? ALREADY DONE?
286 170 50       786 if ( $options->{meta} ) {
287 0         0 while ( my ($k, $v ) = each( %{ $options->{meta} } ) ) {
  0         0  
288 0         0 $self->{meta}->{$k} = [ $v ];
289             }
290             }
291              
292             # Build regexp to split out chords.
293 170 100       992 if ( $config->{settings}->{memorize} ) {
294 1         7 $re_chords = qr/(\[.*?\]|\^)/;
295             }
296             else {
297 169         2621 $re_chords = qr/(\[.*?\])/;
298             }
299              
300 170         486 my $skipcnt = 0;
301 170         600 while ( @$lines ) {
302 1936 50       3738 if ( $skipcnt ) {
303 0         0 $skipcnt--;
304             }
305             else {
306 1936         3838 $diag->{line} = ++$$linecnt;
307             }
308              
309 1936         3724 $_ = shift(@$lines);
310 1936   33     6059 while ( /\\\Z/ && @$lines ) {
311 0         0 chop;
312 0         0 my $cont = shift(@$lines);
313 0         0 $$linecnt++;
314 0         0 $cont =~ s/^\s+//;
315 0         0 $_ .= $cont;
316             }
317              
318             # Uncomment this to allow \uXXXX escapes.
319 1936         6529 s/\\u([0-9a-f]{4})/chr(hex("0x$1"))/ige;
  0         0  
320             # Uncomment this to allow \u{XX...} escapes.
321             # s/\\u\{([0-9a-f]+)\}/chr(hex("0x$1"))/ige;
322              
323 1936         172087 $diag->{orig} = $_;
324             # Get rid of TABs.
325 1936         3555 s/\t/ /g;
326              
327 1936 50       4547 if ( $config->{debug}->{echo} ) {
328 0         0 warn(sprintf("==[%3d]=> %s\n", $diag->{line}, $diag->{orig} ) );
329             }
330              
331 1936 50       4029 if ( $prep->{all} ) {
332             # warn("PRE: ", $_, "\n");
333 0         0 $prep->{all}->($_);
334             # warn("POST: ", $_, "\n");
335 0 0       0 if ( /\n/ ) {
336 0         0 my @a = split( /\n/, $_ );
337 0         0 $_ = shift(@a);
338 0         0 unshift( @$lines, @a );
339 0         0 $skipcnt += @a;
340             }
341             }
342              
343 1936 100       3683 if ( $skip_context ) {
344 4 100       23 if ( /^\s*\{(\w+)\}\s*$/ ) {
345 2         8 my $dir = $self->parse_directive($1);
346 2 50       9 if ( $dir->{name} eq "end_of_$in_context" ) {
347 2         4 $in_context = $def_context;
348 2         167 $skip_context = 0;
349             }
350             }
351 4         10 next;
352             }
353              
354 1932 100       4861 if ( /^\s*\{(new_song|ns)\}\s*$/ ) {
355 32 100       149 last if $self->{body};
356 3         14 next;
357             }
358              
359 1900 100       3976 if ( /^#/ ) {
360              
361             # Handle assets.
362 55         140 my $kw = "";
363 55         128 my $kv = {};
364 55 100       166 if ( /^##(image|asset):\s+(.*)/i ) {
365 1         3 $kw = lc($1);
366 1         4 $kv = parse_kv($2);
367             }
368              
369 55 100       154 if ( $kw eq "image" ) {
370 1         5 my $id = $kv->{id};
371 1 50       3 unless ( $id ) {
372 0         0 do_warn("Missing id for image asset\n");
373 0         0 next;
374             }
375              
376             # In-line image asset.
377 1         502 require MIME::Base64;
378 1         694 require Image::Info;
379              
380             # Read the image.
381 1         3 my $data = '';
382 1   66     12 while ( @$lines && $lines->[0] =~ /^# (.+)/ ) {
383 3         15 $data .= MIME::Base64::decode($1);
384 3         13 shift(@$lines);
385             }
386              
387             # Get info.
388 1         11 my $info = Image::Info::image_info(\$data);
389 1 50       4282 if ( $info->{error} ) {
390 0         0 do_warn($info->{error});
391 0         0 next;
392             }
393              
394             # Store in assets.
395 1   50     10 $self->{assets} //= {};
396             $self->{assets}->{$id} =
397             { data => $data, type => $info->{file_ext},
398             width => $info->{width}, height => $info->{height},
399 1 50       10 $kv->{persist} ? ( persist => 1 ) : (),
400             };
401              
402 1 50       5 if ( $config->{debug}->{images} ) {
403             warn("asset[$id] ", length($data), " bytes, ",
404             "width=$info->{width}, height=$info->{height}",
405 0 0       0 $kv->{persist} ? ", persist" : "",
406             "\n");
407             }
408 1         16 next;
409             }
410              
411 54 50       126 if ( $kw eq "asset" ) {
412 0         0 my $id = $kv->{id};
413 0         0 my $type = $kv->{type};
414 0 0       0 unless ( $id ) {
415 0         0 do_warn("Missing id for asset\n");
416 0         0 next;
417             }
418 0 0       0 unless ( $type ) {
419 0         0 do_warn("Missing type for asset\n");
420 0         0 next;
421             }
422              
423             # Read the data.
424 0         0 my @data;
425 0   0     0 while ( @$lines && $lines->[0] =~ /^# (.+)/ ) {
426 0         0 push( @data, $1 );
427 0         0 shift(@$lines);
428             }
429              
430             # Store in assets.
431 0   0     0 $self->{assets} //= {};
432             $self->{assets}->{$id} =
433             { data => \@data, type => $type,
434             subtype => $config->{delegates}->{$type}->{type},
435             handler => $config->{delegates}->{$type}->{handler},
436 0         0 };
437 0 0       0 if ( $config->{debug}->{images} ) {
438 0         0 warn("asset[$id] ", ::dump($self->{assets}->{$id}));
439             }
440 0         0 next;
441             }
442              
443             # Collect pre-title stuff separately.
444 54 50 33     153 if ( exists $self->{title} || $fragment ) {
445 54         150 $self->add( type => "ignore", text => $_ );
446             }
447             else {
448 0         0 push( @{ $self->{preamble} }, $_ );
  0         0  
449             }
450 54         134 next;
451             }
452              
453 1845 100       3832 if ( $in_context eq "tab" ) {
454 62 100       234 unless ( /^\s*\{(?:end_of_tab|eot)\}\s*$/ ) {
455 49         166 $self->add( type => "tabline", text => $_ );
456 49         103 next;
457             }
458             }
459              
460 1796 50       4120 if ( exists $config->{delegates}->{$in_context} ) {
461             # 'open' indicates open.
462 0 0       0 if ( /^\s*\{(?:end_of_\Q$in_context\E)\}\s*$/ ) {
    0          
463 0 0       0 if ( $config->{delegates}->{$in_context}->{omit} ) {
464             }
465             else {
466 0         0 delete $self->{body}->[-1]->{open};
467             # A subsequent {start_of_XXX} will reopen a new item
468             }
469             }
470             elsif ( $config->{delegates}->{$in_context}->{omit} ) {
471 0         0 next;
472             }
473             else {
474             # Add to an open item.
475 0 0 0     0 if ( $self->{body} && @{ $self->{body} }
  0   0     0  
      0        
476             && $self->{body}->[-1]->{context} eq $in_context
477             && $self->{body}->[-1]->{open} ) {
478 0         0 push( @{$self->{body}->[-1]->{data}}, $_ );
  0         0  
479             }
480              
481             # Else start new item.
482             else {
483 0         0 my %opts;
484             ####TODO
485 0 0 0     0 if ( $xpose || $config->{settings}->{transpose} ) {
486             $opts{transpose} =
487 0   0     0 $xpose + ($config->{settings}->{transpose}//0 );
488             }
489 0         0 my $d = $config->{delegates}->{$in_context};
490             $self->add( type => "delegate",
491             delegate => $d->{module},
492             subtype => $d->{type},
493             handler => $d->{handler},
494 0         0 data => [ $_ ],
495             opts => \%opts,
496             open => 1 );
497             }
498 0         0 next;
499             }
500             }
501              
502             # For now, directives should go on their own lines.
503 1796 100       7273 if ( /^\s*\{(.*)\}\s*$/ ) {
504 1004         3140 my $dir = $1;
505 1004 50       2413 if ( $prep->{directive} ) {
506             # warn("PRE: ", $_, "\n");
507 0         0 $prep->{directive}->($dir);
508             # warn("POST: ", $_, "\n");
509             }
510 1004 100       2834 $self->add( type => "ignore",
511             text => $_ )
512             unless $self->directive($dir);
513 1004         3218 next;
514             }
515              
516 792 50 66     4957 if ( /\S/ && !$fragment && !exists $self->{title} ) {
      66        
517 0         0 do_warn("Missing {title} -- prepare for surprising results");
518 0         0 unshift( @$lines, "{title:$_}");
519 0         0 $skipcnt++;
520 0         0 next;
521             }
522              
523 792 50       1793 if ( $in_context eq "tab" ) {
524 0         0 $self->add( type => "tabline", text => $_ );
525 0         0 warn("OOPS");
526 0         0 next;
527             }
528              
529 792 100       1594 if ( $in_context eq "grid" ) {
530 39         165 $self->add( type => "gridline", $self->decompose_grid($_) );
531 39         136 next;
532             }
533              
534 753 100 33     2504 if ( /\S/ ) {
    50          
535 456 50       1106 if ( $prep->{songline} ) {
536             # warn("PRE: ", $_, "\n");
537 0         0 $prep->{songline}->($_);
538             # warn("POST: ", $_, "\n");
539             }
540 456         1386 $self->add( type => "songline", $self->decompose($_) );
541             }
542             elsif ( exists $self->{title} || $fragment ) {
543 297         789 $self->add( type => "empty" );
544             }
545             else {
546             # Collect pre-title stuff separately.
547 0         0 push( @{ $self->{preamble} }, $_ );
  0         0  
548             }
549             }
550 170 50       711 do_warn("Unterminated context in song: $in_context")
551             if $in_context;
552              
553             # These don't make sense after processing. Or do they?
554             # delete $self->{meta}->{$_} for qw( key_actual key_from );
555              
556 170 50       647 warn("Processed song...\n") if $options->{verbose};
557 170         474 $diag->{format} = "\"%f\": %m";
558              
559 170 50       635 $self->dump(0) if $config->{debug}->{song} > 1;
560              
561 170 100       534 if ( @labels ) {
562 1         4 $self->{labels} = [ @labels ];
563             }
564              
565             # Suppress chords that the user considers 'easy'.
566 170         734 my %suppress;
567 170         502 my $xc = $config->{settings}->{transcode};
568 170         335 for ( @{ $config->{diagrams}->{suppress} } ) {
  170         743  
569 0         0 my $info = ChordPro::Chords::known_chord($_);
570 0 0       0 warn("Unknown chord \"$_\" in suppress list\n"), next
571             unless $info;
572             # Note we do transcode, but we do not transpose.
573 0 0       0 if ( $xc ) {
574 0         0 $info = $info->transcode($xc);
575             }
576 0         0 $suppress{$info->name} = 1;
577             }
578             # Suppress chords that the user don't want.
579 170         367 while ( my ($k,$v) = each %{ $self->{chordsinfo} } ) {
  600         2226  
580 430 100 100     2177 $suppress{$k} = 1 if !is_true($v->{diagram}//1);
581             }
582 170 100       760 @used_chords = map { $suppress{$_} ? () : $_ } @used_chords;
  902         2224  
583              
584 170         382 my $diagrams;
585 170 100       699 if ( exists($self->{settings}->{diagrams} ) ) {
586 5         21 $diagrams = $self->{settings}->{diagrams};
587 5   100     29 $diagrams &&= $config->{diagrams}->{show} || "all";
      66        
588             }
589             else {
590 165         482 $diagrams = $config->{diagrams}->{show};
591             }
592              
593 170 50 66     1776 if ( $diagrams =~ /^(user|all)$/
594             && !ChordPro::Chords::Parser->get_parser($target,1)->has_diagrams ) {
595             do_warn( "Chord diagrams suppressed for " .
596 0 0       0 ucfirst($target) . " chords" ) unless $options->{silent};
597 0         0 $diagrams = "none";
598             }
599              
600 170 50       1527 if ( $diagrams eq "user" ) {
601              
602 0 0 0     0 if ( $self->{define} && @{$self->{define}} ) {
  0         0  
603 0         0 my %h = map { demarkup($_) => 1 } @used_chords;
  0         0  
604             @used_chords =
605 0 0       0 map { $h{$_->{name}} ? $_->{name} : () } @{$self->{define}};
  0         0  
  0         0  
606             }
607             else {
608 0         0 @used_chords = ();
609             }
610             }
611             else {
612 170         349 my %h;
613 900 100       2615 @used_chords = map { $h{$_}++ ? () : $_ }
614 170         466 map { demarkup($_) } @used_chords;
  900         1855  
615             }
616              
617 170 50       892 if ( $config->{diagrams}->{sorted} ) {
618 0         0 @used_chords =
619             sort ChordPro::Chords::chordcompare @used_chords;
620             }
621              
622             # For headings, footers, table of contents, ...
623 170   50     2714 $self->{meta}->{chords} //= [ @used_chords ];
624 170         366 $self->{meta}->{numchords} = [ scalar(@{$self->{meta}->{chords}}) ];
  170         713  
625              
626 170 100       944 if ( $diagrams =~ /^(user|all)$/ ) {
627             $self->{chords} =
628 114         757 { type => "diagrams",
629             origin => "song",
630             show => $diagrams,
631             chords => [ @used_chords ],
632             };
633              
634 114 50       425 if ( %warned_chords ) {
635 0         0 my @a = sort ChordPro::Chords::chordcompare
636             keys(%warned_chords);
637 0         0 my $l;
638 0 0       0 if ( @a > 1 ) {
639 0         0 my $a = pop(@a);
640 0         0 $l = '"' . join('", "', @a) . '" and "' . $a . '"';
641             }
642             else {
643 0         0 $l = '"' . $a[0] . '"';
644             }
645 0         0 do_warn( "No chord diagram defined for $l (skipped)\n" );
646             }
647             }
648              
649 170 50       620 $self->dump(0) if $config->{debug}->{song};
650 170 50       578 $self->dump(1) if $config->{debug}->{songfull};
651              
652 170         907 return $self;
653             }
654              
655             sub add {
656 1238     1238 0 2086 my $self = shift;
657 1238 50       2542 return if $skip_context;
658 1238         5366 push( @{$self->{body}},
659             { context => $in_context,
660 1238 100       1736 $lineinfo ? ( line => $diag->{line} ) : (),
661             @_ } );
662 1238 100       13708 if ( $in_context eq "chorus" ) {
663 108         383 push( @chorus, { context => $in_context, @_ } );
664 108         264 $chorus_xpose = $xpose;
665 108         327 $chorus_xpose_dir = $xpose_dir;
666             }
667             }
668              
669             # Parses a chord and adds it to the song.
670             # It understands markup, parenthesized chords and annotations.
671             # Returns the chord Appearance.
672             sub chord {
673 937     937 0 2360 my ( $self, $orig ) = @_;
674 937 50       2508 Carp::confess unless length($orig);
675              
676             # Intercept annotations.
677 937 100 66     5157 if ( $orig =~ /^\*(.+)/ || $orig =~ /^(\||\s+)$/ ) {
678 3         52 my $i = ChordPro::Chord::Annotation->new
679             ( { name => $orig, text => $1 } );
680             return
681 3         29 ChordPro::Chords::Appearance->new
682             ( key => $self->add_chord($i), info => $i, orig => $orig );
683             }
684              
685             # Check for markup.
686 934         1954 my $markup = $orig;
687 934         3020 my $c = demarkup($orig);
688 934 100       2487 if ( $markup eq $c ) { # no markup
689 927         1581 undef $markup;
690             }
691              
692             # Special treatment for parenthesized chords.
693 934         1795 $c =~ s/^\((.*)\)$/$1/;
694 934 50       2058 do_warn("Double parens in chord: \"$orig\"")
695             if $c =~ s/^\((.*)\)$/$1/;
696              
697             # We have a 'bare' chord now. Parse it.
698 934         2498 my $info = $self->parse_chord($c);
699 934 50       2553 unless ( defined $info ) {
700             # Warning was given.
701             # Make annotation.
702 0         0 my $i = ChordPro::Chord::Annotation->new
703             ( { name => $orig, text => $orig } );
704             return
705 0         0 ChordPro::Chords::Appearance->new
706             ( key => $self->add_chord($i), info => $i, orig => $orig );
707             }
708              
709 934         6864 my $ap = ChordPro::Chords::Appearance->new( orig => $orig );
710              
711             # Handle markup, if any.
712 934 100       15325 if ( $markup ) {
    50          
713 7 100 100     173 if ( $markup =~ s/\>\Q$c\E\%{formatted}
714             ||
715             $markup =~ s/\>\(\Q$c\E\)\(%{formatted})
716             }
717             else {
718 1         10 do_warn("Invalid markup in chord: \"$markup\"\n");
719             }
720 7         41 $ap->format = $markup;
721             }
722             elsif ( (my $m = $orig) =~ s/\Q$c\E/%{formatted}/ ) {
723 927 100       2855 $ap->format = $m unless $m eq "%{formatted}";
724             }
725              
726             # After parsing, the chord can be changed by transpose/code.
727             # info->name is the new key.
728 934         2912 $ap->key = $self->add_chord( $info, $c = $info->name );
729 934         2609 $ap->info = $info;
730              
731 934 100 100     2454 unless ( $info->is_nc || $info->is_note ) {
732             # if ( $info->is_keyboard ) {
733 922 50 0     3354 if ( $::config->{instrument}->{type} eq "keyboard" ) {
    100          
    50          
    0          
734 0         0 push( @used_chords, $c );
735             }
736             elsif ( $info->{origin} ) {
737             # Include if we have diagram info.
738 766 50       1894 push( @used_chords, $c ) if $info->has_diagram;
739             }
740             elsif ( $::running_under_test ) {
741             # Tests run without config and chords, so pretend.
742 156         367 push( @used_chords, $c );
743             }
744             elsif ( ! ( $info->is_rootless
745             || $info->has_diagram
746             || !$info->parser->has_diagrams
747             ) ) {
748             do_warn("Unknown chord: $c")
749 0 0       0 unless $warned_chords{$c}++;
750             }
751             }
752              
753 934         2817 return $ap;
754             }
755              
756             sub decompose {
757 580     580 0 1421 my ($self, $orig) = @_;
758 580         1779 my $line = fmt_subst( $self, $orig );
759 580 100       49215 undef $orig if $orig eq $line;
760 580         3224 $line =~ s/\s+$//;
761 580         5998 my @a = split( $re_chords, $line, -1);
762              
763 580 100       1961 if ( @a <= 1 ) {
764 237 50       1541 return ( phrases => [ $line ],
765             $orig ? ( orig => $orig ) : (),
766             );
767             }
768              
769 343         657 my $dummy;
770 343 100       896 shift(@a) if $a[0] eq "";
771 343 100       2489 unshift(@a, '[]'), $dummy++ if $a[0] !~ $re_chords;
772              
773 343         784 my @phrases;
774             my @chords;
775 343         886 while ( @a ) {
776 1035         1911 my $chord = shift(@a);
777 1035         1997 push(@phrases, shift(@a));
778              
779             # Normal chords.
780 1035 100 100     8612 if ( $chord =~ s/^\[(.*)\]$/$1/ && $chord ne "^" ) {
    100 66        
781 1008 100       3803 push(@chords, $chord eq "" ? "" : $self->chord($chord));
782 1008 100 100     2597 if ( $memchords && !$dummy ) {
783 21 100       48 if ( $memcrdinx == 0 ) {
784 3         5 $memorizing++;
785             }
786 21 100       44 if ( $memorizing ) {
787 20         31 push( @$memchords, $chords[-1] );
788             warn("Chord memorized for $in_context\[$memcrdinx]: ",
789             $chords[-1], "\n")
790 20 50       47 if $config->{debug}->{chords};
791             }
792 21         38 $memcrdinx++;
793             }
794             }
795              
796             # Recall memorized chords.
797             elsif ( $memchords && $in_context ) {
798 20 100 100     77 if ( $memcrdinx == 0 && @$memchords == 0 ) {
    50          
799 1         7 do_warn("No chords memorized for $in_context");
800 1         9 push( @chords, $chord );
801 1         3 undef $memchords;
802             }
803             elsif ( $memcrdinx >= @$memchords ) {
804 0         0 do_warn("Not enough chords memorized for $in_context");
805 0         0 push( @chords, $chord );
806             }
807             else {
808 19         68 push( @chords, $self->chord($memchords->[$memcrdinx]->chord_display));
809             warn("Chord recall $in_context\[$memcrdinx]: ", $chords[-1], "\n")
810 19 50       61 if $config->{debug}->{chords};
811             }
812 20         35 $memcrdinx++;
813             }
814              
815             # Not memorizing.
816             else {
817             # do_warn("No chords memorized for $in_context");
818 7         12 push( @chords, $chord );
819             }
820 1035         2920 $dummy = 0;
821             }
822              
823 343 100       1919 return ( phrases => \@phrases,
824             chords => \@chords,
825             $orig ? ( orig => $orig ) : (),
826             );
827             }
828              
829             sub cdecompose {
830 124     124 0 371 my ( $self, $line ) = @_;
831 124 50       558 $line = fmt_subst( $self, $line ) unless $no_substitute;
832 124         10282 my %res = $self->decompose($line);
833 124 100       756 return ( text => $line ) unless $res{chords};
834 14         81 return %res;
835             }
836              
837             sub decompose_grid {
838 39     39 0 130 my ($self, $line) = @_;
839 39         116 $line =~ s/^\s+//;
840 39         203 $line =~ s/\s+$//;
841 39 50       122 return ( tokens => [] ) if $line eq "";
842              
843 39         69 my $orig;
844             my %res;
845 39 50       311 if ( $line !~ /\|/ ) {
846 0         0 $res{margin} = { $self->cdecompose($line), orig => $line };
847 0         0 $line = "";
848             }
849             else {
850 39 50       315 if ( $line =~ /(.*\|\S*)\s([^\|]*)$/ ) {
851 0         0 $line = $1;
852 0         0 $res{comment} = { $self->cdecompose($2), orig => $2 };
853 0 0       0 do_warn( "No margin cell for trailing comment" )
854             unless $grid_cells->[2];
855             }
856 39 50       149 if ( $line =~ /^([^|]+?)\s*(\|.*)/ ) {
857 0         0 $line = $2;
858 0         0 $res{margin} = { $self->cdecompose($1), orig => $1 };
859 0 0       0 do_warn( "No cell for margin text" )
860             unless $grid_cells->[1];
861             }
862             }
863              
864 39         68 my @tokens;
865 39         252 my @t = split( ' ', $line );
866              
867             # Unfortunately, gets split too.
868 39         138 while ( @t ) {
869 663         939 $_ = shift(@t);
870 663         1195 push( @tokens, $_ );
871 663 50       1354 if ( /\
872 0         0 while ( @t ) {
873 0         0 $_ = shift(@t);
874 0         0 $tokens[-1] .= " " . $_;
875 0 0       0 last if /\<\/span>/;
876             }
877             }
878             }
879              
880 39         121 my $nbt = 0; # non-bar tokens
881 39         155 foreach ( @tokens ) {
882 663 50 33     4905 if ( $_ eq "|:" || $_ eq "{" ) {
    50 33        
    50 33        
    50          
    100          
    100          
    50          
    50          
    50          
    50          
    100          
883 0         0 $_ = { symbol => $_, class => "bar" };
884             }
885             elsif ( /^\|(\d+)(>?)$/ ) {
886 0         0 $_ = { symbol => '|', volta => $1, class => "bar" };
887 0 0       0 $_->{align} = 1 if $2;
888             }
889             elsif ( $_ eq ":|" || $_ eq "}" ) {
890 0         0 $_ = { symbol => $_, class => "bar" };
891             }
892             elsif ( $_ eq ":|:" || $_ eq "}{" ) {
893 0         0 $_ = { symbol => $_, class => "bar" };
894             }
895             elsif ( $_ eq "|" ) {
896 149         515 $_ = { symbol => $_, class => "bar" };
897             }
898             elsif ( $_ eq "||" ) {
899 2         15 $_ = { symbol => $_, class => "bar" };
900             }
901             elsif ( $_ eq "|." ) {
902 0         0 $_ = { symbol => $_, class => "bar" };
903             }
904             elsif ( $_ eq "%" ) {
905 0         0 $_ = { symbol => $_, class => "repeat1" };
906             }
907             elsif ( $_ eq '%%' ) {
908 0         0 $_ = { symbol => $_, class => "repeat2" };
909             }
910             elsif ( $_ eq "/" ) {
911 0         0 $_ = { symbol => $_, class => "slash" };
912             }
913             elsif ( $_ eq "." ) {
914 395         1051 $_ = { symbol => $_, class => "space" };
915 395         615 $nbt++;
916             }
917             else {
918             # Multiple chords in a cell?
919 117         329 my @a = split( /~/, $_, -1 );
920 117 100       242 if ( @a == 1) {
921             # Normal case, single chord.
922 116         297 $_ = { chord => $self->chord($_), class => "chord" };
923             }
924             else {
925             # Multiple chords.
926             $_ = { chords =>
927 1 50 33     3 [ map { ( $_ eq '.' || $_ eq '' )
  2 50       14  
928             ? ''
929             : $_ eq "/"
930             ? "/"
931             : $self->chord($_) } @a ],
932             class => "chords" };
933             }
934 117         320 $nbt++;
935             }
936             }
937 39 50       177 if ( $nbt > $grid_cells->[0] ) {
938 0         0 do_warn( "Too few cells for grid content" );
939             }
940 39         268 return ( tokens => \@tokens, %res );
941             }
942              
943             ################ Parsing directives ################
944              
945             my @directives = qw(
946             chord
947             chordcolour
948             chordfont
949             chordsize
950             chorus
951             column_break
952             columns
953             comment
954             comment_box
955             comment_italic
956             define
957             end_of_bridge
958             end_of_chorus
959             end_of_grid
960             end_of_tab
961             end_of_verse
962             footersize
963             footercolour
964             footerfont
965             grid
966             highlight
967             image
968             meta
969             new_page
970             new_physical_page
971             new_song
972             no_grid
973             pagetype
974             start_of_bridge
975             start_of_chorus
976             start_of_grid
977             start_of_tab
978             start_of_verse
979             subtitle
980             tabcolour
981             tabfont
982             tabsize
983             textcolour
984             textfont
985             textsize
986             title
987             titlesize
988             titlecolour
989             titlefont
990             titles
991             tocsize
992             toccolour
993             tocfont
994             transpose
995             );
996             # NOTE: Flex: start_of_... end_of_... x_...
997              
998             my %abbrevs = (
999             c => "comment",
1000             cb => "comment_box",
1001             cf => "chordfont",
1002             ci => "comment_italic",
1003             colb => "column_break",
1004             cs => "chordsize",
1005             grid => "diagrams", # not really an abbrev
1006             eob => "end_of_bridge",
1007             eoc => "end_of_chorus",
1008             eot => "end_of_tab",
1009             eov => "end_of_verse",
1010             g => "diagrams",
1011             highlight => "comment", # not really an abbrev
1012             ng => "no_grid",
1013             np => "new_page",
1014             npp => "new_physical_page",
1015             ns => "new_song",
1016             sob => "start_of_bridge",
1017             soc => "start_of_chorus",
1018             sot => "start_of_tab",
1019             sov => "start_of_verse",
1020             st => "subtitle",
1021             t => "title",
1022             tf => "textfont",
1023             ts => "textsize",
1024             );
1025              
1026             my $dirpat;
1027              
1028             sub parse_directive {
1029 1006     1006 0 1868 my ( $self, $d ) = @_;
1030              
1031             # Pattern for all recognized directives.
1032 1006 100       2404 unless ( $dirpat ) {
1033             $dirpat =
1034             '(?:' .
1035             join( '|', @directives,
1036 58         205 @{$config->{metadata}->{keys}},
  58         1513  
1037             keys(%abbrevs),
1038             '(?:start|end)_of_\w+' ) .
1039             ')';
1040 58         19901 $dirpat = qr/$dirpat/;
1041             }
1042              
1043             # $d is the complete directive line, without leading/trailing { }.
1044 1006         3123 $d =~ s/^[: ]+//;
1045 1006         3072 $d =~ s/\s+$//;
1046 1006         2767 my $dir = lc($d);
1047 1006         7054 my $arg = "";
1048 1006 100       4078 if ( $d =~ /^(.*?)[: ]\s*(.*)/ ) {
1049 791         2981 ( $dir, $arg ) = ( lc($1), $2 );
1050             }
1051 1006         2214 $dir =~ s/[: ]+$//;
1052             # $dir is the lowcase directive name.
1053             # $arg is the rest, if any.
1054              
1055             # Check for xxx-yyy selectors.
1056 1006 100       24786 if ( $dir =~ /^($dirpat)-(.+)$/ ) {
1057 11   66     66 $dir = $abbrevs{$1} // $1;
1058 11         29 my $sel = $2;
1059 11         46 my $negate = $sel =~ s/\!$//;
1060             $sel = ( $sel eq lc($config->{instrument}->{type}) )
1061             ||
1062             ( $sel eq lc($config->{user}->{name})
1063             ||
1064 11   66     81 ( $self->{meta}->{lc $sel} && is_true($self->{meta}->{lc $sel}->[0]) )
1065             );
1066 11 100       32 $sel = !$sel if $negate;
1067 11 100       26 unless ( $sel ) {
1068 4 100       19 if ( $dir =~ /^start_of_/ ) {
1069 2         10 return { name => $dir, arg => $arg, omit => 2 };
1070             }
1071             else {
1072 2         12 return { name => $dir, arg => $arg, omit => 1 };
1073             }
1074             }
1075             }
1076             else {
1077 995   66     4766 $dir = $abbrevs{$dir} // $dir;
1078             }
1079              
1080 1002         5330 return { name => $dir, arg => $arg, omit => 0 }
1081             }
1082              
1083             sub directive {
1084 1004     1004 0 2312 my ( $self, $d ) = @_;
1085              
1086 1004         2558 my $dd = $self->parse_directive($d);
1087 1004 100       3486 return 1 if $dd->{omit} == 1;
1088              
1089 1002         1786 my $arg = $dd->{arg};
1090 1002 100       2284 if ( $arg ne "" ) {
1091 788         2918 $arg = fmt_subst( $self, $arg );
1092 788 50       93928 return 1 if $arg !~ /\S/;
1093             }
1094 1002         2474 my $dir = $dd->{name};
1095              
1096             # Context flags.
1097              
1098 1002 100       2831 if ( $dir =~ /^start_of_(\w+)$/ ) {
1099 77 50       292 do_warn("Already in " . ucfirst($in_context) . " context\n")
1100             if $in_context;
1101 77         213 $in_context = $1;
1102 77 100       281 if ( $dd->{omit} ) {
1103 2         4 $skip_context = 1;
1104             # warn("Skipping context: $in_context\n");
1105 2         9 return 1;
1106             }
1107 75 100       278 @chorus = (), $chorus_xpose = $chorus_xpose_dir = 0
1108             if $in_context eq "chorus";
1109 75 100 66     329 if ( $in_context eq "grid" ) {
    100          
1110 25 100       217 if ( $arg eq "" ) {
    50          
    0          
1111 3         9 $self->add( type => "set",
1112             name => "gridparams",
1113             value => $grid_arg );
1114             }
1115             elsif ( $arg =~ m/^
1116             (?: (\d+) \+)?
1117             (\d+) (?: x (\d+) )?
1118             (?:\+ (\d+) )?
1119             (?:[:\s+] (.*)? )? $/x ) {
1120 22 50       90 do_warn("Invalid grid params: $arg (must be non-zero)"), return
1121             unless $2;
1122 22   50     262 $grid_arg = [ $2, $3//1, $1//0, $4//0 ];
      100        
      100        
1123 22   50     212 $self->add( type => "set",
1124             name => "gridparams",
1125             value => [ @$grid_arg, $5||"" ] );
1126 22 50 50     164 push( @labels, $5 ) if length($5||"");
1127             }
1128             elsif ( $arg ne "" ) {
1129 0         0 $self->add( type => "set",
1130             name => "gridparams",
1131             value => [ @$grid_arg, $arg ] );
1132 0         0 push( @labels, $arg );
1133             }
1134 25         119 $grid_cells = [ $grid_arg->[0] * $grid_arg->[1],
1135             $grid_arg->[2], $grid_arg->[3] ];
1136             }
1137             elsif ( $arg && $arg ne "" ) {
1138 2         9 $self->add( type => "set",
1139             name => "label",
1140             value => $arg );
1141             push( @labels, $arg )
1142 2 50 33     10 unless $in_context eq "chorus" && !$config->{settings}->{choruslabels};
1143             }
1144             else {
1145 48 50       113 do_warn("Garbage in start_of_$1: $arg (ignored)\n")
1146             if $arg;
1147             }
1148              
1149             # Enabling this always would allow [^] to recall anyway.
1150             # Feature?
1151 75 100       285 if ( $config->{settings}->{memorize} ) {
1152 7   100     38 $memchords = $memchords{$in_context} //= [];
1153 7         11 $memcrdinx = 0;
1154 7         15 $memorizing = 0;
1155             }
1156 75         797 return 1;
1157             }
1158 925 100       2570 if ( $dir =~ /^end_of_(\w+)$/ ) {
1159 75 50       365 do_warn("Not in " . ucfirst($1) . " context\n")
1160             unless $in_context eq $1;
1161 75         332 $self->add( type => "set",
1162             name => "context",
1163             value => $def_context );
1164 75         154 $in_context = $def_context;
1165 75         189 undef $memchords;
1166 75         327 return 1;
1167             }
1168 850 100       2431 if ( $dir =~ /^chorus$/i ) {
1169 30 50       90 if ( $in_context ) {
1170 0         0 do_warn("{chorus} encountered while in $in_context context -- ignored\n");
1171 0         0 return 1;
1172             }
1173              
1174             # Clone the chorus so we can modify the label, if required.
1175 30 100       5311 my $chorus = @chorus ? dclone(\@chorus) : [];
1176              
1177 30 50 66     36762 if ( @$chorus && $arg && $arg ne "" ) {
      33        
1178 0 0 0     0 if ( $chorus->[0]->{type} eq "set" && $chorus->[0]->{name} eq "label" ) {
1179 0         0 $chorus->[0]->{value} = $arg;
1180             }
1181             else {
1182 0         0 unshift( @$chorus,
1183             { type => "set",
1184             name => "label",
1185             value => $arg,
1186             context => "chorus",
1187             } );
1188             }
1189             push( @labels, $arg )
1190 0 0       0 if $config->{settings}->{choruslabels};
1191             }
1192              
1193 30 100       103 if ( $chorus_xpose != ( my $xp = $xpose ) ) {
1194 17         46 $xp -= $chorus_xpose;
1195 17         62 for ( @$chorus ) {
1196 32 100       124 if ( $_->{type} eq "songline" ) {
1197 16         31 for ( @{ $_->{chords} } ) {
  16         51  
1198 61 100       186 next if $_ eq '';
1199 46         162 my $info = $self->{chordsinfo}->{$_->key};
1200 46 50       162 next if $info->is_annotation;
1201 46 50       188 $info = $info->transpose($xp, $xpose <=> 0) if $xp;
1202 46         138 $info = $info->new($info);
1203 46         186 $_ = ChordPro::Chords::Appearance->new
1204             ( key => $self->add_chord($info),
1205             info => $info,
1206             maybe format => $_->format
1207             );
1208             }
1209             }
1210             }
1211             }
1212              
1213 30 100       165 $self->add( type => "rechorus",
1214             @$chorus
1215             ? ( "chorus" => $chorus )
1216             : (),
1217             );
1218 30         143 return 1;
1219             }
1220              
1221             # Song settings.
1222              
1223             # Breaks.
1224              
1225 820 100       2039 if ( $dir eq "column_break" ) {
1226 13         89 $self->add( type => "colb" );
1227 13         59 return 1;
1228             }
1229              
1230 807 100 100     3450 if ( $dir eq "new_page" || $dir eq "new_physical_page" ) {
1231 16         88 $self->add( type => "newpage" );
1232 16         61 return 1;
1233             }
1234              
1235 791 50       1782 if ( $dir eq "new_song" ) {
1236 0         0 die("FATAL - cannot start a new song now\n");
1237             }
1238              
1239             # Comments. Strictly speaking they do not belong here.
1240              
1241 791 100       2526 if ( $dir =~ /^comment(_italic|_box)?$/ ) {
1242 124         613 my %res = $self->cdecompose($arg);
1243 124         407 $res{orig} = $dd->{arg};
1244             $self->add( type => $dir, %res )
1245 124 50 66     1298 unless exists($res{text}) && $res{text} =~ /^[ \t]*$/;
1246 124         591 return 1;
1247             }
1248              
1249             # Images.
1250 667 100       1531 if ( $dir eq "image" ) {
1251 3         19 my $res = parse_kv($arg);
1252 3         10 my $uri;
1253             my $id;
1254 3         0 my %opts;
1255 3         17 while ( my($k,$v) = each(%$res) ) {
1256 9 100 66     134 if ( $k =~ /^(title)$/i && $v ne "" ) {
    100 66        
    100 66        
    50 33        
    100 66        
    50 33        
    50 66        
    100 33        
    50          
    50          
1257 1         7 $opts{lc($k)} = $v;
1258             }
1259             elsif ( $k =~ /^(border|spread|center)$/i && $v =~ /^(\d+)$/ ) {
1260 2         11 $opts{lc($k)} = $v;
1261             }
1262             elsif ( $k =~ /^(width|height)$/i && $v =~ /^(\d+(?:\.\d+)?\%?)$/ ) {
1263 2         14 $opts{lc($k)} = $v;
1264             }
1265             elsif ( $k =~ /^(x|y)$/i && $v =~ /^([-+]?\d+(?:\.\d+)?\%?)$/ ) {
1266 0         0 $opts{lc($k)} = $v;
1267             }
1268             elsif ( $k =~ /^(scale)$/ && $v =~ /^(\d+(?:\.\d+)?)(%)?$/ ) {
1269 1 50       11 $opts{lc($k)} = $2 ? $1/100 : $1;
1270             }
1271             elsif ( $k =~ /^(center|border|spread)$/i ) {
1272 0         0 $opts{lc($k)} = $v;
1273             }
1274             elsif ( $k =~ /^(src|uri)$/i && $v ne "" ) {
1275 0         0 $uri = $v;
1276             }
1277             elsif ( $k =~ /^(id)$/i && $v ne "" ) {
1278 1         5 $id = $v;
1279             }
1280             elsif ( $k =~ /^(anchor)$/i
1281             && $v =~ /^(paper|page|column|float|line)$/ ) {
1282 0         0 $opts{lc($k)} = lc($v);
1283             }
1284             elsif ( $uri ) {
1285 0         0 do_warn( "Unknown image attribute: $k\n" );
1286 0         0 next;
1287             }
1288             # Assume just an image file uri.
1289             else {
1290 2         8 $uri = $k;
1291             }
1292             }
1293              
1294             # If the image name does not have a directory, look it up
1295             # next to the song, and then in the images folder of the
1296             # CHORDPRO_LIB.
1297 3 100 66     16 if ( $uri && $uri !~ m;^([a-z]:)?[/\\];i ) { # not abs
1298 81     81   900 use File::Basename qw(dirname);
  81         222  
  81         21315  
1299 2         93 L: for ( dirname($diag->{file}) ) {
1300 2 50       47 $uri = "$_/$uri", last if -s "$_/$uri";
1301 0         0 for ( ::rsc_or_file("images/$uri") ) {
1302 0 0       0 last unless $_;
1303 0 0       0 $uri = $_, last L if -s $_;
1304             }
1305 0         0 do_warn("Missing image for \"$uri\"");
1306             }
1307             }
1308              
1309             # uri + id -> define asset
1310 3 50 66     16 if ( $uri && $id ) {
1311             # Define a new asset.
1312 0 0       0 if ( %opts ) {
1313 0         0 do_warn("Asset definition \"$id\" does not take attributes");
1314 0         0 return;
1315             }
1316 81     81   46966 use Image::Info;
  81         156583  
  81         709669  
1317 0         0 open( my $fd, '<:raw', $uri );
1318 0 0       0 unless ( $fd ) {
1319 0         0 do_warn("$uri: $!");
1320 0         0 return;
1321             }
1322 0         0 my $data = do { local $/; <$fd> };
  0         0  
  0         0  
1323             # Get info.
1324 0         0 my $info = Image::Info::image_info(\$data);
1325 0 0       0 if ( $info->{error} ) {
1326 0         0 do_warn($info->{error});
1327 0         0 return;
1328             }
1329              
1330             # Store in assets.
1331 0   0     0 $self->{assets} //= {};
1332             $self->{assets}->{$id} =
1333             { data => $data, type => $info->{file_ext},
1334             width => $info->{width}, height => $info->{height},
1335 0         0 };
1336              
1337 0 0       0 if ( $config->{debug}->{images} ) {
1338 0         0 warn("asset[$id] ", length($data), " bytes, ",
1339             "width=$info->{width}, height=$info->{height}",
1340             "\n");
1341             }
1342 0         0 return 1;
1343             }
1344              
1345 3 100       9 $uri = "id=$id" if $id;
1346 3 50       11 unless ( $uri ) {
1347 0         0 do_warn( "Missing image source\n" );
1348 0         0 return;
1349             }
1350 3 50       23 $self->add( type => $uri =~ /\.svg$/ ? "svg" : "image",
1351             uri => $uri,
1352             opts => \%opts );
1353 3         19 return 1;
1354             }
1355              
1356 664 100       1566 if ( $dir eq "title" ) {
1357 174         602 $self->{title} = $arg;
1358 174         367 push( @{ $self->{meta}->{title} }, $arg );
  174         739  
1359 174         899 return 1;
1360             }
1361              
1362 490 100       1449 if ( $dir eq "subtitle" ) {
1363 28         83 push( @{ $self->{subtitle} }, $arg );
  28         139  
1364 28         66 push( @{ $self->{meta}->{subtitle} }, $arg );
  28         85  
1365 28         118 return 1;
1366             }
1367              
1368             # Metadata extensions (legacy). Should use meta instead.
1369             # Only accept the list from config.
1370 462 100   5513   2502 if ( any { $_ eq $dir } @{ $config->{metadata}->{keys} } ) {
  5513         7532  
  462         2220  
1371 225         708 $arg = "$dir $arg";
1372 225         855 $dir = "meta";
1373             }
1374              
1375             # Metadata.
1376 462 100       2133 if ( $dir eq "meta" ) {
1377 263 50       1452 if ( $arg =~ /([^ :]+)[ :]+(.*)/ ) {
1378 263         898 my $key = lc $1;
1379 263         806 my @vals = ( $2 );
1380 263 100       1098 if ( $config->{metadata}->{autosplit} ) {
1381 256         707 @vals = map { s/s\+$//; $_ }
  256         950  
1382 256         5970 split( quotemeta($config->{metadata}->{separator}), $vals[0] );
1383             }
1384 263         646 my $m = $self->{meta};
1385              
1386             # User and instrument cannot be set here.
1387 263 50 33     1243 if ( $key eq "user" || $key eq "instrument" ) {
1388 0         0 do_warn("\"$key\" can be set from config only.\n");
1389 0         0 return 1;
1390             }
1391              
1392 263         646 for my $val ( @vals ) {
1393              
1394 263 100       645 if ( $key eq "key" ) {
1395 92         307 $val =~ s/[\[\]]//g;
1396 92         424 my $info = $self->parse_chord($val);
1397 92         342 my $name = $info->name;
1398 92         221 my $act = $name;
1399              
1400 92 50       257 if ( $capo ) {
1401 0         0 $act = $self->add_chord( $info->transpose($capo) );
1402 0 0       0 $name = $act if $decapo;
1403             }
1404              
1405 92         179 push( @{ $m->{key} }, $name );
  92         372  
1406 92         340 $m->{key_actual} = [ $act ];
1407             # warn("XX key=$name act=$act capo=",
1408             # $capo//""," decapo=$decapo\n");
1409 92         477 return 1;
1410             }
1411              
1412              
1413 171 100 66     790 if ( $key eq "capo" ) {
    100          
1414             do_warn("Multiple capo settings may yield surprising results.")
1415 16 100       69 if exists $m->{capo};
1416              
1417 16   50     64 $capo = $val || undef;
1418 16 50 33     97 if ( $capo && $m->{key} ) {
1419 16 100       53 if ( $decapo ) {
1420             my $key = $self->store_chord
1421 4         39 ($self->{chordsinfo}->{$m->{key}->[-1]}
1422             ->transpose($val));
1423 4         37 $m->{key}->[-1] = $key;
1424             $key = $self->store_chord
1425 4         19 ($self->{chordsinfo}->{$m->{key}->[-1]}
1426             ->transpose($xpose));
1427 4         45 $m->{key_actual} = [ $key ];
1428             }
1429             else {
1430 12         142 my $act = $m->{key_actual}->[-1];
1431 12         48 $m->{key_from} = [ $act ];
1432             my $key = $self->store_chord
1433 12         77 ($self->{chordsinfo}->{$act}->transpose($val));
1434 12         82 $m->{key_actual} = [ $key ];
1435             }
1436             }
1437             }
1438              
1439             elsif ( $key eq "duration" && $val ) {
1440 9         77 $val = duration($val);
1441             }
1442              
1443 171 50 33     544 if ( $config->{metadata}->{strict}
1444 1296     1296   1995 && ! any { $_ eq $key } @{ $config->{metadata}->{keys} } ) {
  171         2320  
1445             # Unknown, and strict.
1446             do_warn("Unknown metadata item: $key")
1447 0 0       0 if $config->{settings}->{strict};
1448 0         0 return;
1449             }
1450              
1451 171 50       819 push( @{ $self->{meta}->{$key} }, $val ) if defined $val;
  171         870  
1452             }
1453             }
1454             else {
1455             do_warn("Incomplete meta directive: $d\n")
1456 0 0       0 if $config->{settings}->{strict};
1457 0         0 return;
1458             }
1459 171         733 return 1;
1460             }
1461              
1462             # Song / Global settings.
1463              
1464 199 100 66     918 if ( $dir eq "titles"
1465             && $arg =~ /^(left|right|center|centre)$/i ) {
1466             $self->{settings}->{titles} =
1467 22 100       185 lc($1) eq "centre" ? "center" : lc($1);
1468 22         140 return 1;
1469             }
1470              
1471 177 100 66     736 if ( $dir eq "columns"
1472             && $arg =~ /^(\d+)$/ ) {
1473             # If there a column specifications in the config, retain them
1474             # if the number of columns match.
1475 19 50 33     162 unless( ref($config->{settings}->{columns}) eq 'ARRAY'
1476 0         0 && $arg == @{$config->{settings}->{columns}}
1477             ) {
1478 19         69 $self->{settings}->{columns} = $arg;
1479             }
1480 19         92 return 1;
1481             }
1482              
1483 158 100 100     693 if ( $dir eq "pagetype" || $dir eq "pagesize" ) {
1484 2         9 $self->{settings}->{papersize} = $arg;
1485 2         9 return 1;
1486             }
1487              
1488 156 100       395 if ( $dir eq "diagrams" ) { # AKA grid
1489 2 100       12 if ( $arg ne "" ) {
1490 1         7 $self->{settings}->{diagrams} = !!is_true($arg);
1491 1 50       10 $self->{settings}->{diagrampos} = lc($arg)
1492             if $arg =~ /^(right|bottom|top|below)$/i;
1493             }
1494             else {
1495 1         6 $self->{settings}->{diagrams} = 1;
1496             }
1497 2         9 return 1;
1498             }
1499 154 100       381 if ( $dir eq "no_grid" ) {
1500 3         18 $self->{settings}->{diagrams} = 0;
1501 3         13 return 1;
1502             }
1503              
1504 151 100       389 if ( $dir eq "transpose" ) {
1505 53   100     284 $propstack{transpose} //= [];
1506              
1507 53 100       273 if ( $arg =~ /^([-+]?\d+)\s*$/ ) {
1508 32         111 my $new = $1;
1509 32         64 push( @{ $propstack{transpose} }, [ $xpose, $xpose_dir ] );
  32         120  
1510 32         213 my %a = ( type => "control",
1511             name => "transpose",
1512             previous => [ $xpose, $xpose_dir ]
1513             );
1514 32         304 $xpose += $new;
1515 32         79 $xpose_dir = $new <=> 0;
1516 32         82 my $m = $self->{meta};
1517 32 100       112 if ( $m->{key} ) {
1518 22         61 my $key = $m->{key}->[-1];
1519 22         47 my $xp = $xpose;
1520 22 100       99 $xp += $capo if $capo;
1521 22         159 my $xpk = $self->{chordsinfo}->{$key}->transpose($xp, $xp <=> 0);
1522 22         117 $self->{chordsinfo}->{$xpk->name} = $xpk;
1523 22         128 $m->{key_from} = [ $m->{key_actual}->[0] ];
1524 22         75 $m->{key_actual} = [ $xpk->name ];
1525             }
1526 32 50       163 $self->add( %a, value => $xpose, dir => $xpose_dir )
1527             if $no_transpose;
1528             }
1529             else {
1530 21         147 my %a = ( type => "control",
1531             name => "transpose",
1532             previous => [ $xpose, $xpose_dir ]
1533             );
1534 21         72 my $m = $self->{meta};
1535 21         51 my ( $new, $dir );
1536 21 50       38 if ( @{ $propstack{transpose} } ) {
  21         72  
1537 21         38 ( $new, $dir ) = @{ pop( @{ $propstack{transpose} } ) };
  21         35  
  21         64  
1538             }
1539             else {
1540 0         0 $new = 0;
1541 0         0 $dir = $config->{settings}->{transpose} <=> 0;
1542             }
1543 21         61 $xpose = $new;
1544 21         52 $xpose_dir = $dir;
1545 21 100       95 if ( $m->{key} ) {
1546 15         56 $m->{key_from} = [ $m->{key_actual}->[0] ];
1547 15         35 my $xp = $xpose;
1548 15 50 66     61 $xp += $capo if $capo && $decapo;
1549             $m->{key_actual} =
1550 15         108 [ $self->{chordsinfo}->{$m->{key}->[-1]}->transpose($xp)->name ];
1551             }
1552 21 100       173 if ( !@{ $propstack{transpose} } ) {
  21         96  
1553 12         113 delete $m->{$_} for qw( key_from );
1554             }
1555 21 50       262 $self->add( %a, value => $xpose, dir => $dir )
1556             if $no_transpose;
1557             }
1558 53         251 return 1;
1559             }
1560              
1561             # More private hacks.
1562 98 50 33     611 if ( !$options->{reference} && $d =~ /^([-+])([-\w.]+)$/i ) {
1563 0 0       0 if ( $2 eq "dumpmeta" ) {
1564 0         0 warn(::dump($self->{meta}));
1565             }
1566 0 0       0 $self->add( type => "set",
1567             name => $2,
1568             value => $1 eq "+" ? 1 : 0,
1569             );
1570 0         0 return 1;
1571             }
1572              
1573 98 100 66     501 if ( !$options->{reference} && $dir =~ /^\+([-\w.]+(?:\.[<>])?)$/ ) {
1574 11         68 $self->add( type => "set",
1575             name => $1,
1576             value => $arg,
1577             );
1578              
1579             # THIS IS BASICALLY A COPY OF THE CODE IN Config.pm.
1580             # TODO: GENERALIZE.
1581 11         26 my $ccfg = {};
1582 11         87 my @k = split( /[:.]/, $1 );
1583 11         27 my $c = \$ccfg; # new
1584 11         23 my $o = $config; # current
1585 11         33 my $lk = pop(@k); # last key
1586              
1587             # Step through the keys.
1588 11         30 foreach ( @k ) {
1589 17         56 $c = \($$c->{$_});
1590 17         55 $o = $o->{$_};
1591             }
1592              
1593             # Turn hash.array into hash.array.> (append).
1594 11 50 33     91 if ( ref($o) eq 'HASH' && ref($o->{$lk}) eq 'ARRAY' ) {
1595 0         0 $c = \($$c->{$lk});
1596 0         0 $o = $o->{$lk};
1597 0         0 $lk = '>';
1598             }
1599              
1600             # Final key. Merge array if so.
1601 11 50 33     152 if ( ( $lk =~ /^\d+$/ || $lk eq '>' || $lk eq '<' )
      33        
1602             && ref($o) eq 'ARRAY' ) {
1603 0 0       0 unless ( ref($$c) eq 'ARRAY' ) {
1604             # Only copy orig values the first time.
1605 0         0 $$c->[$_] = $o->[$_] for 0..scalar(@{$o})-1;
  0         0  
1606             }
1607 0 0       0 if ( $lk eq '>' ) {
    0          
1608 0         0 push( @{$$c}, $arg );
  0         0  
1609             }
1610             elsif ( $lk eq '<' ) {
1611 0         0 unshift( @{$$c}, $arg );
  0         0  
1612             }
1613             else {
1614 0         0 $$c->[$lk] = $arg;
1615             }
1616             }
1617             else {
1618 11         44 $$c->{$lk} = $arg;
1619             }
1620              
1621 11         70 $config->augment($ccfg);
1622 11         46 upd_config();
1623              
1624 11         99 return 1;
1625             }
1626              
1627             # Formatting. {chordsize XX} and such.
1628 87 100       339 if ( $dir =~ m/ ^( text | chord | chorus | tab | grid | diagrams
1629             | title | footer | toc )
1630             ( font | size | colou?r )
1631             $/x ) {
1632 28         99 my $item = $1;
1633 28         53 my $prop = $2;
1634              
1635 28         85 $self->propset( $item, $prop, $arg );
1636              
1637             # Derived props.
1638 28 100       74 $self->propset( "chorus", $prop, $arg ) if $item eq "text";
1639              
1640             #::dump( { %propstack, line => $diag->{line} } );
1641 28         106 return 1;
1642             }
1643              
1644             # define A: base-fret N frets N N N N N N fingers N N N N N N
1645             # define: A base-fret N frets N N N N N N fingers N N N N N N
1646             # optional: base-fret N (defaults to 1)
1647             # optional: N N N N N N (for unknown chords)
1648             # optional: fingers N N N N N N
1649              
1650 59 100 100     218 if ( $dir eq "define" or $dir eq "chord" ) {
1651              
1652 58         206 return $self->define_chord( $dir, $arg );
1653             }
1654              
1655             # Warn about unknowns, unless they are x_... form.
1656             do_warn("Unknown directive: $d\n")
1657 1 50 33     4 if $config->{settings}->{strict} && $d !~ /^x_/;
1658 1         21 return;
1659             }
1660              
1661             sub propset {
1662 37     37 0 85 my ( $self, $item, $prop, $value ) = @_;
1663 37 100       78 $prop = "color" if $prop eq "colour";
1664 37         78 my $name = "$item-$prop";
1665 37   100     236 $propstack{$name} //= [];
1666              
1667 37 100       82 if ( $value eq "" ) {
1668             # Pop current value from stack.
1669 2 50       4 if ( @{ $propstack{$name} } ) {
  2         6  
1670 2         4 my $old = pop( @{ $propstack{$name} } );
  2         4  
1671             # A trailing number after a font directive means there
1672             # was also a size saved. Pop it.
1673 2 50 33     7 if ( $prop eq "font" && $old =~ /\s(\d+(?:\.\d+)?)$/ ) {
1674 0         0 pop( @{ $propstack{"$item-size"} } );
  0         0  
1675             }
1676             }
1677             else {
1678 0         0 do_warn("No saved value for property $item$prop\n" )
1679             }
1680             # Use new current value, if any.
1681 2 50       3 if ( @{ $propstack{$name} } ) {
  2         6  
1682 2         5 $value = $propstack{$name}->[-1]
1683             }
1684             else {
1685 0         0 $value = undef;
1686             }
1687 2         6 $self->add( type => "control",
1688             name => $name,
1689             value => $value );
1690 2         3 return 1;
1691             }
1692              
1693 35 100       66 if ( $prop eq "size" ) {
1694 10 50       81 unless ( $value =~ /^\d+(?:\.\d+)?\%?$/ ) {
1695 0         0 do_warn("Illegal value \"$value\" for $item$prop\n");
1696 0         0 return 1;
1697             }
1698             }
1699 35 100       73 if ( $prop eq "color" ) {
1700 15         28 my $v;
1701 15 50       37 unless ( $v = get_color($value) ) {
1702 0         0 do_warn("Illegal value \"$value\" for $item$prop\n");
1703 0         0 return 1;
1704             }
1705 15         30 $value = $v;
1706             }
1707 35 100       109 $value = $prop eq "font" ? $value : lc($value);
1708 35         105 $self->add( type => "control",
1709             name => $name,
1710             value => $value );
1711 35         50 push( @{ $propstack{$name} }, $value );
  35         100  
1712              
1713             # A trailing number after a font directive is an implicit size
1714             # directive.
1715 35 50 66     127 if ( $prop eq 'font' && $value =~ /\s(\d+(?:\.\d+)?)$/ ) {
1716 0         0 $self->add( type => "control",
1717             name => "$item-size",
1718             value => $1 );
1719 0         0 push( @{ $propstack{"$item-size"} }, $1 );
  0         0  
1720             }
1721             }
1722              
1723             sub add_chord {
1724 991     991 0 2346 my ( $self, $info, $new_id ) = @_;
1725              
1726 991 100       1993 if ( $new_id ) {
1727 942 100       2201 if ( $new_id eq "1" ) {
1728 10         36 state $id = "ch0000";
1729 10         32 $new_id = " $id";
1730 10         24 $id++;
1731             }
1732             }
1733             else {
1734 49         157 $new_id = $info->name;
1735             }
1736 991         2496 $self->{chordsinfo}->{$new_id} = $info->new($info);
1737              
1738 991         4215 return $new_id;
1739             }
1740              
1741             sub define_chord {
1742 70     70 0 184 my ( $self, $dir, $args ) = @_;
1743              
1744             # Split the arguments and keep a copy for error messages.
1745             # Note that quotewords returns an empty result if it gets confused,
1746             # so fall back to the ancient split method if so.
1747 70         225 $args =~ s/^\s+//;
1748 70         328 $args =~ s/\s+$//;
1749 70         283 my @a = quotewords( '[: ]+', 0, $args );
1750 70 100       20598 @a = split( /[: ]+/, $args ) unless @a;
1751              
1752 70         248 my @orig = @a;
1753 70         161 my $show = $dir eq "chord";
1754 70         123 my $fail = 0;
1755 70         138 my $name = shift(@a);
1756 70         281 my $strings = $config->diagram_strings;
1757              
1758             # Process the options.
1759 70         225 my %kv = ( name => $name );
1760 70         217 while ( @a ) {
1761 162         276 my $a = shift(@a);
1762              
1763             # Copy existing definition.
1764 162 100 66     1222 if ( $a eq "copy" || $a eq "copyall" ) {
    100 66        
    100 66        
    100 33        
    100          
    100          
    100          
    50          
1765 11 50       42 if ( my $i = ChordPro::Chords::known_chord($a[0]) ) {
1766 11         37 $kv{$a} = $a[0];
1767 11         30 $kv{orig} = $i;
1768 11         38 shift(@a);
1769             }
1770             else {
1771 0         0 do_warn("Unknown chord to copy: $a[0]\n");
1772 0         0 $fail++;
1773 0         0 last;
1774             }
1775             }
1776              
1777             # display
1778             elsif ( $a eq "display" && @a ) {
1779 2         14 $kv{display} = demarkup($a[0]);
1780             do_warn( "\"display\" should not contain markup, use \"format\"" )
1781 2 50       10 unless $kv{display} eq shift(@a);
1782 2         18 $kv{display} = $self->parse_chord($kv{display},1);
1783 2 50       12 delete $kv{display} unless defined $kv{display};
1784             }
1785              
1786             # format
1787             elsif ( $a eq "format" && @a ) {
1788 9         33 $kv{format} = shift(@a);
1789             }
1790              
1791             # base-fret N
1792             elsif ( $a eq "base-fret" ) {
1793 46 50       235 if ( $a[0] =~ /^\d+$/ ) {
1794 46         181 $kv{base} = shift(@a);
1795             }
1796             else {
1797 0         0 do_warn("Invalid base-fret value: $a[0]\n");
1798 0         0 $fail++;
1799 0         0 last;
1800             }
1801             }
1802             # frets N N ... N
1803             elsif ( $a eq "frets" ) {
1804 57         91 my @f;
1805 57   100     422 while ( @a && $a[0] =~ /^(?:[0-9]+|[-xXN])$/ && @f < $strings ) {
      66        
1806 342         1612 push( @f, shift(@a) );
1807             }
1808 57 50       179 if ( @f == $strings ) {
1809 57 100       140 $kv{frets} = [ map { $_ =~ /^\d+/ ? $_ : -1 } @f ];
  342         1052  
1810             }
1811             else {
1812 0         0 do_warn("Incorrect number of fret positions (" .
1813             scalar(@f) . ", should be $strings)\n");
1814 0         0 $fail++;
1815 0         0 last;
1816             }
1817             }
1818              
1819             # fingers N N ... N
1820             elsif ( $a eq "fingers" ) {
1821 16         32 my @f;
1822             # It is tempting to limit the fingers to 1..5 ...
1823 16   100     64 while ( @a && @f < $strings ) {
1824 96         173 local $_ = shift(@a);
1825 96 100       263 if ( /^[0-9]+$/ ) {
    50          
    50          
1826 90         315 push( @f, 0 + $_ );
1827             }
1828             elsif ( /^[A-MO-WYZ]$/ ) {
1829 0         0 push( @f, $_ );
1830             }
1831             elsif ( /^[-xNX]$/ ) {
1832 6         20 push( @f, -1 );
1833             }
1834             else {
1835 0         0 unshift( @a, $_ );
1836 0         0 last;
1837             }
1838             }
1839 16 50       62 if ( @f == $strings ) {
1840 16         59 $kv{fingers} = \@f;
1841             }
1842             else {
1843 0         0 do_warn("Incorrect number of finger settings (" .
1844             scalar(@f) . ", should be $strings)\n");
1845 0         0 $fail++;
1846 0         0 last;
1847             }
1848             }
1849              
1850             # keys N N ... N
1851             elsif ( $a eq "keys" ) {
1852 8         16 my @f;
1853 8   100     45 while ( @a && $a[0] =~ /^[0-9]+$/ ) {
1854 24         100 push( @f, shift(@a) );
1855             }
1856 8 50       17 if ( @f ) {
1857 8         23 $kv{keys} = \@f;
1858             }
1859             else {
1860 0         0 do_warn("Invalid or missing keys\n");
1861 0         0 $fail++;
1862 0         0 last;
1863             }
1864             }
1865              
1866             elsif ( $a eq "diagram" && @a > 0 ) {
1867 13 50 33     42 if ( $show && !is_true($a[0]) ) {
1868 0         0 do_warn("Useless diagram suppression");
1869 0         0 next;
1870             }
1871 13         39 $kv{diagram} = shift(@a);
1872             }
1873              
1874             # Wrong...
1875             else {
1876             # Insert a marker to show how far we got.
1877 0         0 splice( @orig, @orig-@a, 0, "<<<" );
1878 0         0 splice( @orig, @orig-@a-2, 0, ">>>" );
1879 0         0 do_warn("Invalid chord definition: @orig\n");
1880 0         0 $fail++;
1881 0         0 last;
1882             }
1883             }
1884              
1885 70 50       173 return 1 if $fail;
1886             # All options are verified and stored in %kv;
1887              
1888             # Result structure.
1889 70         210 my $res = { name => $name };
1890              
1891             # Try to find info.
1892 70         238 my $info = $self->parse_chord( $name, "def" );
1893 70 50       183 if ( $info ) {
1894             # Copy the chord info.
1895             $res->{$_} //= $info->{$_} // ''
1896 70   100     2110 for qw( root qual ext bass
      66        
1897             root_canon qual_canon ext_canon bass_canon
1898             root_ord root_mod bass_ord bass_mod
1899             );
1900 70 100       202 if ( $show ) {
1901             $res->{$_} //= $info->{$_}
1902 8   66     90 for qw( base frets fingers keys );
1903             }
1904             }
1905             else {
1906 0         0 $res->{parser} = ChordPro::Chords::get_parser();
1907             }
1908              
1909             # Copy existing definition.
1910 70   66     347 for ( $kv{copyall} // $kv{copy} ) {
1911 70 100       199 next unless defined;
1912 11         34 $res->{copy} = $_;
1913 11         29 my $orig = $res->{orig} = $kv{orig};
1914             $res->{$_} //= $orig->{$_}
1915 11   33     119 for qw( base frets fingers keys );
1916 11 50       48 if ( $kv{copyall} ) {
1917             $res->{$_} //= $orig->{$_}
1918 0   0     0 for qw( display format );
1919             }
1920             }
1921 70         117 for ( qw( display format ) ) {
1922 140 100       336 $res->{$_} = $kv{$_} if defined $kv{$_};
1923             }
1924              
1925             # If we've got diagram visibility, remove it if true.
1926 70 100       186 if ( defined $kv{diagram} ) {
1927 13         30 for ( my $v = $kv{diagram} ) {
1928 13 100       53 if ( is_true($v) ) {
1929 7 100       30 if ( is_ttrue($v) ) {
1930 6         18 next;
1931             }
1932             }
1933             else {
1934 6         15 $v = 0;
1935             }
1936 7         21 $res->{diagram} = $v;
1937             }
1938             }
1939              
1940             # Copy rest of options.
1941 70         163 for ( qw( base frets fingers keys display format ) ) {
1942 420 100       833 next unless defined $kv{$_};
1943 138         288 $res->{$_} = $kv{$_};
1944             }
1945              
1946             # At this time, $res is still just a hash. Time to make a chord.
1947 70   100     210 $res->{base} ||= 1;
1948 70 100       959 $res = ChordPro::Chord::Common->new
1949             ( { %$res, origin => $show ? "inline" : "song" } );
1950 70   33     651 $res->{parser} //= ChordPro::Chords::get_parser();
1951              
1952 70 100       164 if ( $show) {
1953 8         49 my $ci = $res->clone;
1954 8         6300 my $chidx = $self->add_chord( $ci, 1 );
1955             # Combine consecutive entries.
1956 8 100 66     81 if ( defined($self->{body})
1957             && $self->{body}->[-1]->{type} eq "diagrams" ) {
1958 2         10 push( @{ $self->{body}->[-1]->{chords} }, $chidx );
  2         10  
1959             }
1960             else {
1961 6         33 $self->add( type => "diagrams",
1962             show => "user",
1963             origin => "chord",
1964             chords => [ $chidx ] );
1965             }
1966 8         100 return 1;
1967             }
1968              
1969 62         125 my $def = {};
1970 62         148 for ( qw( name base frets fingers keys display format diagram ) ) {
1971 496 100       988 next unless defined $res->{$_};
1972 246         470 $def->{$_} = $res->{$_};
1973             }
1974 62         110 push( @{$self->{define}}, $def );
  62         192  
1975 62         197 my $ret = ChordPro::Chords::add_song_chord($res);
1976 62 50       171 if ( $ret ) {
1977 0         0 do_warn("Invalid chord: ", $res->{name}, ": ", $ret, "\n");
1978 0         0 return 1;
1979             }
1980 62         179 $info = ChordPro::Chords::known_chord($res->{name});
1981 62 50       167 croak("We just entered it?? ", $res->{name}) unless $info;
1982              
1983 62 50       204 $info->dump if $config->{debug}->{x1};
1984              
1985 62         555 return 1;
1986             }
1987              
1988             sub duration {
1989 9     9 0 46 my ( $dur ) = @_;
1990              
1991 9 50       96 if ( $dur =~ /(?:(?:(\d+):)?(\d+):)?(\d+)/ ) {
1992 9 50       81 $dur = $3 + ( $2 ? 60 * $2 :0 ) + ( $1 ? 3600 * $1 : 0 );
    50          
1993             }
1994 9         134 my $res = sprintf( "%d:%02d:%02d",
1995             int( $dur / 3600 ),
1996             int( ( $dur % 3600 ) / 60 ),
1997             $dur % 60 );
1998 9         58 $res =~ s/^[0:]+//;
1999 9         27 return $res;
2000             }
2001              
2002             sub get_color {
2003 15     15 0 42 $_[0];
2004             }
2005              
2006             sub _diag {
2007 23     23   19412 my ( $self, %d ) = @_;
2008 23         157 $diag->{$_} = $d{$_} for keys(%d);
2009             }
2010              
2011             sub msg {
2012 3     3 0 14 my $m = join("", @_);
2013 3         13 $m =~ s/\n+$//;
2014 3         10 my $t = $diag->{format};
2015 3         20 $t =~ s/\\n/\n/g;
2016 3         9 $t =~ s/\\t/\t/g;
2017 3         21 $t =~ s/\%f/$diag->{file}/g;
2018 3         17 $t =~ s/\%n/$diag->{line}/g;
2019 3         14 $t =~ s/\%l/$diag->{orig}/g;
2020 3         12 $t =~ s/\%m/$m/g;
2021 3         46 $t;
2022             }
2023              
2024             sub do_warn {
2025 3     3 0 29 warn(msg(@_)."\n");
2026             }
2027              
2028             # Parse a chord.
2029             # Handles transpose/transcode.
2030             # Returns the chord object.
2031             # No parens or annotations, please.
2032             sub parse_chord {
2033 1112     1112 0 3462 my ( $self, $chord, $def ) = @_;
2034              
2035 1112         2619 my $debug = $config->{debug}->{chords};
2036              
2037 1112 50       2378 warn("Parsing chord: \"$chord\"\n") if $debug;
2038 1112         1654 my $info;
2039 1112         2279 my $xp = $xpose + $config->{settings}->{transpose};
2040 1112 100 100     2623 $xp += $capo if $capo && $decapo;
2041 1112         2586 my $xc = $config->{settings}->{transcode};
2042 1112         2057 my $global_dir = $config->{settings}->{transpose} <=> 0;
2043 1112         1587 my $unk;
2044              
2045             # When called from {define} ignore xc/xp.
2046 1112 100       2152 $xc = $xp = '' if $def;
2047              
2048 1112         3211 $info = ChordPro::Chords::known_chord($chord);
2049 1112 100       2603 if ( $info ) {
2050             warn( "Parsing chord: \"$chord\" found \"",
2051 880 50       1937 $info->name, "\" in ", $info->{_via}, "\n" ) if $debug > 1;
2052 880 50       1783 $info->dump if $debug > 1;
2053             }
2054             else {
2055 232         591 $info = ChordPro::Chords::parse_chord($chord);
2056             warn( "Parsing chord: \"$chord\" parsed ok [",
2057             $info->{system},
2058 232 50 66     1168 "]\n" ) if $info && $debug > 1;
2059             }
2060 1112         2030 $unk = !defined $info;
2061              
2062 1112 100 100     6222 if ( ( $def || $xp || $xc )
      66        
      100        
2063             &&
2064             ! ($info && $info->is_xpxc ) ) {
2065 21         63 local $::config->{settings}->{chordnames} = "relaxed";
2066 21         53 $info = ChordPro::Chords::parse_chord($chord);
2067             }
2068              
2069 1112 0 33     4328 unless ( ( $info && $info->is_xpxc )
      0        
      0        
      33        
2070             ||
2071             ( $def && !( $xc || $xp ) ) ) {
2072             do_warn( "Cannot parse",
2073             $xp ? "/transpose" : "",
2074             $xc ? "/transcode" : "",
2075             " chord \"$chord\"\n" )
2076 0 0 0     0 if $xp || $xc || $config->{debug}->{chords};
    0 0        
    0          
2077             }
2078              
2079 1112 100 66     3149 if ( $xp && $info ) {
2080             # For transpose/transcode, chord must be wellformed.
2081 156   100     733 my $i = $info->transpose( $xp,
2082             $xpose_dir // $global_dir);
2083             # Prevent self-references.
2084 156 100       728 $i->{xp} = $info unless $i eq $info;
2085 156         289 $info = $i;
2086 156 50       446 warn( "Parsing chord: \"$chord\" transposed ",
2087             sprintf("%+d", $xp), " to \"",
2088             $info->name, "\"\n" ) if $debug > 1;
2089             }
2090             # else: warning has been given.
2091              
2092 1112 50       2330 if ( $info ) { # TODO roman?
2093             # Look it up now, the name may change by transcode.
2094 1112 100 33     2637 if ( my $i = ChordPro::Chords::known_chord($info) ) {
    50          
2095             warn( "Parsing chord: \"$chord\" found ",
2096             $i->name, " for ", $info->name,
2097 885 50       2052 " in ", $i->{_via}, "\n" ) if $debug > 1;
2098             $info = $i->new({ %$i, name => $info->name,
2099             $info->{xp} ? ( xp => $info->{xp} ) : (),
2100 885 100       5268 $info->{xc} ? ( xc => $info->{xc} ) : (),
    50          
2101             }) ;
2102 885         4549 $unk = 0;
2103             }
2104             elsif ( $config->{instrument}->{type} eq 'keyboard'
2105             && ( my $k = ChordPro::Chords::get_keys($info) ) ) {
2106 0 0       0 warn( "Parsing chord: \"$chord\" \"", $info->name, "\" not found ",
2107             "but we know what to do\n" ) if $debug > 1;
2108 0         0 $info = $info->new({ %$info, keys => $k }) ;
2109 0         0 $unk = 0;
2110             }
2111             else {
2112 227 50       497 warn( "Parsing chord: \"$chord\" \"", $info->name,
2113             "\" not found in song/config chords\n" ) if $debug;
2114             # warn("XX \'", $info->agnostic, "\'\n");
2115 227         402 $unk = 1;
2116             }
2117             }
2118              
2119 1112 100 66     3036 if ( $xc && $info ) {
2120 20         32 my $key_ord;
2121             $key_ord = $self->{chordsinfo}->{$self->{meta}->{key}->[-1]}->{root_ord}
2122 20 100       118 if $self->{meta}->{key};
2123 20 50 33     63 if ( $xcmov && !defined $key_ord ) {
2124 0         0 do_warn("Warning: Transcoding to $xc without key may yield unexpected results\n");
2125 0         0 undef $xcmov;
2126             }
2127 20         158 my $i = $info->transcode( $xc, $key_ord );
2128             # Prevent self-references.
2129 20 50       107 $i->{xc} = $info unless $i eq $info;
2130 20         32 $info = $i;
2131             warn( "Parsing chord: \"$chord\" transcoded to ",
2132             $info->name,
2133 20 50       54 " (", $info->{system}, ")",
2134             "\n" ) if $debug > 1;
2135 20 100       64 if ( my $i = ChordPro::Chords::known_chord($info) ) {
2136 8 50       23 warn( "Parsing chord: \"$chord\" found \"",
2137             $info->name, "\" in song/config chords\n" ) if $debug > 1;
2138 8         22 $unk = 0;
2139             }
2140             }
2141             # else: warning has been given.
2142              
2143 1112 50       2444 if ( ! $info ) {
2144 0 0       0 if ( my $i = ChordPro::Chords::known_chord($chord) ) {
2145 0         0 $info = $i;
2146             warn( "Parsing chord: \"$chord\" found \"",
2147             $chord, "\" in ",
2148 0 0       0 $i->{_via}, "\n" ) if $debug > 1;
2149 0         0 $unk = 0;
2150             }
2151             }
2152              
2153 1112 50 33     2627 unless ( $info || $def ) {
2154 0 0 0     0 if ( $config->{debug}->{chords} || ! $warned_chords{$chord}++ ) {
2155 0 0       0 warn("Parsing chord: \"$chord\" unknown\n") if $debug;
2156 0 0       0 do_warn( "Unknown chord: \"$chord\"\n" )
2157             unless $chord =~ /^n\.?c\.?$/i;
2158             }
2159             }
2160              
2161 1112 50       2241 if ( $info ) {
2162 1112 0       2257 warn( "Parsing chord: \"$chord\" okay: \"",
    50          
2163             $info->name, "\" \"",
2164             $info->chord_display, "\"",
2165             $unk ? " but unknown" : "",
2166             "\n" ) if $debug > 1;
2167 1112         3290 $self->store_chord($info);
2168 1112         2894 return $info;
2169             }
2170              
2171 0 0       0 warn( "Parsing chord: \"$chord\" not found\n" ) if $debug;
2172 0         0 return;
2173             }
2174              
2175             sub store_chord {
2176 1132     1132 0 2222 my ( $self, $info ) = @_;
2177 1132         3113 $self->{chordsinfo}->{$info->name} = $info;
2178 1132         3051 $info->name;
2179             }
2180              
2181             sub structurize {
2182 13     13 0 48 my ( $self ) = @_;
2183              
2184 13 50       59 return if $self->{structure} eq "structured";
2185              
2186 13         26 my @body;
2187 13         31 my $context = $def_context;
2188              
2189 13         27 foreach my $item ( @{ $self->{body} } ) {
  13         40  
2190 251 100 66     590 if ( $item->{type} eq "empty" && $item->{context} eq $def_context ) {
2191 56         83 $context = $def_context;
2192 56         86 next;
2193             }
2194 195 100 100     489 if ( $item->{type} eq "songline" && $item->{context} eq '' ){ # A songline should have a context - non means verse
2195 36         60 $item->{context} = 'verse';
2196             }
2197 195 100       359 if ( $context ne $item->{context} ) {
2198 49         166 push( @body, { type => $context = $item->{context}, body => [] } );
2199             }
2200 195 100       296 if ( $context ) {
2201 135         190 push( @{ $body[-1]->{body} }, $item );
  135         260  
2202             }
2203             else {
2204 60         135 push( @body, $item );
2205             }
2206             }
2207 13         136 $self->{body} = [ @body ];
2208 13         66 $self->{structure} = "structured";
2209             }
2210              
2211             sub dump {
2212 0     0 0   my ( $self, $full ) = @_;
2213 0           my $a = dclone($self);
2214 0           $a->{config} = ref(delete($a->{config}));
2215 0 0         unless ( $full ) {
2216 0           for my $ci ( keys %{$a->{chordsinfo}} ) {
  0            
2217 0           $a->{chordsinfo}{$ci} = $a->{chordsinfo}{$ci}->simplify;
2218             }
2219             }
2220             # require Data::Dump::Filtered;
2221             # warn Data::Dump::Filtered::dump_filtered($a, sub {
2222             # my ( $ctx, $o ) = @_;
2223             # my $h = { hide_keys => [ 'parser' ] };
2224             # $h->{bless} = ""
2225             # if $ctx->class;
2226             # $h;
2227             # });
2228 0           ::dump($a);
2229             }
2230              
2231             unless ( caller ) {
2232             require DDumper;
2233             binmode STDERR => ':utf8';
2234             ChordPro::Config::configurator();
2235             my $s = ChordPro::Song->new;
2236             $options->{settings}->{transpose} = 0;
2237             for ( @ARGV ) {
2238             if ( /^[a-z]/ ) {
2239             $options->{settings}->{transcode} = $_;
2240             next;
2241             }
2242             # DDumper::DDumper( $s->parse_chord($_) );
2243             my ( undef, $i ) = $s->parse_chord($_);
2244             warn("$_ => ", $i->name, " => ", $s->add_chord($i, $i->name eq 'D'), "\n" );
2245             $xpose++;
2246             }
2247             DDumper::DDumper($s->{chordsinfo});
2248             }
2249              
2250             1;