11

I teach university courses and my goal is to be able to output two pdf's from the same tex file:

  1. A problem set
  2. A problem set with solutions

I already do this manually by calling \newif and then setting \soltrue or \solfalse in the header depending on whether I want to output solutions or not. Then I surround the solutions in \ifsol and \fi and we're off to the races. It's great because edits to the problem set require changes to only one document, not two. The only annoyance is that I have to compile it once, change that pdf's name, then change the header from \soltrue to \solfalse, then recompile again to get both documents. I'd like to use arara to automate this.

I've been following this excellent how-to on creating a yaml rule to get arara to do what I want. In the how-to, the author (cmhughes) creates a rule to compile a tex document into two pdf's, one with one column and the other with two. Obviously my application is slightly different.

I have created the following solutions.yaml rule:

!config
# Make two versions of document depending on if statement "sol"
# author: Shane Auerbach (based on work by Chris Hughes)
# requires arara 3.0+
#
# Sample usage
#
# % arara: solutions
# % arara: solutions: {solutions: true}
# % arara: solutions: {solutions: false}
#
identifier: solutions
name: Solutions
commands: 
- <arara> pdflatex "\newif\ifsol\sol@{trigger}\input{@{file}}" 
- <arara> @{ isWindows( "cmd /c move", "mv" ) } @{getBasename(file)}.pdf @{getBasename(file)}@{trigger.toUpperCase()}.pdf
arguments: 
- identifier: trigger
  flag: <arara> @{parameters.trigger}
  default: false

And this is the test mytex.tex file:

% arara: solutions:  {trigger: true}
% arara: solutions: {trigger: false}

\documentclass{article}
\begin{document}
Question: What is $2+2$?
\ifsol \begin{quote} \textbf{Solution:} $2+2=3$ \end{quote} \fi
\end{document}

It works great. When I run arara on the tex file, it outputs two pdf's, one with the solution and one without, as desired. There are two improvements I would like to make but do not know how to implement:

  1. As is, I can currently only compile the tex file with arara as the file itself has neither \soltrue nor \solfalse in it. I would like to be able to put in something that set it if and only if it was not already set by arara so that I could compile this outside of arara also. But I do not want it to overwrite arara's setting, as this would defeat the purpose. Any ideas?

  2. Currently the pdf files generated are called mytexTRUE.pdf (with solutions) and mytexFALSE.pdf (without solutions). I'd prefer to have something like mytexS.pdf (with solutions) and mytexQ.pdf (without solutions). The only way I could think to implement this would be a conditional statement in solutions.yaml, but from what I've read yaml is not really designed for conditional statements. Any ideas on this front?

If you've read this far, you're a true hero. Thanks! If you skipped the middle, tl;dr: help this stranger make his workflow ever-so-slightly more efficient.

Shane
  • 875
  • Hello! Sorry for missing your question earlier! I will come up with some suggestions for improvements later on. Version 4.0 of arara might have some goodies for this case, but since it's not officially released (it's in the works), I will write a compatible solution for version 3.0 as well. :) – Paulo Cereda Jun 07 '16 at 21:20

1 Answers1

10

A possible way to achieve the first improvement is by exploiting \ifdefined instead of relying on a \newif, as in the original code. Let us see the new TeX code:

\documentclass{article}
\begin{document}
Question: What is $2+2$?
\ifdefined\solutionflag\begin{quote} \textbf{Solution:} $2+2=3$ \end{quote}\fi
\end{document}

The plan here is straightforward: if \solutionflag is defined, that particular code excerpt will be processed. The idea is very similar to using \if<flag>, but it does not complain if this contraption isn't set. :)

The previous code can be normally compiled with your favourite engine and does not require arara. If you want to print the answers, just write \def\solutionflag{} somewhere in the preamble and we are done.

Update: this answer is updated with a rule for version 4.0+. Check the edit history for and older version.

Let us take a look at this improved rule:

!config
identifier: solutions
name: Solutions
authors:
- A duck
commands:
- name: The engine
  command: >
    @{
        prefix = isTrue(solutions, '\\def\\solutionflag{}', '');
        input = '\\input{' + reference.getName() + '}';
        return getCommand(engine, prefix + input, options);
    }
- name: The renaming procedure
  command: >
    @{
        separator = java.io.File.separator;
        prefix = [];
        if (isUnix()) {
            prefix = [ 'mv' ];
        }
        else {
            prefix = [ 'cmd', '/c', 'move' ];
        }
        parent = reference.getParent(); 
        input = parent + separator + getBasename(reference) + '.pdf';
        output = parent + separator + name + '.pdf';
        return getCommand(prefix, input, output);
    }
arguments:
- identifier: engine
  flag: >
    @{
        if ([ 'pdflatex', 'latex', 'xelatex',
              'lualatex' ].contains(parameters.engine)) {
            return parameters.engine;
        }
        else {
            throwError('The provided engine is not valid');
        }
    }
  default: pdflatex
- identifier: name
  flag: >
    @{
        return parameters.name;
    }
  required: true
- identifier: solutions
  flag: >
    @{
        return isTrue(parameters.solutions);
    }
  default: >
    @{
        return false
    }
- identifier: options
  flag: >
    @{
        if (isList(parameters.options)) {
            return parameters.options;
        }
        else {
            throwError('I was expecting a list of options.');
        }
    }

The rule arguments:

  • engine: string, optional, sets the engine. Possible options are pdflatex, latex, xelatex and lualatex. Default: pdflatex.

  • solutions: natural boolean, optional, sets whether the solution macro will be defined in the document scope. Default: false.

  • name: string, required, sets the document name. The document will be renamed to this value. No need to specify the .pdf extension.

  • options: list, optional, sets the list of additional command line parameters to be provided to the engine.

A sample execution:

% arara: solutions: { solutions: true, name: 'answers' }
% arara: solutions: { solutions: false, name: 'questions' }

\documentclass{article}
\begin{document}
Question: What is $2+2$?
\ifdefined\solutionflag\begin{quote} \textbf{Solution:} $2+2=3$ \end{quote}\fi
\end{document}

When running it with arara:

[paulo@cambridge ~] $ $ arara test.tex 
  __ _ _ __ __ _ _ __ __ _ 
 / _` | '__/ _` | '__/ _` |
| (_| | | | (_| | | | (_| |
 \__,_|_|  \__,_|_|  \__,_|

Processing 'test.tex' (size: 264 bytes, last modified: 03/22/2020
06:54:54), please wait.

(Solutions) The engine .................................. SUCCESS
(Solutions) The renaming procedure ...................... SUCCESS
(Solutions) The engine .................................. SUCCESS
(Solutions) The renaming procedure ...................... SUCCESS

Total: 1.25 seconds

We get the following output:

[paulo@cambridge ~] $ ls *.pdf
answers.pdf  questions.pdf

Another interesting suggestion is to improve your document semantics by creating an environment to selectively display solutions. The following code was kindly suggested by David Carlisle (I owe him $5):

\newenvironment{solution}{\ifdefined\solutionflag\else\setbox0\vbox\fi\bgroup}{‌​\par\egroup}

Hope it helps! :)

Paulo Cereda
  • 44,220