#!/usr/bin/env perl # $Id: texfot,v 1.43 2021/05/01 15:49:53 karl Exp $ # Invoke a TeX command, filtering all but interesting terminal output; # do not look at the log or check any output files. # Exit status is that of the subprogram. # Tee the complete (unfiltered) standard output and standard error to # (by default) /tmp/fot. # # Public domain. Originally written 2014 by Karl Berry. my $ident = '$Id: texfot,v 1.43 2021/05/01 15:49:53 karl Exp $'; (my $prg = $0) =~ s,^.*/,,; select STDERR; $| = 1; # no buffering select STDOUT; $| = 1; use IPC::Open3; # control what happens with stderr from the child. use IO::File; # use new_tmpfile for that stderr. # require_order because we don't want getopt to permute anything; # arguments to the tex invocation must remain in order, not handled by us. use Getopt::Long qw(:config require_order); use Pod::Usage; my $opt_debug = 0; my @opt_ignore = (); my $opt_interactive = 0; my $opt_quiet = 0; my $opt_stderr = 1; my $opt_tee = ($ENV{"TMPDIR"} || $ENV{"TMP"} || "/tmp") . "/fot"; my $opt_version = 0; my $opt_help = 0; exit (&main ()); # sub main { my $ret = GetOptions ( "debug!" => \$opt_debug, "ignore=s" => \@opt_ignore, "interactive!" => \$opt_interactive, "quiet!" => \$opt_quiet, "stderr!" => \$opt_stderr, "tee=s" => \$opt_tee, "version" => \$opt_version, "help|?" => \$opt_help) || pod2usage (2); # --help, --version pod2usage ("-exitstatus" => 0, "-verbose" => 2) if $opt_help; # if ($opt_version) { print "$ident\n"; return 0; } die "$prg: missing TeX command, i.e., a non-option argument\n" . "Try --help if you need it." if ! @ARGV; # guess we're going to run something. typically no interaction. close (STDIN) unless $opt_interactive; local *FOTTMP; $FOTTMP = ">$opt_tee"; open (FOTTMP) || die "$prg: aborting, open($FOTTMP) failed: $!"; # We need to separate stderr from stdout. Otherwise they are randomly # merged, not always at line breaks, hence we can lose useful messages. print "$0: invoking: @ARGV\n" unless $opt_quiet; # In order to avoid deadlock when there is lots of stuff on stderr, # we must write it to a temporary file # http://perldoc.perl.org/perlfaq8.html#How-can-I-capture-STDERR-from-an-external-command local *TEXERR = IO::File->new_tmpfile || die "IO::File->new_tmpfile failed: $!"; # But we can process stdout as it comes. local *TEXOUT; my $pid = open3 (undef, \*TEXOUT, ">&TEXERR", @ARGV) || die "$prg: fork(TeX) failed: $! [cmd=@ARGV]\n"; &debug ("open3() returned pid $pid [cmd=@ARGV]"); # It's not ideal to read all of stdout and then all of stderr; it would # be better to intermix them in the original order of child output. # But this is simpler than other ways of avoiding possible deadlock (such # as select, sysread, etc.). &debug ("processing stdout from child"); &process_output (\*TEXOUT, ""); # Be sure everything is drained. &debug ("starting waitpid() for $pid") ; waitpid ($pid, 0) || die "$prog: waitpid($pid) failed: $!\n"; my $child_exit_status = $? >> 8; &debug ("child exit status = $exit_status\n"); &debug ("processing stderr from child"); seek (TEXERR, 0, 0) || warn "seek(stderr) failed: $!"; &process_output (\*TEXERR, "[stderr] "); close (TEXERR) || warn "close(stderr tmpfile) failed: $!"; return $child_exit_status; } # Read filehandle $FH, printing lines that we want to stdout, # prefixed by $PREFIX. # If $PREFIX is null (happens for processing stdout), omit lines by default; # if $PREFIX is non-null (processing stderr), print lines by default. # sub process_output { my ($fh,$prefix) = @_; my $print_next = 0; LINE: while (<$fh>) { my $line = $_; print FOTTMP $line; # tee everything warn "\n" if $opt_debug; # get blank line without texfot: prefix &debug ("looking at line: $_"); &debug ("checking if have print_next (= $print_next)\n"); if ($print_next) { &debug (" printing next ($print_next)\n"); print $prefix; print $line; $print_next = 0; next; } &debug ("checking ignores\n"); next if /^( LaTeX\ Warning:\ You\ have\ requested\ package |LaTeX\ Font\ Warning:\ Some\ font\ shapes |LaTeX\ Font\ Warning:\ Size\ substitutions |Package\ auxhook\ Warning:\ Cannot\ patch |Package\ caption\ Warning:\ Un(supported|known)\ document\ class |Package\ fixltx2e\ Warning:\ fixltx2e\ is\ not\ required |Package\ frenchb?\.ldf\ Warning:\ (Figures|The\ definition) |Package\ layouts\ Warning:\ Layout\ scale |\*\*\*\ Reloading\ Xunicode\ for\ encoding # spurious *** |This\ is\ `?(epsf\.tex|.*\.sty|TAP) # so what |pdfTeX\ warning:.*inclusion:\ fou #nd PDF version ... |pdfTeX\ warning:.*inclusion:\ mul #tiple pdfs with page group |libpng\ warning:\ iCCP:\ Not\ recognizing |!\ $ )/x; # don't anchor user ignores, leave it up to them. for my $user_ignore (@opt_ignore) { &debug ("checking user ignore '$user_ignore'\n"); next LINE if /${user_ignore}/; } &debug ("checking for print_next\n"); if (/^( .*?:[0-9]+: # usual file:lineno: form |! # usual ! form |>\ [^<] # from \show..., but not "> ... |Missing\ character: # good to show (need \tracinglostchars=1) |\\endL.*problem # XeTeX? |\*\*\*\s # *** from some packages or subprograms |l\.[0-9]+\ # line number marking |all\ text\ was\ ignored\ after\ line |.*Fatal\ error |.*for\ symbol.*on\ input\ line )/x) { &debug (" matched for showing ($1)\n"); print $prefix; print $line; next; } &debug ("done with all checks\n"); if ($prefix && $opt_stderr) { &debug ("prefix (stderr), showing line by default: $_"); print $prefix; print $line; } else { &debug ("no prefix (stdout) or no stderr, ignoring line by default: $_"); } } } sub debug { warn ("$prg: ", @_) if $opt_debug; } __END__ =head1 NAME texfot - run TeX, filtering online transcript for interesting messages =head1 SYNOPSIS texfot [I