File Coverage

lib/ChordPro/Songbook.pm
Criterion Covered Total %
statement 92 119 77.3
branch 18 30 60.0
condition 22 43 51.1
subroutine 19 21 90.4
pod 0 5 0.0
total 151 218 69.2


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::Songbook;
9              
10 90     90   642 use strict;
  90         221  
  90         4388  
11 90     90   542 use warnings;
  90         210  
  90         5976  
12 90     90   604 use feature 'state';
  90         197  
  90         12085  
13              
14 90     90   617 use ChordPro;
  90         192  
  90         2496  
15 90     90   505 use ChordPro::Config;
  90         212  
  90         3718  
16 90     90   549 use ChordPro::Files;
  90         184  
  90         15533  
17 90     90   66472 use ChordPro::Song;
  90         492  
  90         7313  
18 90     90   7731 use ChordPro::Utils qw(progress);
  90         2745  
  90         9612  
19              
20 90     90   1353 use Carp;
  90         182  
  90         7540  
21 90     90   684 use List::Util qw(any);
  90         322  
  90         7120  
22 90     90   645 use Storable qw(dclone);
  90         204  
  90         5417  
23 90     90   605 use Ref::Util qw(is_arrayref is_plain_hashref);
  90         176  
  90         5245  
24 90     90   59441 use MIME::Base64;
  90         95525  
  90         252434  
25              
26             my $regtest = defined($ENV{PERL_HASH_SEED}) && $ENV{PERL_HASH_SEED} == 0;
27              
28             sub new {
29 213     213 0 14029274 my ($pkg) = @_;
30 213         6078 bless { songs => [ ] }, $pkg;
31             }
32              
33             sub parse_file {
34 186     186 0 33514 my ( $self, $filename, $opts ) = @_;
35 186   100     1186 $opts //= {};
36 186   100     424 my $meta = { %{$config->{meta}}, %{delete $opts->{meta}//{}} };
  186         1132  
  186         1682  
37 186   100     619 my $defs = { %{delete $opts->{defs}//{}} };
  186         1219  
38              
39             # Check for PDF embedding.
40 186 50       1644 if ( $filename =~ /\.pdf$/i ) {
41 0         0 return $self->embed_file( $filename, $meta, $defs );
42             }
43              
44             # fs_load sets $opts->{_filesource}.
45 186         796 $opts->{fail} = "soft";
46 186 100       1836 my $lines = is_arrayref($filename) ? $filename
47             : fs_load( $filename, $opts );
48 186 50       1183 die( $filename, ": ", $opts->{error}, "\n" ) if $opts->{error};
49              
50             # Sense crd input and convert if necessary.
51 186 50 33     4998 if ( !(defined($options->{a2crd}) && !$options->{a2crd}) and
      66        
      33        
      66        
52             !$options->{fragment}
53 185     185   3178 and any { /\S/ } @$lines # non-blank lines
54 190     190   1890 and $options->{crd} || !any { /^{\s*\w+/ } @$lines ) {
55             warn("Converting $filename to ChordPro format\n")
56 0 0 0     0 if $options->{verbose} || !($options->{a2crd}||$options->{crd});
      0        
57 0         0 require ChordPro::A2Crd;
58 0         0 $lines = ChordPro::A2Crd::a2crd( { lines => $lines } );
59             }
60              
61 186   50     1391 $opts //= {};
62              
63             # Used by tests.
64 186         709 for ( "transpose", "transcode" ) {
65 372 100       2330 next unless exists $opts->{$_};
66 9         69 $config->{settings}->{$_} = $opts->{$_};
67             }
68 186         529 for ( "no-substitute", "no-transpose" ) {
69 372 100       1287 next unless exists $opts->{$_};
70 1         5 $options->{$_} = $opts->{$_};
71             }
72 186 50       923 bless $config => ChordPro::Config:: if is_plain_hashref($config);
73              
74 186         1016 my $linecnt = 0;
75 186         457 my $songs = 0;
76              
77 186         793 while ( @$lines ) {
78             my $song = ChordPro::Song->new($opts)
79             ->parse_song( $lines, \$linecnt,
80 214         26618 { %{dclone($meta)},
81 214   66     2312 "bookmark" => $opts->{bookmark} //= sprintf( "song_%d", 1 + @{ $self->{songs} } ),
  169         3195  
82             },
83             { %$defs } );
84              
85 214         1119 $song->{meta}->{songindex} = 1 + @{ $self->{songs} };
  214         1422  
86 214         486 push( @{ $self->{songs} }, $song );
  214         811  
87 214         598 $songs++;
88              
89             # Copy persistent assets to the songbook.
90 214 100       1384 if ( $song->{assets} ) {
91 4   50     79 $self->{assets} //= {};
92 4         8 while ( my ($k,$v) = each %{$song->{assets}} ) {
  11         55  
93 7 100 100     44 next unless $v->{opts} && $v->{opts}->{persist};
94 2         8 $self->{assets}->{$k} = $v;
95             }
96             }
97             }
98              
99 186 100       405 if ( @{$self->{songs}} > 1 ) {
  186         815  
100 16         60 my $song = $self->{songs}->[-1];
101 16 50 33     223 unless ( $song->{body}
102 16     16   204 && any { $_->{type} ne "ignore" } @{$song->{body}} ) {
  16         109  
103 0         0 pop( @{ $self->{songs} } );
  0         0  
104 0         0 $songs--;
105             }
106             }
107              
108 186 50 66     842 warn("Warning: No songs found in ", $opts->{_filesource}, "\n")
109             unless $songs || $::running_under_test;
110              
111 186         2685 return 1;
112             }
113              
114             sub add {
115 16     16 0 57 my ( $self, $song ) = @_;
116 16         62 push( @{$self->{songs}}, $song );
  16         92  
117 16         431 $self;
118             }
119              
120             sub embed_file {
121 0     0 0   my ( $self, $filename, $meta, $defs ) = @_;
122              
123 0 0         unless ( fs_test( sr => $filename ) ) {
124 0           warn("$filename: $! (skipped)\n");
125 0           return;
126             }
127 0           my $type = "pdf";
128              
129 0           my $song = ChordPro::Song->new( { filesource => $filename } );
130 0           $song->{meta}->{songindex} = 1 + @{ $self->{songs} };
  0            
131             $song->{source} =
132 0           { file => $filename,
133             line => 1,
134             embedding => $type,
135             };
136 0   0       my $title = $defs->{title} // $filename;
137 0           $song->{title} = $title;
138 0           $song->{meta}->{title} = [ $title ];
139 0           push( @{ $self->{songs} }, $song );
  0            
140 0 0         $song->dump(0) if $config->{debug}->{song};
141 0           return 1;
142             }
143              
144             # Used by HTML backend.
145             sub structurize {
146 0     0 0   my ( $self ) = @_;
147              
148 0           foreach my $song ( @{ $self->{songs} } ) {
  0            
149 0           $song->structurize;
150             }
151             }
152              
153             1;