File Coverage

blib/lib/ChordPro/Song.pm
Criterion Covered Total %
statement 791 1108 71.3
branch 446 730 61.1
condition 182 346 52.6
subroutine 40 41 97.5
pod 0 20 0.0
total 1459 2245 64.9


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