2

Following up this answer to my previous question, I have the following problems to fix:

1- If the answers option is disabled, I need setting twofilestrue to generate only the exam without the solution.

2- When using lualatex (i.e. \latexmkiffalse) and the option answers is enabled, I can't generate any pdf file. It goes into an endless compilation loop.

\documentclass{exam}
\usepackage{expl3}

\newif\iftwofiles
\twofilesfalse% false to print one file , true for both at a time

\newif\iflatexmkif
\latexmkiffalse% true to use latexmk , false to use lualatex

\ExplSyntaxOn

\str_new:N \g__diaa_solved_jobname_str

\iflatexmkif
    \str_new:N \l__diaa_latexmk_engine_str
    \str_const:Nn \l__diaa_latexmk { latexmk }
\else
    \str_const:Nn \l__diaa_latex_cmd { lualatex }
    \str_const:Nn \l__diaa_latex_options { -synctex=1 ~ -interaction=nonstopmode ~ -shell-escape }
\fi

\cs_new:Nn \__diaa_build_solved_jobname:
{
\str_gset:Nx \g__diaa_solved_jobname_str { \c_sys_jobname_str }
\str_gremove_all:Nn \g__diaa_solved_jobname_str { " }
\str_gput_left:Nn \g__diaa_solved_jobname_str { "[solved]~ }
\str_gput_right:Nn \g__diaa_solved_jobname_str { " }
}

\sys_if_shell_unrestricted:T
{
    \iflatexmkif

        \sys_if_engine_luatex:T
        { \str_set:Nn \l__diaa_latexmk_engine_str { -lualatex ~ -g ~ -interaction=nonstopmode } }
        \sys_if_engine_pdftex:T
        { \str_set:Nn \l__diaa_latexmk_engine_str { -pdf } }
        \sys_if_engine_xetex:T
        { \str_set:Nn \l__diaa_latexmk_engine_str { -xelatex } }

        \iftwofiles

            \__diaa_build_solved_jobname:
            \sys_shell_now:x
            {% printing the solution
                \l__diaa_latexmk \c_space_tl
                \l__diaa_latexmk_engine_str \c_space_tl
                -usepretex="\string\AtBeginDocument{\string\printanswerstrue}" \c_space_tl
                -jobname=\g__diaa_solved_jobname_str \c_space_tl
                \c_sys_jobname_str
            }
            \sys_shell_now:x
            {% printing the exam
                \l__diaa_latexmk \c_space_tl
                \l__diaa_latexmk_engine_str \c_space_tl
                -usepretex="\string\AtBeginDocument{\string\printanswersfalse}" \c_space_tl
                \c_sys_jobname_str
            }
            \stop

        \else % one file

            \legacy_if:nT { printanswers }
            {
                \__diaa_build_solved_jobname:
                \sys_shell_now:x
                {
                    \l__diaa_latexmk \c_space_tl
                    \l__diaa_latexmk_engine_str \c_space_tl
                    -jobname=\g__diaa_solved_jobname_str \c_space_tl
                    \c_sys_jobname_str
                }
                \stop
            }
        \fi

    \else % lualatex

        \iftwofiles

            \__diaa_build_solved_jobname:
            \sys_shell_now:x
            {% printing the solution
                \l__diaa_latex_cmd \c_space_tl
                -jobname=\g__diaa_solved_jobname_str \c_space_tl
                \l__diaa_latex_options \c_space_tl
                "\string\AtBeginDocument{\string\printanswerstrue}" \c_space_tl
                "\string\input{\c_sys_jobname_str}"
            }
            \sys_shell_now:x
            {% printing the exam
                \l__diaa_latex_cmd \c_space_tl
                -jobname=\c_sys_jobname_str \c_space_tl
                \l__diaa_latex_options \c_space_tl
                "\string\AtBeginDocument{\string\printanswersfalse}" \c_space_tl
                "\string\input{\c_sys_jobname_str}"
            }
            \stop

        \else % one file

            \legacy_if:nT { printanswers } % if answers is enabled
            {
                \__diaa_build_solved_jobname:
                \sys_shell_now:x
                {
                    \l__diaa_latex_cmd \c_space_tl
                    \l__diaa_latex_options \c_space_tl
                    -jobname=\g__diaa_solved_jobname_str \c_space_tl
                    \c_sys_jobname_str
                }
                \stop
            }
        \fi
    \fi
}

\ExplSyntaxOff

\begin{document}
    \begin{questions}
        \question some question
        \begin{solution}
            the solution
        \end{solution}
    \end{questions}
\end{document}
Diaa
  • 9,599
  • For future readers, you may need to check this if you would like to know how to create a TeXStudio command that opens the output PDF file with different name from that of the original tex file (i.e. \jobname). – Diaa May 29 '20 at 04:39

1 Answers1

1

You got an infinite loop because you passed -shell-escape to lualatex when building through \sys_shell_now:x. So the \sys_if_shell_unrestricted:T test passes and you again run lualatex.

If I understand you right the following MWE will do what you want.

If the class answers option is not specified, then only the questions are produced, regardless of whether you call the initial build with -shell-escape. You can override this by uncommenting \bool_gset_true:N \g__diaa_build_two_files_bool, in which case questions and answers will always be produced.

When compiling with -shell-escape, the build will use whatever engine you initiate the compile with (pdflatex, lualatex, xelatex).

You can use latexmk for the build by uncommenting \bool_gset_true:N \g__diaa_use_latexmk_bool (assuming you also initiate the build with with the -shell-escape flag).

Update

You can now:

  • control output of questions using \g__diaa_build_questions_bool.
  • control output of answers using \g__diaa_build_solved_bool.
  • specify options for both LaTeX and Latexmk using \g__diaa_latex_options_str and \g__diaa_latexmk_options_str respectively.
  • control whether Latexmk is used using \g__diaa_use_latexmk_bool.

To ensure that synctex data is correctly written you will need the options -synctex=1 -output-directory=out. This ensures that files are not overwritten by the main compiling process.

MWE

\documentclass[answers]{exam}

\usepackage{expl3}

\ExplSyntaxOn

% Flag to enable Latexmk
\bool_new:N \g__diaa_use_latexmk_bool

% Uncomment the following line to use Latexmk
%\bool_gset_true:N \g__diaa_use_latexmk_bool


% Flag to output questions and/or answers
\bool_new:N \g__diaa_build_questions_bool
\bool_new:N \g__diaa_build_solved_bool

% Always build questions
\bool_gset_true:N \g__diaa_build_questions_bool

% Build answers if class answers option is set
\legacy_if:nT { printanswers }
  {
    \bool_gset_true:N \g__diaa_build_solved_bool
  }

% Uncomment the following line to not build questions
%\bool_gset_false:N \g__diaa_build_questions_bool

% Uncomment the following line to always build answers even if class answers option is not set
%\bool_gset_true:N \g__diaa_build_solved_bool


% Answers jobname variable
\str_new:N \g__diaa_solved_jobname_str

% LaTeX engine and options variables
\str_new:N \g__diaa_latex_cmd_str
\str_const:Nn \g__diaa_latex_options_str { -synctex=1 ~ -output-directory=out ~ -interaction=nonstopmode }

% Latexmk engine and options variables
\str_const:Nn \g__diaa_latexmk_str { latexmk }
\str_const:Nn \g__diaa_latexmk_options_str { -synctex=1 ~ -output-directory=out }
\str_new:N \g__diaa_latexmk_engine_str

% Final build commands
\str_new:N \g__diaa_cmd_questions_str
\str_new:N \g__diaa_cmd_solved_str


% Build the answer jobname
\cs_new:Nn \__diaa_build_solved_jobname:
  {
    \str_gset:Nx \g__diaa_solved_jobname_str { \c_sys_jobname_str }
    \str_gremove_all:Nn \g__diaa_solved_jobname_str { " }
    \str_gput_left:Nn \g__diaa_solved_jobname_str { " }
    \str_gput_right:Nn \g__diaa_solved_jobname_str { ~[solved]" }
  }

% Set engines based on current engine
\cs_new:Nn \__diaa_set_engine:
  {
    \sys_if_engine_luatex:T
      {
        \str_gset:Nn \g__diaa_latex_cmd_str { lualatex }
        \str_gset:Nn \g__diaa_latexmk_engine_str { -lualatex }
      }
    \sys_if_engine_pdftex:T
      {
        \str_gset:Nn \g__diaa_latex_cmd_str { pdflatex }
        \str_gset:Nn \g__diaa_latexmk_engine_str { -pdf }
      }
    \sys_if_engine_xetex:T
      {
        \str_gset:Nn \g__diaa_latex_cmd_str { xelatex }
        \str_gset:Nn \g__diaa_latexmk_engine_str { -xelatex }
      }
  }

% Build LaTeX questions command
\cs_new:Nn \__diaa_build_latex_questions_cmd:
  {
    \str_gset:Nx \g__diaa_cmd_questions_str
      {
        \g__diaa_latex_cmd_str \c_space_tl
        -jobname=\c_sys_jobname_str \c_space_tl
        \g__diaa_latex_options_str \c_space_tl
        "\string\AtBeginDocument{\string\printanswersfalse}" \c_space_tl
        "\string\input{\c_sys_jobname_str}"
      }
  }

% Build LaTeX answers command
\cs_new:Nn \__diaa_build_latex_solved_cmd:
  {
    \str_gset:Nx \g__diaa_cmd_solved_str
      {
        \g__diaa_latex_cmd_str \c_space_tl
        -jobname=\g__diaa_solved_jobname_str \c_space_tl
        \g__diaa_latex_options_str \c_space_tl
        "\string\AtBeginDocument{\string\printanswerstrue}" \c_space_tl
        "\string\input{\c_sys_jobname_str}"
      }
  }

% Build Latexmk questions command
\cs_new:Nn \__diaa_build_latexmk_questions_cmd:
  {
    \str_gset:Nx \g__diaa_cmd_questions_str
      {
        \g__diaa_latexmk_str \c_space_tl
        \g__diaa_latexmk_engine_str \c_space_tl
        \g__diaa_latexmk_options_str \c_space_tl
        -usepretex="\string\AtBeginDocument{\string\printanswersfalse}" \c_space_tl
        \c_sys_jobname_str
      }
  }

% Build Latexmk answers command
\cs_new:Nn \__diaa_build_latexmk_solved_cmd:
  {
    \str_gset:Nx \g__diaa_cmd_solved_str
      {
        \g__diaa_latexmk_str \c_space_tl
        \g__diaa_latexmk_engine_str \c_space_tl
        \g__diaa_latexmk_options_str \c_space_tl
        -usepretex="\string\AtBeginDocument{\string\printanswerstrue}" \c_space_tl
        -jobname=\g__diaa_solved_jobname_str \c_space_tl
        \c_sys_jobname_str
      }
  }


% Build the files
\sys_if_shell_unrestricted:T
  {
    % Set up engines
    \__diaa_set_engine:

    % Build answers jobname
    \__diaa_build_solved_jobname:

    % Set up compile commands
    \bool_if:NTF \g__diaa_use_latexmk_bool
      {
        \__diaa_build_latexmk_questions_cmd:
        \__diaa_build_latexmk_solved_cmd:
      }
      {
        \__diaa_build_latex_questions_cmd:
        \__diaa_build_latex_solved_cmd:
      }

    % Build questions
    \bool_if:NT \g__diaa_build_questions_bool
      {
        \sys_shell_now:x
          {
            \str_use:N \g__diaa_cmd_questions_str
          }
      }

    % Build answers
    \bool_if:NT \g__diaa_build_solved_bool
      {
        \sys_shell_now:x
          {
            \str_use:N \g__diaa_cmd_solved_str
          }
      }
    \stop
  }

\ExplSyntaxOff

\begin{document}
\begin{questions}
  \question One of these things is not like the others; one of these things is
  not the same. Which one is different?
  \begin{oneparchoices}
    \choice John
    \choice Paul
    \choice George
    \choice Ringo
    \CorrectChoice Socrates
  \end{oneparchoices}
\end{questions}
\end{document}
David Purton
  • 25,884
  • I highly appreciate the effort you put into making such a well constructed and informative answer. Thanks again for your time and consideration. – Diaa Apr 22 '20 at 10:35
  • How can I make this document print only the solution without the questions-only file? and I need this behavior for both latexmk and lualatex. Thanks in advance. – Diaa Apr 22 '20 at 11:04
  • Another issue; when setting \g__diaa_latex_options {-synctex=1} and \g__diaa_latexmk_engine_str { -lualatex ~ -synctex=1}, I have an error SyncTeX: Can't rename <file name>.synctex(busy) to <file name>.synctex.gz since I appended to the compiler command (e.g. lualatex) the option -synctex=1 in my texstudio settings. So, is there a way to keep my texstudio command as it is and avoid this conflict of applying synctex? – Diaa Apr 22 '20 at 11:12
  • Even worse, none of the output files have the inherent tex sync between the source and the output. I would be grateful if you could confirm that the output files on your files have the sync feature working between the source and output. – Diaa Apr 22 '20 at 12:37
  • I can't see that you could ever hope to get forward search to the answers file working, since there is no way to know whether you want to go to the questions or solutions file. – David Purton Apr 23 '20 at 01:39
  • The [solved] file does inverse sync correctly. But the questions file does not. I don't know a lot about sync tex, but I assume this is because it outputs to the same filename as the initial run and the data gets messed up. This is also what is behind your file busy error. You'd need to add a [questions] suffix to the question build to make sync tex work. The fact that the inverse sync with the [solved] file doesn't work for you is probably just because you don't use whatever command line is needed when opening the PDF. But the data is there and does work for me. – David Purton Apr 23 '20 at 01:41
  • It should be straight forward to output only the answers by setting a suitable flag. I'll update my answer above. – David Purton Apr 23 '20 at 01:44
  • Oooo! You could use the -output-directory=dir option to put all the output files in a sub directory. This gives me both a questions and answers file that inverse sync. It won't forward sync of course since your editor can't know that you put the output files somewhere else. – David Purton Apr 23 '20 at 02:24
  • If you're using MikTeX, you could possibly use -aux-directory=dir which will put everything except the final PDF in a different directory. This should work because the initial build process does not produce a PDF at all when -shell-escape is used. – David Purton Apr 23 '20 at 05:20
  • Many many thanks for your great help. However, may I know why this doesn't output the questions-only file? I expect to have a [Exam]~*.pdf file. – Diaa Apr 23 '20 at 05:24
  • For me, I would like to have all the files in the same folder so that I don't need to go back and forth :) – Diaa Apr 23 '20 at 05:25
  • 1
  • Is it possible to have an approach where I can change the output file name after compilation instead of changing the \jobname? – Diaa Oct 09 '20 at 15:20
  • Because your last approach results in creating three sets of auxiliary files: one named after the main tex file, another set is named after the output exam PDF, and the last set of auxiliary files is named after the solution PDF file. So, I need to have only one set of auxiliary files named after the main tex file while renaming the output PDF based on whether or not the answer options is enabled. – Diaa Oct 09 '20 at 15:58
  • @Diaa, You should just use an ordinary Tex file (i.e., remove all the custom build code and don't use -shell_escape) and compile it with a shell script, which moves the files. – David Purton Oct 09 '20 at 21:50
  • But how can I rename the output PDF file after the successful compilation to something like [Exam] <\jobname>.pdf when answers option is disabled, and [Solution] <\jobname>.pdf when asnwers are enabled? – Diaa Oct 09 '20 at 21:54
  • @Diaa You can't do this from inside the TeX file. You have to do it externally. Think about it. You can't rename until the compile is finished, but by then it's too late to run external code. – David Purton Oct 09 '20 at 22:02
  • I am trying to create a bat file for those commands, however, I don't know if I am calling the system cmd right or not. Is this correct lualatex -synctex=1 -interaction=nonstopmode -shell-escape "\string\AtBeginDocument{\string\printanswersfalse}" "\string\input{<file name with spaces>.tex}" to run on windows command prompt? – Diaa Oct 09 '20 at 22:23
  • 1
    Sorry, I use Linux not windows. You would be better asking a new question. – David Purton Oct 09 '20 at 22:31
  • I would be grateful if you could take a look at my relevant followup question. – Diaa Dec 01 '20 at 23:11
  • If I would like to have the following \newlength{\headerTotalHeight} \AtBeginDocument{\settototalheight\headerTotalHeight{\headerContents}\geometry{headheight = \headerTotalHeight}} inside the \ExplSyntax code of this question, what should be done? – Diaa Dec 02 '20 at 23:11