Sponsoring The Perl Toolchain Summit 2025: Help make this important event another success Learn more

#!/usr/bin/perl
use strict;
sub generate_songbook {
my ($self, $sb, $options) = @_;
my @book;
foreach my $song ( @{$sb->{songs}} ) {
if ( @book ) {
push(@book, "") if $options->{'backend-option'}->{tidy};
push(@book, "-- New song");
}
push(@book, @{generate_song($song, $options)});
}
push( @book, "");
\@book;
}
my $single_space = 0; # suppress chords line when empty
my $lyrics_only = 0; # suppress all chords lines
sub generate_song {
my ($s, $options) = @_;
my $tidy = $options->{'backend-option'}->{tidy};
$single_space = $options->{'single-space'};
$lyrics_only = $options->{'lyrics-only'};
$s->structurize
if ( $options->{'backend-option'}->{structure} // '' ) eq 'structured';
my @s;
push(@s, "-- Title: " . $s->{title})
if defined $s->{title};
if ( defined $s->{subtitle} ) {
push(@s, map { +"-- Subtitle: $_" } @{$s->{subtitle}});
}
push(@s, "") if $tidy;
my $ctx = "";
foreach my $elt ( @{$s->{body}} ) {
if ( $elt->{context} ne $ctx ) {
push(@s, "-- End of $ctx") if $ctx;
push(@s, "-- Start of $ctx") if $ctx = $elt->{context};
}
if ( $elt->{type} eq "empty" ) {
push(@s, "***SHOULD NOT HAPPEN***")
if $s->{structure} eq 'structured';
push(@s, "");
next;
}
if ( $elt->{type} eq "colb" ) {
push(@s, "-- Column break");
next;
}
if ( $elt->{type} eq "newpage" ) {
push(@s, "-- New page");
next;
}
if ( $elt->{type} eq "songline" ) {
push(@s, songline($elt));
next;
}
if ( $elt->{type} eq "tabline" ) {
push(@s, $elt->{text});
next;
}
if ( $elt->{type} eq "chorus" ) {
push(@s, "") if $tidy;
push(@s, "-- Start of chorus*");
foreach my $e ( @{$elt->{body}} ) {
if ( $e->{type} eq "empty" ) {
push(@s, "");
next;
}
if ( $e->{type} eq "songline" ) {
push(@s, songline($e));
next;
}
}
push(@s, "-- End of chorus*");
push(@s, "") if $tidy;
next;
}
if ( $elt->{type} eq "tab" ) {
push(@s, "") if $tidy;
push(@s, "-- Start of tab");
push(@s, map { $_->{text} } @{$elt->{body}} );
push(@s, "-- End of tab");
push(@s, "") if $tidy;
next;
}
if ( $elt->{type} eq "verse" ) {
push(@s, "") if $tidy;
push(@s, "-- Start of verse");
foreach my $e ( @{$elt->{body}} ) {
if ( $e->{type} eq "empty" ) {
push(@s, "***SHOULD NOT HAPPEN***")
if $s->{structure} eq 'structured';
next;
}
if ( $e->{type} eq "songline" ) {
push(@s, songline($e));
next;
}
if ( $e->{type} eq "comment" ) {
push(@s, "-c- " . $e->{text});
next;
}
if ( $e->{type} eq "comment_italic" ) {
push(@s, "-i- " . $e->{text});
next;
}
}
push(@s, "-- End of verse");
push(@s, "") if $tidy;
next;
}
if ( $elt->{type} =~ /^comment(?:_italic|_box)?$/ ) {
push(@s, "") if $tidy;
push(@s, "-- $elt->{text}");
push(@s, "") if $tidy;
next;
}
if ( $elt->{type} eq "image" ) {
my @args = ( "image:", $elt->{uri} );
while ( my($k,$v) = each( %{ $elt->{opts} } ) ) {
push( @args, "$k=$v" );
}
foreach ( @args ) {
next unless /\s/;
$_ = '"' . $_ . '"';
}
push( @s, "+ @args" );
next;
}
if ( $elt->{type} eq "set" ) {
if ( $elt->{name} eq "lyrics-only" ) {
$lyrics_only = $elt->{value}
unless $lyrics_only > 1;
}
next;
}
}
push(@s, "-- End of $ctx") if $ctx;
\@s;
}
sub songline {
my ($elt) = @_;
my $t_line = "";
if ( $lyrics_only
or
$single_space && ! ( $elt->{chords} && join( "", @{ $elt->{chords} } ) =~ /\S/ )
) {
$t_line = join( "", @{ $elt->{phrases} } );
$t_line =~ s/\s+$//;
return $t_line;
}
unless ( $elt->{chords} ) {
return ( "", join( " ", @{ $elt->{phrases} } ) );
}
my $c_line = "";
foreach ( 0..$#{$elt->{chords}} ) {
$c_line .= $elt->{chords}->[$_] . " ";
$t_line .= $elt->{phrases}->[$_];
my $d = length($c_line) - length($t_line);
$t_line .= "-" x $d if $d > 0;
$c_line .= " " x -$d if $d < 0;
}
s/\s+$// for ( $t_line, $c_line );
return ( $c_line, $t_line );
}
1;