File Coverage

lib/ChordPro/Config.pm
Criterion Covered Total %
statement 482 718 67.1
branch 161 318 50.6
condition 81 210 38.5
subroutine 49 56 87.5
pod 0 28 0.0
total 773 1330 58.1


line stmt bran cond sub pod time code
1             #! perl
2              
3             package main;
4              
5             our $options;
6             our $config;
7              
8             package ChordPro::Config;
9              
10 90     90   1469 use v5.26;
  90         393  
11 90     90   562 use utf8;
  90         206  
  90         667  
12 90     90   2952 use Carp;
  90         216  
  90         7966  
13 90     90   668 use feature qw( signatures state );
  90         199  
  90         18255  
14 90     90   676 no warnings "experimental::signatures";
  90         183  
  90         4949  
15              
16 90     90   63970 use ChordPro;
  90         397  
  90         26505  
17 90     90   849 use ChordPro::Files;
  90         206  
  90         17407  
18 90     90   682 use ChordPro::Paths;
  90         195  
  90         5333  
19 90     90   676 use ChordPro::Utils;
  90         227  
  90         15502  
20 90     90   577 use ChordPro::Utils qw( enumerated );
  90         217  
  90         5238  
21 90     90   586 use Scalar::Util qw(reftype);
  90         168  
  90         4874  
22 90     90   524 use List::Util qw(any);
  90         179  
  90         5564  
23 90     90   537 use Storable 'dclone';
  90         208  
  90         4535  
24 90     90   67387 use Hash::Util;
  90         428699  
  90         1137  
25 90     90   18202 use Ref::Util qw( is_arrayref is_hashref );
  90         222  
  90         15598  
26              
27             #sub hmerge($$;$);
28             #sub clone($);
29             #sub default_config();
30              
31 342     342 0 321060 sub new ( $pkg, $cf = {} ) {
  342         965  
  342         837  
  342         688  
32 342         24240 bless $cf => $pkg;
33             }
34              
35             sub pristine_config {
36 90     90   53974 use ChordPro::Config::Data;
  90         410  
  90         812941  
37 209     209 0 293310 __PACKAGE__->new(ChordPro::Config::Data::config());
38             }
39              
40 208     208 0 592 sub configurator ( $opts = undef ) {
  208         1783  
  208         474  
41              
42             # Test programs call configurator without options.
43             # Prepare a minimal config.
44 208 100       1043 unless ( $opts ) {
45 89         470 my $cfg = pristine_config();
46 89         250 $config = $cfg;
47 89         554 $cfg->split_fc_aliases;
48 89         431 $options = { verbose => 0 };
49 89         576 process_config( $cfg, "" );
50 89         630 $cfg->{settings}->{lineinfo} = 0;
51 89         735 return $cfg;
52             }
53 119 100       896 if ( keys(%$opts) ) {
54 9   50     21 $options = { %{$options//{}}, %$opts };
  9         81  
55             }
56              
57 119         311 my @cfg;
58 119   100     807 my $verbose = $options->{verbose} //= 0;
59              
60             # Load defaults.
61 119 50       530 warn("Reading: \n") if $verbose > 1;
62 119         573 my $cfg = pristine_config();
63              
64             # Default first.
65 119         674 @cfg = prep_configs( $cfg, "" );
66             # Bubble default config to be the first.
67 119 50       730 unshift( @cfg, pop(@cfg) ) if @cfg > 1;
68              
69             # Collect other config files.
70             my $add_config = sub {
71 11     11   34 my $fn = shift;
72 11         59 $cfg = get_config( $fn );
73 11         175 push( @cfg, $cfg->prep_configs($fn) );
74 119         1241 };
75              
76 119         429 foreach my $c ( qw( sysconfig userconfig config ) ) {
77 357 100       1737 next if $options->{"no$c"};
78 9 100       57 if ( ref($options->{$c}) eq 'ARRAY' ) {
79 2         5 $add_config->($_) foreach @{ $options->{$c} };
  2         14  
80             }
81             else {
82 7 50       29 warn("Adding config for $c\n") if $verbose;
83 7         34 $add_config->( $options->{$c} );
84             }
85             }
86              
87             # Now we have a list of all config files. Weed out dups.
88 119         770 for ( my $a = 0; $a < @cfg; $a++ ) {
89 249 50 66     2441 if ( $a && $cfg[$a]->{_src} eq $cfg[$a-1]->{_src} ) {
90 0 0       0 if ( $a == $#cfg ) {
91             # If this is the last entry, splice/redo will create
92             # a new, empty entry triggering issue #550.
93 0         0 pop(@cfg);
94 0         0 last;
95             }
96 0         0 splice( @cfg, $a, 1 );
97 0         0 redo;
98             }
99 249 50       938 warn("Config[$a]: ", $cfg[$a]->{_src}, "\n" )
100             if $verbose;
101             }
102              
103 119         334 $cfg = shift(@cfg);
104 119 50       526 warn("Process: $cfg->{_src}\n") if $verbose > 1;
105              
106             # Presets.
107 119 50       578 if ( $options->{reference} ) {
108 0         0 $cfg->{user}->{name} = "chordpro";
109 0         0 $cfg->{user}->{fullname} = ::runtimeinfo("short");
110             }
111             else {
112             $cfg->{user}->{name} =
113             lc( $ENV{USER} || $ENV{LOGNAME}
114 119   50     28263 || getlogin() || getpwuid($<) || "chordpro" );
115 119   50     599 $cfg->{user}->{fullname} = eval { (getpwuid($<))[6] } || "";
116             }
117              
118             # Add some extra entries to prevent warnings.
119 119         563 for ( qw(title subtitle footer) ) {
120 357 100       1682 next if exists($cfg->{pdf}->{formats}->{first}->{$_});
121 238         994 $cfg->{pdf}->{formats}->{first}->{$_} = "";
122             }
123              
124             my $backend_configurator =
125 119         2230 UNIVERSAL::can( $options->{backend}, "configurator" );
126              
127             # Apply config files
128 119         455 foreach my $new ( @cfg ) {
129 130         546 my $file = $new->{_src}; # for diagnostics
130             # Handle obsolete keys.
131 130         448 my $ps = $new->{pdf};
132 130 50       665 if ( exists $ps->{diagramscolumn} ) {
133 0   0     0 $ps->{diagrams}->{show} //= "right";
134 0         0 delete $ps->{diagramscolumn};
135 0         0 warn("$file: pdf.diagramscolumn is obsolete, use pdf.diagrams.show instead\n");
136             }
137 130 50       926 if ( exists $ps->{formats}->{default}->{'toc-title'} ) {
138 0   0     0 $new->{toc}->{title} //= $ps->{formats}->{default}->{'toc-title'};
139 0         0 delete $ps->{formats}->{default}->{'toc-title'};
140 0         0 warn("$file: pdf.formats.default.toc-title is obsolete, use toc.title instead\n");
141             }
142              
143             # Page controls.
144             # Check for old and newer keywords conflicts.
145 130 50 33     976 if ( $ps->{songbook}
      33        
146             && is_hashref($ps->{songbook})
147 0         0 && %{$ps->{songbook}} ) {
148             # Using new style page controls.
149 0         0 my @depr;
150 0         0 for ( qw( front-matter back-matter sort-pages ) ) {
151 0 0       0 push( @depr, $_) if $ps->{$_};
152             }
153             push( @depr, "even-odd-songs" )
154 0 0 0     0 if defined($ps->{'even-odd-songs'}) && $ps->{'even-odd-songs'} <= 0;
155             push( @depr, "pagealign-songs" )
156 0 0 0     0 if defined($ps->{'pagealign-songs'}) && $ps->{'pagealign-songs'} != 1;
157 0 0       0 if ( @depr ) {
158             warn("Config \"$file\" uses \"pdf.songbook\", ignoring ",
159 0         0 enumerated( map { qq{"pdf.$_"} } @depr ), "\n" );
  0         0  
160 0         0 delete $ps->{$_} for @depr;
161             }
162             }
163             else {
164 130         708 migrate_songbook_pagectrl( $new, $ps );
165             }
166              
167             # use DDP; p $ps->{songbook}, as => "after \"$file\"";
168              
169             # Process.
170 130         133262 local $::config = dclone($cfg);
171 130         1186 process_config( $new, $file );
172             # Merge final.
173 130         1088 $cfg = hmerge( $cfg, $new );
174             # die("PANIC! Config merge error")
175             # unless UNIVERSAL::isa( $cfg->{settings}->{strict}, 'JSON::Boolean' );
176             # use DDP; p $cfg->{pdf}->{songbook}, as => "accum after \"$file\"";
177             }
178              
179             # Handle defines from the command line.
180             # $cfg = hmerge( $cfg, prp2cfg( $options->{define}, $cfg ) );
181             # use DDP; p $options->{define}, as => "clo";
182 119         526 prpadd2cfg( $cfg, %{$options->{define}} );
  119         1440  
183 119         687 migrate_songbook_pagectrl($cfg);
184             # use DDP; p $cfg->{pdf}->{songbook}, as => "accum after clo";
185              
186             # Sanitize added extra entries.
187 119         387 for my $format ( qw(title subtitle footer) ) {
188             delete($cfg->{pdf}->{formats}->{first}->{$format})
189 357 100 50     2366 if ($cfg->{pdf}->{formats}->{first}->{$format} // 1) eq "";
190 357         745 for my $c ( qw(title first default filler) ) {
191 1428         2694 for my $class ( $c, $c."-even" ) {
192 2856         6073 my $t = $cfg->{pdf}->{formats}->{$class}->{$format};
193             # Allowed: null, false, [3], [[3], ...].
194 2856 100       6224 next unless defined $t;
195 833 50       1669 $cfg->{pdf}->{formats}->{$class}->{$format} = ["","",""], next
196             unless $t;
197 833 50       1786 die("Config error in pdf.formats.$class.$format: not an array\n")
198             unless is_arrayref($t);
199 833 50       2589 $t = [ $t ] unless is_arrayref($t->[0]);
200 833         1550 for ( @$t) {
201 833 50 33     3154 die("Config error in pdf.formats.$class.$format: ",
202             scalar(@$_), " fields instead of 3\n")
203             if @$_ && @$_ != 3;
204             }
205 833         2425 $cfg->{pdf}->{formats}->{$class}->{$format} = $t;
206             }
207             }
208             }
209              
210 119 50       844 if ( $cfg->{pdf}->{fontdir} ) {
211 119         304 my @a;
212 119 50       1426 if ( ref($cfg->{pdf}->{fontdir}) eq 'ARRAY' ) {
213 119         275 @a = @{ $cfg->{pdf}->{fontdir} };
  119         571  
214             }
215             else {
216 0         0 @a = ( $cfg->{pdf}->{fontdir} );
217             }
218 119         447 $cfg->{pdf}->{fontdir} = [];
219 119 50       1762 my $split = $^O =~ /^MS*/ ? qr(;) : qr(:);
220 119         565 foreach ( @a ) {
221 0         0 push( @{ $cfg->{pdf}->{fontdir} },
222 0         0 map { expand_tilde($_) } split( $split, $_ ) );
  0         0  
223             }
224             }
225             else {
226 0         0 $cfg->{pdf}->{fontdir} = [];
227             }
228              
229 119         285 my @allfonts = keys(%{$cfg->{pdf}->{fonts}});
  119         1022  
230 119         403 for my $ff ( @allfonts ) {
231             # Derived chords can have size or color only. Disable
232             # this test for now.
233 1428         1896 unless ( 1 || $cfg->{pdf}->{fonts}->{$ff}->{name}
234             || $cfg->{pdf}->{fonts}->{$ff}->{description}
235             || $cfg->{pdf}->{fonts}->{$ff}->{file} ) {
236             delete( $cfg->{pdf}->{fonts}->{$ff} );
237             next;
238             }
239 1428   50     7028 $cfg->{pdf}->{fonts}->{$ff}->{color} //= "foreground";
240 1428   100     6124 $cfg->{pdf}->{fonts}->{$ff}->{background} //= "background";
241 1428         2436 for ( qw(name file description size) ) {
242             delete( $cfg->{pdf}->{fonts}->{$ff}->{$_} )
243 5712 100       15062 unless defined( $cfg->{pdf}->{fonts}->{$ff}->{$_} );
244             }
245             }
246              
247 119 50       2124 if ( defined $options->{diagrams} ) {
    50          
    50          
248             warn( "Invalid value for diagrams: ",
249             $options->{diagrams}, "\n" )
250 0 0       0 unless $options->{diagrams} =~ /^(all|none|user)$/i;
251 0         0 $cfg->{diagrams}->{show} = lc $options->{'diagrams'};
252             }
253             elsif ( defined $options->{'user-chord-grids'} ) {
254             $cfg->{diagrams}->{show} =
255 0 0       0 $options->{'user-chord-grids'} ? "user" : 0;
256             }
257             elsif ( defined $options->{'chord-grids'} ) {
258             $cfg->{diagrams}->{show} =
259 0 0       0 $options->{'chord-grids'} ? "all" : 0;
260             }
261              
262 119         386 for ( qw( transpose transcode decapo lyrics-only strict ) ) {
263 595 100       1713 next unless defined $options->{$_};
264 18         124 $cfg->{settings}->{$_} = $options->{$_};
265             }
266              
267 119         338 for ( "cover", "front-matter", "back-matter" ) {
268 357 100       1070 next unless defined $options->{$_};
269 6         26 $cfg->{pdf}->{songbook}->{$_} = $options->{$_};
270             }
271              
272 119 50       491 if ( defined $options->{'chord-grids-sorted'} ) {
273 0         0 $cfg->{diagrams}->{sorted} = $options->{'chord-grids-sorted'};
274             }
275              
276             # For convenience...
277 119         452 bless( $cfg, __PACKAGE__ );
278              
279 119 50       450 return $cfg if $options->{'cfg-print'};
280              
281             # Backend specific configs.
282 119 100       517 $backend_configurator->($cfg) if $backend_configurator;
283              
284             # Locking the hash is mainly for development.
285 119         883 $cfg->lock;
286              
287 119 50       329827 if ( $options->{verbose} > 1 ) {
288 0   0     0 my $cp = ChordPro::Chords::get_parser() // "";
289 0         0 warn("Parsers:\n");
290 0         0 while ( my ($k, $v) = each %{ChordPro::Chords::Parser->parsers} ) {
  0         0  
291 0 0       0 warn( " $k",
292             $v eq $cp ? " (active)": "",
293             "\n");
294             }
295             }
296              
297 119         116414 return $cfg;
298             }
299              
300             # Get the decoded contents of a single config file.
301 132     132 0 297 sub get_config ( $file ) {
  132         324  
  132         258  
302 132 50       637 Carp::confess("FATAL: Undefined config") unless defined $file;
303 132         477 my $verbose = $options->{verbose};
304 132 50       517 warn("Reading: $file\n") if $verbose > 1;
305 132         857 $file = expand_tilde($file);
306              
307 132 50       1224 if ( $file =~ /\.json$/i ) {
    0          
308 132 50       1332 if ( my $lines = fs_load( $file, { split => 1, fail => "soft" } ) ) {
309 132         15161 my $new = json_load( join( "\n", @$lines, '' ), $file );
310 132         1568 precheck( $new, $file );
311 132         2766 return __PACKAGE__->new($new);
312             }
313             else {
314 0         0 die("Cannot open config $file [$!]\n");
315             }
316             }
317             elsif ( $file =~ /\.prp$/i ) {
318 0 0       0 if ( fs_test( efr => $file ) ) {
319 0         0 require ChordPro::Config::Properties;
320 0         0 my $cfg = Data::Properties->new;
321 0         0 $cfg->parse_file($file);
322 0         0 return __PACKAGE__->new($cfg->data);
323             }
324             else {
325 0         0 die("Cannot open config $file [$!]\n");
326             }
327             }
328             else {
329 0         0 Carp::confess("Unrecognized config type: $file\n");
330             }
331             }
332              
333             # Check config for includes, and prepend them.
334 249     249 0 710 sub prep_configs ( $cfg, $src ) {
  249         640  
  249         713  
  249         582  
335 249         1195 $cfg->{_src} = $src;
336              
337 249         589 my @res;
338              
339             # If there are includes, add them first.
340 249         2223 my ( $vol, $dir, undef ) = fn_splitpath($cfg->{_src});
341 249         882 foreach my $c ( @{ $cfg->{include} } ) {
  249         1414  
342             # Check for resource names.
343 119 50 0     708 if ( $c !~ m;[/.]; ) {
    0          
344 119         770 $c = CP->findcfg($c);
345             }
346             elsif ( $dir ne ""
347             && !fn_is_absolute($c) ) {
348             # Prepend dir of the caller, if needed.
349 0         0 $c = fn_catpath( $vol, $dir, $c );
350             }
351 119         653 my $cfg = get_config($c);
352             # Recurse.
353 119         2171 push( @res, $cfg->prep_configs($c) );
354             }
355              
356             # Push this and return.
357 249         1532 $cfg->split_fc_aliases;
358 249         1503 $cfg->expand_font_shortcuts;
359 249         894 push( @res, $cfg );
360 249         1290 return @res;
361             }
362              
363 219     219 0 643 sub process_config ( $cfg, $file ) {
  219         624  
  219         716  
  219         508  
364 219         933 my $verbose = $options->{verbose};
365              
366 219 50       1131 warn("Process: $file\n") if $verbose > 1;
367              
368 219 100       1080 if ( $cfg->{tuning} ) {
369 208         1717 my $res =
370             ChordPro::Chords::set_tuning( $cfg );
371 208 50       2857 warn( "Invalid tuning in config: ", $res, "\n" ) if $res;
372 208         1054 $cfg->{_tuning} = $cfg->{tuning};
373 208         837 $cfg->{tuning} = [];
374             }
375              
376 219         1197 ChordPro::Chords::reset_parser;
377 219         1484 ChordPro::Chords::Parser->reset_parsers;
378 219         1194 local $::config = dclone(hmerge( $::config, $cfg ));
379 219 100       11669 if ( $cfg->{chords} ) {
380 208         2371 ChordPro::Chords::push_parser($cfg->{notes}->{system});
381 208         874 my $c = $cfg->{chords};
382 208 50 66     2296 if ( @$c && $c->[0] eq "append" ) {
383 0         0 shift(@$c);
384             }
385 208         834 foreach ( @$c ) {
386 49742         140550 my $res =
387             ChordPro::Chords::add_config_chord($_);
388             warn( "Invalid chord in config: ",
389 49742 50       173408 $_->{name}, ": ", $res, "\n" ) if $res;
390             }
391 208 50       1116 if ( $verbose > 1 ) {
392 0         0 warn( "Processed ", scalar(@$c), " chord entries\n");
393 0         0 warn( "Totals: ",
394             ChordPro::Chords::chord_stats(), "\n" );
395             }
396 208         1145 $cfg->{_chords} = delete $cfg->{chords};
397 208         1065 ChordPro::Chords::pop_parser();
398             }
399 219         1530 $cfg->split_fc_aliases;
400 219         1295 $cfg->expand_font_shortcuts;
401             }
402              
403             # Expand pdf.fonts.foo: bar to pdf.fonts.foo { description: bar }.
404              
405 470     470 0 5573 sub expand_font_shortcuts ( $cfg ) {
  470         999  
  470         904  
406 470 100       92292 return unless exists $cfg->{pdf}->{fonts};
407 210         602 for my $f ( keys %{$cfg->{pdf}->{fonts}} ) {
  210         2196  
408 2506 100       8001 next if ref($cfg->{pdf}->{fonts}->{$f}) eq 'HASH';
409 2501         6295 for ( $cfg->{pdf}->{fonts}->{$f} ) {
410 2501         4520 my $v = $_;
411 2501         7003 $v =~ s/\s*;\s*$//;
412 2501         3925 my $i = {};
413              
414             # Break out ;xx=yy properties.
415 2501         11038 while ( $v =~ s/\s*;\s*(\w+)\s*=\s*(.*?)\s*(;|$)/$3/ ) {
416 624         2400 my ( $k, $v ) = ( $1, $2 );
417 624 50       3593 if ( $k =~ /^(colou?r|background|frame|numbercolou?r|size)$/ ) {
418 624         1509 $k =~ s/colour/color/;
419 624         2997 $v =~ s/^(['"]?)(.*)\1$/$2/;
420 624         3389 $i->{$k} = $v;
421             }
422             else {
423 0         0 warn("Unknown font property: $k (ignored)\n");
424             }
425             }
426              
427             # Break out size.
428 2501 50       21775 if ( $v =~ /(.*?)(?:\s+(\d+(?:\.\d+)?))?\s*(?:;|$)/ ) {
429 2501 100 33     15991 $i->{size} //= $2 if $2;
430 2501         4722 $v = $1;
431             }
432              
433             # Check for filename.
434 2501 100       9434 if ( $v =~ /^.*\.(ttf|otf)$/i ) {
    100          
435 1         5 $i->{file} = $v;
436             }
437             # Check for corefonts.
438             elsif ( is_corefont($v) ) {
439 2081         4384 $i->{name} = is_corefont($v);
440             }
441             else {
442 419         1395 $i->{description} = $v;
443             $i->{description} .= " " . delete($i->{size})
444 419 100       2134 if $i->{size};
445             }
446 2501         17618 $_ = $i;
447             }
448             }
449             }
450              
451 90     90   1132 use Storable qw(dclone);
  90         215  
  90         662045  
452              
453             # Split fontconfig aliases into separate entries.
454              
455 557     557 0 1360 sub split_fc_aliases ( $cfg ) {
  557         1177  
  557         1181  
456              
457 557 100       3933 if ( $cfg->{pdf}->{fontconfig} ) {
458             # Orig.
459 297         970 my $fc = $cfg->{pdf}->{fontconfig};
460             # Since we're going to delete/insert keys, we need a copy.
461 297         3614 my %fc = %$fc;
462 297         2109 while ( my($k,$v) = each(%fc) ) {
463             # Split on comma.
464 2643         8955 my @k = split( /\s*,\s*/, $k );
465 2643 100       9886 if ( @k > 1 ) {
466             # We have aliases. Delete the original.
467 624         1555 delete( $fc->{$k} );
468             # And insert individual entries.
469 624         102601 $fc->{$_} = dclone($v) for @k;
470             }
471             }
472             }
473             }
474              
475             # Reverse of config_expand_font_shortcuts.
476              
477 1     1 0 9 sub simplify_fonts( $cfg ) {
  1         5  
  1         2  
478              
479 1 50       13 return $cfg unless $cfg->{pdf}->{fonts};
480              
481 1         3 foreach my $font ( keys %{$cfg->{pdf}->{fonts}} ) {
  1         8  
482 5         13 for ( $cfg->{pdf}->{fonts}->{$font} ) {
483 5 100       16 next unless is_hashref($_);
484              
485             delete $_->{color}
486 4 50 33     12 if $_->{color} && $_->{color} eq "foreground";
487             delete $_->{background}
488 4 50 33     16 if $_->{background} && $_->{background} eq "background";
489              
490 4 100 33     26 if ( exists( $_->{file} ) ) {
    100 33        
    50          
491 1         3 delete $_->{description};
492 1         4 delete $_->{name};
493             }
494             elsif ( exists( $_->{description} ) ) {
495 2         5 delete $_->{name};
496 2 100 66     24 if ( $_->{size} && $_->{description} !~ /\s+[\d.]+$/ ) {
497 1         6 $_->{description} .= " " . $_->{size};
498             }
499 2         5 delete $_->{size};
500 2 50       12 $_ = $_->{description} if keys %$_ == 1;
501             }
502             elsif ( exists( $_->{name} )
503             && exists( $_->{size})
504             && keys %$_ == 2
505             ) {
506 1         8 $_ = $_->{name} .= " " . $_->{size};
507             }
508             }
509             }
510             }
511              
512 249     249 0 646 sub migrate_songbook_pagectrl( $self, $ps = undef ) {
  249         598  
  249         596  
  249         476  
513              
514             # Migrate old to new.
515 249   66     1437 $ps //= $self->{pdf};
516 249   100     1486 my $sb = $ps->{songbook} // {};
517 249         917 for ( qw( front-matter back-matter ) ) {
518 498 50       1957 $sb->{$_} = delete($ps->{$_}) if $ps->{$_};
519             }
520 249         1053 for ( $ps->{'even-odd-pages'} ) {
521 249 100       1339 next unless defined;
522 3         13 $sb->{'dual-pages'} = !!$_;
523 3 100       18 $sb->{'align-songs-spread'} = 1 if $_ < 0;
524             }
525 249         1001 for ( $ps->{'pagealign-songs'} ) {
526 249 100       998 next unless defined;
527 4         17 $sb->{'align-songs'} = !!$_;
528 4         18 $sb->{'align-songs-extend'} = $_ > 1;
529             }
530 249         933 for ( $ps->{'sort-pages'} ) {
531 249 50       939 next unless defined;
532 0         0 my $a = $_;
533 0         0 $a =~ s/\s+//g;
534 0         0 my ( $sort, $desc, $spread, $compact );
535 0         0 $sort = $desc = "";
536 0         0 for ( split( /,/, lc $a ) ) {
537 0 0       0 if ( $_ eq "title" ) {
    0          
    0          
    0          
    0          
538 0         0 $sort = "title";
539             }
540             elsif ( $_ eq "subtitle" ) {
541 0   0     0 $sort //= "subtitle";
542             }
543             elsif ( $_ eq "2page" ) {
544 0         0 $spread++;
545             }
546             elsif ( $_ eq "desc" ) {
547 0         0 $desc = "-";
548             }
549             elsif ( $_ eq "compact" ) {
550 0         0 $compact++;
551             }
552             else {
553 0         0 warn("??? \"$_\"\n");
554             }
555             }
556 0         0 $sb->{'sort-songs'} = "${desc}${sort}";
557 0 0       0 $sb->{'compact-songs'} = 1 if $compact;
558 0 0       0 $sb->{'align-songs-spread'} = 1 if $spread;
559             }
560 249         930 $ps->{songbook} = $sb;
561             # Remove the obsoleted entries.
562             delete( $ps->{$_} )
563 249         1562 for qw( even-odd-pages sort-pages pagealign-songs );
564              
565             }
566              
567 0     0 0 0 sub config_final ( %args ) {
  0         0  
  0         0  
568 0   0     0 my $delta = $args{delta} || 0;
569 0   0     0 my $default = $args{default} || 0;
570 0         0 $options->{'cfg-print'} = 1;
571              
572 0         0 my $defcfg; # pristine config
573             my $cfg; # actual config
574 0 0 0     0 if ( $default || $delta ) {
575 0         0 local $options->{nosysconfig} = 1;
576 0         0 local $options->{nouserconfig} = 1;
577 0         0 local $options->{noconfig} = 1;
578 0         0 $defcfg = pristine_config();
579 0         0 split_fc_aliases($defcfg);
580 0         0 expand_font_shortcuts($defcfg);
581 0 0       0 if ( $delta ) {
582 0         0 delete $defcfg->{chords};
583 0         0 delete $defcfg->{include};
584             }
585 0         0 bless $defcfg => __PACKAGE__;
586 0 0       0 $cfg = $defcfg if $default;
587             }
588              
589 0   0     0 $cfg //= configurator($options);
590              
591             # Remove unwanted data.
592 0         0 $cfg->unlock;
593 0         0 $cfg->{tuning} = delete $cfg->{_tuning};
594 0 0       0 if ( $delta ) {
595 0         0 for ( qw( tuning ) ) {
596 0 0       0 delete($cfg->{$_}) unless defined($cfg->{$_});
597             }
598 0         0 for my $f ( keys( %{$cfg->{pdf}{fonts}} ) ) {
  0         0  
599 0         0 for ( qw( background color ) ) {
600 0 0       0 next if defined($defcfg->{pdf}{fonts}{$f}{$_});
601 0         0 delete($cfg->{pdf}{fonts}{$f}{$_});
602 0         0 delete($defcfg->{pdf}{fonts}{$f}{$_});
603             }
604             }
605             }
606 0         0 delete $cfg->{_chords};
607 0         0 delete $cfg->{chords};
608 0         0 delete $cfg->{_src};
609              
610 0         0 my $parser = JSON::Relaxed::Parser->new( key_order => 1 );
611              
612             # Load schema.
613 0         0 my $schema = do {
614 0         0 my $schema = CP->findres( "config.schema", class => "config" );
615 0         0 my $data = fs_load( $schema, { split => 0 } );
616 0         0 $parser->decode($data);
617             };
618              
619             # Delta cannot handle reference config yet.
620 0 0       0 if ( $delta ) {
621 0         0 $defcfg->unlock;
622 0         0 $cfg->reduce( $defcfg );
623 0         0 return $parser->encode( data => {%$cfg},
624             pretty => 1, schema => $schema );
625             }
626              
627 0         0 my $config = do {
628 0         0 my $config = CP->findres( "chordpro.json", class => "config" );
629 0         0 my $data = fs_load( $config, { split => 0 } );
630 0         0 $parser->decode($data);
631             };
632              
633             # $cfg = hmerge( $config, $cfg );
634 0         0 $cfg->simplify_fonts;
635 0         0 return $parser->encode( data => {%{$cfg}},
  0         0  
636             pretty => 1, schema => $schema );
637             }
638              
639 0     0 0 0 sub convert_config ( $from, $to ) {
  0         0  
  0         0  
  0         0  
640             # This is a completely independent function.
641              
642             # Establish a key order retaining parser.
643 0         0 my $parser = JSON::Relaxed::Parser->new( key_order => 1 );
644              
645             # First find and process the schema.
646 0         0 my $schema = CP->findres( "config.schema", class => "config" );
647 0         0 my $o = { split => 0, fail => 'soft' };
648 0         0 my $data = fs_load( $schema, $o );
649 0 0       0 die("$schema: ", $o->{error}, "\n") if $o->{error};
650 0         0 $schema = $parser->decode($data);
651              
652             # Then load the config to be converted.
653 0         0 my $new;
654 0         0 $o = { split => 1, fail => 'soft' };
655 0         0 $data = fs_load( $from, $o );
656 0 0       0 die("Cannot open config $from [", $o->{error}, "]\n") if $o->{error};
657 0         0 $data = join( "\n", @$data );
658              
659 0 0       0 if ( $data =~ /^\s*#/m ) { # #-comments -> prp
660 0         0 require ChordPro::Config::Properties;
661 0         0 my $cfg = Data::Properties->new;
662 0         0 $cfg->parse_file($from);
663 0         0 $new = $cfg->data;
664             }
665             else { # assume JSON, RJSON, RRJSON
666 0         0 $new = $parser->decode($data);
667             }
668              
669             # And re-encode it using the schema.
670 0         0 my $res = $parser->encode( data => $new, pretty => 1,
671             nounicodeescapes => 1, schema => $schema );
672             # use DDP; p $res;
673             # Add trailer.
674 0         0 $res .= "\n// End of Config.\n";
675              
676             # Write if out.
677 0 0 0     0 if ( $to && $to ne "-" ) {
678 0 0       0 open( my $fd, '>', $to )
679             or die("$to: $!\n");
680 0         0 print $fd $res;
681 0         0 $fd->close;
682             }
683             else {
684 0         0 print $res;
685             }
686              
687 0         0 1;
688             }
689              
690             # Config in properties format.
691              
692 0     0 0 0 sub cfg2props ( $o, $path = "" ) {
  0         0  
  0         0  
  0         0  
693 0   0     0 $path //= "";
694 0         0 my $ret = "";
695 0 0       0 if ( !defined $o ) {
    0          
    0          
    0          
696 0         0 $ret .= "$path: undef\n";
697             }
698             elsif ( is_hashref($o) ) {
699 0 0       0 $path .= "." unless $path eq "";
700 0         0 for ( sort keys %$o ) {
701 0         0 $ret .= cfg2props( $o->{$_}, $path . $_ );
702             }
703             }
704             elsif ( is_arrayref($o) ) {
705 0 0       0 $path .= "." unless $path eq "";
706 0         0 for ( my $i = 0; $i < @$o; $i++ ) {
707 0         0 $ret .= cfg2props( $o->[$i], $path . "$i" );
708             }
709             }
710             elsif ( $o =~ /^\d+$/ ) {
711 0         0 $ret .= "$path: $o\n";
712             }
713             else {
714 0         0 $o =~ s/\\/\\\\/g;
715 0         0 $o =~ s/"/\\"/g;
716 0         0 $o =~ s/\n/\\n/;
717 0         0 $o =~ s/\t/\\t/;
718 0         0 $o =~ s/([^\x00-\xff])/sprintf("\\x{%x}", ord($1))/ge;
  0         0  
719 0         0 $ret .= "$path: \"$o\"\n";
720             }
721              
722 0         0 return $ret;
723             }
724              
725             # Locking/unlocking. Locking the hash is mainly for development, to
726             # trap accidental modifications and typos.
727              
728 355     355 0 815 sub lock ( $self ) {
  355         859  
  355         695  
729 355         2230 Hash::Util::lock_hashref_recurse($self);
730             }
731              
732 276     276 0 678 sub unlock ( $self ) {
  276         676  
  276         549  
733 276         2124 Hash::Util::unlock_hashref_recurse($self);
734             }
735              
736 5     5 0 13 sub is_locked ( $self ) {
  5         10  
  5         23  
737 5         83 Hash::Util::hashref_locked($self);
738             }
739              
740             # Augment / Reduce.
741              
742 4     4 0 2153 sub augment ( $self, $hash ) {
  4         10  
  4         10  
  4         15  
743              
744 4         24 my $locked = $self->is_locked;
745 4 100       60 $self->unlock if $locked;
746              
747 4         7262 $self->_augment( $hash, "" );
748              
749 4 100       21 $self->lock if $locked;
750              
751 4         6414 $self;
752             }
753              
754              
755 15     15   738 sub _augment ( $self, $hash, $path ) {
  15         28  
  15         21  
  15         22  
  15         18  
756              
757 15         44 for my $key ( keys(%$hash) ) {
758              
759             warn("Config augment error: unknown item $path$key\n")
760 17 0 33     54 unless exists $self->{$key}
      33        
      0        
      0        
      0        
      0        
761             || $path =~ /^pdf\.(?:info|fonts|fontconfig)\./
762             || $path =~ /^pdf\.formats\.\w+-even\./
763             || $path =~ /^(meta|gridstrum\.symbols)\./
764             || $path =~ /^markup\.shortcodes\./
765             || $path =~ /^delegates\./
766             || $key =~ /^_/;
767              
768             # Hash -> Hash.
769             # Hash -> Array.
770 17 100 66     66 if ( ref($hash->{$key}) eq 'HASH' ) {
    100          
771 11 50       29 if ( ref($self->{$key}) eq 'HASH' ) {
    0          
772              
773             # Hashes. Recurse.
774 11         56 _augment( $self->{$key}, $hash->{$key}, "$path$key." );
775             }
776             elsif ( ref($self->{$key}) eq 'ARRAY' ) {
777              
778             # Hash -> Array.
779             # Update single array element using a hash index.
780 0         0 foreach my $ix ( keys(%{$hash->{$key}}) ) {
  0         0  
781 0 0       0 die unless $ix =~ /^\d+$/;
782 0         0 $self->{$key}->[$ix] = $hash->{$key}->{$ix};
783             }
784             }
785             else {
786             # Overwrite.
787 0         0 $self->{$key} = $hash->{$key};
788             }
789             }
790              
791             # Array -> Array.
792             elsif ( ref($hash->{$key}) eq 'ARRAY'
793             and ref($self->{$key}) eq 'ARRAY' ) {
794              
795             # Arrays. Overwrite or append.
796 2 50       6 if ( @{$hash->{$key}} ) {
  2         7  
797 2         6 my @v = @{ $hash->{$key} };
  2         8  
798 2 50       10 if ( $v[0] eq "append" ) {
    100          
799 0         0 shift(@v);
800             # Append the rest.
801 0         0 push( @{ $self->{$key} }, @v );
  0         0  
802             }
803             elsif ( $v[0] eq "prepend" ) {
804 1         3 shift(@v);
805             # Prepend the rest.
806 1         3 unshift( @{ $self->{$key} }, @v );
  1         5  
807             }
808             else {
809             # Overwrite.
810 1         5 $self->{$key} = $hash->{$key};
811             }
812             }
813             else {
814             # Overwrite.
815 0         0 $self->{$key} = $hash->{$key};
816             }
817             }
818              
819             else {
820             # Overwrite.
821 4         16 $self->{$key} = $hash->{$key};
822             }
823             }
824              
825 15         30 $self;
826             }
827              
828 90     90   1161 use constant DEBUG => 0;
  90         353  
  90         454996  
829              
830 1     1 0 2027 sub reduce ( $self, $hash ) {
  1         3  
  1         3  
  1         2  
831              
832 1         5 my $locked = $self->is_locked;
833              
834 1         9 warn("O: ", qd($hash,1), "\n") if DEBUG > 1;
835 1         2 warn("N: ", qd($self,1), "\n") if DEBUG > 1;
836 1         6 my $state = _reduce( $self, $hash, "" );
837              
838 1 50       29 $self->lock if $locked;
839              
840 1         3 warn("== ", qd($self,1), "\n") if DEBUG > 1;
841 1         5 return $self;
842             }
843              
844 68     68   111 sub _ref ( $self ) {
  68         120  
  68         100  
845 68   66     419 reftype($self) // ref($self);
846             }
847              
848 6     6   11 sub _reduce ( $self, $orig, $path ) {
  6         14  
  6         9  
  6         13  
  6         8  
849              
850 6         10 my $state;
851              
852 6 100 66     15 if ( _ref($self) eq 'HASH' && _ref($orig) eq 'HASH' ) {
853              
854 3         7 warn("D: ", qd($self,1), "\n") if DEBUG && !%$orig;
855 3 50       11 return 'D' unless %$orig;
856              
857 3         15 my %hh = map { $_ => 1 } keys(%$self), keys(%$orig);
  12         32  
858 3         17 for my $key ( sort keys(%hh) ) {
859              
860             warn("Config reduce error: unknown item $path$key\n")
861 6 0 33     25 unless exists $self->{$key}
      33        
862             || $key =~ /^_/
863             || $path =~ /^pdf\/\.fonts\./;
864              
865 6 50       17 unless ( exists $orig->{$key} ) {
866 0         0 warn("D: $path$key\n") if DEBUG;
867 0         0 delete $self->{$key};
868 0   0     0 $state //= 'M';
869 0         0 next;
870             }
871              
872             # Hash -> Hash.
873 6 100 66     18 if ( _ref($orig->{$key}) eq 'HASH'
    100 66        
    50 100        
      50        
      50        
      33        
      33        
      33        
      33        
      33        
874             and _ref($self->{$key}) eq 'HASH'
875             or
876             _ref($orig->{$key}) eq 'ARRAY'
877             and _ref($self->{$key}) eq 'ARRAY' ) {
878             # Recurse.
879 4         37 my $m = _reduce( $self->{$key}, $orig->{$key}, "$path$key." );
880 4 50 33     29 delete $self->{$key} if $m eq 'D' || $m eq 'I';
881 4 50 100     25 $state //= 'M' if $m ne 'I';
882             }
883              
884             elsif ( ($self->{$key}//'') eq ($orig->{$key}//'') ) {
885 1         3 warn("I: $path$key\n") if DEBUG;
886 1         4 delete $self->{$key};
887             }
888             elsif ( !defined($self->{$key})
889             and _ref($orig->{$key}) eq 'ARRAY'
890 0         0 and !@{$orig->{$key}}
891             or
892             !defined($orig->{$key})
893             and _ref($self->{$key}) eq 'ARRAY'
894 0         0 and !@{$self->{$key}} ) {
895             # Properties input [] yields undef.
896 0         0 warn("I: $path$key\n") if DEBUG;
897 0         0 delete $self->{$key};
898             }
899             else {
900             # Overwrite.
901 1         2 warn("M: $path$key => $self->{$key}\n") if DEBUG;
902 1   50     10 $state //= 'M';
903             }
904             }
905 3   50     16 return $state // 'I';
906             }
907              
908 3 50 33     9 if ( _ref($self) eq 'ARRAY' && _ref($orig) eq 'ARRAY' ) {
909              
910             # Arrays.
911 3 100   5   27 if ( any { _ref($_) } @$self ) {
  5         41  
912             # Complex arrays. Recurse.
913 1         8 for ( my $key = 0; $key < @$self; $key++ ) {
914 1         12 my $m = _reduce( $self->[$key], $orig->[$key], "$path$key." );
915             #delete $self->{$key} if $m eq 'D'; # TODO
916 1 50 50     13 $state //= 'M' if $m ne 'I';
917             }
918 1   50     6 return $state // 'I';
919             }
920              
921             # Simple arrays (only scalar values).
922 2 100       17 if ( my $dd = @$self - @$orig ) {
923 1         10 $path =~ s/\.$//;
924 1 50       4 if ( $dd > 0 ) {
925             # New is larger. Check for prepend/append.
926             # Deal with either one, not both. Maybe later.
927 1         4 my $t;
928 1         4 for ( my $ix = 0; $ix < @$orig; $ix++ ) {
929 1 50       7 next if $orig->[$ix] eq $self->[$ix];
930 1         4 $t++;
931 1         3 last;
932             }
933 1 50       7 unless ( $t ) {
934 0         0 warn("M: $path append @{$self}[-$dd..-1]\n") if DEBUG;
935 0         0 splice( @$self, 0, $dd, "append" );
936 0         0 return 'M';
937             }
938 1         2 undef $t;
939 1         5 for ( my $ix = $dd; $ix < @$self; $ix++ ) {
940 2 50       11 next if $orig->[$ix-$dd] eq $self->[$ix];
941 0         0 $t++;
942 0         0 last;
943             }
944 1 50       5 unless ( $t ) {
945 1         2 warn("M: $path prepend @{$self}[0..$dd-1]\n") if DEBUG;
946 1         5 splice( @$self, $dd );
947 1         4 unshift( @$self, "prepend" );
948 1         5 return 'M';
949             }
950 0         0 warn("M: $path => @$self\n") if DEBUG;
951 0         0 $state = 'M';
952             }
953             else {
954 0         0 warn("M: $path => @$self\n") if DEBUG;
955 0         0 $state = 'M';
956             }
957 0   0     0 return $state // 'I';
958             }
959              
960             # Equal length arrays with scalar values.
961 1         2 my $t;
962 1         4 for ( my $ix = 0; $ix < @$orig; $ix++ ) {
963 1 50       5 next if $orig->[$ix] eq $self->[$ix];
964 1         290 warn("M: $path$ix => $self->[$ix]\n") if DEBUG;
965 1         6 $t++;
966 1         3 last;
967             }
968 1 50       6 if ( $t ) {
969 1         1 warn("M: $path\n") if DEBUG;
970 1         9 return 'M';
971             }
972 0         0 warn("I: $path\[]\n") if DEBUG;
973 0         0 return 'I';
974             }
975              
976             # Two scalar values.
977 0         0 $path =~ s/\.$//;
978 0 0       0 if ( $self eq $orig ) {
979 0         0 warn("I: $path\n") if DEBUG;
980 0         0 return 'I';
981             }
982              
983 0         0 warn("M $path $self\n") if DEBUG;
984 0         0 return 'M';
985             }
986              
987 8858     8858 0 13204 sub hmerge( $left, $right, $path = "" ) {
  8858         12995  
  8858         13254  
  8858         14167  
  8858         12385  
988              
989             # Merge hashes. Right takes precedence.
990             # Based on Hash::Merge::Simple by Robert Krimen.
991              
992 8858         65276 my %res = %$left;
993              
994 8858         27547 for my $key ( keys(%$right) ) {
995              
996             warn("Config error: unknown item $path$key\n")
997 36751 50 66     87733 unless exists $res{$key}
      66        
      33        
      33        
      33        
      33        
      33        
      33        
      33        
      33        
      33        
998             || $path eq "pdf.fontconfig."
999             || $path =~ /^pdf\.(?:info|fonts)\./
1000             || $path =~ /^pdf\.formats\.\w+-even\./
1001             || ( $path =~ /^pdf\.formats\./ && $key =~ /\w+-even$/ )
1002             || $path =~ /^(meta|gridstrum\.symbols)\./
1003             || $path =~ /^delegates\./
1004             || $path =~ /^parser\.preprocess\./
1005             || $path =~ /^markup\.shortcodes\./
1006             || $path =~ /^debug\./
1007             || $key =~ /^_/;
1008              
1009 36751 100 66     131403 if ( ref($right->{$key}) eq 'HASH'
    100 100        
1010             and
1011             ref($res{$key}) eq 'HASH' ) {
1012             # Hashes. Recurse.
1013 8507         25835 $res{$key} = hmerge( $res{$key}, $right->{$key}, "$path$key." );
1014             }
1015             elsif ( ref($right->{$key}) eq 'ARRAY'
1016             and
1017             ref($res{$key}) eq 'ARRAY' ) {
1018             warn("AMERGE $key: ",
1019             join(" ", map { qq{"$_"} } @{ $res{$key} }),
1020             " + ",
1021 3157         4938 join(" ", map { qq{"$_"} } @{ $right->{$key} }),
1022             " \n") if 0;
1023             # Arrays. Overwrite or append.
1024 3157 100       4599 if ( @{$right->{$key}} ) {
  3157         9562  
1025 1769         2670 my @v = @{ $right->{$key} };
  1769         16318  
1026 1769 50       5551 if ( $v[0] eq "append" ) {
    50          
1027 0         0 shift(@v);
1028             # Append the rest.
1029             warn("PRE: ",
1030             join(" ", map { qq{"$_"} } @{ $res{$key} }),
1031             " + ",
1032 0         0 join(" ", map { qq{"$_"} } @v),
1033             "\n") if 0;
1034 0         0 push( @{ $res{$key} }, @v );
  0         0  
1035             warn("POST: ",
1036 0         0 join(" ", map { qq{"$_"} } @{ $res{$key} }),
1037             "\n") if 0;
1038             }
1039             elsif ( $v[0] eq "prepend" ) {
1040 0         0 shift(@v);
1041             # Prepend the rest.
1042 0         0 unshift( @{ $res{$key} }, @v );
  0         0  
1043             }
1044             else {
1045             # Overwrite.
1046 1769         7161 $res{$key} = $right->{$key};
1047             }
1048             }
1049             else {
1050             # Overwrite.
1051 1388         3557 $res{$key} = $right->{$key};
1052             }
1053             }
1054             else {
1055             # Overwrite.
1056 25087         47906 $res{$key} = $right->{$key};
1057             }
1058             }
1059              
1060 8858         470636 return \%res;
1061             }
1062              
1063 40     40 0 130 sub clone ( $source ) {
  40         109  
  40         82  
1064              
1065 40 50       154 return if not defined($source);
1066              
1067 90     90   1085 use Storable;
  90         224  
  90         129565  
1068 40         85019 my $clone = Storable::dclone($source);
1069 40         454 $clone->unlock;
1070 40         108511 return $clone;
1071              
1072             }
1073              
1074 132     132 0 390 sub precheck ( $cfg, $file ) {
  132         307  
  132         332  
  132         262  
1075              
1076 132         719 my $verbose = $options->{verbose};
1077 132 50       597 warn("Verify config \"$file\"\n") if $verbose > 1;
1078 132         357 my $p;
1079             $p = sub {
1080 283922     283922   520539 my ( $o, $path ) = @_;
1081 283922   100     493393 $path //= "";
1082 283922 100       773317 if ( is_hashref($o) ) {
    100          
1083 50376 100       103239 $path .= "." unless $path eq "";
1084 50376         177493 for ( sort keys %$o ) {
1085 119944         279533 $p->( $o->{$_}, $path . $_ );
1086             }
1087             }
1088             elsif ( is_arrayref($o) ) {
1089 19175 50       39836 $path .= "." unless $path eq "";
1090 19175         43999 for ( my $i = 0; $i < @$o; $i++ ) {
1091 163846         390368 $p->( $o->[$i], $path . "$i" );
1092             }
1093             }
1094 132         1552 };
1095              
1096 132         701 $p->($cfg);
1097             }
1098              
1099              
1100             ## Data::Properties compatible API.
1101             #
1102             # Note: Lookup always takes the context into account.
1103             # Note: Always signals undefined values.
1104              
1105             my $prp_context = "";
1106              
1107 12     12 0 22 sub get_property ( $p, $prp, $def = undef ) {
  12         23  
  12         24  
  12         29  
  12         23  
1108 12 100       107 for ( split( /\./,
1109             $prp_context eq ""
1110             ? $prp
1111             : "$prp_context.$prp" ) ) {
1112 34 100       133 if ( /^\d+$/ ) {
1113 5 50       14 die("No config $prp\n") unless _ref($p) eq 'ARRAY';
1114 5         22 $p = $p->[$_];
1115             }
1116             else {
1117 29 50       68 die("No config $prp\n") unless _ref($p) eq 'HASH';
1118 29         99 $p = $p->{$_};
1119             }
1120             }
1121 12   66     44 $p //= $def;
1122 12 50       33 die("No config $prp\n") unless defined $p;
1123 12         77 $p;
1124             }
1125              
1126             *gps = \&get_property;
1127              
1128             sub set_property {
1129 0     0 0 0 ...;
1130             }
1131              
1132 2     2 0 7 sub set_context ( $self, $ctx = "" ) {
  2         5  
  2         6  
  2         4  
1133 2         6 $prp_context = $ctx;
1134             }
1135              
1136 0     0 0 0 sub get_context () {
  0         0  
1137 0         0 $prp_context;
1138             }
1139              
1140             # For testing
1141 90     90   884 use Exporter 'import';
  90         212  
  90         27402  
1142             our @EXPORT = qw( _c );
1143 12     12   344276 sub _c ( @args ) { $::config->gps(@args) }
  12         40  
  12         23  
  12         60  
1144              
1145             # For convenience.
1146 112     112 0 238 sub diagram_strings ( $self ) {
  112         252  
  112         212  
1147             # tuning is usually removed from the config.
1148             # scalar( @{ $self->{tuning} } );
1149 112         623 ChordPro::Chords::strings();
1150             }
1151              
1152 0     0 0   sub diagram_keys ( $self ) {
  0            
  0            
1153 0           $self->{kbdiagrams}->{keys};
1154             }
1155              
1156             # For debugging messages.
1157 0     0 0   sub qd ( $val, $compact = 0 ) {
  0            
  0            
  0            
1158 90     90   82444 use Data::Dumper qw();
  90         1236583  
  90         47287  
1159 0           local $Data::Dumper::Sortkeys = 1;
1160 0           local $Data::Dumper::Indent = 1;
1161 0           local $Data::Dumper::Quotekeys = 0;
1162 0           local $Data::Dumper::Deparse = 1;
1163 0           local $Data::Dumper::Terse = 1;
1164 0           local $Data::Dumper::Trailingcomma = !$compact;
1165 0           local $Data::Dumper::Useperl = 1;
1166 0           local $Data::Dumper::Useqq = 0; # I want unicode visible
1167 0           my $x = Data::Dumper::Dumper($val);
1168 0 0         if ( $compact ) {
1169 0           $x =~ s/^bless\( (.*), '[\w:]+' \)$/$1/s;
1170 0           $x =~ s/\s+/ /gs;
1171             }
1172 0 0         defined wantarray ? $x : warn($x,"\n");
1173             }
1174              
1175             1;