1

I have a memoir document which contains my PhD thesis. I would like to automatically create a separate PDF for every chapter, as well as a master PDF. I found this arara rule, which seems to do exactly what I want. However, the format is not compatible with arara 4.0. The main issue seems to be that it was possible to use items as an argument, which would then automatically be iterated (?). arara 4.0 does not accept this items argument. I was able to define a new argument chapters, and to iterate through it. I don't know how to compile a new PDF when within the MVEL (?) environment though.

Here is my code so far. It first compiles the whole document, and then iterates over chapters, printing their names (line 9). The goal would be to instead run something like the following command on line 9.

- <arara> @{engine} "\includeonly{@{chapter}}\input{@{chapter}}"

!config
identifier: makechapters
name: MakeChapters
commands:
- command: <arara> @{ isTrue( compileAll, engine.concat(' ').concat(file) )}
- command: >
    @{
      foreach (chapter : chapters) {
        System.out.println(chapter)
      }
    }
arguments:
- identifier: engine
  flag: <arara> @{parameters.engine}
  default: xelatex
- identifier: compileAll
  flag: <arara> @{parameters.compileAll}
  default: true
- identifier: chapters
  flag: <arara> @{parameters.chapters}
  default: []
0

I think this might be a nice start for our rule:

!config
identifier: makechapters
name: MakeChapters
commands:
- name: Making chapters
  command: >
    @{
        if (compile == 'all') {
            return getCommand(engine, file);
        }
        else {
            entries = [];
            base = getBasename(file);
            foreach (chapter: chapters) {
                entries.add(getCommand(engine, '\\includeonly{' + chapter +
                                               '}\\input{' + base + '}'));
            }
            return entries;
        }
    }
arguments:
- identifier: engine
  flag: >
    @{
        if ([ 'pdflatex', 'latex', 'xelatex',
              'lualatex' ].contains(parameters.engine)) {
            return parameters.engine;
        }
        else {
            throwError('The provided engine is not valid');
        }
    }
  default: xelatex
- identifier: compile
  flag: >
    @{
        if ([ 'all', 'chapters' ].contains(parameters.compile)) {
            return parameters.compile;
        }
        else {
            throwError('I was expecting "all" or "chapters".');
        }
    }
  default: 'all'
- identifier: chapters
  flag: >
    @{
        if (isList(parameters.chapters)) {
            return parameters.chapters;
        }
        else {
            throwError('I was expecting a list of chapters.');
        }
    }

Let's break it down:

!config
identifier: makechapters
name: MakeChapters

These are the headers, nothing new here. :)

commands:

Now we are creating the commands (actually, just one, as we will see in a minute).

- name: Making chapters
  command: >
    @{

Here it goes, the heart of our rule.

        if (compile == 'all') {
            return getCommand(engine, file);
        }

I decided to make compile a string variable instead of a boolean one, so we could specify two values for it: all, in case we want to compile the document, or chapters, in case we want to compile each chapter.

        else {

Now we are inside the else branch, i.e, we will compile each chapter individually.

            entries = [];
            base = getBasename(file);

The entries variable will hold a list of commands, each one for a chapter, and the base variable holds the basename of the current file reference.

            foreach (chapter: chapters) {
                entries.add(getCommand(engine, '\\includeonly{' + chapter +
                                               '}\\input{' + base + '}'));
            }

Note that chapters holds a list of strings, so we can iterate the values and, for each one, we create a corresponding command with the structure you provided before (note that I had to escape the backslash) and add to the list.

            return entries;
        }
    }

Now we simply return the list of commands and let arara do the rest.

Time to check the arguments:

arguments:

Let's go!

- identifier: engine
  flag: >
    @{
        if ([ 'pdflatex', 'latex', 'xelatex',
              'lualatex' ].contains(parameters.engine)) {
            return parameters.engine;
        }
        else {
            throwError('The provided engine is not valid');
        }
    }
  default: xelatex

Nothing new here, I just added a check to ensure the value is within a list of valid engine names.

- identifier: compile
  flag: >
    @{
        if ([ 'all', 'chapters' ].contains(parameters.compile)) {
            return parameters.compile;
        }
        else {
            throwError('I was expecting "all" or "chapters".');
        }
    }
  default: 'all'

Same idea of the previous argument, I check whether compile, when specified, contains either all or chapters.

- identifier: chapters
  flag: >
    @{
        if (isList(parameters.chapters)) {
            return parameters.chapters;
        }
        else {
            throwError('I was expecting a list of chapters.');
        }
    }

And at last, but not least, we ensure chapters is an actual list. :)

Let's see this rule in action (via --dry-run).

Header:

% arara: makechapters: { chapters: [ foo, bar ], compile: 'chapters' }

Output:

[DR] (MakeChapters) Making chapters
-----------------------------------------------------------------
Authors: No authors provided
About to run: [ xelatex, \includeonly{foo}\input{foo} ]

[DR] (MakeChapters) Making chapters
-----------------------------------------------------------------
Authors: No authors provided
About to run: [ xelatex, \includeonly{bar}\input{bar} ]

Header:

% arara: makechapters: { engine: pdflatex }

Output:

[DR] (MakeChapters) Making chapters
-----------------------------------------------------------------
Authors: No authors provided
About to run: [ pdflatex, test.tex ]

Hope it helps. :) I am not sure about the trick of compiling each chapter individually (never seen it before, to be honest), but that's the idea. Perhaps it's advisable to also set up -jobname for the underlying engine?

| improve this answer | |
  • Thank you! I am getting Missing \begin{document}., because on line 15, we first have \includeonly{chapter} and then \input{chapter; the second one should actually be input{file}, at least when I look at the 3.0 compatible version. Changing that fixes it. However, now all the PDF files have the main file's name, i.e. it is overwritten every time. When I tried to add "-jobname=" + chapter to the command, I get a stack overlow with the chapter.aux – folran Sep 18 '19 at 12:48
  • @folran what happens if you add -jobname= right after the engine? Try replacing the second getCommand(engine, ... by getCommand(engine, '-jobname=' + chapter, ... and see if it works. – Paulo Cereda Sep 18 '19 at 13:14
  • @folran we can also rename the resulting PDF before processing the next one. :) Which operating system do you use? – Paulo Cereda Sep 18 '19 at 13:16
  • Yeah I also figured that changing the filename would be a good idea. That fixed the problem. Now, next issue: how do I first run biber on the main file? – folran Sep 18 '19 at 13:23
  • Update: got it! Will post my complete solution when I have time. – folran Sep 18 '19 at 13:30

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.