1#! /usr/bin/env perl
2
3###########################################################
4# Prepare a LaTeX run for two-way communication with Perl #
5# By Scott Pakin <scott+pt@pakin.org>                     #
6###########################################################
7
8#-------------------------------------------------------------------
9# This is file `perltex.pl',
10# generated with the docstrip utility.
11#
12# The original source files were:
13#
14# perltex.dtx  (with options: `perltex')
15#
16# This is a generated file.
17#
18# Copyright (C) 2004 by Scott Pakin <scott+pt@pakin.org>
19#
20# This file may be distributed and/or modified under the conditions
21# of the LaTeX Project Public License, either version 1.2 of this
22# license or (at your option) any later version.  The latest
23# version of this license is in:
24#
25#    http://www.latex-project.org/lppl.txt
26#
27# and version 1.2 or later is part of all distributions of LaTeX
28# version 1999/12/01 or later.
29#-------------------------------------------------------------------
30
31use Safe;
32use Opcode;
33use Getopt::Long;
34use Pod::Usage;
35use File::Basename;
36use POSIX;
37use warnings;
38use strict;
39my $latexprog;
40my $runsafely = 1;
41my @permittedops;
42my $progname = basename $0;
43my $jobname = "texput";
44my @latexcmdline;
45my $toperl;
46my $fromperl;
47my $toflag;
48my $fromflag;
49my $doneflag;
50my $logfile;
51my $sandbox = new Safe;
52my $latexpid;
53$latexprog = $ENV{"PERLTEX"} || "latex";
54Getopt::Long::Configure("require_order", "pass_through");
55GetOptions("help"     => sub {pod2usage(-verbose => 1)},
56           "latex=s"  => \$latexprog,
57           "safe!"    => \$runsafely,
58           "permit=s" => \@permittedops) || pod2usage(2);
59@latexcmdline = @ARGV;
60my $firstcmd = 0;
61for ($firstcmd=0; $firstcmd<=$#latexcmdline; $firstcmd++) {
62    my $option = $latexcmdline[$firstcmd];
63    next if substr($option, 0, 1) eq "-";
64    if (substr ($option, 0, 1) ne "\\") {
65        $jobname = basename $option, ".tex" ;
66        $latexcmdline[$firstcmd] = "\\input $option";
67    }
68    last;
69}
70push @latexcmdline, "" if $#latexcmdline==-1;
71my $separator = "";
72foreach (1 .. 20) {
73    $separator .= chr(ord("A") + rand(26));
74}
75$toperl = $jobname . ".topl";
76$fromperl = $jobname . ".frpl";
77$toflag = $jobname . ".tfpl";
78$fromflag = $jobname . ".ffpl";
79$doneflag = $jobname . ".dfpl";
80$logfile = $jobname . ".lgpl";
81$latexcmdline[$firstcmd] =
82    sprintf '\makeatletter' . '\def%s{%s}' x 6 . '\makeatother%s',
83    '\plmac@tag', $separator,
84    '\plmac@tofile', $toperl,
85    '\plmac@fromfile', $fromperl,
86    '\plmac@toflag', $toflag,
87    '\plmac@fromflag', $fromflag,
88    '\plmac@doneflag', $doneflag,
89    $latexcmdline[$firstcmd];
90foreach my $file ($toperl, $fromperl, $toflag, $fromflag, $doneflag) {
91    unlink $file while -e $file;
92}
93open (LOGFILE, ">$logfile") || die "open(\"$logfile\"): $!\n";
94defined ($latexpid = fork) || die "fork: $!\n";
95unshift @latexcmdline, $latexprog;
96if (!$latexpid) {
97    exec {$latexcmdline[0]} @latexcmdline;
98    die "exec('@latexcmdline'): $!\n";
99}
100@permittedops=(":browse") if $#permittedops==-1;
101@permittedops=(Opcode::full_opset()) if !$runsafely;
102$sandbox->permit_only (@permittedops);
103while (1) {
104    my $awaitexists = sub {
105      while (!-e $_[0]) {
106          sleep 0;
107          if (waitpid($latexpid, &WNOHANG)==-1) {
108              foreach my $file ($toperl, $fromperl, $toflag,
109                                $fromflag, $doneflag) {
110                  unlink $file while -e $file;
111              }
112              undef $latexpid;
113              exit 0;
114          }
115      }
116    };
117    $awaitexists->($toflag);
118    my $entirefile;
119    {
120        local $/ = undef;
121        open (TOPERL, "<$toperl") || die "open($toperl): $!\n";
122        $entirefile = <TOPERL>;
123        close TOPERL;
124    }
125    my ($optag, $macroname, @otherstuff) =
126        map {chomp; $_} split "$separator\n", $entirefile;
127    $macroname =~ s/^[^A-Za-z]+//;
128    $macroname =~ s/\W/_/g;
129    $macroname = "latex_" . $macroname;
130    if ($optag eq "USE") {
131      foreach (@otherstuff) {
132          s/\\/\\\\/g;
133          s/\'/\\\'/g;
134          $_ = "'$_'";
135      }
136    }
137    my $perlcode;
138    if ($optag eq "DEF") {
139      $perlcode =
140          sprintf "sub %s {%s}\n",
141          $macroname, $otherstuff[0];
142    }
143    else {
144      $perlcode = sprintf "%s (%s);\n", $macroname, join(", ", @otherstuff);
145    }
146    print LOGFILE "#" x 31, " PERL CODE ", "#" x 32, "\n";
147    print LOGFILE $perlcode, "\n";
148    undef $_;
149    my $result;
150    {
151        my $warningmsg;
152        local $SIG{__WARN__} =
153            sub {chomp ($warningmsg=$_[0]); return 0};
154        $result = $sandbox->reval ($perlcode);
155        if (defined $warningmsg) {
156            $warningmsg =~ s/at \(eval \d+\) line \d+\W+//;
157            print LOGFILE "# ===> $warningmsg\n\n";
158        }
159    }
160    $result="" if !$result;
161    if ($@) {
162        my $msg = $@;
163        $msg =~ s/at \(eval \d+\) line \d+\W+//;
164        $msg =~ s/\s+/ /;
165        $result = "\\PackageError{perltex}{$msg}";
166        my @helpstring;
167        if ($msg =~ /\btrapped by\b/) {
168            @helpstring =
169                ("The preceding error message comes from Perl.  Apparently,",
170                 "the Perl code you tried to execute attempted to perform an",
171                 "`unsafe' operation.  If you trust the Perl code (e.g., if",
172                 "you wrote it) then you can invoke perltex with the --nosafe",
173                 "option to allow arbitrary Perl code to execute.",
174                 "Alternatively, you can selectively enable Perl features",
175                 "using perltex's --permit option.  Don't do this if you don't",
176                 "trust the Perl code, however; malicious Perl code can do a",
177                 "world of harm to your computer system.");
178        }
179        else {
180            @helpstring =
181              ("The preceding error message comes from Perl.  Apparently,",
182               "there's a bug in your Perl code.  You'll need to sort that",
183               "out in your document and re-run perltex.");
184        }
185        my $helpstring = join ("\\MessageBreak\n", @helpstring);
186        $helpstring =~ s/\.  /.\\space\\space /g;
187        $result .= "{$helpstring}";
188    }
189    print LOGFILE "%" x 30, " LATEX RESULT ", "%" x 30, "\n";
190    print LOGFILE $result, "\n\n";
191    $result .= '\endinput';
192    open (FROMPERL, ">$fromperl") || die "open($fromperl): $!\n";
193    syswrite FROMPERL, $result;
194    close FROMPERL;
195    unlink $toflag while -e $toflag;
196    unlink $toperl while -e $toperl;
197    unlink $doneflag while -e $doneflag;
198    open (FROMFLAG, ">$fromflag") || die "open($fromflag): $!\n";
199    close FROMFLAG;
200    $awaitexists->($toperl);
201    unlink $fromflag while -e $fromflag;
202    open (DONEFLAG, ">$doneflag") || die "open($doneflag): $!\n";
203    close DONEFLAG;
204}
205END {
206    close LOGFILE;
207    if (defined $latexpid) {
208        kill (9, $latexpid);
209        exit 1;
210    }
211    exit 0;
212}
213
214__END__
215
216=head1 NAME
217
218perltex - enable LaTeX macros to be defined in terms of Perl code
219
220=head1 SYNOPSIS
221
222perltex
223[B<--help>]
224[B<--latex>=I<program>]
225[B<-->[B<no>]B<safe>]
226[B<--permit>=I<feature>]
227[I<latex options>]
228
229=head1 DESCRIPTION
230
231LaTeX -- through the underlying TeX typesetting system -- produces
232beautifully typeset documents but has a macro language that is
233difficult to program.  In particular, support for complex string
234manipulation is largely lacking.  Perl is a popular general-purpose
235programming language whose forte is string manipulation.  However, it
236has no typesetting capabilities whatsoever.
237
238Clearly, Perl's programmability could complement LaTeX's typesetting
239strengths.  B<perltex> is the tool that enables a symbiosis between
240the two systems.  All a user needs to do is compile a LaTeX document
241using B<perltex> instead of B<latex>.  (B<perltex> is actually a
242wrapper for B<latex>, so no B<latex> functionality is lost.)  If the
243document includes a C<\usepackage{perltex}> in its preamble, then
244C<\perlnewcommand> and C<\perlrenewcommand> macros will be made
245available.  These behave just like LaTeX's C<\newcommand> and
246C<\renewcommand> except that the macro body contains Perl code instead
247of LaTeX code.
248
249=head1 OPTIONS
250
251B<perltex> accepts the following command-line options:
252
253=over 4
254
255=item B<--help>
256
257Display basic usage information.
258
259=item B<--latex>=I<program>
260
261Specify a program to use instead of B<latex>.  For example,
262C<--latex=pdflatex> would typeset the given document using
263B<pdflatex> instead of ordinary B<latex>.
264
265=item B<-->[B<no>]B<safe>
266
267Enable or disable sandboxing.  With the default of C<--safe>,
268B<perltex> executes the code from a C<\perlnewcommand> or
269C<\perlrenewcommand> macro within a protected environment that
270prohibits ``unsafe'' operations such as accessing files or executing
271external programs.  Specifying C<--nosafe> gives the LaTeX document
272I<carte blanche> to execute any arbitrary Perl code, including that
273which can harm the user's files.  See L<Safe> for more information.
274
275=item B<--permit>=I<feature>
276
277Permit particular Perl operations to be performed.  The C<--permit>
278option, which can be specified more than once on the command line,
279enables finer-grained control over the B<perltex> sandbox.  See
280L<Opcode> for more information.
281
282=back
283
284These options are then followed by whatever options are normally
285passed to B<latex> (or whatever program was specified with
286C<--latex>), including, for instance, the name of the F<.tex> file to
287compile.
288
289=head1 EXAMPLES
290
291In its simplest form, B<perltex> is run just like B<latex>:
292
293    perltex myfile.tex
294
295To use B<pdflatex> instead of regular B<latex>, use the C<--latex>
296option:
297
298    perltex --latex=pdflatex myfile.tex
299
300If LaTeX gives a ``C<trapped by operation mask>'' error and you trust
301the F<.tex> file you're trying to compile not to execute malicious
302Perl code (e.g., because you wrote it yourself), you can disable
303B<perltex>'s safety mechansisms with C<--nosafe>:
304
305    perltex --nosafe myfile.tex
306
307The following command gives documents only B<perltex>'s default
308permissions (C<:browse>) plus the ability to open files and invoke the
309C<time> command:
310
311    perltex --permit=:browse --permit=:filesys_open
312      --permit=time myfile.tex
313
314=head1 ENVIRONMENT
315
316B<perltex> honors the following environment variables:
317
318=over 4
319
320=item PERLTEX
321
322Specify the filename of the LaTeX compiler.  The LaTeX compiler
323defaults to ``C<latex>''.  The C<PERLTEX> environment variable
324overrides this default, and the C<--latex> command-line option (see
325L</OPTIONS>) overrides that.
326
327=back
328
329=head1 FILES
330
331While compiling F<jobname.tex>, B<perltex> makes use of the following
332files:
333
334=over 4
335
336=item F<jobname.lgpl>
337
338log file written by Perl; helpful for debugging Perl macros
339
340=item F<jobname.topl>
341
342information sent from LaTeX to Perl
343
344=item F<jobname.frpl>
345
346information sent from Perl to LaTeX
347
348=item F<jobname.tfpl>
349
350``flag'' file whose existence indicates that F<jobname.topl> contains
351valid data
352
353=item F<jobname.ffpl>
354
355``flag'' file whose existence indicates that F<jobname.frpl> contains
356valid data
357
358=item F<jobname.dfpl>
359
360``flag'' file whose existence indicates that F<jobname.ffpl> has been
361deleted
362
363=back
364
365=head1 NOTES
366
367B<perltex>'s sandbox defaults to what L<Opcode> calls ``C<:browse>''.
368
369=head1 SEE ALSO
370
371latex(1), pdflatex(1), perl(1), Safe(3pm), Opcode(3pm)
372
373=head1 AUTHOR
374
375Scott Pakin, I<scott+pt@pakin.org>
376