File Coverage

blib/lib/TeX/AutoTeX/File.pm
Criterion Covered Total %
statement 24 395 6.0
branch 0 234 0.0
condition 0 86 0.0
subroutine 8 32 25.0
pod 24 24 100.0
total 56 771 7.2


line stmt bran cond sub pod time code
1             package TeX::AutoTeX::File;
2              
3             #
4             # $Id: File.pm,v 1.36.2.5 2011/01/22 04:53:23 thorstens Exp $
5             # $Revision: 1.36.2.5 $
6             # $Source: /cvsroot/arxivlib/arXivLib/lib/TeX/AutoTeX/File.pm,v $
7             #
8             # $Date: 2011/01/22 04:53:23 $
9             # $Author: thorstens $
10             #
11              
12 2     2   11 use strict;
  2         3  
  2         700  
13             ### use warnings;
14 2     2   13 use Carp;
  2         4  
  2         203  
15              
16             our ($VERSION) = '$Revision: 1.36.2.5 $' =~ m{ \$Revision: \s+ (\S+) }x;
17              
18 2     2   12 use Scalar::Util qw(weaken);
  2         3  
  2         156  
19              
20 2     2   1193 use TeX::AutoTeX::Exception;
  2         6  
  2         17  
21 2     2   1586 use TeX::AutoTeX::HyperTeX;
  2         8  
  2         80  
22 2     2   1287 use TeX::AutoTeX::PostScript;
  2         5  
  2         71  
23 2         355 use TeX::AutoTeX::Config qw(
24             $CRYPT
25             $DVIPS
26             $TEXCHR
27             $DIRECTIVE_FILE
28             %TEX_BINARIES
29             $TEX_PATH
30 2     2   20 );
  2         5  
31              
32 2     2   1643 use arXiv::FileGuess qw(guess_file_type is_tex_type type_name);
  2         5514  
  2         13261  
33              
34             sub new {
35 0     0 1   my ($class, $fileset, $filename) = @_;
36              
37 0           my $self = {
38             flags => {},
39             dvi_flags => q{},
40             fileset => $fileset,
41             binaries => {%TEX_BINARIES},
42             untaint_regexp => qr/[^&\s;]+/,
43             };
44 0 0 0       if ($filename =~ /^($self->{untaint_regexp})$/ && $filename !~ /^\./) {
45 0           $self->{filename} = $1; # untaint
46             } else {
47 0           throw TeX::AutoTeX::InvNameException("Invalid filename: '$filename'.");
48             }
49 0           weaken $self->{fileset};
50 0           bless $self, $class;
51             }
52              
53             sub filename {
54 0     0 1   my $self = shift;
55 0           return $self->{filename};
56             }
57              
58             sub type {
59 0     0 1   my $self = shift;
60             # we cache the value
61 0 0         $self->{type} = $self->determine_type() if !defined $self->{type};
62 0           return $self->{type};
63             }
64              
65             sub determine_type {
66 0     0 1   my $self = shift;
67              
68 0 0         return 'TYPE_README' if $self->{filename} eq $DIRECTIVE_FILE;
69 0           my $fullname = "$self->{fileset}->{dir}/$self->{filename}";
70 0 0         return 'TYPE_DIRECTORY' if -d $fullname;
71 0           my ($type, $texformat, $error) = guess_file_type($fullname);
72 0 0         $self->{fileset}->{log}->error($error) if $error;
73 0 0         $self->{tex_format} = lc $texformat if defined $texformat;
74 0           return $type;
75             }
76              
77             sub filetype_name {
78 0     0 1   my $self = shift;
79 0           return arXiv::FileGuess::type_name($self->type());
80             }
81              
82             sub type_is_tex {
83 0     0 1   my $self = shift;
84 0           return arXiv::FileGuess::is_tex_type($self->type());
85             }
86              
87             sub assign_tex_priority {
88             # TS: 12/2010
89             # trying to solve the dependency tree in general is equivalent to the halting problem
90             # these are heuristics that work in practice on the material arXiv deals with,
91             # but they are also easily fooled
92              
93 0     0 1   my $self = shift;
94 0 0         if ($self->{flags}->{toplevelfile}){
95 0           return 4;
96             }
97             # FileGuess incorrectly identifies some files (especially style files)
98             # as tex files that aren't.
99 0 0 0       if (!$self->type_is_tex()
      0        
100             || $self->{filename} eq 'auto_gen_ps.log'
101             || $self->{filename} =~ /\.(?:sty|st|cls)$/i){
102 0           return 0;
103             }
104             # take advantage of logic in arXiv::FileGuess
105 0 0         if (0 <= index $self->{type}, 'priority2') {
    0          
    0          
106 0           return 1;
107             } elsif (0 <= index $self->{type}, 'priority') {
108 0           return 2;
109             } elsif ('_MAC' eq substr $self->{type}, -4) {
110 0           return 3;
111             }
112             # rate based on filename and contents
113 0 0         open(my $CURRENTFILE, '<', "$self->{fileset}->{dir}/$self->{filename}")
114             || $self->{fileset}->{log}->error("Could not open '$self->{filename}': $!");
115 0           my $docstycls = 0;
116             # grant higher priority to files that end with tex-related extension
117 0 0         $docstycls++ if $self->{filename} =~ /\.(?:la)?tex$/i;
118 0           while (<$CURRENTFILE>) {
119 0 0         if (/^\s*\\document(?:style|class)/){
120 0           $docstycls++;
121 0           last;
122             }
123             }
124 0           while (<$CURRENTFILE>) {
125 0 0         if (/^\s*\\begin\s*\{\s*document\s*\}/){
126 0           $docstycls++;
127 0           last;
128             }
129             }
130 0 0         close $CURRENTFILE or $self->{fileset}->{log}->verbose("couldn't close file: $!");
131 0           return $docstycls;
132             }
133              
134             sub type_override {
135 0     0 1   my ($self, $type) = @_;
136 0 0         if (my $override = $self->{fileset}->override($type)) {
137 0           $self->{type} = $override;
138 0           $self->{fileset}->{log}->verbose(<<"EOM");
139             Directive file has overriden $self->{filename}'s type to be '$type' ($override).
140             EOM
141 0           return;
142             }
143 0           $self->{fileset}->{log}->verbose(<<"EOW");
144             Directive file wanted to override $self->{filename}'s type as '$type',
145             but this type has no override.
146             EOW
147 0           return 1;
148             }
149              
150             sub set_flag {
151 0     0 1   my ($self, $flag) = @_;
152 0           return $self->{flags}->{$flag} = 1;
153             }
154              
155             sub check_flag {
156 0     0 1   my ($self, $flag) = @_;
157 0           return exists $self->{flags}->{$flag};
158             }
159              
160             sub set_dvi_flags {
161 0     0 1   my $self = shift;
162 0           return $self->{dvi_flags} = shift;
163             }
164              
165             sub get_dvi_flags {
166 0     0 1   my $self = shift;
167 0           return $self->{dvi_flags};
168             }
169              
170             sub process {
171 0     0 1   my ($self, $process) = @_;
172              
173 0           my $result;
174 0 0         if ($self->type_is_tex()) {
    0          
    0          
175 0           $result = $self->process_tex($process);
176             } elsif ($self->type() eq 'TYPE_ENCRYPTED') {
177 0           $result = $self->process_encrypted($process);
178             } elsif ($self->type() eq 'TYPE_DVI') {
179 0           $result = $self->process_dvitype($process);
180             }
181 0           return $result;
182             }
183              
184             sub process_tex {
185 0     0 1   my ($self, $process) = @_;
186              
187 0           my $log = $self->{fileset}->{log};
188 0           my $dir = $self->{fileset}->{dir};
189              
190 0           my $tex_passes = 4;
191 0           my ($new_type, $dont_hyper, $try_amslplain) =
192             TeX::AutoTeX::HyperTeX::copy_source_from_hyper(
193             $self->type(),
194             $self->{filename},
195             $dir,
196             $log,
197             $self->{fileset}->{local_hyper_transform}
198             );
199 0 0         if ($self->{type} ne $new_type) {
200 0           $log->verbose(<<"EOM");
201             Changing type from '$self->{type}' to '$new_type' on recommendation of
202             TeX::AutoTeX::HyperTeX::copy_source_from_hyper.
203             EOM
204 0           $self->{type} = $new_type;
205             }
206              
207 0 0         my $try_hyper = $dont_hyper ? 0 : !$process->{nohypertex};
208 0 0         $log->verbose('Will not attempt to use hypertex.') unless $try_hyper;
209              
210             # initalize @to_try with the different formats in the order we want to try them
211 0           my @to_try;
212 0 0 0       if ($process->{branch} eq '3' || $process->{branch} =~ m{texlive/}) {
    0          
213 0 0 0       if ($self->type() eq 'TYPE_LATEX' || $self->type() eq 'TYPE_LATEX2e') {
    0          
214 0 0         @to_try = map {$try_hyper ? ("h$_", $_) : $_ } qw(latex pdflatex tex);
  0            
215             } elsif ($self->type() eq 'TYPE_PDFLATEX') {
216 0 0         @to_try = map {$try_hyper ? ("h$_", $_) : $_ } qw(pdflatex tex);
  0            
217             } else {
218             # we always try tex, in case of amslplain or such
219 0 0         @to_try = $try_hyper ? qw(htex tex) : 'tex';
220             # we always try latex, not just tex, because sometimes latex papers are misidentified as tex
221 0 0         push @to_try, $try_hyper ? qw(hlatex latex) : 'latex';
222             }
223             } elsif ($process->{branch} eq '2') {
224             # deal with legacy stuff
225 0 0         if (exists $self->{tex_format}) {
    0          
    0          
226 0 0         if ($self->{tex_format} eq 'bigtex') {
    0          
    0          
    0          
227 0           $log->verbose('Using bigtex');
228 0           $self->{binaries}->{HTEX} = $self->{binaries}->{TEX} = $self->{binaries}->{BIGTEX};
229             } elsif ($self->{tex_format} eq 'biglatex') {
230 0           $log->verbose('Using biglatex');
231 0           $self->{binaries}->{HLATEX2E} = $self->{binaries}->{LATEX2E} = $self->{binaries}->{BIGLATEX};
232             } elsif ($self->{tex_format} eq 'latex209') {
233 0 0         @to_try = $try_hyper? qw(hlatex209 latex209) : 'latex209';
234             } elsif ($self->{tex_format} eq 'latex') {
235 0 0         @to_try = $try_hyper ? qw(hlatex2e hlatex209 latex2e latex209) : qw(latex2e latex209);
236             }
237             } elsif ($self->type() eq 'TYPE_LATEX') {
238 0 0         @to_try = $try_hyper ? qw(hlatex2e hlatex209 latex2e latex209) : qw(latex2e latex209);
239             } elsif ($self->type() eq 'TYPE_LATEX2e') {
240 0 0         @to_try = $try_hyper ? qw(hlatex2e latex2e) : 'latex2e';
241             }
242             # under some circumstances, what looked like latex files are actually
243             # tex files with amsart.
244 0 0         if ($try_amslplain) {
245 0 0         push @to_try, $try_hyper ? qw(hamslplain amslplain) : 'amslplain';
246             }
247 0 0         if (@to_try == 0) {
248             # we always try latex, not just tex, because sometimes latex papers are misidentified as tex
249 0 0         @to_try = map { $try_hyper ? ("h$_", $_) : $_ } qw(tex latex2e);
  0            
250             } else {
251 0 0         push @to_try, $try_hyper ? qw(htex tex) : 'tex';
252             }
253             } else {
254 0           $log->verbose('unknown TeX branch');
255             }
256              
257 0           my $failed = 0;
258 0           my %written = ();
259 0           foreach my $tex_type (@to_try) {
260 0 0         if ('h' eq substr $tex_type, 0, 1) {
261 0           $self->swap_source('hyper');
262             } else {
263 0           $self->swap_source('nohyper');
264             }
265              
266 0           my ($stime, $program, $old_format);
267             # get the appropriate program name from the variables set in TeX::AutoTeX::Config
268 0 0         if ($tex_type =~ /amslplain/) {
269 0           $old_format = $self->{tex_format};
270 0           $self->{tex_format} = 'amslplain';
271 0 0         if ('h' eq substr $tex_type, 0, 1) {
272 0           $program = $self->{binaries}->{HTEX};
273             } else {
274 0           $program = $self->{binaries}->{TEX};
275             }
276             } else {
277 0           $program = $self->{binaries}->{uc($tex_type)};
278             }
279 0 0         if (! $self->{fileset}->{utime}) {# set mtime and atime on all (non-dot-)files in CWD back 10 seconds
280 0           my $setbacktime = time() - 10;
281             ### {#TS: extensive logging
282             ### my @allfiles = glob("*");
283             ### local $" = "]\n\t[";
284             ### $log->verbose("current file contents:\n\t[@allfiles]");
285             ### }
286 0 0         opendir my $CDIR, $dir or $log->verbose("opening directory '$dir' for reading failed: $!");
287 0           my $numfiles = utime $setbacktime, $setbacktime,
288 0           map { "$dir/$_" }
289 0 0         map { /^($self->{untaint_regexp})$/o }
290 0           grep { !/^\./ && -f "$dir/$_" }
291             readdir $CDIR;
292 0 0         closedir $CDIR or $log->verbose("closing directory $dir failed: $!");
293             #TS Note:
294             # if we don't reset (a|m)time each time the loop processes a new file
295             # the .with_hyper and .without_hyper files will not have proper stat
296             # values and will be removed. if we globally reset (a|m)time, things go
297             # awry with inclusion checking. Therefore make sure to reset utime in
298             # swap_source for hyper files in HyperTeX.pm.
299 0           $self->{fileset}->{utime}++;
300             }
301 0           $stime = time;
302 0           $failed = $self->run_tex_attempt($program, $tex_passes, $process, $stime, \%written, $tex_type);
303 0 0         if ($tex_type =~ /amslplain/) {
304 0           $self->{tex_format} = $old_format;
305             }
306 0           $self->clean_aux_files($stime);
307 0 0         if (!$failed) {
308 0           my $logfile = $self->basename() . '.log';
309 0 0         unlink "$dir/$logfile" or
310             $log->error("Could not remove file '$logfile'.");
311 0           last;
312             }
313             }
314 0 0         if ($failed) {
315 0           $log->verbose("We failed utterly to process the TeX file '$self->{filename}'");
316             }
317             # ensure that no copies of the tex file are left behind
318 0           unlink map {"$dir/$self->{filename}.$_"} qw(with_hyper without_hyper);
  0            
319 0           return $failed;
320             }
321              
322             sub process_encrypted {
323 0     0 1   my ($self, $process) = @_;
324              
325 0           my $log = $self->{fileset}->{log};
326 0           my $dir = $self->{fileset}->{dir};
327 0           my $file = $self->{filename};
328              
329 0           $log->verbose( "Decrypting file '$file'");
330 0           my $newfile = $file;
331 0 0         if ('.cry' ne lc substr $newfile, -4, 4, q{}) {
332             # throw exception
333 0           $log->error("filename '$file' does not end in '.cry'.");
334             }
335              
336             # arXiv specific
337 0           my $key = $process->{decryption_key};
338 0           $log->verbose("running: '$CRYPT $newfile $key'");
339              
340             # ensure proper path so that correct programs decry and cipher are found
341 0 0         if (index($ENV{PATH}, $TEX_PATH) != 0) {
342 0           local $ENV{PATH} = "$TEX_PATH/bin:" . $ENV{PATH};
343             }
344 0           my $fullname = "$dir/$newfile";
345             # The following two lines are necessary to get decry to work
346             {
347 0           open my $OUTFH, '>', $fullname; close $OUTFH;
  0            
  0            
348 0           chmod oct(666), $fullname;
349             }
350 0           $log->verbose("path is: '$ENV{PATH}'");
351 0   0       my $response = `$CRYPT $fullname $key` || "[no response, exit code $?]";
352 0 0         if ($?) {
353 0           $log->error("$CRYPT error response: '$response'");
354             }
355 0 0 0       if (! -T $fullname || -z _) {
356 0           $log->error("'$file' didn't decrypt to a text file.");
357             }
358 0 0         unlink "$dir/$file" or $log->error("unable to remove '$file': $!");
359 0           return;
360             }
361              
362             sub process_dvitype {
363 0     0 1   my ($self, $process) = @_;
364              
365 0           my $log = $self->{fileset}->{log};
366 0           my $dir = $self->{fileset}->{dir};
367 0           my $file = $self->{filename};
368              
369 0 0         my $dvi_flags = $process->{branch} =~ m{texlive/}? '-R2' : '-R';
370 0 0         $dvi_flags .= " $self->{dvi_flags}" if $self->{dvi_flags};
371 0 0         $dvi_flags .= ' -t landscape' if $self->{flags}->{landscape};
372 0 0         $dvi_flags .= $self->{flags}->{keepcomments}? ' -K0' : ' -K1';
373              
374 0           my $response;
375 0 0         my $setenv = qq{export HOME=/tmp @{[$ENV{TEXMFCNF}? "TEXMFCNF=$ENV{TEXMFCNF}": q{}]} PATH=$process->{tex_env_path}};
  0            
376 0           my $crdir = substr $dir, length $TEX_PATH;
377 0           while (1) {
378 0           $log->verbose(" ~~~~~~~~~~~ Processing file '$file'");
379 0           my $dvipscommand = qq#$TEXCHR $TEX_PATH "($setenv; cd $crdir && $DVIPS $dvi_flags -z '$file' -o )" 2>&1#;
380 0           $log->verbose('Running: ' . substr $dvipscommand, length "$TEXCHR $TEX_PATH ");
381 0           my $dvipstime = time;
382 0           $response = `$dvipscommand`;
383 0 0         last if !$?;
384 0           $log->verbose("$DVIPS $dvi_flags -z produced an error: $?\nResponse was $response\nRetrying without '-z'");
385 0 0 0       if (-e "$dir/head.tmp" && -e "$dir/body.tmp") {
386 0           unlink "$dir/head.tmp", "$dir/body.tmp";
387 0           $log->verbose('removed dvips leftover head.tmp and body.tmp');
388             }
389             # dvips -z may have core-dumped. remove only newly generated core file(s)
390 0 0         if (my @corefiles = glob "$dir/core\.[0-9]*") {
391 0           @corefiles =
392 0 0         map { m/(.*)/ }
393 0           grep { m{^$dir/core\.\d+$} && (stat "$dir/$_")[9] >= $dvipstime }
394             @corefiles;
395 0 0         if (@corefiles) {
396 0           unlink @corefiles;
397 0           $log->verbose("removed one or more core files: @corefiles");
398             }
399             }
400 0           $dvipscommand = qq#$TEXCHR $TEX_PATH "($setenv; cd $crdir && $DVIPS $dvi_flags '$file' -o )" 2>&1#;
401 0           $log->verbose('Running: ' . substr $dvipscommand, length "$TEXCHR $TEX_PATH ");
402 0           $response = `$dvipscommand`;
403 0 0         last if !$?;
404 0           $log->verbose("$DVIPS $dvi_flags produced an error: $?\nResponse was $response.");
405 0           $log->error('Failed to produce postscript from dvi.');
406             }
407 0           $log->verbose("dvi(h)ps said ...\n$response.");
408              
409 0           my %commondvipsheaders;
410 0           @commondvipsheaders{qw(
411             tex.pro
412             texc.pro
413             texps.pro
414             hps.pro
415             special.pro
416             color.pro
417             finclude.pro
418             alt-rule.pro
419             head.tmp
420             body.tmp
421             8r.enc
422             texnansi.enc
423             )} = ();
424              
425 0           while ($response =~ m{<(?:\.//?)*([^><\n]+)>}g) {
426 0           my $included = $1;
427 0 0         if ($included !~ m{^/}){
428 0 0 0       if (-e "$dir/$included") {
    0          
429 0           $self->{fileset}->new_File($included)->set_flag('used_by_dvips');
430 0           $log->verbose("'$included' no longer required ... it's in the postscript file.");
431             } elsif (!(exists $commondvipsheaders{$included} || '.pfb' eq substr $included, -4)) {
432 0           $log->verbose("'$included' was apparently included, but cannot be deleted, because it cannot be found in cwd.");
433             }
434             }
435             }
436 0           my $psfile = $file;
437 0           substr $psfile, -3, 3, 'ps';
438             # Change %%Title if wanted
439 0 0         if (my $stampref = $process->get_stamp()) {
440 0           TeX::AutoTeX::PostScript::fix_ps_title(
441             $psfile,
442             $dir,
443             $stampref->[0],
444             $log
445             );
446 0           TeX::AutoTeX::PostScript::stamp_postscript(
447             $psfile,
448             $dir,
449             $stampref,
450             $log
451             );
452             }
453 0           $self->{fileset}->new_File($psfile)->set_flag('main_postscript');
454              
455 0           return;
456             }
457              
458             sub slurp_log {
459 0     0 1   my $self = shift;
460 0           my $log = $self->basename() . '.log';
461              
462 0 0         open(my $LOG, '<', "$self->{fileset}->{dir}/$log")
463             || $self->{fileset}->{log}->error("Could not open log file '$log' produced by (la)tex.");
464 0           local $/ = undef;
465 0           my $log_contents = <$LOG>;
466 0           close $LOG;
467 0           return \$log_contents;
468             }
469              
470             sub clean_aux_files {
471 0     0 1   my ($self, $stime) = @_;
472              
473 0 0         opendir(my $WORKING_DIR, $self->{fileset}->{dir})
474             || $self->{fileset}->{log}->error("Can't open processing directory: $!");
475 0 0 0       if (my @auxfiles =
  0 0          
476             grep { /\.aux$/ && -f "$self->{fileset}->{dir}/$_" && (stat(_))[9] >= $stime }
477             readdir $WORKING_DIR) {
478             my $numauxfiles = unlink(
479             map { "$self->{fileset}->{dir}/$_" }
480 0   0       map { m/(.*)/ }
481             @auxfiles
482             )
483             || $self->{fileset}->{log}->error("Could not remove one of the auxfiles: @auxfiles.");
484 0           $self->{fileset}->{log}->verbose("unlinked $numauxfiles '.aux' files");
485             }
486 0           closedir($WORKING_DIR);
487 0           return;
488             }
489              
490             sub swap_source {
491 0     0 1   my ($self, $hyper) = @_;
492              
493 0           my $file = $self->{filename};
494 0           my $dir = $self->{fileset}->{dir};
495              
496             # remove aux files if any
497 0           my $basename = $self->basename();
498 0           foreach my $auxfile (grep {-e "$dir/$_"} map {"$basename.$_"} qw(aux lot lof toc)) {
  0            
  0            
499 0 0         unlink "$dir/$auxfile" or $self->{fileset}->{log}->error("failed to remove '$auxfile'.");
500 0           $self->{fileset}->{log}->verbose("removed aux file '$auxfile'");
501             }
502              
503             # remove the existing file to be processed, then link the
504             # with/without hyper version to it
505 0 0         unlink "$dir/$file" or $self->{fileset}->{log}->error("failed to remove '$file'.");
506              
507             # note that (hard-)linking does not change atime or mtime
508 0 0         if ($hyper eq 'hyper') {
509 0 0         link "$dir/$file.with_hyper", "$dir/$file"
510             or $self->{fileset}->{log}->verbose("failed to rename '$file'");
511             } else {
512 0 0         link "$dir/$file.without_hyper", "$dir/$file"
513             or $self->{fileset}->{log}->verbose("failed to rename '$file'");
514             }
515 0           return;
516             }
517              
518             sub run_tex_attempt {
519 0     0 1   my $self = shift;
520 0           my ($program, $tex_passes, $process, $stime, $written, $tex_type) = @_;
521              
522 0           my $log = $self->{fileset}->{log};
523              
524 0 0         if (!defined $TEXCHR) {
525 0           throw TeX::AutoTeX::TexChrException('TEXCHR not set.');
526             }
527 0 0 0       if ($process->{branch} eq '2' && !defined $ENV{TEXMFCNF}) {
528 0           throw TeX::AutoTeX::TexMFCnfException('TEXMFCNF not set for /2 branch.');
529             }
530             #TS: find the source of STDIN input to be used. this was historically used for
531             # macros which required user input -- e.g. big or little (b/l)
532 0           my $latex_input = $self->basename() . '.inp';
533 0 0         my $feeder = -e "$self->{fileset}->{dir}/$latex_input" ? qq{'$latex_input'} : '/dev/null';
534              
535 0   0       my $tex_format = $self->{tex_format} || q{};
536 0           my $escaped_tex_format = q{};
537              
538 0 0 0       if (!$tex_format && $tex_type eq 'htex') {
539 0           $tex_format = 'htex';
540             }
541 0 0 0       if ($tex_format !~ /209$/ && $tex_type =~ /209/) {
542 0           $tex_format .= '209';
543             }
544 0 0 0       if ($tex_format =~ /^latex209/ && $program =~ /^h/) {
545 0           $tex_format = 'hlatex209';
546             }
547 0 0         if ($tex_format){
548             ## TS: FIXME
549             ## static lookup table instead of (convoluted) regexp. should go into
550             ## TeX::AutoTeX::Config? here is the list of all formats available in
551             ## arXiv's tex installation. For texlive 2009 and newer arXiv doesn't
552             ## build custom formats any longer.
553 0           my %known_formats;
554 0           @{$known_formats{'3'}}{qw(
  0            
555             amstex
556             htex
557             tex
558             latex
559             biglatex
560             pdfamstex
561             pdflatex
562             pdftex
563             )} = ();
564              
565 0           @{$known_formats{'2'}}{qw(
  0            
566             amslatex1.1
567             amslplain
568             amstex
569             biglatex
570             bigtex
571             cp-aa
572             hlatex209
573             hlatex2e
574             hlatex
575             hlplain
576             hplain
577             htex
578             latex209
579             latex2e
580             latex
581             lplain
582             plain
583             tex
584             texsis
585             )} = ();
586              
587 0 0         if (exists $known_formats{$process->{branch}}{$tex_format}) {
588 0           $log->verbose("Using format file $tex_format");
589 0           $escaped_tex_format = q{&} . $tex_format;
590             # escape (&) in $tex_format and remove double quotes
591             # TS: where would those come from after the lookup table replaced older code?
592 0           $escaped_tex_format =~ s/&/\\\&/g;
593 0           $escaped_tex_format =~ s/"//g; # "
594             } else {
595 0           $log->verbose("'$tex_format' is not a valid TeX format; will ignore.");
596             }
597             }
598              
599 0 0         my $setenv = qq{export HOME=/tmp @{[$ENV{TEXMFCNF}? "TEXMFCNF=$ENV{TEXMFCNF}": q{}]} PATH=$process->{tex_env_path}};
  0            
600 0 0         $log->verbose(qq{TEXMFCNF is @{[$ENV{TEXMFCNF}? "set to: '$ENV{TEXMFCNF}'": 'unset.']}});
  0            
601 0           my $crdir = substr $self->{fileset}->{dir}, length $TEX_PATH;
602 0           my $runtexcommand = qq#$TEXCHR $TEX_PATH "($setenv; cd $crdir && $program $escaped_tex_format '$self->{filename}' < $feeder)" 2>&1#;
603              
604 0           my $passes = 0;
605 0           my $rerun = 0;
606 0           my $extra_pass = 0;
607 0           my $xfontcreate = 0;
608 0           my $lastlog_ref;
609             my $failed;
610              
611 0           my @ORDER = qw(first second third fourth fifth sixth seventh);
612              
613             PASSES:
614 0           while ($passes < $tex_passes) {
615 0           $log->verbose(" ~~~~~~~~~~~ Running $tex_type for the $ORDER[$passes] time ~~~~~~~~");
616 0           $log->verbose('Running: ' . substr $runtexcommand, length "$TEXCHR $TEX_PATH ");
617 0           my $out = `$runtexcommand`;
618 0           $log->verbose($out);
619 0           $lastlog_ref = $self->slurp_log();
620              
621             # TS: This is due to peculiarities of feynmf and similar dynamical font
622             # creation. If we get an error exit status from latex then we need to
623             # check for new font files and possibly rerun. Only do this once, in
624             # case the non-zero exit status is due to some problem other than
625             # font-creation and persistent, otherwise this could loop indefinitely.
626 0 0         if ($?) {
627 0 0 0       if (!$xfontcreate && $self->extra_fontcreation_pass($stime)) {
628 0           $xfontcreate++;
629 0           redo PASSES;
630             } else {
631             #the message below is slightly misleading because $program for
632             #latex2e hyper/nohyper is the same.
633 0           $log->verbose("$program '$self->{filename}' failed.");
634 0           $self->trash_tex_aux_files($stime, $written);
635 0           my $dvi = $self->basename() . '.dvi';
636 0 0         if (-e "$self->{fileset}->{dir}/$dvi") {
637 0           $log->verbose("removing leftover dvi file '$dvi'");
638 0 0         unlink "$self->{fileset}->{dir}/$dvi" or
639             $log->verbose("Could not remove file '$dvi'.");
640             }
641 0           $failed = 1;
642 0           last;
643             }
644             }
645              
646 0 0 0       if ($passes == 0 && ($tex_type eq 'hlatex2e' ||
      0        
647             $tex_type eq 'latex2e' ||
648             $tex_type =~ /h?pdflatex/o)) {
649 0 0         if (0 <= index ${$lastlog_ref}, 'LaTeX Warning: Writing file `') {
  0            
650 0           while(${$lastlog_ref} =~ /LaTeX Warning: Writing file \`([^']*)\'\./g) { # ')){
  0            
651 0           $written->{$1}++;
652             }
653             }
654             }
655              
656             # TS: added $tex_format b/c otherwise &amslplain will only be processed twice
657 0 0 0       if ($tex_type =~ /latex/i || $tex_format) {
658 0 0 0       if (0 <= index(${$lastlog_ref}, q{Label(s) may have changed. Rerun}) ||
  0   0        
  0   0        
659 0           0 <= index(${$lastlog_ref}, q{Warning: Citation(s) may have changed.}) ||
660 0           0 <= index(${$lastlog_ref}, q{Table widths have changed. Rerun LaTeX.}) ||
661             0 <= index(${$lastlog_ref}, q{Rerun to get citations correct.})
662             ) {
663 0           $rerun = 1;
664             } else {
665 0           $rerun = $self->extra_pass($stime);
666             }
667             } else {
668 0           $rerun = 0;
669             }
670             # TS: this seems to be contraproductive to the $tex_passes=4 for &amslplain
671             # and possibly others
672 0 0 0       last unless $rerun || $passes == 0;
673 0           $passes++;
674             } # End of while ($passes < $tex_passes)
675              
676 0 0 0       if ($passes == $tex_passes && $rerun) {
677 0           $log->verbose("WARNING: Reached max number of passes, possibly failed to get CROSS-REFERENCES right.");
678             }
679 0           $self->trash_tex_aux_files($stime, $written);
680 0           return $failed;
681             }
682              
683             sub extra_pass {
684 0     0 1   my ($self, $younger_than) = @_;
685              
686 0           opendir my $CDIR, $self->{fileset}->{dir};
687 0 0 0       my $tocloflot =
688 0           grep {/\.(?:toc|lof|lot)$/ && -f "$self->{fileset}->{dir}/$_" && ((stat(_))[9] >= $younger_than)}
689             readdir $CDIR;
690 0           closedir $CDIR;
691 0 0         if ($tocloflot) {
692             #TS: here a toc/lof/lot will always lead to max number of runs
693             # this is more robust than attempting to keep it to minimum
694             # in particular since a long toc etc. can lead to a shift between
695             # 3rd and 4th pass without any indication of such in the log
696              
697 0           $self->{fileset}->{log}->verbose('LaTeX wrote a .toc, .lof, or .lot file - running extra passes');
698 0           return 1;
699             }
700 0           return 0;
701             }
702              
703             sub extra_fontcreation_pass {
704 0     0 1   my ($self, $younger_than) = @_;
705              
706 0           opendir my $CDIR, $self->{fileset}->{dir};
707 0 0 0       my $mftfm =
708 0           grep {/\.(?:mf|tfm)$/ && -f "$self->{fileset}->{dir}/$_" && ((stat(_))[9] >= $younger_than)}
709             readdir $CDIR;
710 0           closedir $CDIR;
711 0 0         if ($mftfm) {
712 0           $self->{fileset}->{log}->verbose(<<"EOM");
713             LaTeX wrote a .tfm or .mf file -- this indicates feynmf or similar dynamic font generation.
714             Ignoring non-zero exit status and starting over retaining the new font files!
715             EOM
716 0           return 1;
717             }
718 0           return 0;
719             }
720              
721             sub trash_tex_aux_files {
722 0     0 1   my $self = shift;
723 0           my ($younger_than, $written) = @_;
724              
725 0           my $dir = $self->{fileset}->{dir};
726 0           opendir(my $TEMPDIR, $dir);
727 0           my @files =
728 0 0 0       map {/^(.*)$/} # untaint
      0        
729 0           grep {!/^\./ # no dot files
730             && !/\.(mf|log)$/ # need metafont files and associated log for labels
731             && -f "$dir/$_" # recently modified files only
732             && (stat(_))[9] >= $younger_than}
733             readdir $TEMPDIR;
734 0           closedir $TEMPDIR;
735              
736 0           foreach my $file (@files) {
737             # Warn about files written by latex but still delete them.
738             # We do not want people to include figures by dumping them out
739             # from the tex file using the LaTeX2e filecontents environment.
740             # Simeon-21Jul2000
741 0 0         if ($written->{$file}) {
742 0           $self->{fileset}->{log}->verbose("TeX wrote out '$file', going to delete it as we don't permit filecontents inclusion of figures.");
743 0           delete $written->{$file};
744             }
745 0           my $fmt = $self->{fileset}->new_File($file)->type();
746 0 0         next if grep {$fmt eq 'TYPE_' . $_} qw(DVI POSTSCRIPT PDF);
  0            
747              
748 0           my $age = (stat("$dir/$file"))[9]; #TS FIXME: expensive stat for logging
749 0           $self->{fileset}->{log}->verbose("Removing (La)TeX AUX file called '$file' ($age >= $younger_than)");
750 0 0         unlink "$dir/$file"
751             or $self->{fileset}->{log}->error("failed to remove '$file': $!");
752             }
753 0           return 0;
754             }
755              
756             #######################################################################
757             # basename()
758             # takes filename from AutoTeX::File object, removes its extension if it has
759             # one, caches and returns result.
760             # a special case is a filename ending in '.'. in this
761             # context, it should also be removed.
762             # substr/rindex is 3x faster than regexp s/\.[^.]*$//;
763              
764             sub basename {
765 0     0 1   my $self = shift;
766 0 0         if (!defined $self->{basename}) {
767 0 0         if (0 < index $self->{filename}, q{.}) {
768 0           $self->{basename} = substr $self->{filename}, 0, rindex($self->{filename}, q{.});
769             } else {
770 0           $self->{basename} = $self->{filename};
771             }
772             }
773 0           return $self->{basename};
774             }
775              
776             1;
777              
778             __END__