diff --git a/DESCRIPTION b/DESCRIPTION index 78f827ba8e..1e9726b5a5 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: knitr Type: Package Title: A General-Purpose Package for Dynamic Report Generation in R -Version: 1.45.14 +Version: 1.45.15 Authors@R: c( person("Yihui", "Xie", role = c("aut", "cre"), email = "xie@yihui.name", comment = c(ORCID = "0000-0003-0645-5666")), person("Abhraneel", "Sarma", role = "ctb"), diff --git a/NEWS.md b/NEWS.md index b7fb581320..1f87287441 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,6 +6,8 @@ - `knitr::spin()` now recognizes `# %%` as a valid code chunk delimiter (thanks, @kylebutts, #2307). +- `knitr::spin()` also recognizes `#|` comments as code chunks now (thanks, @kylebutts, #2320). + - Chunk hooks can have the `...` argument now. Previously, only arguments `before`, `options`, `envir`, and `name` are accepted. If a chunk hook function has the `...` argument, all the aforementioned four arguments are passed to the hook. This means the hook function no longer has to have the four arguments explicitly in its signature, e.g., `function(before, ...)` is also valid if only the `before` argument is used inside the hook. See for more information. - For package vignettes, PNG plots will be optimized by `optipng` and `pngquant` if they are installed and can be found from the system `PATH` variable. This can help reduce the package size if vignettes contain PNG plots (thanks, @nanxstats, ). diff --git a/R/spin.R b/R/spin.R index 6bb564c47d..33ccde0334 100644 --- a/R/spin.R +++ b/R/spin.R @@ -3,8 +3,7 @@ #' This function takes a specially formatted R script and converts it to a #' literate programming document. By default normal text (documentation) should #' be written after the roxygen comment (\code{#'}) and code chunk options are -#' written after \code{#+} or \code{# \%\%} or \code{#-} or \code{# ----} or -#' any of these combinations replacing \code{#} with \code{--}. +#' written after \code{#|} or \code{#+} or \code{# \%\%} or \code{# ----}. #' #' Obviously the goat's hair is the original R script, and the wool is the #' literate programming document (ready to be knitted). @@ -79,7 +78,6 @@ spin = function( r = rle((is_matchable & grepl(doc, x)) | i) # inline expressions are treated as doc instead of code n = length(r$lengths); txt = vector('list', n); idx = c(0L, cumsum(r$lengths)) - p1 = gsub('\\{', '\\\\{', paste0('^', p[1L], '.*', p[2L], '$')) for (i in seq_len(n)) { block = x[seq(idx[i] + 1L, idx[i + 1])] @@ -90,18 +88,24 @@ spin = function( # R code; #+/- indicates chunk options block = strip_white(block) # rm white lines in beginning and end if (!length(block)) next - rc <- '^(#|--)+(\\+|-|\\s+%%| ----+| @knitr)' - if (length(opt <- grep(rc, block))) { - opts = gsub(paste0(rc, '\\s*|-*\\s*$'), '', block[opt]) - opts = paste0(ifelse(opts == '', '', ' '), opts) - block[opt] = paste0(p[1L], opts, p[2L]) + + rc = '^(#|--)+(\\+|-| %%| ----+| @knitr)(.*?)\\s*-*\\s*$' + j1 = grep(rc, block) + # pipe comments (#|) should start a code chunk if they are not preceded by + # chunk opening tokens + j2 = setdiff(pipe_comment_start(block), j1 + 1) + + if (length(j3 <- c(j1, j2))) { + block[j1] = paste0(p[1], gsub(rc, '\\3', block[j1]), p[2]) + block[j2] = paste0(p[1], p[2], '\n', block[j2]) + # close each chunk if there are multiple chunks in this block - if (any(opt > 1)) { - j = opt[opt > 1] - block[j] = paste(p[3L], block[j], sep = '\n') + if (any(j3 > 1)) { + j = j3[j3 > 1] + block[j] = paste0(p[3], '\n', block[j]) } } - if (!grepl(p1, block[1L])) { + if (!startsWith(block[1L], p[1L])) { block = c(paste0(p[1L], p[2L]), block) } c('', block, p[3L], '') @@ -155,6 +159,15 @@ spin = function( c(paste0(b, '{r'), '}', b, paste0(i, 'r \\1 ', i)) } +# find the position of the starting `#|` in a consecutive block of `#|` comments +pipe_comment_start = function(x) { + i = startsWith(x, '#| ') + r = rle(i) + l = r$lengths + j = cumsum(l) - l + 1 + j[r$values] +} + #' Spin a child R script #' #' This function is similar to \code{\link{knit_child}()} but is used in R diff --git a/man/spin.Rd b/man/spin.Rd index 67913bbcb2..0ca3ece232 100644 --- a/man/spin.Rd +++ b/man/spin.Rd @@ -59,8 +59,7 @@ If \code{text} is \code{NULL}, the path of the final output document, This function takes a specially formatted R script and converts it to a literate programming document. By default normal text (documentation) should be written after the roxygen comment (\code{#'}) and code chunk options are -written after \code{#+} or \code{# \%\%} or \code{#-} or \code{# ----} or -any of these combinations replacing \code{#} with \code{--}. +written after \code{#|} or \code{#+} or \code{# \%\%} or \code{# ----}. } \details{ Obviously the goat's hair is the original R script, and the wool is the diff --git a/tests/testit/test-spin.R b/tests/testit/test-spin.R index 39c0a56524..ccecb9c393 100644 --- a/tests/testit/test-spin.R +++ b/tests/testit/test-spin.R @@ -1,32 +1,47 @@ library(testit) -spin_w_tempfile = function(..., format = "Rmd") { - tmp = tempfile(fileext = ".R") - writeLines(c(...), tmp) - spinned = spin(tmp, knit = FALSE, format = format) - result = readLines(spinned) - file.remove(c(tmp, spinned)) - result +spin_text = function(..., format = "Rmd") { + x = spin(text = c(...), knit = FALSE, format = format) + xfun::split_lines(x) } assert("spin() detects lines for documentation", { - (spin_w_tempfile("#' test", "1 * 1", "#' test") %==% + (spin_text("#' test", "1 * 1", "#' test") %==% c("test", "", "```{r}", "1 * 1", "```", "", "test")) # a multiline string literal contains the pattern of doc or inline - (spin_w_tempfile("code <- \"", "#' test\"") %==% + (spin_text("code <- \"", "#' test\"") %==% c("", "```{r}", "code <- \"", "#' test\"", "```", "")) - (spin_w_tempfile("code <- \"", "{{ 1 + 1 }}", "\"") %==% + (spin_text("code <- \"", "{{ 1 + 1 }}", "\"") %==% c("", "```{r}", "code <- \"", "{{ 1 + 1 }}", "\"", "```", "")) # a multiline symbol contains the pattern of doc or inline - (spin_w_tempfile("`", "#' test", "`") %==% + (spin_text("`", "#' test", "`") %==% c("", "```{r}", "`", "#' test", "`", "```", "")) - (spin_w_tempfile("`", "{{ 1 + 1 }}", "`") %==% + (spin_text("`", "{{ 1 + 1 }}", "`") %==% c("", "```{r}", "`", "{{ 1 + 1 }}", "`", "```", "")) }) assert("spin() uses proper number of backticks", { - (spin_w_tempfile("{{ '`' }}") %==% c("``r '`' ``")) - (spin_w_tempfile("{{`x`}}") %==% c("``r `x` ``")) - (spin_w_tempfile("x <- '", "```", "'") %==% + (spin_text("{{ '`' }}") %==% c("``r '`' ``")) + (spin_text("{{`x`}}") %==% c("``r `x` ``")) + (spin_text("x <- '", "```", "'") %==% c("", "````{r}", "x <- '", "```", "'", "````", "")) }) + +assert("spin() generates code chunks with pipe comments `#|`", { + ( + spin_text("", "#| echo: false", "#| message: false", "#| include: false", "1+1", "#| eval: false", "2 + 2", "", "#' Text") %==% + c('', '```{r}', '#| echo: false', '#| message: false', '#| include: false', '1+1', '```', '```{r}', '#| eval: false', '2 + 2', '```', '', 'Text') + ) + + # https://github.com/yihui/knitr/issues/2314 + ( + spin_text('#| echo: false', '1+1', '#| label: test', '1+1') %==% + c('', '```{r}', '#| echo: false', '1+1', '```', '```{r}', '#| label: test', '1+1', '```', '') + ) + + # Has a `# %%` already + ( + spin_text('# %%', '#| echo: false', '1+1', '#| label: test', '1+1') %==% + c('', '```{r}', '#| echo: false', '1+1', '```', '```{r}', '#| label: test', '1+1', '```', '') + ) +})