diff options
Diffstat (limited to 'support/texdirflatten/texdirflatten')
-rwxr-xr-x | support/texdirflatten/texdirflatten | 411 |
1 files changed, 411 insertions, 0 deletions
diff --git a/support/texdirflatten/texdirflatten b/support/texdirflatten/texdirflatten new file mode 100755 index 0000000000..e820e182a4 --- /dev/null +++ b/support/texdirflatten/texdirflatten @@ -0,0 +1,411 @@ +#! /usr/bin/env perl + +# Use: +# $ perldoc texdirflatten +# to see embedded documentation. Alternatively, you can create manual +# or HTML pages using pod2man and pod2html. + +=pod + +=head1 NAME + +B<texdirflatten> - Collects all components of a (La)TeX file in a +single output directory -- i.e., flattens its hierarchy. + +=head1 SYNOPSIS + +texdirflatten [-1|--onetex] [-f|--file input.tex] [-o outputdir] + [--imgexts .ext1,.ext2,...] [--debug] [-V|--version] [-?|--help] + +=head1 DESCRIPTION + +This Perl script parses a LaTeX file recursively, scanning all child +files, and collects details of any included and other data files, such +as graphics and BiBTeX bibliography files. These component files, are +then all put into a single directory (thus "flattening" the document's +directory tree). This is useful in distributing manuscripts to +collaborators or in submitting to journals. + +=for comment +Run without parameters to see usage. + +=head1 OPTIONS + +=over + +=item B<--file>, B<-f> I<input.tex> + +Specifies input (La)TeX file. + +=item B<--onetex>, B<-1> + +If specified, produces a single TeX file by expanding all \input and +\include commands in place. + +=item B<--output>, B<-o> I<outputdir> + +Directory to collect all files. B<texdirflatten> will copy each source +file, graphics and bibliography file to this directory. It will be +created if it is unexistent. If unspecified, it defaults to C<flat/>. + +=item B<--imgexts> I<.ext1,.ext2[,...]> + +Prepends to the prioritized list of image extensions to search when +trying to find the image to copy to output folder. The first file with +the extension found will be copied and search stopped. Default order +is C<"", ".eps", ".pdf", ".pstex"> (note that it includes files with +no extension). If using pdflatex, one may want to add PDF before EPS +with C<--imgexts .pdf>. Don't forget the dot before the extension! + +=item B<--debug> + +Enables copious amounts of debugging output - useful if something is going wrong. + +=item B<--version>, B<-V> + +Displays the current version number and the usage and exits. + +=item B<--help>, B<-?> + +Show this manual page. + +=back + +=head1 EXAMPLES + +The following example scans C<manuscript.tex> in the current directory +and gathers it and all its components in the C<submit_01/> directory: + + $ texdirflatten -f manuscript.tex -o submit_01 + +=head1 CAVEATS + +Please take backups before running this command. No warranties +whatsoever provided. + +You may need to run C<epstopdf> on EPS files if you are using C<pdflatex>: + + $ for i in *.eps; do epstopdf $i; done + +=head1 BUGS + +Bug reports and patches are welcome. + +=head1 AUTHOR + +Cengiz Gunay <cengique<AT>users.sf.net> + +=head1 COPYRIGHT AND LICENSE + +Copyleft 2003-2017, Cengiz Gunay + +This library is free software; you may redistribute it and/or modify +it under the same terms as Perl itself. + +=cut + +# TODO: +# - parse BIBINPUTS environment variable and search figures and such +# there as well. +# - also copy bibtex style file + +package texdirflatten; + +use Getopt::Long qw(GetOptions); +use Pod::Usage; +use strict; +use warnings; +use re 'eval'; + +# Global +my $outputdir = "flat"; # Output directory +my $file = ""; # File to read +my @imgexts = ("", ".eps", ".pdf", ".pstex"); # Ordered list of image extensions to search and copy +my $imgexts_pre; # Prepend to @imgexts +my $onetex = 0; # Indicates all input and include commands be expanded +my $version = "1.3"; # 2017-02-02 +my $DEBUG = 0; # Enable debugging output +my $help; + +# parse helpers +my %texfiles; + +# GetOpt::Long:: +my $result = GetOptions ("output|o=s" => \$outputdir, + "onetex|1" => \$onetex, + "file|f=s" => \$file, + "imgexts=s" => \$imgexts_pre, + "debug" => \$DEBUG, + "version|V" => sub { print "texdirflatten version $version\n"; + pod2usage( -section => "SYNOPSIS"); }, + "help|?" => \$help ); + +# regexp for none or an even number of backslashes (\): +my $unescaped = + '(?<! \\\\)(?(?= \\\\) (?: \\\\\\\\)* )?'; + +# counter for files translated (like LyX does) +$::filecount = 0; + +pod2usage( -verbose => 2 ) if ($help); + +pod2usage( -section => "SYNOPSIS") if ($file eq ""); + +if ($imgexts_pre) { + unshift(@imgexts, split(/,/, $imgexts_pre)); + print "imgexts: ". join(",", @imgexts) . "\n" if $DEBUG; +} + +system "mkdir $outputdir" if (not -d $outputdir); + +# start recursing +parseTeX($file, "", popfile($file), 0); + +sub parseTeX { + my $file = shift; + my $inputdir = shift; + my $outfile = "$outputdir/" . shift; + my $level = shift; + + # Add a proper TeX suffix if it's missing + $outfile .= '.tex' if ! ($outfile =~ /\.tex\s*$/i ); + + my @flats; + my @longs; + my $popped; + + open my $FILE, $file or die "Cannot find file to read $file\n"; + + # Read to whole file first, and then scan for regexps + my $contents = ""; + while(<$FILE>) { + $contents = $contents . $_; + } + close $FILE; + + ## First, remove comments so that input commands inside are ignored. + # Also remove trailing newline and all whitespace at the beginning + # of the following line like TeX does it. Only for %'s following + # the proper backslash pattern. + $contents =~ s/( $unescaped )\%.*?\n[ \t]*/$1/gx; + + # Look for graphics include statement + if ($contents =~ /\\input\@path\{\{([^}]*)\}\}/) { + $inputdir = $1; + print "Found input directory: $inputdir\n" if $DEBUG; + } + + print "Parsing '$file' in directory '$inputdir'\n"; + + # Default value + #$inputdir = './' if (-z $inputdir); + + # three cases: graphics, inputs and bibs + + # an \includegraphics statement + @::flats = (); + @::longs = (); + #$::popped = ""; + + $contents =~ + s/\\includegraphics(\[[^\]]*\])?\{([^}]*)\} + (?{ $::popped = flattenfilename($2); + push @::longs, $2; push @::flats, $::popped }) + /\\includegraphics$1\{$::popped\}/gmx; + + if ($#::flats > -1) { + my ($long, $flat); + + foreach $long (@::longs) { + $flat = shift @::flats; + + # convert LyX directory dots + $long =~ s/\\lyxdot /./g; + $flat =~ s/\\lyxdot /./g; + + print "Looking for graphics: '$long'\n" if $DEBUG; + + my @dirs = ("", "$inputdir", $inputdir . '../figures/'); + + seekfile($long, $flat, \@imgexts, + ["", "$inputdir", $inputdir . '../figures/']); + + } + } + + # Must do bibs before input commands because, when expanded, they + # add new bibs inside. + replacebibs(\$contents, $inputdir); + + # an \input or \include statement + @flats = (); + @longs = (); + $contents =~ + s/\\(input|include)\{([^}]*)\}/"\\${1}\{" . + ($popped = flattenfilename($2) and push @longs, $2 and push @flats,$popped and $popped) + . "\}"/egm; + + if ($#flats > -1) { + #print "Found " . scalar @longs . " items on line: '@flats'.\n"; + + my ($long, $flat); + + foreach $long (@longs) { + $flat = shift @flats; + print "Found Input/Include: '$long'\n" if $DEBUG; + # recurse to parse that tex file unless it's done already + + if (not exists $texfiles{$flat}) { + if (! -r $long ) { + if (-r $inputdir . $long) { + $long = $inputdir . $long; + } elsif (-r "$long.tex") { + $long .= '.tex'; + } + } + $texfiles{$flat} = $long; + my $newcontents = parseTeX($long, basedir($long), $flat, $level + 1); #later + if ( $onetex ) { + # If specified, expand inclusion directives + die "Failed to find input/include to expand for '$flat'!\n" + if (! ( $contents =~ + s/\\(input|include) \{ \Q$flat\E \} + (?{ print "===Found \\$1\{$flat\} to expand.\n" if $DEBUG; }) + / ($1 eq 'include' ? "\\clearpage\n" : '') . "$newcontents"/gxem ) ); + } + } + } + } + + #print "Writing to \"$outfile\"\n" . + # "----------------------------------------\n" . + # "$contents\n" . + # "----------------------------------------\n"; + + if (! $onetex || $level == 0) { # do output at zero recurse level + # open flat output file + open my $COPY, ">$outfile" or die "Cannot write file $outfile\n"; + + # write to flat copy + print $COPY $contents; + + close $COPY; + } else { + return $contents; + } +} + +# Look for file to copy +sub seekfile { + my ($file, $dest, $exts, $dirs) = @_; + my @exts = @$exts; + my @dirs = @$dirs; + + OUT: foreach my $dir (@dirs) { + foreach my $ext (@exts) { + my $tryfile = "$dir$file$ext"; + if (-r $tryfile) { + # add the extension to $dest only if it hasn't have any dots + #(! ($dest =~ /\./)) ? $ext : ""; => better not to have this + my $dext = $ext; + + if (system("cp $tryfile $outputdir/$dest$dext") != 0) { + die "Cannot copy $tryfile!\n"; + } + # If we found it, then get out of the loop + print "Copied: '$tryfile' (ext: $ext) -> '$dest$dext'\n" if $DEBUG; + last OUT; + } + } + } +} + +# Return everything after the last / +sub popfile { + my $file = shift (@_); + + my @a = split /\//, $file; + + #print "Popping $file... split: @a ($#a), pop: " . pop(@a) . "\n"; + pop @a; +} + +# Make a new file by flattening directory names +sub flattenfilename { + local $_ = shift; + + # convert slashes to pluses, dots to Xs + tr|/|+|; + tr|\.|X|; + + # add a unique number at the beginning so files don't start with a + # dot + $_ = sprintf("%03d_", ++$::filecount) . "$_"; + + print "===Flattened file to '$_'\n" if $DEBUG; + + return $_; +} + +sub basedir { + my $file = shift (@_); + + my @a = split /\//, $file; + + #print "Popping $file... split: @a ($#a), pop: " . pop(@a) . "\n"; + pop @a; # remove file part + my $basedir = join('/', @a); # combine the rest with /'s + $basedir .= '/' if (! $basedir =~ /^$/); # add a trailing / if not empty + $basedir; +} + +# get the bibliography files +sub replacebibs { + my ($contentsref, $inputdir) = @_; + #my $contents = $$contentsref; + my $popped; + + #print "bib contents: $contents\n"; + + local @::flats = (); + local @::longs = (); + local @::refs; + + $" = ','; # set list item separator + # search for \bibliography statements + $$contentsref =~ + s/\\bibliography(\[[^\]]*\])? { + (?{ @::refs = (); #print "Found bibliography!\n"; #initialize ref list + }) (?: \s* ([^}, ]+)(?=[,}]),? + (?{ # for each word do: + #local (@_flats, @_longs, @_refs); + #print "Word: \"$+\", "; + $popped = flattenfilename($+); push @::longs, $+; + push @::flats,$popped; push @::refs, $popped; + }) )+ \s* } + (?{ #print "\n+++ $#::flats; $#::refs; $#::longs\n"; + #push @::flats, @_flats; push @longs, @_longs; push @refs, @_refs; + #print "Refs: @::refs\n"; + })/"\\bibliography" . ($1 || "") . "{@::refs}"/egmx;# && + #print "bib contents: $$contentsref\n"; + #print "\n*** $#::flats; $#::refs; $#::longs\n"; + $" = ' '; # restore list item separator + + if ($#::flats > -1) { + #print "Found " . scalar @::longs . " items on line: '@::flats'.\n"; + + my ($long, $flat); + + foreach $long (@::longs) { + $flat = shift @::flats; + print "Found bib. file: '$long'\n" if $DEBUG; + # recurse to parse that tex file unless it's done already + + # convert LyX directory dots + $long =~ s/\\lyxdot /./g; + + seekfile($long, "$flat", [".bib"], + ["", "$inputdir"]); + } + } +} |