#!/usr/bin/env perl
#
# latexdiff-vc - wrapper script for applying latexdiff to rcs managed files
# and for automatised creation of postscript or pdf from difference file
#
# Copyright (C) 2005-13 F J Tilmann (tilmann@gfz-potsdam.de)
#
# Repository: https://github.com/ftilmann/latexdiff
# CTAN page: http://www.ctan.org/tex-archive/support/latexdiff
#
#
# Contributors: S Utcke, H Bruyninckx
#
# 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 .
#
# Detailed usage information at the end of the file
#
# version 1.0.4alpha:
# - [ Patch #3486 contributed by cbarbu] add additional compilation for styles using chbar + avoid pdf/postscripts repeat in the code
# version 1.0.3: Bug fix: replace use of system('cp...') with File::Copy::copy (Patch contributed by D. Bremner)
# Quotes around system call file arguments to allow filenames with spaces (Patch contributed by ssteve)
# version 1.0.2: - option --so to use latexdiff-so
# version 1.0.1 (change version numbering to match that of latexdiff)
# - Option --fast to use latexdiff-fast,
# - git support (thanks to Bjorn Magnus Mathisen, Santi BĂ©jar, Pietro Battiston and Stefan Alfredson for patches) - UNTESTED
# version 0.25:
# - bbl is allowed as alternative extension (instead of .tex)
# version 0.26a
# - Bug fix: it copes now correctly with the possibility that there are no changes between current
# and archived version
use Getopt::Long ;
use Pod::Usage qw/pod2usage/ ;
use File::Temp qw/tempdir/ ;
use File::Basename qw/dirname/;
use File::Copy;
use strict ;
use warnings ;
my $versionstring=< 1);
# Variables
my ($file1,$file2,$diff,$diffbase,$answer,$options,$infile,$append,$dirname,$cwd);
my (@files,@ldoptions,@tmpfiles,@ptmpfiles,@difffiles,$extracomp); # ,
Getopt::Long::Configure('pass_through','bundling');
GetOptions('revision|r:s' => \@revs,
'cvs' => \$cvs,
'rcs' => \$rcs,
'svn' => \$svn,
'git' => \$git,
'dir|d:s' => \$dir,
'fast' => \$fast,
'so' => \$so,
'postscript|ps' => \$postscript,
'pdf' => \$pdf,
'force' => \$force,
'version' => \$version,
'help|h' => \$help);
$extracomp = join(" ",grep(/BAR/,@ARGV)); # special latexdiff options requiring additional compilation
if ( $help ) {
pod2usage(1) ;
}
if ( $version ) {
die $versionstring ;
}
if ( $so ) {
$latexdiff='latexdiff-so';
}
if ( $fast ) {
die "Cannot specify more than one of --fast or --so " if ($so);
$latexdiff='latexdiff-fast';
}
if ( $cvs ) {
$vc="CVS";
}
if ( $rcs ) {
die "Cannot specify more than one of --cvs, --rcs --svn or --git." if ($vc);
$vc="RCS";
}
if ( $svn ) {
die "Cannot specify more than one of --cvs, --rcs --svn or --git." if ($vc);
$vc="SVN";
}
if ( $git ) {
die "Cannot specify more than one of --cvs, --rcs, --svn or --git." if ($vc);
$vc="GIT";
}
# check whether the first file name or first passed-through option for latexdiff got misinterpreted as an option to an empty -r option
if ( @revs && ( -f $revs[$#revs] || $revs[$#revs] =~ /^-/ ) ) {
unshift @ARGV,$revs[$#revs];
$revs[$#revs]="";
}
# check whether the first file name or first passed-through option for latexdiff got misinterpreted as an option to an empty -d option
if ( defined($dir) && ( -f $dir || $dir =~ /^-/ ) ) {
unshift @ARGV,$dir;
$dir="";
}
#print "DEBUG: latexdiff-vc command line: ", join(" ",@ARGV), "\n";
$file2=pop @ARGV;
( defined($file2) && $file2 =~ /\.(tex|bbl)$/ ) or pod2usage("Must specify at least one tex or bbl file");
if (! $vc && scalar(@revs)>0 ) {
# have to guess $vc
# check whether we have a special name
if ( $0 =~ /-cvs$/ ) {
$vc="CVS";
} elsif ( $0 =~ /-rcs$/ ) {
$vc="RCS";
} elsif ( $0 =~ /-svn$/ ) {
$vc="SVN";
} elsif ( $0 =~ /-git$/ ) {
$vc="GIT";
} elsif ( -e "$file2,v" ) {
print STDERR "Guess you are using RCS ...\n";
$vc="RCS";
} elsif ( -d ".svn" ) {
print STDERR "Guess you are using SVN ...\n";
$vc="SVN";
} elsif ( -d ".git" ) {
print STDERR "Guess you are using GIT ...\n";
$vc="GIT";
} elsif ( -e "CVSROOT" || defined($ENV{"CVSROOT"}) ) {
print STDERR "Guess you are using CVS ...\n";
$vc="CVS";
} else {
print STDERR "Cannot figure out version control system, so I default to CVS\n";
$vc="CVS";
}
}
if (defined($dir) && $dir=~/^\.\/?/ ) {
print STDERR "You wrote -dir=. but you do not really like to do that, do you ?\n";
exit 10
}
if ( scalar(@revs)>0 ) {
if ( $vc eq "CVS" ) {
$diffcmd = "cvs diff -u -r";
$patchcmd = "patch -R -p0";
} elsif ( $vc eq "RCS" ) {
$diffcmd = "rcsdiff -u -r";
$patchcmd = "patch -R -p0";
} elsif ( $vc eq "SVN" ) {
$diffcmd = "svn diff -r ";
$patchcmd = "patch -R -p0";
} elsif ( $vc eq "GIT" ) {
$diffcmd = "git diff -r --relative --no-prefix ";
$patchcmd = "patch -R -p0";
# alternatively:
# $diffcmd = "git diff ";
# $patchcmd = "patch -R -p1";
} else {
print STDERR "Unknown versioning system $vc \n";
exit 10;
}
}
# make file list (last arguments), initial arguments have to be passed to latexdiff
# We assume an argument is a valid file rather than a latexdiff argument
# if it has extension .tex or .bbl
@files=($file2);
while( $file1=pop @ARGV ) {
if ( $file1 =~ /\.(tex|bbl)$/ ) {
# $file1 looks like a valid file name and is prepended to file list
unshift @files, $file1 ;
} else {
# $file1 looks like an option for latexdiff, push it back to argument stack
unshift @ldoptions, $file1 ;
}
}
if ( scalar(@revs) == 0 ) {
pod2usage("When -r option is not used, two .tex files (old and new) must be given on the command line") unless @files==2;
# compare two files
$file1=shift @files ;
}
if ( scalar(@revs) == 2 ) {
$append = "-diff$revs[0]-$revs[1]";
} elsif ( scalar(@revs) == 1 || $revs[0] ) {
$append = "-diff$revs[0]";
} else {
$append = "-diff";
}
if ( defined ($dir) && ! $dir ) {
# bare -d option => choose directory name
($dir=$append) =~ s/^-//;
}
if ( ($vc eq "SVN" || $vc eq "CVS") && scalar(@revs)) {
length($revs[$#revs]) > 0 or $revs[$#revs]="HEAD";
length($revs[0]) > 0 or $revs[0]="HEAD";
}
#exit ;
# cycle through all files
@difffiles=();
while ( $infile=$file2=shift @files ) {
print STDERR "Working on $infile \n";
if ( scalar(@revs) == 1 ) {
($file1=$infile) =~ s/\.(tex|bbl)/-oldtmp-$$.$1/ ;
push @tmpfiles,$file1;
# compare file with previous version ($revs[0]="") or specified version
### system("$diffcmd$revs[0] $infile| $patchcmd -o$file1") ;
if (system("$diffcmd$revs[0] \"$infile\" | $patchcmd -o\"$file1\"")==0 and -z $file1 ) {
# no differences detected, i.e. file is equal to current version
copy($infile,$file1) || die "copy($infile,$file1) failed: $!";
}
} elsif ( scalar(@revs) == 2 ) {
($file1=$infile) =~ s/\.(tex|bbl)/-oldtmp-$$.$1/ ;
$file2 =~ s/\.(tex|bbl)/-newtmp-$$.$1/ ;
push @tmpfiles,$file2;
if (system("$diffcmd$revs[1] $infile | $patchcmd -o$file2")==0 and -z $file2 ) {
copy($infile,$file2) || die "copy($infile,$file2) failed: $!";
}
if (system("$diffcmd$revs[0] $infile | $patchcmd -o$file1")==0 and -z $file1 ) {
copy($infile,$file1) || die "copy($infile,$file1) failed: $!";
};
}
if ( -z $file1 || -z $file2) {
print STDERR "One or both of the files to compare are empty. Possibly something went wrong in the retrieval of older versions. Aborting ...\n" ;
exit(10);
}
# Get name of difference file
if ( defined($dir) ) {
$diff="$dir/$infile" ;
} else {
($diff=$infile) =~ s/\.(tex|bbl)$/$append.$1/ ;
}
# make directories if needed
$dirname=dirname($diff) ;
system("mkdir -p $dirname") unless ( -e $dirname );
# Remaining options are passed to latexdiff
$options = join(" ",@ldoptions);
if ( -e $diff && ! $force ) {
print STDERR "OK to overwrite existing file $diff (y/n)? ";
$answer = ;
unless ($answer =~ /^y/i ) {
unlink @tmpfiles;
die "Abort ... " ;
}
}
print "Running $latexdiff\n";
unless ( system("$latexdiff $options \"$file1\" \"$file2\" > \"$diff\"") == 0 ) {
print STDERR "Something went wrong in $latexdiff. Deleting $diff and abort\n" ; unlink $diff ; exit(5)
};
print "Generated difference file $diff\n";
if ( ( $postscript or $pdf ) and !( scalar(@revs) && greptex( qr/\\document(?:class|style)/ , $diff ) ) ) {
# save filename for later processing if postscript or pdf conversion is requested and either two-file mode was used (scalar(@revs)==0) or the diff file contains documentclass statement (ie. is a root document)
push @difffiles, $diff ;
}
unlink @tmpfiles;
}
foreach $diff ( @difffiles ) {
chomp($cwd=(`pwd`));
if (defined($dir)) {
( $diff =~ s/$dir\/?// ) ;
chdir $dir ;
}
@ptmpfiles=();
( $diffbase=$diff) =~ s/\.(tex)$// ;
# adapt magically changebar styles to [pdftex] display driver if pdf output was selected
if ( $pdf ) {
system("sed \"s/Package\\[dvips\\]/Package[pdftex]/\" \"$diff\" > \"$diff.tmp$$\" ; \\mv \"$diff.tmp$$\" \"$diff\"");
$latexcmd = "pdflatex";
} elsif ( $postscript ) {
$latexcmd = "latex";
}
if ( $pdf | $postscript ) {
print STDERR "PDF: $pdf Postscript: $postscript cwd $cwd\n";
if ( system("grep -q \'^[^%]*\\\\bibliography\' \"$diff\"") == 0 ) {
system("$latexcmd --interaction=batchmode \"$diff\"; bibtex \"$diffbase\";");
push @ptmpfiles, "$diffbase.bbl","$diffbase.bbl" ;
}
# if special needs, as CHANGEBAR
if ( $extracomp ) {
# print "Extracomp\n";
system("$latexcmd --interaction=batchmode \"$diff\";");
}
# final compilation
system("$latexcmd --interaction=batchmode \"$diff\";"); # needed if cross-refs
system("$latexcmd \"$diff\";"); # final, with possible error messages
if ( $postscript ) {
my $dvi="$diffbase.dvi";
my $ps="$diffbase.ps";
system("dvips -o $ps $dvi");
push @ptmpfiles, "$diffbase.aux","$diffbase.log",$dvi ;
print "Generated postscript file $ps\n";
}
elsif ( $pdf ) {
push @ptmpfiles, "$diffbase.aux","$diffbase.log";
}
}
unlink @ptmpfiles;
chdir $cwd;
}
# greptex returns 1 if regex is not matched in filename
# 0 if there is a match
sub greptex {
my ($regex,$filename)=@_;
my ($i)=0;
open (FH, $filename) or die("Couldn't open $filename: $!");
while () {
next if /^\s*%/; # skip comment lines
if ( m/$regex/ ) {
close(FH);
return(0);
}
# only scan 25 lines
$i++;
last if $i>25 ;
}
close(FH);
return(1);
}
=head1 NAME
latexdiff-vc - wrapper script that calls latexdiff for different versions of a file under version management (CVS, RCS or SVN)
=head1 SYNOPSIS
B [ F ] [ F ] B<-r> [F] [B<-r> F] F [ F ...]
or
B [ F ] [ F ][ B<--postscript> | B<--pdf> ] F F
=head1 DESCRIPTION
I is a wrapper script that applies I to a
file, or multiple files under version control (CVS, RCS or SVN), and optionally runs the
sequence of C and C or C commands necessary to
produce pdf or postscript output of the difference tex file(s). It can
also be applied to a pair of files to automatise the generation of difference
file in postscript or pdf format.
=head1 OPTIONS
=over 4
=item B<--rcs>, B<--svn>, B<--cvs>, or B<--git>
Set the version system.
If no version system is specified, latexdiff-vc will venture a guess.
latexdiff-cvs and latexdiff-rcs are variants of latexdiff-vc which default to
the respective versioning system. However, this default can still be overridden using the options above.
=item B<-r>, B<-r> F or B<--revision>, B<--revision=>F
Choose revision (under RCS, CVS, SVN or GIT). One or two B<-r> options can be
specified, and they result in different behaviour:
=over 4
=item B -r F ...
compares F with the most recent version checked into RCS.
=item B -r F F ...
compares F with revision F.
=item B -r F -r F F ...
compares revisions F and F of F.
Multiple files can be specified for all of the above options. All files must have the
extension C<.tex>, though.
=item B F F
compares two files.
=back
The name of the difference file is generated automatically and
reported to stdout.
=item B<-d> or B<--dir> B<-d> F or B<--dir=>F
Rather than appending the string C and optionally the version
numbers given to the output-file, this will prepend a directory name C
to the
original filename, creating the directory and subdirectories should they not exist already. This is particularly useful in order to clone a
complete directory hierarchy. Optionally, a pathname F can be specified, which is prepended instead of C.
=item B<--fast> or B<--so>
Use C or C, respectively (instead of C).
=item B<--ps> or B<--postscript>
Generate postscript output from difference file. This will run the
sequence C on the difference file (do not use
this option in the rare cases, where three C commands are
required if you care about correct referencing). If the difference
file contains a C<\bibliography> tag, run the sequence C.
=item B<--pdf>
Generate pdf output from difference file using C. This will
run the sequence C on the difference file, or
C for files requiring bibtex.
=item B<--force>
Overwrite existing diff files without asking for confirmation. Default
behaviour is to ask for confirmation before overwriting an existing difference
file.
=item B<--help> or
B<-h>
Show help text
=item B<--version>
Show version number
=back
All other options are passed on to C.
=head1 SEE ALSO
L
=head1 PORTABILITY
I uses external commands and is therefore
limited to Unix-like systems. It also requires the RCS version control
system and latex to be installed on the system. Modules from Perl 5.8
or higher are required.
=head1 BUG REPORTING
Please submit bug reports using the issue tracker of the github repository page I,
or send them to I. Include the serial number of I
(option C<--version>)
.
=head1 AUTHOR
Copyright (C) 2005,2012 Frederik Tilmann
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License Version 3
Contributors: S Utcke, H Bruyninckx
=cut