#!/usr/bin/perl

#     Copyright © 2009-2010 by Nicola Vitacolonna. All rights reserved.
#
#     This program is free software: you can redistribute it and/or modify
#     it under the terms of the GNU General Public License as published by
#     the Free Software Foundation, either version 3 of the License, or
#     (at your option) any later version.
# 
#     This program is distributed in the hope that it will be useful,
#     but WITHOUT ANY WARRANTY; without even the implied warranty of
#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#     GNU General Public License for more details.
# 
#     You should have received a copy of the GNU General Public License
#     along with this program.  If not, see <http://www.gnu.org/licenses/>.

use strict;
use warnings;

#===============================================================================
# Start of configurable parameters
#===============================================================================
my $LILYPONDFOLDER = '/Applications'; # Custom path to Lilypond.app
my $TEX = 'pdflatex'; # TeX processor
my $LILYDIR = '-lily'; # Default suffix for directory for lily-XXX files
my $OUTPUTDIR = '-out'; # Default suffix for output directory
my $RSYNC = 'rsync'; # Path to the rsync executable
my $VERBOSE = 0; # Set to 1 for added verbosity
my $DEBUG = 0; # Set to 1 for debugging purposes only
# Custom vars by RobUr
my $DELTEMPDIRS = 1; # Set to 0 to keep temp dirs
my $NOTIFY = 0; # Set to 1 for Growl notification (requires growlnotify -> http://growl.info/extras.php)
#===============================================================================
# End of configurable parameters
#===============================================================================

$ENV{'PATH'} .= ':/Applications/LilyPond.app/Contents/Resources/bin';
if ($LILYPONDFOLDER) {
	$ENV{'PATH'} .= ':' . $LILYPONDFOLDER . '/LilyPond.app/Contents/Resources/bin:'
}
my $VERSION = '2.0.1';
my $RELEASE_DATE = '2010/8/20';
my $LILYPONDBOOK = 'lilypond-book'; # Executable
my $extra_options = ''; # Extra options to be passed to lilypond-book
$extra_options .= ' --verbose' if $VERBOSE;

print "This is LilyPond-Book engine $VERSION ($RELEASE_DATE) by Nicola Vitacolonna.\n";
print 'Filename: ' . $ARGV[0] . "\n" if $VERBOSE;
if ($DEBUG) {
	print 'Environment PATH: ' . $ENV{'PATH'} . "\n";
	print 'lilypond-book path: ' . `which $LILYPONDBOOK`;
	print "$TEX path: " . `which $TEX`;
	print "dvips path: " . `which dvips`;
	print "ps2pdf path: " . `which ps2pdf`;
	print 'rsync path: ' . `which rsync`;
	print `$RSYNC --version`;
}
if ($ARGV[0] =~ /(\\|\%)/) {
	print "Sorry, your filename must not contain the character '$1'.";
	print " Please rename your file.\n";
	exit(1);
}
my ($suffix) = ($ARGV[0] =~ /(\.(tex|ly|lytex|tely))$/);
unless (defined $suffix) {
	print "Sorry, the only valid suffixes for $ARGV[0] are .tex, .lytex, .tely and .ly.\n";
	exit(1);
}
my ($jobname) = ($ARGV[0] =~ /^(.+)$suffix$/);
die "Please give a non-empty name to your file." unless (defined $jobname);
if ($jobname =~ /\s/) {
	print "WARNING: '$jobname$suffix' contains spaces,";
	print "	which *may* cause trouble.";
	print "	If compilation fails, try to rename '$jobname$suffix'";
	print "	without using spaces.\n";
}

# Search for %! LILYPOND directives in the source file
if (open(SOURCE, '<', $ARGV[0])) {
	my $line;
	while ($line = <SOURCE>) {
		next if ($line =~ /^\s*$/);
		last if ($line !~ /^\s*%/); # Read until the first non-comment line is found
		if ($line =~ /^\s*%\s?!LILYPOND/) { # Parse %! LILYPOND directives
			my ($directive, $value) =
				($line =~ /^\s*%\s?!LILYPOND\s+(tex)\s*=\s*(.*)/);
			unless (defined $directive) {
				print "WARNING: skipping wrong directive: $line";
				next;
			}
			if ($directive eq 'tex') {
				$TEX = $value;
				# Filter potentially dangerous characters
				$TEX =~ s/[\;"'~&]//g;
				if ($TEX ne $value) {
					print "WARNING: stripped forbidden characters in ";
					print $line;
					print "The following characters are not allowed: \ ; \" ' ~ &\n";
				}
				# Strip leading and trailing spaces
				$TEX =~ s/^\s*//;
				$TEX =~ s/\s*$//;
				print "Set TeX processor to '$TEX'.\n" if $VERBOSE;
			}
		} # end if
	} # end while
	close(SOURCE);
}
else {
	print "WARNING: parsing $ARGV[0] for % !LILYPOND directives has failed." if $VERBOSE;
}

my @texfiles = ();
if (opendir(CURRDIR, '.')) {
	if (@texfiles = map { ($_ =~ /\.tex$/) ? $_ : () } readdir(CURRDIR)) {
		if (map { ($_ =~ /^$jobname\.tex$/) ? () : $_ } @texfiles) {
			print "WARNING: the source directory contains .tex files other than";
			print " the main source. If the main source includes those files,";
			print " lilypond-book will fail.";
			print " If you get an error, try changing their suffix to .lytex.\n";
		}
	}
}
else {
	print "ERROR: could not access the source directory.\n";
	exit(1);
}
# Create the output dir and copy all the needed files there
runbabyrun("mkdir -p $jobname$OUTPUTDIR");
my @exclude = ();
push(@exclude, '.*');
push(@exclude, '*.ly');
push(@exclude, '*.tex');
push(@exclude, "$jobname$OUTPUTDIR");
push(@exclude, "$jobname$LILYDIR");
push(@exclude, "$jobname.pdf");
my $filter = '';
foreach my $e (@exclude) {
	$filter .= "--filter 'exclude $e' ";
}
my $cp_opt = '-az';
$cp_opt .= 'v' if $VERBOSE;
my $rsync_cmd = "$RSYNC $cp_opt $filter ./ ./$jobname$OUTPUTDIR";
runbabyrun($rsync_cmd, 'rsync');

# Remove existing .tex and .ly files from the output directory
# (these may cause lilypond-book to fail with:
# "lilypond-book: error: Output would overwrite input file; use --output.")
system("cd $jobname$OUTPUTDIR && rm -f $jobname.tex *.ly");
if ($TEX eq 'pdflatex') {
	$extra_options .= ' --pdf';
}
# Run lilypond-book!
my $lily_cmd = "$LILYPONDBOOK --latex-program=$TEX --output \"$jobname$OUTPUTDIR\" --lily-output-dir \"$jobname$LILYDIR\" $extra_options \"$jobname$suffix\"";
print "Going to process $jobname$suffix ...\n";
runbabyrun($lily_cmd, 'lilypond-book',
	"Sorry, an error has occurred during the engraving process.\n");

# Typeset with (pdf|xe|)latex
if ($TEX eq 'xelatex') {
	$TEX .= ' --output-driver="xdvipdfmx -q -E"';
}
runbabyrun("cd $jobname$OUTPUTDIR && $TEX $jobname.tex", $TEX);
if ($TEX eq 'latex') {
	runbabyrun("cd $jobname$OUTPUTDIR && dvips -Ppdf $jobname.dvi", 'dvips');
	runbabyrun("cd $jobname$OUTPUTDIR && ps2pdf $jobname.ps", 'ps2pdf');
}
# Move output to the source directory
runbabyrun("cd $jobname$OUTPUTDIR && mv $jobname.pdf ../", 'mv',
	"Sorry, I could not move $jobname.pdf to the source directory.\n");
# Copy auxiliary files to the source directory.
# This is needed for further processing by tools like bibtex, makeindex, etc.
my @aux = ();
if (opendir(OUTDIR, "./$jobname$OUTPUTDIR")) {
	@aux = map { ($_ =~ /^$jobname\..+$/) ? $_ : () } readdir(OUTDIR);
	@aux = map { ($_ =~ /\.(tex|ly|lytex|tely|dep|bib)$/) ? () : $_ } @aux;
	closedir(OUTDIR);
}
else {
	print "ERROR: access to ./$jobname$OUTPUTDIR has failed.";
	exit(1);
}
# Quote filenames
@aux = map { (1) ? quote($_) : $_ } @aux;
print "Auxiliary files: @aux.\n" if $VERBOSE;
$cp_opt = '-pR';
$cp_opt .= 'v' if $VERBOSE;
system("cd $jobname$OUTPUTDIR && cp $cp_opt " . join(' ', @aux) . ' ../');
my $exit_status = verifySystemCall('cp');

# Delete output and lily dir
if ($DELTEMPDIRS == 1){
	print "Deleting temp dirs ...\n";
	my $rm_opt = '-Rf';
	$rm_opt .= 'v' if $VERBOSE;
	system("rm $rm_opt $jobname$OUTPUTDIR && rm $rm_opt $jobname$LILYDIR");
	$exit_status = verifySystemCall('rm');
}

print "Done";
print " (exit status: " . $exit_status . ')' if $VERBOSE;
print ".\n";

if (($NOTIFY == 1) && ($exit_status == 0)){
	system("growlnotify -n LilyPond -a $LILYPONDFOLDER/LilyPond.app -d Info -t \"" . $LILYPONDBOOK . "\" -m \"" . $jobname . ".pdf ready.\"");
}

exit($exit_status);

# verifySystemCall()
#
#  Usage    : my $exit_status = verifySystemCall($program_name);
#  Returns  : -1 if the program couldn't be run;
#             -2 if the program died (a msg is printed to stdout);
#             the exit code of the program, otherwise.
sub verifySystemCall {
	my $prog = shift;
	return -1 if ($? == -1);
    if ($? & 127) {
	    my $sig = ($? & 127);
	    my $core = ($? & 128) ? 'with' : 'without';
		print "$prog died with signal $sig, $core coredump.\n";
		return -2;
	}
	elsif (($? >> 8) != 0) {
		my $x = $? >> 8;
		return $x;
	}
	return 0;
}

# Runs a command and exits if the command fails.
#
#  runbabyrun($cmd): run the given command
#  runbabyrun($cmd, $prog): run the given command, use $prog as the name of the
#                           program in error messages
#  runbabyrun($cmd, $prog, $msg): also specify a custom error message
sub runbabyrun {
	my $cmd = shift;
	my $prog =  shift;
	$prog = ($cmd =~ /^(\w+)/) unless defined $prog;
	my $msg = shift;
	$msg = "Sorry, an error has occurred on running $prog.\n" unless defined $msg;
	print "Executing command: " . $cmd . "\n" if $DEBUG;
	system($cmd);
	my $exit_code = verifySystemCall($prog);
	if ($exit_code != 0) {
		print $msg;
		print "$cmd has exited with exit status $exit_code.\n" if $DEBUG;
		exit($exit_code);
	}
}

sub quote {
	my $str = shift;
	return "\"" . $str . "\"";
}
