#!/usr/bin/env perl
 
###
### (C) 2005 by Marco Danelutto (marcod@di.unipi.it)
### (C) 2007 by Francesco Nidito (nids@di.unipi.it)
###
### This  program is free  software; you  can redistribute  it and/or  modify it
### under the  terms of the GNU  Library General Public License  as published by
### the Free Software  Foundation; either version 2 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  Library General  Public
### License for more details.
 
###
### mktex: re-compiles  a latex file as  soon as it has  been modified refreshes
### the xdvi  display to  show the new  contents also  run bibtex, if  there are
### unresolved cite commands
###
 
# number of seconds to sleep before iterating
$SECITER = 1;
# default command to recompile
$TEXCMD = "latex";
# default command to bibtex
$bibcmd = "bibtex";
# default editor (if needed)
$EDITORCMD = "emacs";
 
###########################################################################
############### DO NOT MODIFY BELOW THIS LINE #############################
###########################################################################
 
# the arguments are the files that have to be observed
%filetimes = ();
 
# install signal handler to control finalization
$SIG{"INT"} = \&close_up;
$SIG{"HUP"} = \&rescan_files;
 
use Getopt::Std;
getopts("s:m:c:hpf");
 
# Usage ...
if($opt_h) {
    printusage();
    exit;
}
 
# the name of the latex command
if($opt_c) {
    $texcmd = $opt_c;
} else {
    $texcmd = $TEXCMD;
}
 
# how much seconds before iterating again ...
if($opt_s) {
    $secondi = $opt_s;
} else {
    $secondi = $SECITER;
}
 
# check for main file param
unless($opt_m) {
    # punish the user...
    print "Main file should be supplied (with or without extension)\n\n";
    # ...but teach him what to do!
    printusage();
    exit 1;
}
 
# mainfile without extension
if($opt_m =~ /(.*)\.tex/) {
    $mainfile = $1;
} else {
    $mainfile = $opt_m;
}
 
# mainfile to compile
$mainfiletex = $mainfile.".tex";
 
# look for include files ...
@files = ();
createfileslist($mainfile);
 
# get the pid of the xdvi process (must be already there ...)
$pidxdvi = getxdvipid($mainfile);
print "---> Updating xdvi view pid = $pidxdvi\n";
 
# main cycle to watch for modifications
while(true) {
    $recomp = 0;
    foreach $f (@files) {
	$t = (stat($f))[9];
	if($t != $filetimes{$f}) {
	    print "---> Recompile (due to $f)\n";
	    $filetimes{$f} = $t;  # adjust the modified file time
	    $recomp = 1;
	}
    }
    if($recomp == 1) {
	print "---> Recompiling $mainfiletex \n";
	recompile($mainfiletex);
	citations($mainfile);
	generate_warning($mainfile);
	$recomp = 0;
    } else {
	### print "---> No need to recompile\n";
    }
    sleep $secondi;
}
exit;
 
#### prints the usage
sub printusage {
    print "Usage is:\nmktex -m mainFileName [-c texcommand] [-s seconds] [-p] [-f]\n";
    print "\t-c cmd\trun cmd instead of latex (e.g. -c pdflatef) \n";
    print "\t-s sec\twait sec seconds before starting a new loop iteration\n";
    print "\t-p\tgenerate postscript when program terminates (via ^C)\n";
    print "\t-f\tgenerate PDF when program terminates (via ^C)\n";
}
 
#### throws away the list and create it back
sub createfileslist {
  # if it's not the first time let's recompile (maybe some file has changed)
  if( @files > 0 ) {
      print "---> Recompiling $mainfiletex \n";
      recompile($mainfiletex);
      citations($mainfile);
      generate_warning($mainfile);
  }
 
  @files = (); #cleans up the files list
  push( @files, $mainfiletex );
 
  parsefile($_[0]);
 
  # get initial times ...
  foreach $f (@files) {
    print "---> Observing file: $f\t";
    ### gather initial stats ...
    $wrtime = (stat($f))[9];
    print "last modified at $wrtime\n";
    $filetimes{$f} = $wrtime;
  }
}
 
#### recursively enters the refered files and provides a list of them
sub parsefile {
  my $file = shift;
  my $FILETEX;
 
  open $FILETEX, "cat $file.tex|"
    or die "Cannot open $file.tex\n";
 
  local $_;
  while(<$FILETEX>) {
    ### process tex files included with an \input directive
    if($_ !~ m/^\s*%/) {
      if($_ =~ m/\\(input|include){([^}]+)}/) {
	  $inpfile = $2;
	  $inpfiletex = $2.".tex";
 
	  # we do not want to observe files twice!
	  my $seen = grep {/$inpfiletex/} @files;
	  if( 0 == $seen ){
	      print "---> Includes $inpfiletex\n";
	      @files = (@files,$inpfiletex);
	      parsefile($inpfile)
	  }
      }
      ### process ps or pdf file included with an \includegraphics directive
      elsif($_ =~ m/\\includegraphics([^{}]*){([^}]+)}/) {
	$inpfile = $2;
 
	# we do not want to observe files twice!
	my $seen = grep {/$inpfile/} @files;
	if( 0 == $seen ){
	  print "---> Includegraphics $inpfile\n";
	  @files = (@files,$inpfile);
	}
      }
    }
  }
  close $FILETEX;
};
 
#### sub to analyze log for unresolved citations
sub citations {
    print "---> Looking for unresolved citations ...\n";
    $file = $_[0];
    open FD, "cat $file.log|" or die "cannot analyze $file.log\n";
    $bibbe = 0;
    while(<FD>) {
	if(/LaTeX Warning: Citation (.*) on page (.*)/) {
	    print "---> Citation $1 undefined -> running bibtex\n";
	    $bibbe = 1;
	}
    }
    close(FD);
    if($bibbe==1) {
	bibtex($file);
	### after bitexing file, compile it another 2 times to get the labels fixed
	recompile($file);
	recompile($file);
    }
    print "---> (unresolved citations) Done!\n";
}
 
#### subroutine to refresh xdvi preview
sub refresh {
    my $pidx = $_[0];
    print "---> KILL(USR1,$pidx)\n";
    kill(USR1,$pidx);
    return;
}
 
##### subroutine to recompile
sub recompile {
    print "---> Recompiling ... \n";
    my $file = $_[0]; ## file to recompile
    if(($pid=fork) == 0) {
	exec "$texcmd $file";
	print "---> Error while compiling $file with $texcmd";
	## continue in case of errors
    } else {
	waitpid($pid,0);
	## when finisehd, update screen view sending a SIGUSR1 to the xdvi process
	print "---> sending SIGUSR1 to $pidxdvi\n";
	### kill(USR1,$pidxdvi);
	refresh($pidxdvi);
    }
    print "---> (recompiling) Done!\n";
    return;
}
 
#### subroutine to bibtex
sub bibtex {
    print "---> Running bibtex ... \n";
    my $file = $_[0];
    if(($pid=fork) == 0) {
	exec "$bibcmd $file";
	print "---> Error while compiling $file with $bibcmd\n";
	## continue in case of errors
    } else {
	waitpid($pid,0);
    }
    print "---> (bibtex) Done!\n";
}
 
### subroutine to fetch xdvi pid
sub getxdvipid {
    print "---> Getting xdvi pid ... \n";
    my $pid = 0;
    my $file = $_[0];
    my $xdvi = "xdvi";
    open SYS, "uname|"
	or die "cannot uname\n";
    $line = <SYS>;
    chop $line;
    print "---> executing on $line\n";
    close SYS;
    if($line eq "Darwin") {
	print "---> changing xdvi exec name to xdvi.bin (Darwin)\n";
	$xdvi = "xdvi.bin";
    }
    open FD,"ps x|"
	or die "Cannot ps x\n";
    while(<FD>) {
	### print "---> PID: $_";
	if(/(\s*)([0-9]*)(.*)$xdvi(.*)$file(.*)/) {
	    print "---> PID matched: $_\n";
	    $pid = $2;
	    #last;
	}
    }
    close(FD);
    if($pid == 0) {
	if(($pid = fork) == 0) {
	    exec "xdvi $file";
	} else {
            # must recompute the $pid in case it is a child process of exec'ed process
	    # actually, must wait that the exec succeeded, to view its pid ... therefore
	    sleep 1;
	    # now go on ...
	    open FD,"ps x|"
		or die "Cannot ps x\n";
	    while(<FD>) {
		### print "---> PID: $_";
		if(/(\s*)([0-9]*)(.*)$xdvi(.*)$file(.*)/) {
		    print "---> PID matched: $_\n";
		    $pid = $2;
		    last;
		}
	    }
	    close(FD);
	}
    }
    print "---> (getting xdvi pid) Done!\n";
    return $pid;
}
 
### subroutine to handle program termination, depending on the flags passed,
### generates pdf or ps out of the dvi and shows the warning in the .log file
 
sub close_up {
    print "---> TERMINATION ... \n";
    if($opt_f) {
	generate_pdf($mainfile);
    }
    if($opt_p) {
	generate_ps($mainfile);
    }
    generate_warning($mainfile);
    exit(0);
}
 
### handles the rescan of the files
sub rescan_files {
  createfileslist($mainfile);
}
 
### generate pdf out of dvi
sub generate_pdf {
    print "---> GENERATING PDF ... \n";
    my $file = $_[0];
    system("dvipdf $file");
    return;
}
 
### generate ps out of dvi
sub generate_ps {
    print "---> GENERATING PS ... \n";
    my $file = $_[0];
    system("dvips $file.dvi -o");
    return;
}
 
### displays warnings in the log file
sub generate_warnings {
    print "---> GENERATING WARNINGS ... \n";
    print "=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=\n";
    my $file = $_[0];
    system("cat $file.log | grep arni");
    print "=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=\n";
    return;
}
 
sub generate_warning {
    print "---> GENERATING WARNINGS ... \n";
 
    my $file = $_[0];
    open FW, "cat $file.log|";
    my $first_border = 1;
    my $last_border = 0;
    while(<FW>) {
	if(/(.*)arning(.*)/) {
	    my $line = $_;
	    if($first_border == 1) {
		print "=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=\n";
	    }
	    print "=*=  $line";
	    $first_border = 0;
	    $last_border = 1;
	}
    }
    if($last_border == 1) {
	print "=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=\n";
    }
    return;
}
 
### fine lavori