Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: quarto
Title: R Interface to 'Quarto' Markdown Publishing System
Version: 1.4.4.9028
Version: 1.4.4.9029
Authors@R: c(
person("JJ", "Allaire", , "jj@posit.co", role = "aut",
comment = c(ORCID = "0000-0003-0174-9868")),
Expand Down
9 changes: 5 additions & 4 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,11 @@

- `qmd_to_r_script()` extracts R code cells from Quarto documents and
creates R scripts. This experimental function preserves chunk options
using `#|` syntax, adds YAML metadata as spin-style headers, and handles
mixed-language documents by filtering only R cells. Complements the
existing `add_spin_preamble()` function for working with R scripts in
Quarto workflows (#208, quarto-dev/quarto-cli#9112).
using `#|` syntax, adds YAML metadata as spin-style headers, handles
mixed-language documents by filtering only R cells, skips chunks with
`purl: false`, and properly processes `eval: false` chunks by commenting
out their code. Complements the existing `add_spin_preamble()` function
for working with R scripts in Quarto workflows (#208, #277, quarto-dev/quarto-cli#9112).

- `quarto_available()` checks if Quarto CLI is found (thanks, @hadley,
#187).
Expand Down
27 changes: 25 additions & 2 deletions R/utils-extract.R
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@
#' - Adding the document's YAML metadata as a spin-style header
#' - Creating an R script that can be rendered with the same options
#'
#' ## Chunk option handling:
#' - Chunks with `purl: false` are completely skipped and not included in the output
#' - Chunks with `eval: false` have their code commented out (prefixed with `# `) in the R script
#' - All other chunk options are preserved as `#|` comment headers
#'
#' ## File handling:
#' - If the output R script already exists, the function will abort with an error
#' - Non-R code cells (e.g., Python, Julia, Observable JS) are ignored
Expand All @@ -35,7 +40,15 @@
#' more details on rendering R scripts with Quarto.
#'
#' The resulting R script uses Quarto's executable cell format with `#|`
#' comments to preserve chunk options like `echo`, `eval`, `output`, etc.
#' comments to preserve chunk options like `label`, `echo`, `output`, etc.
#'
#' The resulting R script could also be `source()`d in R, as any `eval = FALSE` will be commented out.
#'
#' ## Limitations:
#' This function relies on static analysis of the Quarto document by `quarto inspect`. This means that
#' any \pkg{knitr} specific options like `child=` or specific feature like [knitr::read_chunk()] are not supported.
#' They rely on tangling or knitting by \pkg{knitr} itself. For this support,
#' one should look at [knitr::hook_purl()] or [knitr::purl()].
#'
#' @return Invisibly returns the path to the created R script file, or
#' `NULL` if no R code cells were found.
Expand Down Expand Up @@ -110,12 +123,22 @@ qmd_to_r_script <- function(qmd, script = NULL) {
}

r_codeCells <- codeCells[codeCells$language == "r", ]

content <- character(nrow(r_codeCells))
for (i in seq_len(nrow(r_codeCells))) {
row <- r_codeCells[i, ]
metadata_list <- as.list(row$metadata)
metadata_clean <- metadata_list[!is.na(metadata_list)]
if (isFALSE(metadata_clean$purl)) {
# cell with purl: false should be skipped
next
}
if (isFALSE(metadata_clean$eval)) {
# cell with eval: false should be commented out in R script
row$source <- paste(
c(paste0("# ", head(xfun::split_lines(row$source), -1)), ""),
collapse = "\n"
)
}
content[i] <- paste(
c(create_code_preamble(metadata_clean), row$source),
collapse = "\n"
Expand Down
20 changes: 19 additions & 1 deletion man/qmd_to_r_script.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions tests/testthat/_snaps/utils-extract/purl.R
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,16 @@
#' ---
#'

#| label: my-label
#| echo: false
#| output: asis
cat("Hello, world")

#| echo: true
cat("more")

#| eval: false
# # This code should not run.
# 1 + a


7 changes: 7 additions & 0 deletions tests/testthat/_snaps/utils-extract/purl.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,10 @@ cat("more")
more
:::
::::

::: cell
``` {.r .cell-code}
# # This code should not run.
# 1 + a
```
:::
18 changes: 18 additions & 0 deletions tests/testthat/resources/purl-r.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ format: html
## Section

```{r}
#| label: my-label
#| echo: false
#| output: asis
cat("Hello, world")
Expand All @@ -16,4 +17,21 @@ cat("Hello, world")
```{r}
#| echo: true
cat("more")
```

## A section that should be commented out

```{r}
#| eval: false
# This code should not run.
1 + a
```

## A section explicitly commented out with `purl = FALSE`

```{r}
#| purl: false
# This code should not be included in the purl output.
# but it works
1 + 1
```
41 changes: 41 additions & 0 deletions tests/testthat/test-utils-extract.R
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,47 @@ test_that("qmd_to_r_script() writes R file that renders", {
)
})

test_that("qmd_to_r_script() comment cells with eval = TRUE", {
r_script <- withr::local_tempfile(pattern = "purl", fileext = ".R")

qmd_to_r_script(
resources_path("purl-r.qmd"),
script = r_script
)
content <- xfun::file_string(r_script)
expect_match(
content,
"# # This code should not run.",
fixed = TRUE
)
expect_no_match(
content,
"(?<!# )# This code should not run\\.",
perl = TRUE
)
})

test_that("qmd_to_r_script() ignore cells with purl = FALSE", {
r_script <- withr::local_tempfile(pattern = "purl", fileext = ".R")

qmd_to_r_script(
resources_path("purl-r.qmd"),
script = r_script
)
content <- xfun::file_string(r_script)
expect_no_match(
content,
"#| purl: false",
fixed = TRUE
)
expect_no_match(
content,
"# This code should not be included in the purl output.",
fixed = TRUE
)
})


test_that("qmd_to_r_script() do nothing on file with no code", {
skip_if_no_quarto()
expect_message(
Expand Down