0

I was hoping to keep all my R Markdown and other input files in a sub-folder (e.g., .../source), and have the final output pdf automatically go to the parent folder (.../). In case it is relevant, the output is to beamer_presentation. Is this possible with R Markdown or Pandoc?

I want to keep the .tex file for reference and debugging, but I consider it to be an intermediate output, so I would rather keep it in the same folder as the input file, not the final output destination with the pdf file.

I found this answer, which shows how to set the output_dir for the render() function right in the document YAML header, which works ... except that it puts the .tex file in the same output directory as the final .pdf file, which is not what I want.

I was able to achieve the desired result with a manual call to xelatex with the -output-directory=.. option (something similar appears to be possible with pdflatex, but that engine is not an option for my use case): e.g., xelatex -output-directory=.. input.tex. But I have not figured out how to pass this argument through rmarkdown to pandoc (and on to xelatex as the pdf-engine). I tried the following, but the final pdf file still ended up in the same directory as the input file:

  • pdf-engine-opt: "-output-directory=.." in the YAML header (based on the pandoc documentation). This appeared to have no effect.
  • pandoc_args: ["--pdf-engine-opt=--output-directory=.."] in the YAML header.
    • This seems to add the argument to the pandoc call (visible in the 'render' tab in RStudio), but does not change the location of the pdf file.
  • EDIT: I found other answers explaining that the pdf-engine is actually called by tinytex::latexmk, which explains why the pandoc arguments don't do anything. I tried adding this line to an r chunk (the default setup chunk) with the include=FALSE option:
    options(tinytex.engine_args = '--output-directory=..')
    
    • This seemed to produce the desired effect, except that it produced an Error:

    Error: LaTeX failed to compile input.tex. See https://yihui.org/tinytex/r/#debugging for debugging tips.
    Execution halted

It looks like there is a way to pass command-line arguments to pandoc via the pandoc_options() function in the rmarkdown package, but I can't figure out how to use that without having to manually set all the other arguments in a render() call. And if it did not work with pandoc_args:, I'm not sure what more the rmarkdown functions can do to pass on the --output-directory= option to xelatex.

Is this a rmarkdown issue or a pandoc limitation, or am I doing something wrong?

EDIT:

The general advice is to keep .tex, auxiliary files, and the final output pdf in the same directory, to avoid problems with TeX rendering. As an alternative, is there a way to run some R code or system commands to automatically move/copy the final output to a desired destination?

  • @KJ: good point. But I would rather automate the publishing process, rather than *manually* move a file every time it is output (or for every commit in my vcs). I notice that `rmarkdown::output_format` has an `on_exit` argument, where you can specify a function to run when `rmarkdown::render()` finishes. I can probably figure out a function to move the output file to the desired location. But I would appreciate an example of how to do that in a way that still allows me to use the output specifications in the YAML header (i.e., a way that works with the Knit button in RStudio). – Jonathan Whiteley Aug 24 '23 at 00:28

1 Answers1

0

What worked for me was modifying the code from this answer to add the desired commands after rmarkdown::render().

knit: 
  (function(inputFile, encoding, ...) {
    render_out <- rmarkdown::render(inputFile, encoding = encoding, ...);
    out_path <- unlist( strsplit(render_out, split = "/") );
    out_path <- file.path("..", out_path[length(out_path)]);
    success <- file.copy( render_out, out_path, overwrite = TRUE );
    if (!isFALSE(success)) {
      message("Output file copied to '", normalizePath(out_path), "'" );
      message("Deleting file '", render_out, "'" );
      file.remove( render_out )
    };
  })

rmarkdown::render() returns the value of the output file (if pandoc = TRUE), so I was able to use that to add follow-up commands using that information. I used file.copy followed by file.remove because file.rename is not as portable, according to the documentation, bur feel free to convince me otherwise.

If you want to customize it for your desired path, you can change the statements beginning with out_path <- to build your own destination path. Note that the statements must end with ; in this context.

Here's a shorter version if you don't want the output messages:

knit: 
  (function(inputFile, encoding, ...) {
    render_out <- rmarkdown::render(inputFile, encoding = encoding, ...);
    out_path <- unlist( strsplit(render_out, split = "/") );
    out_path <- file.path("..", out_path[length(out_path)]);
    success <- file.copy( render_out, out_path, overwrite = TRUE );
    if (!isFALSE(success)) file.remove( render_out );
  })

I admit that I'm not totally familiar with the syntax here, and it feels like an ugly hack: it's multiple R expressions wrapped in what looks like a hook, within a YAML header, so this is not your typical R coding environment. For example, adding a colon (:) anywhere, including messages, will cause (YAML) syntax errors. Also, no objects created before rmarkdown::render(...) will persist after (unless you use the <<- assignment operator to create objects in the global environment).

Some more information about this YAML entry is available in the R Markdown Cookbook. But it is light on details and I don't have the programming knowledge to fully explain what it does or how.

Why do it this way?

This approach allows processing of all intermediate files to happen in the same directory as the input file, avoiding issues with resources not being found because they are somewhere other than where they are expected (as widely recommended by the TeX community). But it automates the process of moving the final output to a desired location. This makes output folders cleaner, especially for users who are just interested in the final documents, not the source or intermediate outputs.