With knitr, preserve chunk options when purling chunks into separate files
Asked Answered
S

1

2

For teaching purposes, I would like to purl the chunks of my .Rnw file into separate files. This answer explains how to do it:

How to purl each chunks in .Rmd file to multiple .R files using Knitr

BUT the method does not preserve the chunk options. Since the chunks I have produce plots, it's important to preserve the fig.width and fig.height options. Ideally I would like a chunk that looks like this:

<<plot, fig.width = 3, fig.height = 5, outwidth = '.75\\textwidth'>>=
plot (1,1)
@

to become a file named plot.R that looks like this:

#+ fig.width = 3, fig.height = 5
plot (1,1)

That is, turn the chunk options fig.width and fig.height into a format that will be recognized by spin(), as purl() does, and get rid of the chunk options that are irrelevant, or create problems for spin() into Word, such as out.width. All in the spirit of creating code files that are user-friendly.

Surplus answered 7/3, 2016 at 22:57 Comment(0)
N
1

Since the answer you refer to doesn't copy the header line from the results of purl, you lose everything besides the chunk name. While you could adapt it to paste in the headers, it's actually not hard to build a function to parse the output of purl—much easier than trying to parse a Rmd or Rnw document, anyway, and easier than sorting out exactly how knitr does so.

purl_chunks <- function(input_file){
  purled <- knitr::purl(input_file)    # purl original file; save name to variable
  lines <- readLines(purled)    # read purled file into a character vector of lines
  starts <- grep('^## ----.*-+', lines)    # use grep to find header row indices
  stops <- c(starts[-1] - 1L, length(lines))   # end row indices
  # extract chunk names from headers
  names <- sub('^## ----([^-]([^,=]*[^,=-])*)[,-][^=].*', '\\1', lines[starts])
  names <- ifelse(names == lines[starts], '', paste0('_', names)) # clean if no chunk name
  # make nice file names with chunk number and name (if exists)
  file_names <- paste0('chunk_', seq_along(starts), names, '.R')
  for(chunk in seq_along(starts)){    # loop over header rows
    # save the lines in the chunk to a file
    writeLines(lines[starts[chunk]:stops[chunk]], con = file_names[chunk])
  }
  unlink(purled)    # delete purled file of entire document
}

A couple notes:

  • While the regex works for what I've thrown at it, it may yet be fallible. Tested:
    • no chunk name
    • no name but chunk settings
    • name with hyphens
    • single character names
    • names with spaces, including after (before the comma/brace)
  • As .Rnw and .Rmd files both purl the same, it works for either.
  • It uses the default setting (1L) for purl's documentation parameter. 0L wouldn't have chunk headers and is thus pointless here anyway, but it would handle 2L (which would include text chunks as roxygen comments) stupidly. It could be rebuilt for such, though, if you wanted text chunks with the following code chunk.
  • While the output header lines of purl don't look exactly like your example above (they start with ## ---- and are filled with hyphens), they spin properly; results obey chunk options.
Nicolina answered 8/3, 2016 at 9:42 Comment(1)
Amazing, works like a charm. Thanks a million. This is what I wish I knew how to do... I will study it and learn.Surplus

© 2022 - 2024 — McMap. All rights reserved.