File Coverage

blib/lib/ChordPro/Song.pm
Criterion Covered Total %
statement 791 1108 71.3
branch 446 730 61.1
condition 184 349 52.7
subroutine 40 41 97.5
pod 0 20 0.0
total 1461 2248 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   551 use strict;
  79         161  
  79         2420  
11 79     79   443 use warnings;
  79         182  
  79         2076  
12              
13 79     79   497 use ChordPro;
  79         213  
  79         2126  
14 79     79   550 use ChordPro::Chords;
  79         191  
  79         2808  
15 79     79   31604 use ChordPro::Chords::Appearance;
  79         214  
  79         2792  
16 79     79   659 use ChordPro::Chords::Parser;
  79         221  
  79         1838  
17 79     79   403 use ChordPro::Output::Common;
  79         193  
  79         3819  
18 79     79   517 use ChordPro::Utils;
  79         180  
  79         6964  
19              
20 79     79   513 use Carp;
  79         205  
  79         3709  
21 79     79   510 use List::Util qw(any);
  79         170  
  79         4920  
22 79     79   37697 use File::LoadLines;
  79         1028853  
  79         5493  
23 79     79   684 use Storable qw(dclone);
  79         192  
  79         3746  
24 79     79   554 use feature 'state';
  79         216  
  79         6022  
25 79     79   573 use Text::ParseWords qw(quotewords);
  79         184  
  79         4321  
26 79     79   536 use File::Basename qw(basename);
  79         244  
  79         856000  
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 2395 my ( $pkg, $filesource ) = @_;
77              
78 171         411 $xpose = 0;
79 171         591 $grid_arg = [ 4, 4, 1, 1 ]; # 1+4x4+1
80 171         436 $in_context = $def_context;
81 171         461 @used_chords = ();
82 171         407 %warned_chords = ();
83 171         405 %memchords = ();
84 171         406 %propstack = ();
85 171         804 ChordPro::Chords::reset_song_chords();
86 171         359 @labels = ();
87 171         1652 @chorus = ();
88 171         376 $capo = undef;
89 171         300 $xcmov = undef;
90 171         621 upd_config();
91              
92 171         615 $diag->{format} = $config->{diagnostics}->{format};
93 171         506 $diag->{file} = $filesource;
94 171         482 $diag->{line} = 0;
95 171         417 $diag->{orig} = "(at start of song)";
96              
97 171         1749 bless { chordsinfo => {},
98             meta => {},
99             structure => "linear",
100             } => $pkg;
101             }
102              
103             sub upd_config {
104 351     351 0 1146 $decapo = $config->{settings}->{decapo};
105 351         751 $lineinfo = $config->{settings}->{lineinfo};
106 351         578 $intervals = @{ $config->{notes}->{sharp} };
  351         940  
107             }
108              
109       169     sub ::break() {}
110              
111             sub parse_song {
112 169     169 0 613 my ( $self, $lines, $linecnt, $meta, $defs ) = @_;
113 169 50       694 die("OOPS! Wrong meta") unless ref($meta) eq 'HASH';
114 169         180478 local $config = dclone($config);
115              
116 169 50       1203 warn("Processing song ", $diag->{file}, "...\n") if $options->{verbose};
117 169         717 ::break();
118 169         353 my @configs;
119             #
120 169 50       1264 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     895 if ( !$options->{nosongconfig} && $diag->{file} ) {
144 78 50       253 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     428 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         238 for ( "prp", "json" ) {
161 156         693 ( my $cf = $diag->{file} ) =~ s/\.\w+$/.$_/;
162 156 100       624 $cf .= ".$_" if $cf eq $diag->{file};
163 156 50       2285 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         485 my $tuncheck = join("|",@{$config->{tuning}});
  169         819  
172 169         620 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         1082 $config->unlock;
211              
212 169 50       277369 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         673 for ( qw( transpose transcode decapo lyrics-only ) ) {
219 676 100       1857 next unless defined $options->{$_};
220 18         83 $config->{settings}->{$_} = $options->{$_};
221             }
222             # Catch common error.
223 169 50       955 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         948 $config->lock;
232 169         299516 for ( keys %{ $config->{meta} } ) {
  169         946  
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         551 $no_transpose = $options->{'no-transpose'};
243 169         397 $no_substitute = $options->{'no-substitute'};
244 169         403 my $fragment = $options->{fragment};
245 169         541 my $target = $config->{settings}->{transcode};
246 169 100       591 if ( $target ) {
247 2 50       16 unless ( ChordPro::Chords::Parser->have_parser($target) ) {
248 2 50       24 if ( my $file = ::rsc_or_file("config/notes/$target.json") ) {
249 2         14 for ( ChordPro::Config::get_config($file) ) {
250 2         17 my $new = $config->hmerge($_);
251 2         7 local $config = $new;
252 2         17 ChordPro::Chords::Parser->new($new);
253             }
254             }
255             }
256 2 50       25 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         13 ChordPro::Chords::set_parser($target);
261 2         12 my $p = ChordPro::Chords::get_parser;
262 2         12 $xcmov = $p->movable;
263 2 50       9 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         638 $target = $self->{system};
272             }
273              
274 169         618 upd_config();
275 169         1213 $self->{source} = { file => $diag->{file}, line => 1 + $$linecnt };
276 169         641 $self->{system} = $config->{notes}->{system};
277 169         481 $self->{config} = $config;
278 169 50       735 $self->{meta} = $meta if $meta;
279 169         463 $self->{chordsinfo} = {};
280 169   66     1018 $target //= $self->{system};
281              
282             # Preprocessor.
283 169         1025 my $prep = make_preprocessor( $config->{parser}->{preprocess} );
284              
285             # Pre-fill meta data, if any. TODO? ALREADY DONE?
286 169 50       734 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       967 if ( $config->{settings}->{memorize} ) {
294 1         6 $re_chords = qr/(\[.*?\]|\^)/;
295             }
296             else {
297 168         2750 $re_chords = qr/(\[.*?\])/;
298             }
299              
300 169         478 my $skipcnt = 0;
301 169         615 while ( @$lines ) {
302 1925 50       3787 if ( $skipcnt ) {
303 0         0 $skipcnt--;
304             }
305             else {
306 1925         3766 $diag->{line} = ++$$linecnt;
307             }
308              
309 1925         3814 $_ = shift(@$lines);
310 1925   33     5988 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         6436 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         166302 $diag->{orig} = $_;
324             # Get rid of TABs.
325 1925         4031 s/\t/ /g;
326              
327 1925 50       7001 if ( $config->{debug}->{echo} ) {
328 0         0 warn(sprintf("==[%3d]=> %s\n", $diag->{line}, $diag->{orig} ) );
329             }
330              
331 1925 50       4020 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       3641 if ( $skip_context ) {
344 4 100       21 if ( /^\s*\{(\w+)\}\s*$/ ) {
345 2         6 my $dir = $self->parse_directive($1);
346 2 50       16 if ( $dir->{name} eq "end_of_$in_context" ) {
347 2         5 $in_context = $def_context;
348 2         6 $skip_context = 0;
349             }
350             }
351 4         9 next;
352             }
353              
354 1921 100       4641 if ( /^\s*\{(new_song|ns)\}\s*$/ ) {
355 32 100       136 last if $self->{body};
356 3         9 next;
357             }
358              
359 1889 100       3974 if ( /^#/ ) {
360              
361             # Handle assets.
362 55         143 my $kw = "";
363 55         129 my $kv = {};
364 55 100       192 if ( /^##(image|asset):\s+(.*)/i ) {
365 1         4 $kw = lc($1);
366 1         3 $kv = parse_kv($2);
367             }
368              
369 55 100       147 if ( $kw eq "image" ) {
370 1         4 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         507 require MIME::Base64;
378 1         668 require Image::Info;
379              
380             # Read the image.
381 1         4 my $data = '';
382 1   66     12 while ( @$lines && $lines->[0] =~ /^# (.+)/ ) {
383 3         16 $data .= MIME::Base64::decode($1);
384 3         14 shift(@$lines);
385             }
386              
387             # Get info.
388 1         4 my $info = Image::Info::image_info(\$data);
389 1 50       3747 if ( $info->{error} ) {
390 0         0 do_warn($info->{error});
391 0         0 next;
392             }
393              
394             # Store in assets.
395 1   50     7 $self->{assets} //= {};
396             $self->{assets}->{$id} =
397             { data => $data, type => $info->{file_ext},
398             width => $info->{width}, height => $info->{height},
399 1         8 };
400              
401 1 50       4 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         11 next;
407             }
408              
409 54 50       128 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     162 if ( exists $self->{title} || $fragment ) {
443 54         137 $self->add( type => "ignore", text => $_ );
444             }
445             else {
446 0         0 push( @{ $self->{preamble} }, $_ );
  0         0  
447             }
448 54         149 next;
449             }
450              
451 1834 100       3706 if ( $in_context eq "tab" ) {
452 62 100       221 unless ( /^\s*\{(?:end_of_tab|eot)\}\s*$/ ) {
453 49         123 $self->add( type => "tabline", text => $_ );
454 49         100 next;
455             }
456             }
457              
458 1785 50       4075 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       7144 if ( /^\s*\{(.*)\}\s*$/ ) {
496 997         2994 my $dir = $1;
497 997 50       2316 if ( $prep->{directive} ) {
498             # warn("PRE: ", $_, "\n");
499 0         0 $prep->{directive}->($dir);
500             # warn("POST: ", $_, "\n");
501             }
502 997 100       2803 $self->add( type => "ignore",
503             text => $_ )
504             unless $self->directive($dir);
505 997         3102 next;
506             }
507              
508 788 50 66     4614 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       1709 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       1631 if ( $in_context eq "grid" ) {
522 39         141 $self->add( type => "gridline", $self->decompose_grid($_) );
523 39         118 next;
524             }
525              
526 749 100 33     2422 if ( /\S/ ) {
    50          
527 452 50       1115 if ( $prep->{songline} ) {
528             # warn("PRE: ", $_, "\n");
529 0         0 $prep->{songline}->($_);
530             # warn("POST: ", $_, "\n");
531             }
532 452         1292 $self->add( type => "songline", $self->decompose($_) );
533             }
534             elsif ( exists $self->{title} || $fragment ) {
535 297         785 $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       638 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       650 warn("Processed song...\n") if $options->{verbose};
549 169         459 $diag->{format} = "\"%f\": %m";
550              
551 169 50       639 $self->dump(0) if $config->{debug}->{song} > 1;
552              
553 169 100       532 if ( @labels ) {
554 1         4 $self->{labels} = [ @labels ];
555             }
556              
557             # Suppress chords that the user considers 'easy'.
558 169         367 my %suppress;
559 169         486 my $xc = $config->{settings}->{transcode};
560 169         345 for ( @{ $config->{diagrams}->{suppress} } ) {
  169         745  
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         360 while ( my ($k,$v) = each %{ $self->{chordsinfo} } ) {
  599         2280  
572 430 100 100     2108 $suppress{$k} = 1 if !is_true($v->{diagram}//1);
573             }
574 169 100       711 @used_chords = map { $suppress{$_} ? () : $_ } @used_chords;
  902         2184  
575              
576 169         379 my $diagrams;
577 169 100       669 if ( exists($self->{settings}->{diagrams} ) ) {
578 5         14 $diagrams = $self->{settings}->{diagrams};
579 5   100     22 $diagrams &&= $config->{diagrams}->{show} || "all";
      66        
580             }
581             else {
582 164         435 $diagrams = $config->{diagrams}->{show};
583             }
584              
585 169 50 66     1674 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       1414 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         336 my %h;
605 900 100       2530 @used_chords = map { $h{$_}++ ? () : $_ }
606 169         478 map { demarkup($_) } @used_chords;
  900         1914  
607             }
608              
609 169 50       837 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     2751 $self->{meta}->{chords} //= [ @used_chords ];
616 169         355 $self->{meta}->{numchords} = [ scalar(@{$self->{meta}->{chords}}) ];
  169         658  
617              
618 169 100       1014 if ( $diagrams =~ /^(user|all)$/ ) {
619             $self->{chords} =
620 114         707 { type => "diagrams",
621             origin => "song",
622             show => $diagrams,
623             chords => [ @used_chords ],
624             };
625              
626 114 50       421 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       577 $self->dump(0) if $config->{debug}->{song};
642 169 50       506 $self->dump(1) if $config->{debug}->{songfull};
643              
644 169         821 return $self;
645             }
646              
647             sub add {
648 1220     1220 0 2053 my $self = shift;
649 1220 50       2437 return if $skip_context;
650 1220         5119 push( @{$self->{body}},
651             { context => $in_context,
652 1220 100       1749 $lineinfo ? ( line => $diag->{line} ) : (),
653             @_ } );
654 1220 100       13578 if ( $in_context eq "chorus" ) {
655 106         360 push( @chorus, { context => $in_context, @_ } );
656 106         201 $chorus_xpose = $xpose;
657 106         292 $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 2272 my ( $self, $orig ) = @_;
666 937 50       2457 Carp::confess unless length($orig);
667              
668             # Intercept annotations.
669 937 100 66     5369 if ( $orig =~ /^\*(.+)/ || $orig =~ /^(\||\s+)$/ ) {
670 3         46 my $i = ChordPro::Chord::Annotation->new
671             ( { name => $orig, text => $1 } );
672             return
673 3         23 ChordPro::Chords::Appearance->new
674             ( key => $self->add_chord($i), info => $i, orig => $orig );
675             }
676              
677             # Check for markup.
678 934         1865 my $markup = $orig;
679 934         2995 my $c = demarkup($orig);
680 934 100       2481 if ( $markup eq $c ) { # no markup
681 927         1561 undef $markup;
682             }
683              
684             # Special treatment for parenthesized chords.
685 934         1774 $c =~ s/^\((.*)\)$/$1/;
686 934 50       2105 do_warn("Double parens in chord: \"$orig\"")
687             if $c =~ s/^\((.*)\)$/$1/;
688              
689             # We have a 'bare' chord now. Parse it.
690 934         2423 my $info = $self->parse_chord($c);
691 934 50       2687 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         6681 my $ap = ChordPro::Chords::Appearance->new( orig => $orig );
702              
703             # Handle markup, if any.
704 934 100       15102 if ( $markup ) {
    50          
705 7 100 100     165 if ( $markup =~ s/\>\Q$c\E\%{formatted}
706             ||
707             $markup =~ s/\>\(\Q$c\E\)\(%{formatted})
708             }
709             else {
710 1         19 do_warn("Invalid markup in chord: \"$markup\"\n");
711             }
712 7         32 $ap->format = $markup;
713             }
714             elsif ( (my $m = $orig) =~ s/\Q$c\E/%{formatted}/ ) {
715 927 100       2869 $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         3011 $ap->key = $self->add_chord( $info, $c = $info->name );
721 934         2597 $ap->info = $info;
722              
723 934 100 100     2448 unless ( $info->is_nc || $info->is_note ) {
724             # if ( $info->is_keyboard ) {
725 922 50 0     3520 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       1859 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         389 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         2714 return $ap;
746             }
747              
748             sub decompose {
749 576     576 0 1442 my ($self, $orig) = @_;
750 576         1917 my $line = fmt_subst( $self, $orig );
751 576 100       48220 undef $orig if $orig eq $line;
752 576         3241 $line =~ s/\s+$//;
753 576         5937 my @a = split( $re_chords, $line, -1);
754              
755 576 100       1865 if ( @a <= 1 ) {
756 233 50       1333 return ( phrases => [ $line ],
757             $orig ? ( orig => $orig ) : (),
758             );
759             }
760              
761 343         623 my $dummy;
762 343 100       909 shift(@a) if $a[0] eq "";
763 343 100       2273 unshift(@a, '[]'), $dummy++ if $a[0] !~ $re_chords;
764              
765 343         796 my @phrases;
766             my @chords;
767 343         884 while ( @a ) {
768 1035         2034 my $chord = shift(@a);
769 1035         1974 push(@phrases, shift(@a));
770              
771             # Normal chords.
772 1035 100 100     8735 if ( $chord =~ s/^\[(.*)\]$/$1/ && $chord ne "^" ) {
    100 66        
773 1008 100       3688 push(@chords, $chord eq "" ? "" : $self->chord($chord));
774 1008 100 100     2599 if ( $memchords && !$dummy ) {
775 21 100       54 if ( $memcrdinx == 0 ) {
776 3         6 $memorizing++;
777             }
778 21 100       45 if ( $memorizing ) {
779 20         40 push( @$memchords, $chords[-1] );
780             warn("Chord memorized for $in_context\[$memcrdinx]: ",
781             $chords[-1], "\n")
782 20 50       50 if $config->{debug}->{chords};
783             }
784 21         30 $memcrdinx++;
785             }
786             }
787              
788             # Recall memorized chords.
789             elsif ( $memchords && $in_context ) {
790 20 100 100     85 if ( $memcrdinx == 0 && @$memchords == 0 ) {
    50          
791 1         15 do_warn("No chords memorized for $in_context");
792 1         11 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         73 push( @chords, $self->chord($memchords->[$memcrdinx]->chord_display));
801             warn("Chord recall $in_context\[$memcrdinx]: ", $chords[-1], "\n")
802 19 50       67 if $config->{debug}->{chords};
803             }
804 20         37 $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         3296 $dummy = 0;
813             }
814              
815 343 100       1968 return ( phrases => \@phrases,
816             chords => \@chords,
817             $orig ? ( orig => $orig ) : (),
818             );
819             }
820              
821             sub cdecompose {
822 124     124 0 362 my ( $self, $line ) = @_;
823 124 50       604 $line = fmt_subst( $self, $line ) unless $no_substitute;
824 124         10166 my %res = $self->decompose($line);
825 124 100       730 return ( text => $line ) unless $res{chords};
826 14         70 return %res;
827             }
828              
829             sub decompose_grid {
830 39     39 0 102 my ($self, $line) = @_;
831 39         114 $line =~ s/^\s+//;
832 39         185 $line =~ s/\s+$//;
833 39 50       104 return ( tokens => [] ) if $line eq "";
834              
835 39         83 my $orig;
836             my %res;
837 39 50       141 if ( $line !~ /\|/ ) {
838 0         0 $res{margin} = { $self->cdecompose($line), orig => $line };
839 0         0 $line = "";
840             }
841             else {
842 39 50       294 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       159 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         66 my @tokens;
857 39         232 my @t = split( ' ', $line );
858              
859             # Unfortunately, gets split too.
860 39         100 while ( @t ) {
861 663         905 $_ = shift(@t);
862 663         1158 push( @tokens, $_ );
863 663 50       1340 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         92 my $nbt = 0; # non-bar tokens
873 39         100 foreach ( @tokens ) {
874 663 50 33     4894 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         500 $_ = { symbol => $_, class => "bar" };
889             }
890             elsif ( $_ eq "||" ) {
891 2         9 $_ = { 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         1031 $_ = { symbol => $_, class => "space" };
907 395         621 $nbt++;
908             }
909             else {
910             # Multiple chords in a cell?
911 117         343 my @a = split( /~/, $_, -1 );
912 117 100       248 if ( @a == 1) {
913             # Normal case, single chord.
914 116         296 $_ = { chord => $self->chord($_), class => "chord" };
915             }
916             else {
917             # Multiple chords.
918             $_ = { chords =>
919 1 50 33     5 [ map { ( $_ eq '.' || $_ eq '' )
  2 50       14  
920             ? ''
921             : $_ eq "/"
922             ? "/"
923             : $self->chord($_) } @a ],
924             class => "chords" };
925             }
926 117         284 $nbt++;
927             }
928             }
929 39 50       143 if ( $nbt > $grid_cells->[0] ) {
930 0         0 do_warn( "Too few cells for grid content" );
931             }
932 39         2102 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 1839 my ( $self, $d ) = @_;
1022              
1023             # Pattern for all recognized directives.
1024 999 100       2319 unless ( $dirpat ) {
1025             $dirpat =
1026             '(?:' .
1027             join( '|', @directives,
1028 57         198 @{$config->{metadata}->{keys}},
  57         1437  
1029             keys(%abbrevs),
1030             '(?:start|end)_of_\w+' ) .
1031             ')';
1032 57         18950 $dirpat = qr/$dirpat/;
1033             }
1034              
1035             # $d is the complete directive line, without leading/trailing { }.
1036 999         3050 $d =~ s/^[: ]+//;
1037 999         3071 $d =~ s/\s+$//;
1038 999         2658 my $dir = lc($d);
1039 999         7135 my $arg = "";
1040 999 100       3970 if ( $d =~ /^(.*?)[: ]\s*(.*)/ ) {
1041 787         2920 ( $dir, $arg ) = ( lc($1), $2 );
1042             }
1043 999         2209 $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       23819 if ( $dir =~ /^($dirpat)-(.+)$/ ) {
1049 11   66     67 $dir = $abbrevs{$1} // $1;
1050 11         25 my $sel = $2;
1051 11         45 my $negate = $sel =~ s/\!$//;
1052             $sel = ( $sel eq lc($config->{instrument}->{type}) )
1053             ||
1054             ( $sel eq lc($config->{user}->{name})
1055             ||
1056 11   66     76 ( $self->{meta}->{lc $sel} && is_true($self->{meta}->{lc $sel}->[0]) )
1057             );
1058 11 100       31 $sel = !$sel if $negate;
1059 11 100       31 unless ( $sel ) {
1060 4 100       16 if ( $dir =~ /^start_of_/ ) {
1061 2         14 return { name => $dir, arg => $arg, omit => 2 };
1062             }
1063             else {
1064 2         11 return { name => $dir, arg => $arg, omit => 1 };
1065             }
1066             }
1067             }
1068             else {
1069 988   66     4657 $dir = $abbrevs{$dir} // $dir;
1070             }
1071              
1072 995         5149 return { name => $dir, arg => $arg, omit => 0 }
1073             }
1074              
1075             sub directive {
1076 997     997 0 2258 my ( $self, $d ) = @_;
1077              
1078 997         2397 my $dd = $self->parse_directive($d);
1079 997 100       2751 return 1 if $dd->{omit} == 1;
1080              
1081 995         1872 my $arg = $dd->{arg};
1082 995 100       2155 if ( $arg ne "" ) {
1083 784         2860 $arg = fmt_subst( $self, $arg );
1084 784 50       94044 return 1 if $arg !~ /\S/;
1085             }
1086 995         2508 my $dir = $dd->{name};
1087              
1088             # Context flags.
1089              
1090 995 100       2737 if ( $dir =~ /^start_of_(\w+)$/ ) {
1091 76 50       246 do_warn("Already in " . ucfirst($in_context) . " context\n")
1092             if $in_context;
1093 76         219 $in_context = $1;
1094 76 100       235 if ( $dd->{omit} ) {
1095 2         6 $skip_context = 1;
1096             # warn("Skipping context: $in_context\n");
1097 2         8 return 1;
1098             }
1099 74 100       279 @chorus = (), $chorus_xpose = $chorus_xpose_dir = 0
1100             if $in_context eq "chorus";
1101 74 100 66     373 if ( $in_context eq "grid" ) {
    100          
1102 25 100       178 if ( $arg eq "" ) {
    50          
    0          
1103 3         13 $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       80 do_warn("Invalid grid params: $arg (must be non-zero)"), return
1113             unless $2;
1114 22   50     247 $grid_arg = [ $2, $3//1, $1//0, $4//0 ];
      100        
      100        
1115 22   50     204 $self->add( type => "set",
1116             name => "gridparams",
1117             value => [ @$grid_arg, $5||"" ] );
1118 22 50 50     155 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         126 $grid_cells = [ $grid_arg->[0] * $grid_arg->[1],
1127             $grid_arg->[2], $grid_arg->[3] ];
1128             }
1129             elsif ( $arg && $arg ne "" ) {
1130 2         12 $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       127 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     31 $memchords = $memchords{$in_context} //= [];
1145 7         12 $memcrdinx = 0;
1146 7         13 $memorizing = 0;
1147             }
1148 74         762 return 1;
1149             }
1150 919 100       2631 if ( $dir =~ /^end_of_(\w+)$/ ) {
1151 74 50       368 do_warn("Not in " . ucfirst($1) . " context\n")
1152             unless $in_context eq $1;
1153 74         288 $self->add( type => "set",
1154             name => "context",
1155             value => $def_context );
1156 74         158 $in_context = $def_context;
1157 74         176 undef $memchords;
1158 74         317 return 1;
1159             }
1160 845 100       2503 if ( $dir =~ /^chorus$/i ) {
1161 30 50       88 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       4765 my $chorus = @chorus ? dclone(\@chorus) : [];
1168              
1169 30 50 66     30229 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       131 if ( $chorus_xpose != ( my $xp = $xpose ) ) {
1186 17         40 $xp -= $chorus_xpose;
1187 17         53 for ( @$chorus ) {
1188 32 100       119 if ( $_->{type} eq "songline" ) {
1189 16         27 for ( @{ $_->{chords} } ) {
  16         52  
1190 61 100       181 next if $_ eq '';
1191 46         156 my $info = $self->{chordsinfo}->{$_->key};
1192 46 50       161 next if $info->is_annotation;
1193 46 50       234 $info = $info->transpose($xp, $xpose <=> 0) if $xp;
1194 46         146 $info = $info->new($info);
1195 46         195 $_ = 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       154 $self->add( type => "rechorus",
1206             @$chorus
1207             ? ( "chorus" => $chorus )
1208             : (),
1209             );
1210 30         136 return 1;
1211             }
1212              
1213             # Song settings.
1214              
1215             # Breaks.
1216              
1217 815 100       1928 if ( $dir eq "column_break" ) {
1218 13         74 $self->add( type => "colb" );
1219 13         58 return 1;
1220             }
1221              
1222 802 100 100     3385 if ( $dir eq "new_page" || $dir eq "new_physical_page" ) {
1223 16         120 $self->add( type => "newpage" );
1224 16         70 return 1;
1225             }
1226              
1227 786 50       1768 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       2431 if ( $dir =~ /^comment(_italic|_box)?$/ ) {
1234 124         611 my %res = $self->cdecompose($arg);
1235 124         509 $res{orig} = $dd->{arg};
1236             $self->add( type => $dir, %res )
1237 124 50 66     1286 unless exists($res{text}) && $res{text} =~ /^[ \t]*$/;
1238 124         595 return 1;
1239             }
1240              
1241             # Images.
1242 662 100       1508 if ( $dir eq "image" ) {
1243 3         16 my $res = parse_kv($arg);
1244 3         11 my $uri;
1245             my $id;
1246 3         0 my %opts;
1247 3         18 while ( my($k,$v) = each(%$res) ) {
1248 9 100 66     149 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         11 $opts{lc($k)} = $v;
1253             }
1254             elsif ( $k =~ /^(width|height)$/i && $v =~ /^(\d+(?:\.\d+)?\%?)$/ ) {
1255 2         9 $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         5 $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         10 $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     17 if ( $uri && $uri !~ m;/\\; ) { # basename
1290 79     79   857 use File::Basename qw(dirname);
  79         271  
  79         20664  
1291 2         111 L: for ( dirname($diag->{file}) ) {
1292 2 50       48 $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     15 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   44083 use Image::Info;
  79         147613  
  79         661032  
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       9 $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       24 $self->add( type => $uri =~ /\.svg$/ ? "svg" : "image",
1343             uri => $uri,
1344             opts => \%opts );
1345 3         21 return 1;
1346             }
1347              
1348 659 100       1505 if ( $dir eq "title" ) {
1349 173         550 $self->{title} = $arg;
1350 173         376 push( @{ $self->{meta}->{title} }, $arg );
  173         764  
1351 173         826 return 1;
1352             }
1353              
1354 486 100       1341 if ( $dir eq "subtitle" ) {
1355 28         74 push( @{ $self->{subtitle} }, $arg );
  28         118  
1356 28         69 push( @{ $self->{meta}->{subtitle} }, $arg );
  28         80  
1357 28         114 return 1;
1358             }
1359              
1360             # Metadata extensions (legacy). Should use meta instead.
1361             # Only accept the list from config.
1362 458 100   5453   2396 if ( any { $_ eq $dir } @{ $config->{metadata}->{keys} } ) {
  5453         7568  
  458         2174  
1363 225         708 $arg = "$dir $arg";
1364 225         441 $dir = "meta";
1365             }
1366              
1367             # Metadata.
1368 458 100       2172 if ( $dir eq "meta" ) {
1369 263 50       1425 if ( $arg =~ /([^ :]+)[ :]+(.*)/ ) {
1370 263         885 my $key = lc $1;
1371 263         858 my @vals = ( $2 );
1372 263 100       1111 if ( $config->{metadata}->{autosplit} ) {
1373 256         742 @vals = map { s/s\+$//; $_ }
  256         1016  
1374 256         6020 split( quotemeta($config->{metadata}->{separator}), $vals[0] );
1375             }
1376 263         692 my $m = $self->{meta};
1377              
1378             # User and instrument cannot be set here.
1379 263 50 33     1181 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         642 for my $val ( @vals ) {
1385              
1386 263 100       620 if ( $key eq "key" ) {
1387 92         316 $val =~ s/[\[\]]//g;
1388 92         471 my $info = $self->parse_chord($val);
1389 92         358 my $name = $info->name;
1390 92         231 my $act = $name;
1391              
1392 92 50       261 if ( $capo ) {
1393 0         0 $act = $self->add_chord( $info->transpose($capo) );
1394 0 0       0 $name = $act if $decapo;
1395             }
1396              
1397 92         183 push( @{ $m->{key} }, $name );
  92         337  
1398 92         312 $m->{key_actual} = [ $act ];
1399             # warn("XX key=$name act=$act capo=",
1400             # $capo//""," decapo=$decapo\n");
1401 92         536 return 1;
1402             }
1403              
1404              
1405 171 100 66     740 if ( $key eq "capo" ) {
    100          
1406             do_warn("Multiple capo settings may yield surprising results.")
1407 16 100       62 if exists $m->{capo};
1408              
1409 16   50     61 $capo = $val || undef;
1410 16 50 33     82 if ( $capo && $m->{key} ) {
1411 16 100       53 if ( $decapo ) {
1412             my $key = $self->store_chord
1413 4         33 ($self->{chordsinfo}->{$m->{key}->[-1]}
1414             ->transpose($val));
1415 4         18 $m->{key}->[-1] = $key;
1416             $key = $self->store_chord
1417 4         20 ($self->{chordsinfo}->{$m->{key}->[-1]}
1418             ->transpose($xpose));
1419 4         19 $m->{key_actual} = [ $key ];
1420             }
1421             else {
1422 12         133 my $act = $m->{key_actual}->[-1];
1423 12         39 $m->{key_from} = [ $act ];
1424             my $key = $self->store_chord
1425 12         65 ($self->{chordsinfo}->{$act}->transpose($val));
1426 12         68 $m->{key_actual} = [ $key ];
1427             }
1428             }
1429             }
1430              
1431             elsif ( $key eq "duration" && $val ) {
1432 9         60 $val = duration($val);
1433             }
1434              
1435 171 50 33     604 if ( $config->{metadata}->{strict}
1436 1296     1296   2014 && ! any { $_ eq $key } @{ $config->{metadata}->{keys} } ) {
  171         2246  
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       686 push( @{ $self->{meta}->{$key} }, $val ) if defined $val;
  171         883  
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         721 return 1;
1452             }
1453              
1454             # Song / Global settings.
1455              
1456 195 100 66     840 if ( $dir eq "titles"
1457             && $arg =~ /^(left|right|center|centre)$/i ) {
1458             $self->{settings}->{titles} =
1459 22 100       186 lc($1) eq "centre" ? "center" : lc($1);
1460 22         149 return 1;
1461             }
1462              
1463 173 100 66     662 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     109 unless( ref($config->{settings}->{columns}) eq 'ARRAY'
1468 0         0 && $arg == @{$config->{settings}->{columns}}
1469             ) {
1470 19         69 $self->{settings}->{columns} = $arg;
1471             }
1472 19         91 return 1;
1473             }
1474              
1475 154 100 100     651 if ( $dir eq "pagetype" || $dir eq "pagesize" ) {
1476 2         7 $self->{settings}->{papersize} = $arg;
1477 2         19 return 1;
1478             }
1479              
1480 152 100       416 if ( $dir eq "diagrams" ) { # AKA grid
1481 2 100       11 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         6 $self->{settings}->{diagrams} = 1;
1488             }
1489 2         10 return 1;
1490             }
1491 150 100       385 if ( $dir eq "no_grid" ) {
1492 3         16 $self->{settings}->{diagrams} = 0;
1493 3         12 return 1;
1494             }
1495              
1496 147 100       374 if ( $dir eq "transpose" ) {
1497 53   100     286 $propstack{transpose} //= [];
1498              
1499 53 100       277 if ( $arg =~ /^([-+]?\d+)\s*$/ ) {
1500 32         125 my $new = $1;
1501 32         69 push( @{ $propstack{transpose} }, [ $xpose, $xpose_dir ] );
  32         115  
1502 32         198 my %a = ( type => "control",
1503             name => "transpose",
1504             previous => [ $xpose, $xpose_dir ]
1505             );
1506 32         100 $xpose += $new;
1507 32         84 $xpose_dir = $new <=> 0;
1508 32         88 my $m = $self->{meta};
1509 32 100       116 if ( $m->{key} ) {
1510 22         64 my $key = $m->{key}->[-1];
1511 22         48 my $xp = $xpose;
1512 22 100       65 $xp += $capo if $capo;
1513 22         124 my $xpk = $self->{chordsinfo}->{$key}->transpose($xp);
1514 22         151 $self->{chordsinfo}->{$xpk->name} = $xpk;
1515 22         117 $m->{key_from} = [ $m->{key_actual}->[0] ];
1516 22         74 $m->{key_actual} = [ $xpk->name ];
1517             }
1518 32 50       173 $self->add( %a, value => $xpose, dir => $xpose_dir )
1519             if $no_transpose;
1520             }
1521             else {
1522 21         159 my %a = ( type => "control",
1523             name => "transpose",
1524             previous => [ $xpose, $xpose_dir ]
1525             );
1526 21         63 my $m = $self->{meta};
1527 21         53 my ( $new, $dir );
1528 21 50       43 if ( @{ $propstack{transpose} } ) {
  21         93  
1529 21         48 ( $new, $dir ) = @{ pop( @{ $propstack{transpose} } ) };
  21         43  
  21         68  
1530             }
1531             else {
1532 0         0 $new = 0;
1533 0         0 $dir = $config->{settings}->{transpose} <=> 0;
1534             }
1535 21         53 $xpose = $new;
1536 21         37 $xpose_dir = $dir;
1537 21 100       71 if ( $m->{key} ) {
1538 15         49 $m->{key_from} = [ $m->{key_actual}->[0] ];
1539 15         49 my $xp = $xpose;
1540 15 50 66     62 $xp += $capo if $capo && $decapo;
1541             $m->{key_actual} =
1542 15         190 [ $self->{chordsinfo}->{$m->{key}->[-1]}->transpose($xp)->name ];
1543             }
1544 21 100       166 if ( !@{ $propstack{transpose} } ) {
  21         112  
1545 12         88 delete $m->{$_} for qw( key_from );
1546             }
1547 21 50       92 $self->add( %a, value => $xpose, dir => $dir )
1548             if $no_transpose;
1549             }
1550 53         256 return 1;
1551             }
1552              
1553             # More private hacks.
1554 94 50 33     630 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     459 if ( !$options->{reference} && $dir =~ /^\+([-\w.]+(?:\.[<>])?)$/ ) {
1566 11         78 $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         26 my $ccfg = {};
1574 11         87 my @k = split( /[:.]/, $1 );
1575 11         24 my $c = \$ccfg; # new
1576 11         24 my $o = $config; # current
1577 11         36 my $lk = pop(@k); # last key
1578              
1579             # Step through the keys.
1580 11         36 foreach ( @k ) {
1581 17         51 $c = \($$c->{$_});
1582 17         46 $o = $o->{$_};
1583             }
1584              
1585             # Turn hash.array into hash.array.> (append).
1586 11 50 33     106 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     129 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         41 $$c->{$lk} = $arg;
1611             }
1612              
1613 11         74 $config->augment($ccfg);
1614 11         47 upd_config();
1615              
1616 11         97 return 1;
1617             }
1618              
1619             # Formatting.
1620 83 100       261 if ( $dir =~ /^(text|chord|tab|grid|diagrams|title|footer|toc)(font|size|colou?r)$/ ) {
1621 24         63 my $item = $1;
1622 24         40 my $prop = $2;
1623 24         41 my $value = $arg;
1624              
1625 24 100       47 $prop = "color" if $prop eq "colour";
1626 24         52 my $name = "$item-$prop";
1627 24   50     118 $propstack{$name} //= [];
1628              
1629 24 50       54 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       43 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         12 my $v;
1656 8 50       22 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         18 $value = $v;
1661             }
1662 24 100       57 $value = $prop eq 'font' ? $value : lc($value);
1663 24         77 $self->add( type => "control",
1664             name => $name,
1665             value => $value );
1666 24         32 push( @{ $propstack{$name} }, $value );
  24         62  
1667              
1668             # A trailing number after a font directive is an implisit size
1669             # directive.
1670 24 50 66     73 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         95 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     187 if ( $dir eq "define" or $dir eq "chord" ) {
1687              
1688 58         221 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     5 if $config->{settings}->{strict} && $d !~ /^x_/;
1694 1         19 return;
1695             }
1696              
1697             sub add_chord {
1698 991     991 0 2391 my ( $self, $info, $new_id ) = @_;
1699              
1700 991 100       1917 if ( $new_id ) {
1701 942 100       2209 if ( $new_id eq "1" ) {
1702 10         35 state $id = "ch0000";
1703 10         24 $new_id = " $id";
1704 10         23 $id++;
1705             }
1706             }
1707             else {
1708 49         140 $new_id = $info->name;
1709             }
1710 991         2584 $self->{chordsinfo}->{$new_id} = $info->new($info);
1711              
1712 991         4067 return $new_id;
1713             }
1714              
1715             sub define_chord {
1716 70     70 0 193 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         227 $args =~ s/^\s+//;
1722 70         301 $args =~ s/\s+$//;
1723 70         285 my @a = quotewords( '[: ]+', 0, $args );
1724 70 100       21058 @a = split( /[: ]+/, $args ) unless @a;
1725              
1726 70         272 my @orig = @a;
1727 70         153 my $show = $dir eq "chord";
1728 70         107 my $fail = 0;
1729 70         215 my $name = shift(@a);
1730 70         283 my $strings = $config->diagram_strings;
1731              
1732             # Process the options.
1733 70         245 my %kv = ( name => $name );
1734 70         197 while ( @a ) {
1735 162         274 my $a = shift(@a);
1736              
1737             # Copy existing definition.
1738 162 100 66     1139 if ( $a eq "copy" || $a eq "copyall" ) {
    100 66        
    100 66        
    100 33        
    100          
    100          
    100          
    50          
1739 11 50       53 if ( my $i = ChordPro::Chords::known_chord($a[0]) ) {
1740 11         37 $kv{$a} = $a[0];
1741 11         31 $kv{orig} = $i;
1742 11         32 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         15 $kv{display} = demarkup($a[0]);
1754             do_warn( "\"display\" should not contain markup, use \"format\"" )
1755 2 50       9 unless $kv{display} eq shift(@a);
1756 2         12 $kv{display} = $self->parse_chord($kv{display},1);
1757 2 50       13 delete $kv{display} unless defined $kv{display};
1758             }
1759              
1760             # format
1761             elsif ( $a eq "format" && @a ) {
1762 9         36 $kv{format} = shift(@a);
1763             }
1764              
1765             # base-fret N
1766             elsif ( $a eq "base-fret" ) {
1767 46 50       230 if ( $a[0] =~ /^\d+$/ ) {
1768 46         169 $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         91 my @f;
1779 57   100     413 while ( @a && $a[0] =~ /^(?:[0-9]+|[-xXN])$/ && @f < $strings ) {
      66        
1780 342         1563 push( @f, shift(@a) );
1781             }
1782 57 50       180 if ( @f == $strings ) {
1783 57 100       141 $kv{frets} = [ map { $_ =~ /^\d+/ ? $_ : -1 } @f ];
  342         1078  
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         33 my @f;
1796             # It is tempting to limit the fingers to 1..5 ...
1797 16   100     68 while ( @a && @f < $strings ) {
1798 96         153 local $_ = shift(@a);
1799 96 100       285 if ( /^[0-9]+$/ ) {
    50          
    50          
1800 90         323 push( @f, 0 + $_ );
1801             }
1802             elsif ( /^[A-MO-WYZ]$/ ) {
1803 0         0 push( @f, $_ );
1804             }
1805             elsif ( /^[-xNX]$/ ) {
1806 6         20 push( @f, -1 );
1807             }
1808             else {
1809 0         0 unshift( @a, $_ );
1810 0         0 last;
1811             }
1812             }
1813 16 50       51 if ( @f == $strings ) {
1814 16         60 $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         12 my @f;
1827 8   100     40 while ( @a && $a[0] =~ /^[0-9]+$/ ) {
1828 24         96 push( @f, shift(@a) );
1829             }
1830 8 50       20 if ( @f ) {
1831 8         22 $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     42 if ( $show && !is_true($a[0]) ) {
1842 0         0 do_warn("Useless diagram suppression");
1843 0         0 next;
1844             }
1845 13         43 $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       176 return 1 if $fail;
1860             # All options are verified and stored in %kv;
1861              
1862             # Result structure.
1863 70         187 my $res = { name => $name };
1864              
1865             # Try to find info.
1866 70         219 my $info = $self->parse_chord( $name, "def" );
1867 70 50       193 if ( $info ) {
1868             # Copy the chord info.
1869             $res->{$_} //= $info->{$_} // ''
1870 70   100     2129 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       204 if ( $show ) {
1875             $res->{$_} //= $info->{$_}
1876 8   66     135 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     301 for ( $kv{copyall} // $kv{copy} ) {
1885 70 100       178 next unless defined;
1886 11         29 $res->{copy} = $_;
1887 11         28 my $orig = $res->{orig} = $kv{orig};
1888             $res->{$_} //= $orig->{$_}
1889 11   33     133 for qw( base frets fingers keys );
1890 11 50       44 if ( $kv{copyall} ) {
1891             $res->{$_} //= $orig->{$_}
1892 0   0     0 for qw( display format );
1893             }
1894             }
1895 70         138 for ( qw( display format ) ) {
1896 140 100       349 $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         32 for ( my $v = $kv{diagram} ) {
1902 13 100       47 if ( is_true($v) ) {
1903 7 100       34 if ( is_ttrue($v) ) {
1904 6         19 next;
1905             }
1906             }
1907             else {
1908 6         15 $v = 0;
1909             }
1910 7         22 $res->{diagram} = $v;
1911             }
1912             }
1913              
1914             # Copy rest of options.
1915 70         138 for ( qw( base frets fingers keys display format ) ) {
1916 420 100       817 next unless defined $kv{$_};
1917 138         352 $res->{$_} = $kv{$_};
1918             }
1919              
1920             # At this time, $res is still just a hash. Time to make a chord.
1921 70   100     217 $res->{base} ||= 1;
1922 70 100       988 $res = ChordPro::Chord::Common->new
1923             ( { %$res, origin => $show ? "inline" : "song" } );
1924 70   33     557 $res->{parser} //= ChordPro::Chords::get_parser();
1925              
1926 70 100       222 if ( $show) {
1927 8         49 my $ci = $res->clone;
1928 8         6262 my $chidx = $self->add_chord( $ci, 1 );
1929             # Combine consecutive entries.
1930 8 100 66     73 if ( defined($self->{body})
1931             && $self->{body}->[-1]->{type} eq "diagrams" ) {
1932 2         4 push( @{ $self->{body}->[-1]->{chords} }, $chidx );
  2         19  
1933             }
1934             else {
1935 6         35 $self->add( type => "diagrams",
1936             show => "user",
1937             origin => "chord",
1938             chords => [ $chidx ] );
1939             }
1940 8         101 return 1;
1941             }
1942              
1943 62         131 my $def = {};
1944 62         152 for ( qw( name base frets fingers keys display format diagram ) ) {
1945 496 100       960 next unless defined $res->{$_};
1946 246         496 $def->{$_} = $res->{$_};
1947             }
1948 62         124 push( @{$self->{define}}, $def );
  62         185  
1949 62         253 my $ret = ChordPro::Chords::add_song_chord($res);
1950 62 50       165 if ( $ret ) {
1951 0         0 do_warn("Invalid chord: ", $res->{name}, ": ", $ret, "\n");
1952 0         0 return 1;
1953             }
1954 62         186 $info = ChordPro::Chords::known_chord($res->{name});
1955 62 50       167 croak("We just entered it?? ", $res->{name}) unless $info;
1956              
1957 62 50       194 $info->dump if $config->{debug}->{x1};
1958              
1959 62         549 return 1;
1960             }
1961              
1962             sub duration {
1963 9     9 0 33 my ( $dur ) = @_;
1964              
1965 9 50       87 if ( $dur =~ /(?:(?:(\d+):)?(\d+):)?(\d+)/ ) {
1966 9 50       117 $dur = $3 + ( $2 ? 60 * $2 :0 ) + ( $1 ? 3600 * $1 : 0 );
    50          
1967             }
1968 9         141 my $res = sprintf( "%d:%02d:%02d",
1969             int( $dur / 3600 ),
1970             int( ( $dur % 3600 ) / 60 ),
1971             $dur % 60 );
1972 9         42 $res =~ s/^[0:]+//;
1973 9         29 return $res;
1974             }
1975              
1976             sub get_color {
1977 8     8 0 24 $_[0];
1978             }
1979              
1980             sub _diag {
1981 23     23   16401 my ( $self, %d ) = @_;
1982 23         122 $diag->{$_} = $d{$_} for keys(%d);
1983             }
1984              
1985             sub msg {
1986 3     3 0 13 my $m = join("", @_);
1987 3         16 $m =~ s/\n+$//;
1988 3         9 my $t = $diag->{format};
1989 3         11 $t =~ s/\\n/\n/g;
1990 3         9 $t =~ s/\\t/\t/g;
1991 3         28 $t =~ s/\%f/$diag->{file}/g;
1992 3         24 $t =~ s/\%n/$diag->{line}/g;
1993 3         17 $t =~ s/\%l/$diag->{orig}/g;
1994 3         14 $t =~ s/\%m/$m/g;
1995 3         73 $t;
1996             }
1997              
1998             sub do_warn {
1999 3     3 0 22 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 3539 my ( $self, $chord, $def ) = @_;
2008              
2009 1112         2518 my $debug = $config->{debug}->{chords};
2010              
2011 1112 50       2389 warn("Parsing chord: \"$chord\"\n") if $debug;
2012 1112         1656 my $info;
2013 1112         2204 my $xp = $xpose + $config->{settings}->{transpose};
2014 1112 100 100     2701 $xp += $capo if $capo && $decapo;
2015 1112         2131 my $xc = $config->{settings}->{transcode};
2016 1112         2119 my $global_dir = $config->{settings}->{transpose} <=> 0;
2017 1112         1508 my $unk;
2018              
2019             # When called from {define} ignore xc/xp.
2020 1112 100       2152 $xc = $xp = '' if $def;
2021              
2022 1112         3180 $info = ChordPro::Chords::known_chord($chord);
2023 1112 100       2624 if ( $info ) {
2024             warn( "Parsing chord: \"$chord\" found \"",
2025 880 50       1979 $info->name, "\" in ", $info->{_via}, "\n" ) if $debug > 1;
2026 880 50       1693 $info->dump if $debug > 1;
2027             }
2028             else {
2029 232         654 $info = ChordPro::Chords::parse_chord($chord);
2030             warn( "Parsing chord: \"$chord\" parsed ok [",
2031             $info->{system},
2032 232 50 66     1090 "]\n" ) if $info && $debug > 1;
2033             }
2034 1112         1996 $unk = !defined $info;
2035              
2036 1112 100 100     6092 if ( ( $def || $xp || $xc )
      66        
      100        
2037             &&
2038             ! ($info && $info->is_xpxc ) ) {
2039 21         73 local $::config->{settings}->{chordnames} = "relaxed";
2040 21         56 $info = ChordPro::Chords::parse_chord($chord);
2041             }
2042              
2043 1112 0 33     4240 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     3208 if ( $xp && $info ) {
2054             # For transpose/transcode, chord must be wellformed.
2055 156   100     743 $info = $info->transpose( $xp,
2056             $xpose_dir // $global_dir);
2057 156 50       482 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       2293 if ( $info ) { # TODO roman?
2064             # Look it up now, the name may change by transcode.
2065 1112 100 33     2630 if ( my $i = ChordPro::Chords::known_chord($info) ) {
    50          
2066             warn( "Parsing chord: \"$chord\" found ",
2067             $i->name, " for ", $info->name,
2068 885 50       2072 " in ", $i->{_via}, "\n" ) if $debug > 1;
2069 885         5376 $info = $i->new({ %$i, name => $info->name }) ;
2070 885         4505 $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       546 warn( "Parsing chord: \"$chord\" \"", $info->name,
2081             "\" not found in song/config chords\n" ) if $debug;
2082             # warn("XX \'", $info->agnostic, "\'\n");
2083 227         436 $unk = 1;
2084             }
2085             }
2086              
2087 1112 100 66     2949 if ( $xc && $info ) {
2088 20         28 my $key_ord;
2089             $key_ord = $self->{chordsinfo}->{$self->{meta}->{key}->[-1]}->{root_ord}
2090 20 100       108 if $self->{meta}->{key};
2091 20 50 33     65 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         154 $info = $info->transcode( $xc, $key_ord );
2096             warn( "Parsing chord: \"$chord\" transcoded to ",
2097             $info->name,
2098 20 50       60 " (", $info->{system}, ")",
2099             "\n" ) if $debug > 1;
2100 20 100       63 if ( my $i = ChordPro::Chords::known_chord($info) ) {
2101 8 50       20 warn( "Parsing chord: \"$chord\" found \"",
2102             $info->name, "\" in song/config chords\n" ) if $debug > 1;
2103 8         16 $unk = 0;
2104             }
2105             }
2106             # else: warning has been given.
2107              
2108 1112 50       2434 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     2693 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       2238 if ( $info ) {
2127 1112 0       2315 warn( "Parsing chord: \"$chord\" okay: \"",
    50          
2128             $info->name, "\" \"",
2129             $info->chord_display, "\"",
2130             $unk ? " but unknown" : "",
2131             "\n" ) if $debug > 1;
2132 1112         3281 $self->store_chord($info);
2133 1112         2782 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 2230 my ( $self, $info ) = @_;
2142 1132         3026 $self->{chordsinfo}->{$info->name} = $info;
2143 1132         3033 $info->name;
2144             }
2145              
2146             sub structurize {
2147 13     13 0 44 my ( $self ) = @_;
2148              
2149 13 50       51 return if $self->{structure} eq "structured";
2150              
2151 13         28 my @body;
2152 13         29 my $context = $def_context;
2153              
2154 13         27 foreach my $item ( @{ $self->{body} } ) {
  13         52  
2155 251 100 66     565 if ( $item->{type} eq "empty" && $item->{context} eq $def_context ) {
2156 56         82 $context = $def_context;
2157 56         92 next;
2158             }
2159 195 100 100     481 if ( $item->{type} eq "songline" && $item->{context} eq '' ){ # A songline should have a context - non means verse
2160 36         52 $item->{context} = 'verse';
2161             }
2162 195 100       351 if ( $context ne $item->{context} ) {
2163 49         183 push( @body, { type => $context = $item->{context}, body => [] } );
2164             }
2165 195 100       369 if ( $context ) {
2166 135         169 push( @{ $body[-1]->{body} }, $item );
  135         258  
2167             }
2168             else {
2169 60         105 push( @body, $item );
2170             }
2171             }
2172 13         99 $self->{body} = [ @body ];
2173 13         49 $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;