#!/usr/bin/perl
# $Id: Setlist.cgi,v 1.6 2007-05-20 17:44:27 steve Exp $
# Setlist.cgi [options] infile... make the title index
# .../Setlist.cgi from web. make a setlist
#
) {
s/\n//;
my ($f, $t, $ttl) = split(/\|/, $_);
next unless $f;
push(@songlist, $f);
$timeMap{$f} = $t;
$titleMap{$f} = $ttl;
}
#print STDERR join(" ", @songlist) . "\n";
### do the specified operation
if ($op eq "add") {
# === error checking
push(@list, $name);
} elsif ($op eq "up") {
my $i;
for ($i = @list; $i--;) { last if $list[$i] eq $name; }
if ($i > 0) {
my $t = $list[$i];
$list[$i] = $list[$i - 1];
$list[$i - 1] = $t;
}
} elsif ($op eq "dn") {
my $i;
for ($i = @list; $i--;) { last if $list[$i] eq $name; }
if ($i >= 0 && $i < @list-1) {
my $t = $list[$i];
$list[$i] = $list[$i + 1];
$list[$i + 1] = $t;
}
} elsif ($op eq "del") {
my $i;
for ($i = @list; $i--;) { last if $list[$i] eq $name; }
delete(@list[$i]) if $i >= 0;
} elsif ($op eq "save" && -d "$songDir/Sets" && -w "$songDir/Sets") {
# should do name washing, error checking...
umask 2;
if (open(OUT, ">${songDir}Sets/$pageTitle.html")) {
print OUT "\n";
print OUT " \n";
print OUT " Set list: $pageTitle\n";
print OUT " \n";
print OUT " \n";
print OUT " Set list:";
print OUT " $pageTitle
\n";
print OUT " \n" . songLinks() . "\n
\n";
print OUT "
\n";
print OUT " list: $list\n";
# Here's the mostly-hidden form for re-editing the setlist:
print OUT join("\n",
( "\n"
);
print OUT " \n";
print OUT "\n";
close OUT;
$message = "wrote $pageTitle.html";
} else {
$message = "can't write $songDir/Sets/$pageTitle.html";
}
}
### Add up the times for the listed songs.
$totalTime = 0; # total time for list
$noTime = 0; # #songs with no time
$nSongs = 0;
for my $song (@list) {
my $t = $timeMap{$song};
if ($t =~ /([0-9]+)\:([0-9]+)/) {
$totalTime += ($1 * 60) + $2;
} else {
++$noTime;
}
++$nSongs;
}
$ttime = sprintf("%d:%02d", $totalTime/60, $totalTime%60);
### Build the page
#
# We're doing this *very* crudely with links, because it's too stupidly
# hard to do the right thing in the form with buttons. Note, however, that
# this means that you have to keep robots away from the page, otherwise
# you get a combinatorial explosion that will blow your site's bandwidth
# to smithereens. Be warned.
$content = "\n";
$content .= " \n";
$content .= (" Set list " .
($pageTitle? $pageTitle : "Maker") . "\n");
$content .= " \n";
$content .= " \n";
$content .= "Set list: "
. ($pageTitle? $pageTitle : "Maker") . "
\n";
# The form should be at the end if $ro is set
$content .= (" ");
$content .= $message . "\n";
$content .= "
\n";
$content .= " no songlist file $songlistFile\n" unless -f $songlistFile;
if (1) {
$content .= "\n";
for my $f (@list) {
my $t = $timeMap{$f}; $t = "" unless $t;
my $ttl = $titleMap{$f};
my $up = opLink($f, "up", "^");
my $dn = opLink($f, "dn", "v");
my $del= opLink($f, "del", "x");
$content .= " \n";
if (!$ro) {
$content .= " | $up $dn $del | \n"; # up, down, delete
$content .= " $f | \n"; # shortname
}
$content .= " $t | \n"; # time
if (-f "$songDir/$f.html") { # title link
$content .= " $ttl | \n";
} else {
$content .= " $ttl | \n";
}
$content .= "
\n";
}
$content .= (" " . ($ro? "" : " | | ") .
"
|
|
");
$content .= (" " . ($ro? "" : " | time | ") .
" $ttime | " .
" \ $nSongs songs " .
($noTime? "; \ \ $noTime untimed" : "") .
" | \n" .
"
\n");
$content .= "
\n";
}
# Real quick kludge -- list of all songs with add link.
# We could wrap it up in a big honking form and use submit buttons
# with name=name, value=song because the operation is always the same.
@songlist = sort(@songlist) if $sort;
if (!$ro) {
my $total = 0;
my $notime= 0;
$content .= "
\n";
$content .= "\n" if $cols > 0;
my ($td, $etd) = (($cols > 0? "| ": ""), ($cols > 0? " | ": ""));
my $c = 0;
for my $f (@songlist) {
my $t = $timeMap{$f}; $t = "" unless $t;
my $ttl = $titleMap{$f};
$total++;
$notime++ unless $t;
$content .= "\n" if $c == 0 && $cols > 0;
$content .= " $td" . opLink($f, "add", $f) . "$etd\n";
$content .= " | $t | \n" if $cols > 0;
if ($cols > 2 || $cols == 0) {
# no title. Otherwise it links to the song lyrics.
} elsif (-f "$songDir/$f.html") {
$content .= " $ttl | \n";
} else {
$content .= " $ttl | \n";
}
$c = ($c + 1) % $cols if $cols;
$content .= "
\n" if $c == 0 && $cols;
$content .= " | | \n" if $c && $cols;
}
$content .= "\n" if $c > 0 && $cols;
$content .= "
\n" if $cols;
$content .= "$total songs total; \ $notime without times.";
$content .= " \ Click a filename to add a song to the setlist.";
$content .= " \ Titles link to the songs' lyric pages, ";
$content .= "if I have permission to post them on this site.";
$content .= (" \ You can use the command line below to print" .
" a custom songbook for the set." );
$content .= "
\n";
$content .= "\n";
$content .= "for f in";
for my $f (@list) {
$content .= " $f";
}
$content .= "; do psselect -p1 \$f.ps | lpr; done";
$content .= "
\n";
}
# Here's the stuff we'll paste into LJ or some such:
$content .= "
\n";
# ok, eventually we'll quote this to make it really simple.
$content .= "Setlist links:
\n";
$content .= "\n"
. entityEncode("Set list:\n".
" $pageTitle" .
"\n")
. entityEncode("\n" . songLinks() . "
\n")
. "\n";
$content .= "link to this page:
\n";
$content .= ("" .
"\<a href='" . roURL() . "' \>$pageTitle\</a\>" .
"
");
# End of the content
$content .= "
\n";
$content .= ("" .
"$cgidir" .
"$cginame" .
"
\n");
$content .= " \n\n";
### Return the page:
print "Content-type: text/html\n";
print "Content-length: " . length($content) . "\n";
print "\n";
print $content;
exit 0;
sub songLinks {
my $content = '';
my $i = 1;
for my $f (@list) {
my $ttl = $titleMap{$f};
if (-f "$songDir/$f.html") {
$content .= " $i. $ttl";
} else {
$content .= " $i. $ttl";
}
$content .= "
\n";
++$i;
}
$content;
}
sub opLink {
my ($f, $op, $txt) = @_;
my $list=join("+", @list);
return ("$txt");
}
sub opURL {
my ($f, $op) = @_;
my $list=join("+", @list);
return ("$this?" .
($ro? "ro=$ro;" : "") .
($pageTitle? "title=$pageTitle;" : "") .
($sort? "sort=$sort;" : "") .
($f? "name=$f;" : "") .
($op? "op=$op;" : "") .
($cols? "cols=$cols;" : "") .
"list=$list");
}
# read-only URL for this setlist
sub roURL {
my ($base) = @_;
$$base = $this unless $base;
my $list=join("+", @list);
return ("$this?" .
"ro=1;" .
($pageTitle? "title=$pageTitle;" : "") .
($cols? "cols=$cols;" : "") .
"list=$list");
}
# entity encode (protect) a string
sub entityEncode {
my ($s) = @_;
$s =~ s/\&/&/gs;
$s =~ s/\>/>/gs;
$s =~ s/\</gs;
return $s;
}
### Setup: read songs to get their titles and times.
# It's assumed that the Makefile will do this to generate
# the songlist file (songlist.txt)
sub setup {
$i = 0;
foreach $infile (@ARGV) {
$title = "";
$subtitle = "";
$key = "";
$timing = "";
if ($infile !~ /\./) { $infile .= ".flk"; }
open(STDIN, $infile);
getTitle();
close(STDIN);
if ($verbose > 1) {
print STDERR " $infile: $timing\t$title\n";
}
if ($title) {
$fn = $infile;
$fn =~ s/\.[^.]*$//; # $fn is filename without extension
$fns{$title} = $fn; # fns maps title => fn
$titles{$fn} = $title; # titles maps fn => title
$subtitles{$fn} = $subtitle;
$keys{$fn} = $key;
$times{$fn} = $timing;
$titleList[$i] = $title;
$fnList[$i] = $fn;
$title =~ s/^A //;
$title =~ s/^The //;
$shortTitles[$i] = $title;
$fns{$title} = $title;
$i++;
}
}
if ($verbose) {
print STDERR "*** $i songs processed\n";
}
# Write a file of name|time|title on stdout
for $fn (@fnList) {
print $fn . "|" . $times{$fn} . "|" . $titles{$fn} . "\n";
}
}
### Process input in FlkTeX:
sub getTitle {
while () {
if (/^[ \t]*$/) { } # blank line
elsif (/^[ \t]*\%.*$/) { } # comment: ignore
# Variable-setting macros:
elsif (/\\begin\{song/) { begSong($_); } # Song
elsif (/\\subtitle/) { $subtitle = getContent($_); }
elsif (/\\key/) { $key = getContent($_); }
elsif (/\\category/) { $category = getContent($_); }
elsif (/\\dedication/) { $dedication = getContent($_); }
elsif (/\\license/) { $license = getContent($_); }
elsif (/\\timing/) { $timing = getContent($_); }
elsif (/\\created/) { $created = getContent($_); }
elsif (/\\notice/) { $notice = getContent($_); }
elsif (/\\cvsid/) { $cvsid = getContent($_); }
elsif (/\\music/) { $music = getContent($_); }
elsif (/\\lyrics/) { $lyrics = getContent($_); }
elsif (/\\arranger/) { $arranger = getContent($_); }
elsif (/\\performer/) { $performer = getContent($_); }
elsif ($title) { return; }
}
}
########################################################################
###
### Macro handlers:
###
### Each of the following routines handles a LaTeX macro.
###
### Separate verses.
sub sepVerse {
if ($vlines) { endVerse(); }
}
### Handle a blank line.
sub blankLine {
if ($vlines) { endVerse(); }
if ($plain) {
print "\n";
$plines = 0;
}
}
### Begin a song:
### Stash the title, put out the header.
sub begSong {
my ($line) = @_; # input line
$line =~ s/^.*song\}//;
$title = getContent($line);
}
########################################################################
###
### Block conversion:
###
### Each of these routines converts the start or end of a
### delimited block of lines to output format.
###
sub doHeader {
if ($html) { htmlHeader(); }
else { textHeader(); }
$header ++;
}
sub center {
# === need to handle multiple lines ===
my ($text) = @_;
$text =~ s/^[ \t]*//;
$text =~ s/[ \t]*\n$//;
$text =~ s/\\copyright/Copyright/;
my $w = $WIDTH - length($text);
for ( ; $w > 0; $w -= 2) { $text = " " . $text; }
print "$text\n";
}
sub hcenter {
my ($h, $text) = @_;
$text =~ s/^[ \t]*//;
$text =~ s/\\copyright/\©/;
$text =~ s/\n/\
/g;
$text = "$text";
print "$text\n";
}
sub textHeader {
center "$title\n";
if ($subtitle) { center "$subtitle\n"; }
if ($notice) { center "$notice\n"; }
if ($license) { center "$license\n"; }
if ($dedication) { center "$dedication\n"; }
print "\n";
}
sub htmlHeader {
hcenter 1, $title;
if ($subtitle) { hcenter 2, $subtitle; }
if ($notice) { hcenter 3, $notice; }
if ($license) { hcenter 3, $license; }
if ($dedication) { hcenter 3, $dedication; }
print "\n";
}
sub footer {
}
########################################################################
###
### Line conversion:
###
### Each of these routines converts a single line of mixed chords
### and text.
###
### Process the current line:
### Does any necessary dispatching.
sub doLine {
# Put out the header, if this is the very first line.
if (! $header) { doHeader(); }
if ($plain) {
if ($plines == 0) {
if ($html) { print "\n"; }
else { print "\n"; }
}
$_ = deTeX($_);
if ($html) { s/\~/ /g; } else { s/\~/ /g; }
s/\\newline/$NL/g;
s/\\\///g;
indentLine($_, $indent);
$plines ++;
} else {
if ($vlines == 0) { begVerse(); }
if ($tables) { print tableLine($_); }
else { print chordLine($_); }
$vlines ++;
}
}
### Put out a plain line, possibly indented.
sub indentLine {
my ($line, $indent) = @_;
$line =~ s/^[ \t]*//;
while ($indent--) { $line = " ".$line; }
print $line;
}
### Convert an ordinary line to chords + text
# === does not insert indent yet.
sub chordLine {
my ($line) = @_; # input line
my $cline = ""; # chord line
my $dline = ""; # dest. (text) line
my ($scol, $ccol, $dcol, $inchord, $inmacro) = ($indent, 0, 0, 0, 0);
my $c = ''; # current character
my $p = 0; # current position
$line = deTeX($line);
$line =~ s/^[ \t]*//;
$line =~ s/\\sus/sus/g;
$line =~ s/\\min/m/g;
for ($p = 0; $p < length($line); $p++) {
$c = substr($line, $p, 1);
if ($c eq "\n" || $c eq "\r") { break; }
if ($c eq '[') { $inchord ++; }
elsif ($c eq ']') { $inchord --; }
elsif ($c eq ' ') { if (!$inchord) { $scol ++; } }
elsif ($c eq "\t") {
if (!$inchord) { do {$scol ++; } while ($scol % 8); } }
else {
if ($inchord) {
while ($ccol < $scol) { $cline .= ' '; $ccol ++ }
$cline .= $c;
$ccol ++;
} else {
while ($dcol < $scol) { $dline .= ' '; $dcol ++ }
$dline .= $c;
$dcol ++;
$scol++;
}
}
}
# The result has a newline appended to it.
return (($cline eq "")? $dline : $cline . "\n" . $dline);
}
### Convert a line to a table
### When using tables, each line becomes a separate table.
### This, in turn, becomes a row in a table containing the verse.
sub tableLine {
}
### Convert a line to XML
sub xmlLine {
}
### Remove LaTeX constructs.
### This would be easier with a table.
sub deTeX {
my ($txt) = @_; # input line
while ($txt =~ /\%/) { # TeX comments eat the line break, too.
$txt =~ s/\%.*$//;
$txt .= ;
}
while ($txt =~ /\{\\em[ \t\n]/
|| $txt =~ /\{\\tt[ \t\n]/
|| $txt =~ /\{\\bf[ \t\n]/) {
# This will fail if there's a \bf and \em in one line in that order
if ($txt =~ /\{\\em[ \t\n]/) {
$txt =~ s/\{\\em[ \t\n]/$EM/;
while (! $txt =~ /\}/) { $txt .= ; }
$txt =~ s/\}/$_EM/;
}
if ($txt =~ /\{\\tt[ \t\n]/) {
$txt =~ s/\{\\tt[ \t\n]/$TT/;
while (! $txt =~ /\}/) { $txt .= ; }
$txt =~ s/\}/$_TT/;
}
if ($txt =~ /\{\\bf[ \t\n]/) {
$txt =~ s/\{\\bf[ \t\n]/$BF/;
while (! $txt =~ /\}/) { $txt .= ; }
$txt =~ s/\}/$_BF/;
}
}
$txt =~ s/\\&/$AMP/g;
$txt =~ s/\\;/$SP/g;
$txt =~ s/\\ /$SP/g;
$txt =~ s/\\ldots/.../g;
$txt =~ s/\\\\/$NL/g;
return $txt
}
### getContent(line): get what's between macro braces.
sub getContent {
my ($line) = @_; # input line
# Throw away everything up to the "{"
$line =~ s/^[^{]*\{//;
$line = deTeX($line);
# Suck in more lines if we haven't seen the closing brace
while ($line !~ /\}/) { $line .= ; $line = deTeX($line); }
# Throw away everything after the "}"
$line =~ s/\}[^}]*$//;
$line =~ s/\n$//;
return $line;
}
1;