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 Answer 1

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 = {},

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .