3

I guess this is somewhat similar to Animations in LaTeX, except what I would want is something like this:

Say you have a .tex document, which uses some macro \tester as parameter. I'd like to load a package, where I could specify some values for this \tester, and have the package automatically loop through those values, and generate an individual PDF for each value of \tester. Finally, I'd want a "post action" to run in the shell, once the loop is over. So, just by specifying a \usepackage like:

\documentclass{article}

...
\usepackage[vartoken=\tester,varvals={0,1,2},postactioncode={ls *.pdf}]{repeat-build}
...

\begin{document}
... testing: \tester
\end{document}

... I'd like the loop to run automatically, generating separate PDFs for \tester = 0, 1 and 2.

Is there a package like that already?

1

I'm not sure if such a package exists; but I managed to cook one up, so I'll be posting it here. (if a package like this hasn't existed, and if it is useful, feel free to mod and eventually post on CTAN; unfortunately, I currently do not have the time to do that procedure, or to maintain in general)

The only problem so far seems to me to be, that the variable declaration is a bit complicated:

...
\ifx\tester\undefined{%
  \global\protected\def\tester{aa} %
}\fi
\usepackage[vartoken=\tester,varvals={0,...,2},postactioncode={\pacode}]{repeat-build}
\unprotect\tester

\begin{document}
...

The .tex example (below) must be compiled with "shell escape"; so the this is how it can be compiled, and the expected messages on Linux:

$ pdflatex -shell-escape repeat-build-test.tex
...
 repeat-build: \rpbuild@jobname is undefined: first run!
 repeat-build: vartoken defined as: macro:->\tester 
 repeat-build: varvals defined as: macro:->0,...,2
 repeat-build: have {3} vaues in varvals
 repeat-build:  ... we got here - time to start loopin' ... 
+ pdflatex -shell-escape -interaction=batchmode -jobname repeat-build-test-0 \makeatletter\gdef\rpbuild@jobname{repeat-build-test} \gdef\tester {0} \makeatother\input{repeat-build-test}
This is pdfTeX, Version 3.1415926-2.3-1.40.12 (TeX Live 2011)
 \write18 enabled.
entering extended mode
+ pdflatex -shell-escape -interaction=batchmode -jobname repeat-build-test-1 \makeatletter\gdef\rpbuild@jobname{repeat-build-test} \gdef\tester {1} \makeatother\input{repeat-build-test}
This is pdfTeX, Version 3.1415926-2.3-1.40.12 (TeX Live 2011)
 \write18 enabled.
entering extended mode
+ pdflatex -shell-escape -interaction=batchmode -jobname repeat-build-test-2 \makeatletter\gdef\rpbuild@jobname{repeat-build-test} \gdef\tester {2} \makeatother\input{repeat-build-test}
This is pdfTeX, Version 3.1415926-2.3-1.40.12 (TeX Live 2011)
 \write18 enabled.
entering extended mode
 repeat-build: post action code:
+ pwd
/path/to/src/tex/repeat-build
+ ls repeat-build-test-0.log repeat-build-test-1.log repeat-build-test-2.log repeat-build-test-0.aux repeat-build-test-1.aux repeat-build-test-2.aux
repeat-build-test-0.aux  repeat-build-test-1.aux  repeat-build-test-2.aux
repeat-build-test-0.log  repeat-build-test-1.log  repeat-build-test-2.log
+ rm -vrf repeat-build-test-0.log repeat-build-test-1.log repeat-build-test-2.log repeat-build-test-0.aux repeat-build-test-1.aux repeat-build-test-2.aux
removed `repeat-build-test-0.log' ...
removed `repeat-build-test-2.aux'
+ convert -density 150 repeat-build-test-0.pdf repeat-build-test-1.pdf repeat-build-test-2.pdf -crop 400x400+250+320 +repage -delay 5 -loop 0 repeat-build-test.gif
 repeat-build: Finished. Due to the use of repeat-build, 
 will now exit without compiling the master document. 
 Comment the `\usepackage[...]{repeat-build}` 
 line in your document code to go back to normal 
 compilation behavior. 

 ) )
No pages of output.
Transcript written on repeat-build-test.log.

... and this is the output that it generates:

repeat-build-test.gif

Here is repeat-build-test.tex:

\documentclass{article}

% declare iteration variable;
%  wish it was easier (just \def\tester{...}), but:
%  must have it \protected (eTex), so
%  it arrives as token in the package
%  code! (vartoken=\noexpand\tester
%  in options [] doesn't work)
% with \ifx we do protection from subsequent runs;
%  and because of it, we need \global inside!
\ifx\tester\undefined{%
  \global\protected\def\tester{aa} %
}\fi

% write post action code for package;
  % NOTE: \write18 in Linux is sh - not bash!
  % (so cannot do expansion like `ls *.{log,aux}`;
  %  must do `ls *.log *.aux`)
  % Latex will compact \pacode in single line; remember semicolons
  % also, comment sh lines here with % - not # !!
\def\pacode{%
  pwd; %
  ls \jobname-*.log \jobname-*.aux; %
  rm -vrf \jobname-*.log \jobname-*.aux; %
  convert -density 150 *.pdf %
    -crop 400x400+250+320 +repage %
    -delay 5 -loop 0 %
    \jobname.gif %
  ; %
  %eog \jobname.gif ; % run viewer
}

% load package (also defs \unprotect)
\usepackage[vartoken=\tester,varvals={0,...,2},postactioncode={\pacode}]{repeat-build}

% now unprotect the iteration variable macro:
% (although here we don't really need to)
\unprotect\tester

\usepackage{xcolor} % \pagecolor
\pagecolor{yellow!15}

\begin{document}

  \typeout{indoc tester: \tester}

  \title{Test of repeat-build}
  \author{sdaau}

  \maketitle

  \begin{abstract}
  The abstract text goes here.
  \end{abstract}

  \section{Test section}

  Current variable \verb!\tester! is: \tester

\end{document}

Here is repeat-build.sty:

\NeedsTeXFormat{LaTeX2e}[1994/06/01]
\ProvidesPackage{repeat-build}[2014/06/21 repeat-build]

\RequirePackage{xstring} % \StrLen
\RequirePackage{pgfkeys}
\RequirePackage{pgfopts} % \ProcessPgfOptions
\RequirePackage{pgffor}  % \foreach
\ifx\pgfmathincluded\undefined %
  \RequirePackage{pgfmath} % \pgfmathsetmacro
\fi
%\RequirePackage{trace} % debug

\def\rptypeout#1{\immediate\typeout{ repeat-build: #1}}

% \DeclareOption{vartoken}{
%   \gdef{\vartoken}{...}
% }

\pgfkeys{
  /rpbuild/.cd,
  vartoken/.store in = \rpbuild@vartoken, % expands?!
  vartoken/.default = \undefined,
  % note: varvals is "undefined" if not mentioned in options [];
  %  becomes .default value if it is mentioned, but unset [varvals,]
  %  it doesn't help if .initial or .default are before .store in -
  %  - the varvals remains undef'd if not mentioned in options!
  % note: even {0,...,0} has length 1; so just use {} to init that!
  varvals/.initial = {},
  varvals/.default = {},
  varvals/.store in = \rpbuild@varvals,
  % actually, this is how to set a default value for
  %  options not mentioned in the []: by using .initial
  %  and .get!
  actioncode/.initial={%
    % NOTE: \write18 in Linux is sh - not bash!
    % (so cannot do expansion like `ls *.{log,aux}`;
    %  must do `ls *.log *.aux`)
    % this is the sh script part only! Usable macros:
    %  \rpbuild@jobname - originating \jobname
    %  \rpbuild@iter - iterator (current value);
    %  \rpbuild@ind - numeric index for jobname
    %  \rpbuild@indlp - same as @ind, but (left) zero-padded
    %  \mybs - backslash escaping macro for shell
    % do not use quotes printf '\rpbuild@jobname...' here;
    % they will propagate inside the variable!
    % also, comment bash lines here with % - not # !!
    % ---
    % this works - using printf in shell to left zero-pad @ind number:
    %nnamecmd="printf \rpbuild@jobname-\myperc 0\rpbuild@lengthnumchars d \rpbuild@ind" ;
    %nname=$($nnamecmd);
    % with already zero-padded from Latex: @indlp:
    nname="\rpbuild@jobname-\rpbuild@indlp" ;
    %echo $nname ; % debug
    tcmd="pdflatex
      -shell-escape %\tikzexternalcheckshellescape %(avoid loading tikz)
      %-halt-on-error % will disallow interaction that stops on errors - halts in sense of exit!
      -interaction=batchmode % silent/quiet; will not halt on any error!
      %-interaction=scrollmode % stops on some errors
      %-interaction=errorstopmode % stops on all errors
      -jobname "$nname" %
      '% % (c0)
      \string\makeatletter
      \string\gdef\mybs\string\rpbuild@jobname{\rpbuild@jobname}
      \string\gdef\mybs\rpbuild@vartoken{\rpbuild@iter}
      %\string\typeout{INB4 INPUT \string\rpbuild@jobname}
      \string\makeatother
      \string\input{\rpbuild@jobname}%
      '" ;
    %echo "$tcmd" ;% | hexdump -C ; % debug
    %set -x; % better for debug - inside the eval, to avoid repeated prints
    eval "set -x; $tcmd" ; % execute
  },
  actioncode/.get = \rpbuild@actcode,
  postactioncode/.store in = \rpbuild@postcode,
  postactioncode/.default = {}, % (c1)
}

\def\unprotect#1{ %
  % http://tex.stackexchange.com/a/57233/2595
  \rptypeout{unprotecting: #1} %
  \edef#1{\expandafter\strip@prefix\meaning#1} %
}

\gdef\rpbuild@exitallifneeded{}

% \ProcessOptions\relax
\ProcessPgfOptions{/rpbuild}\relax

% \leftpadzero{\totalcharslength}{\inputnumstring}{\outstrcmd}
% (isn't expandable itself, but returns into a macro that will be)
\def\leftpadzero#1#2#3{%
  \xdef#3{#2} %initialize; %\xdef\rpbuild@tmpoutstr{#2}%
  \StrLen{#3}[\rpbuild@tmpinnumchars]%
  % z(ero)c(hars)toadd - total;
  \pgfmathtruncatemacro{\rpbuild@tmpzctoadd}{#1-\rpbuild@tmpinnumchars}%
  \ifnum\rpbuild@tmpzctoadd>0{%
    % start the foreach from 1, for accurate count
    \foreach \ix in {1,...,\rpbuild@tmpzctoadd}{%
      \xdef#3{0#3}% concatenate a "0" from left
    }%
  }\fi%
  %\rpbuild@tmpoutstr% return;
  % no return here - now we have it in the #3 macro
} % end def \leftpadzero


% check first if we're running in the inner loop:
\ifx\rpbuild@jobname\undefined{%

  \rptypeout{\string\r pbuild@jobname is undefined: first run!} %

  % in these checks, in order to exit properly, must have:
  %  ... \expandafter \endinput } \fi ... ; else getting:
  %  (\end occurred inside a group at level 1) warning +
  %  ### simple group (level 1) entered at line 51 ({) ...
  %  Because of that, we cannot use \else - so the conditions
  %  are repeated in order to get console printouts!
  % Though, below we can use \else, because that is where
  %  the \endinput is!

  % check if we have vartoken
  \ifx\rpbuild@vartoken\undefined{ %
    \rptypeout{vartoken undefined; will not run!} %
    \expandafter\endinput %
  }\fi %
  \ifx\rpbuild@vartoken\undefined\else{ %
    \rptypeout{vartoken defined as: \meaning\rpbuild@vartoken} %
  }\fi

  % check if we have varvals
  \ifx\rpbuild@varvals\undefined{ %
    \rptypeout{varvals undefined; will not run!} %
    \expandafter\endinput %
  }\fi %
  \ifx\rpbuild@varvals\undefined\else{ %
    \rptypeout{varvals defined as: \meaning\rpbuild@varvals} %
  }\fi

  % check if we have range - by looping the
  % argument, and calculating "last index"
  \def\rpbuild@length{0}
  \foreach \rpbuild@iter in \rpbuild@varvals{ %
    \pgfmathtruncatemacro{\rpbuild@length}{\rpbuild@length+1} %
    \global\let\rpbuild@length\rpbuild@length % must globalize!
  }
  \ifnum\rpbuild@length>0{%
    \rptypeout{have {\rpbuild@length} vaues in varvals} %
  }\else{%
    \rptypeout{do not have vaues in varvals; will not run!} %
    \expandafter\endinput %
  }\fi

  % "The control sequence \pdfshellescape is (only?) available in pdftex"
  % http://tex.stackexchange.com/a/13253/2595
  \ifnum\pdfshellescape=1{ %
    % Yes, enabled
  }\else{ %
    % No, disabled
    \rptypeout{** Sorry, you'll need to call with `pdflatex -shell-escape`} %
    \rptypeout{** to have this work; will not run!} %
    \expandafter\endinput %
  }\fi

  \rptypeout{ ... we got here - time to start loopin' ... } %

  % \rpbuild@jobname is the master, originating \jobname,
  % but also a signal that we're inside the loop!
  \edef\rpbuild@jobname{\jobname}%
  \StrLen{\rpbuild@length}[\rpbuild@lengthnumchars]

  % http://tex.stackexchange.com/a/69294/2595
  % must escape backslash like this, to have escaped linefeed
  % character \n in \write18 output for the shell (raw ASCII)!
  \begingroup
    \catcode `~=11
    \gdef\mytilde{~}
    \catcode `\|=0
    \catcode `\\=11
    |gdef|LF{\n} % \LF becomes (ASCII) "\n" (verbatim char!)
    |gdef|n{\n} % \n becomes (ASCII) "\n" (verbatim char!)
    |gdef|ELF{\\n} % \ELF becomes (ASCII) "\\n" - escaped backslash for shell!
  |endgroup

  % get expandable ASCII macros
  %  cannot do it like this:
  %   \edef\mybs{\string\char092} \typeout{mybs \mybs}
  %  must use the uccode/uppercase trick (as in:
  %  http://tex.stackexchange.com/q/60951/#comment129115_60960 )
  \bgroup
    \uccode`A=37 % chr(37) percent sign %
    \uccode`B=92 % chr(92) backslash    \
    \uppercase{
      \gdef\myperc{A}
      \gdef\mybs{B}
      \gdef\mybsbs{BB}
      %\typeout{==A==B==} % debug
    }
  \egroup

  % run the loop with the action code

  % since we will use shell-escape,
  %  we might as well use printf in the (ba)sh shell (Linux)
  %  to format the numbers added to the filename;
  %  but can also use a Latex function.
  % also, note that line endings here will be gobbled by
  %  latex (replaced by space), so the whole \cmd will
  %  arrive to (ba)sh as a single line - remember semicolon delimiters!
  %  heading tab space will be gobbled too
  % also note that \rp.. is seen by bash as "\r", so that
  %  must be escaped; with just \mybs nowork; in this case,
  %  have to also use single quotes (instead of double -
  %  in "\string\def\string\rpbuild@..")
  \gdef\rpbuild@ind{0}
  \gdef\rpbuild@indlp{0} %index, left padded
  \foreach \rpbuild@iter in \rpbuild@varvals{ %
    %\leftpadzero{5}{120}{\ret} %\typeout{ret \ret} % 00120
    \leftpadzero{\rpbuild@lengthnumchars}{\rpbuild@ind}{\rpbuild@indlp} %\typeout{ret \rpbuild@indlp} %
    % compose command(s)
    % (below unused snippet left for dev reference):
%     \edef\cmd{%
%       % do not use quotes printf '\rpbuild@jobname...' here;
%       % they will propagate inside the variable!
%       % also, comment bash lines here with % - not # !!
%       nnamecmd="printf \rpbuild@jobname-\myperc 0\rpbuild@lengthnumchars d \rpbuild@iter" ;
%       nname=$($nnamecmd);
%       echo $nname ;
%       echo
%       pdflatex
%         -shell-escape %\tikzexternalcheckshellescape
%         -halt-on-error
%         -interaction=batchmode
%         -jobname "$nname"
%         '%
%         \string\def\mybs\string\rpbuild@jobname{\rpbuild@jobname}
%         \string\input{\rpbuild@jobname}%
%         '
%     } % \cmd
    % execute
    \immediate\write18{%
%       \cmd% OK
      \rpbuild@actcode% OK
    }%
    % increase count for file/jobname:
    \pgfmathtruncatemacro{\rpbuild@ind}{\rpbuild@ind+1} %
    \global\let\rpbuild@ind\rpbuild@ind % globalize
  } % end \foreach

  % do post action code, if any:
  \ifx\rpbuild@postcode\undefined{%
    \rptypeout{no post action code} %
  }\else{%
    \rptypeout{post action code:} %
    \edef\cmd{
      eval "set -x; \rpbuild@postcode" ; % execute
    }
    \immediate\write18{\cmd} %
  }\fi

  \rptypeout{%
    Finished. Due to the use of repeat-build, ^^J
    will now exit without compiling the master document. ^^J
    Comment the `\string\usepackage[...]{repeat-build}` ^^J
    line in your document code to go back to normal ^^J
    compilation behavior. ^^J
  } %
  \gdef\rpbuild@exitallifneeded{\stop}
}%\fi % end \ifx\rpbuild@jobname\undefined
\else{%
  %\ifx\rpbuild@jobname is not undefined!
  \rptypeout{ > in loop, building \jobname ^^J
  (repeat-build package will get out of the way now) ... } %
}\fi % end \ifx\rpbuild@jobname\undefined

% Latex2e \stop - exit prematurely/stop compilation
%  *without* an error prompt;
\rpbuild@exitallifneeded %

\endinput

% (c0)
% escape trouble:
%\string\gdef\mybsbs\mybs\string\rpbuild@jobname{\rpbuild@jobname}
%\string\gdef\mybsbs\mybs\rpbuild@vartoken{\rpbuild@iter}
%\string\typeout{INB4 INPUT \mybsbs\mybs\rpbuild@jobname}
%% ! Use of \\ doesn't match its definition.
%% <write> INB4 INPUT \\r ... epeat-build-test
% is like this: ...

% (c1)
% do not use spaces in keys for usepackage;
% e.g. for below getting:
% ! Package pgfkeys Error: I do not know the key '/rpbuild/postactioncode'
%post action code/.store in = \rpbuild@postcode,
%post action code/.default = {},

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.