From 7472039b4cd3791079f4ba7ca9a385b5e877f901 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Wed, 2 Jul 2025 15:02:38 +0200 Subject: [PATCH 1/7] refactor for a as_yaml_block helper --- R/blog.R | 3 +-- R/metadata.R | 3 +-- R/utils.R | 6 ++++++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/R/blog.R b/R/blog.R index 94e245e3..8f347da2 100644 --- a/R/blog.R +++ b/R/blog.R @@ -89,8 +89,7 @@ make_post_yaml <- function(title, ...) { if (length(yml_values$categories) == 0) { yml_values <- yml_values[names(yml_values) != "categories"] } - yml_values <- as_yaml(yml_values) - yml_values <- paste0("---\n", yml_values, "---\n") + yml_values <- as_yaml_block(yml_values) yml_values } diff --git a/R/metadata.R b/R/metadata.R index d7bc6c3e..b0002851 100644 --- a/R/metadata.R +++ b/R/metadata.R @@ -89,7 +89,6 @@ write_yaml_metadata_block <- function(..., .list = NULL) { if (length(meta) == 0) { return() } - res <- as_yaml(meta) - yaml_block <- paste0("---\n", res, "---\n") + yaml_block <- as_yaml_block(meta) knitr::asis_output(yaml_block) } diff --git a/R/utils.R b/R/utils.R index fc965140..c9d9caa2 100644 --- a/R/utils.R +++ b/R/utils.R @@ -24,6 +24,12 @@ write_yaml <- function(x, file) { yaml::write_yaml(x, file, handlers = yaml_handlers) } +as_yaml_block <- function(x) { + # Convert to YAML and wrap in a block + yaml_content <- as_yaml(x) + paste0("---\n", yaml_content, "---\n") +} + # inline knitr:::merge_list() merge_list <- function(x, y) { From 28055ae9cc39b676dc25a3aa114fef153f2ba14a Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Wed, 2 Jul 2025 15:33:31 +0200 Subject: [PATCH 2/7] Add a function to add a knit spin preamble to a script --- NAMESPACE | 1 + R/spin.R | 56 +++++++++++ man/add_spin_preamble.Rd | 40 ++++++++ tests/testthat/_snaps/spin.md | 9 ++ .../_snaps/spin/spin_preamble-empty.R | 6 ++ tests/testthat/_snaps/spin/spin_preamble.R | 7 ++ tests/testthat/test-spin.R | 97 +++++++++++++++++++ 7 files changed, 216 insertions(+) create mode 100644 R/spin.R create mode 100644 man/add_spin_preamble.Rd create mode 100644 tests/testthat/_snaps/spin.md create mode 100644 tests/testthat/_snaps/spin/spin_preamble-empty.R create mode 100644 tests/testthat/_snaps/spin/spin_preamble.R create mode 100644 tests/testthat/test-spin.R diff --git a/NAMESPACE b/NAMESPACE index eb15c20c..a6750f2e 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,5 +1,6 @@ # Generated by roxygen2: do not edit by hand +export(add_spin_preamble) export(check_newer_version) export(is_using_quarto) export(new_blog_post) diff --git a/R/spin.R b/R/spin.R new file mode 100644 index 00000000..5912304d --- /dev/null +++ b/R/spin.R @@ -0,0 +1,56 @@ +#' Add spin preamble to R script +#' +#' Adds a minimal spin preamble to an R script file if one doesn't already exist. +#' The preamble includes a title derived from the filename and is formatted as +#' a YAML block suitable preprended with `#'` for [knitr::spin()]. +#' +#' This is useful to prepare R scripts for use with +#' Quarto Script rendering support. +#' See +#' +#' @section Preamble format: +#' For a script named `analysis.R`, the function adds this preamble: +#' ``` +#' #' --- +#' #' title: analysis +#' #' --- +#' #' +#' +#' # Original script content starts here +#' ``` +#' +#' This is the minimal preamble required for Quarto Script rendering, so that +#' [Engine Bindings](https://quarto.org/docs/computations/execution-options.html#engine-binding) works. +#' +#' @param script Path to the R script file +#' @return Invisibly returns the script path if modified, otherwise invisible NULL +#' @export +add_spin_preamble <- function(script) { + if (!fs::file_exists(script)) { + cli::cli_abort(c( + "File {.file {script}} does not exist.", + "Please provide a valid file path." + )) + } + content <- xfun::read_utf8(script) + + # if files starts with a spin preamble, do nothing + if (grepl("^\\s*#'", content[1])) { + cli::cli_inform(c( + "File {.file {script}} already has a spin preamble.", + "No changes made. Edit manually if needed." + )) + return(invisible()) + } + # prepend the spin preamble + filename <- fs::path_file(fs::path_ext_remove(script)) + preamble <- paste( + "#'", + xfun::split_lines(as_yaml_block(list(title = filename))) + ) + new_content <- c(preamble, "", content) # Changed "\n" to "" + xfun::write_utf8(new_content, con = script) + + cli::cli_inform("Added spin preamble to {.file {script}}") + return(invisible(script)) +} diff --git a/man/add_spin_preamble.Rd b/man/add_spin_preamble.Rd new file mode 100644 index 00000000..4d3af7cf --- /dev/null +++ b/man/add_spin_preamble.Rd @@ -0,0 +1,40 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/spin.R +\name{add_spin_preamble} +\alias{add_spin_preamble} +\title{Add spin preamble to R script} +\usage{ +add_spin_preamble(script) +} +\arguments{ +\item{script}{Path to the R script file} +} +\value{ +Invisibly returns the script path if modified, otherwise invisible NULL +} +\description{ +Adds a minimal spin preamble to an R script file if one doesn't already exist. +The preamble includes a title derived from the filename and is formatted as +a YAML block suitable preprended with \verb{#'} for \code{\link[knitr:spin]{knitr::spin()}}. +} +\details{ +This is useful to prepare R scripts for use with +Quarto Script rendering support. +See \url{https://quarto.org/docs/computations/render-scripts.html#knitr} +} +\section{Preamble format}{ + +For a script named \code{analysis.R}, the function adds this preamble: + +\if{html}{\out{
}}\preformatted{#' --- +#' title: analysis +#' --- +#' + +# Original script content starts here +}\if{html}{\out{
}} + +This is the minimal preamble required for Quarto Script rendering, so that +\href{https://quarto.org/docs/computations/execution-options.html#engine-binding}{Engine Bindings} works. +} + diff --git a/tests/testthat/_snaps/spin.md b/tests/testthat/_snaps/spin.md new file mode 100644 index 00000000..86b896ee --- /dev/null +++ b/tests/testthat/_snaps/spin.md @@ -0,0 +1,9 @@ +# add_spin_preamble checks for file existence + + Code + add_spin_preamble("non_existent_file.R") + Condition + Error in `add_spin_preamble()`: + ! File 'non_existent_file.R' does not exist. + Please provide a valid file path. + diff --git a/tests/testthat/_snaps/spin/spin_preamble-empty.R b/tests/testthat/_snaps/spin/spin_preamble-empty.R new file mode 100644 index 00000000..240b926b --- /dev/null +++ b/tests/testthat/_snaps/spin/spin_preamble-empty.R @@ -0,0 +1,6 @@ +#' --- +#' title: report +#' --- +#' + + diff --git a/tests/testthat/_snaps/spin/spin_preamble.R b/tests/testthat/_snaps/spin/spin_preamble.R new file mode 100644 index 00000000..c2c8b203 --- /dev/null +++ b/tests/testthat/_snaps/spin/spin_preamble.R @@ -0,0 +1,7 @@ +#' --- +#' title: report +#' --- +#' + +x <- 1 +y <- 2 diff --git a/tests/testthat/test-spin.R b/tests/testthat/test-spin.R new file mode 100644 index 00000000..4bba1200 --- /dev/null +++ b/tests/testthat/test-spin.R @@ -0,0 +1,97 @@ +test_that("add_spin_preamble checks for file existence", { + expect_snapshot( + error = TRUE, + add_spin_preamble("non_existent_file.R") + ) +}) + +test_that("add_spin_preamble adds preamble to file without one", { + # Create temporary file + tmp_dir <- withr::local_tempdir() + withr::local_dir(tmp_dir) + script <- "report.R" + writeLines(c("x <- 1", "y <- 2"), script) + + # Add preamble + expect_message( + result <- add_spin_preamble(script), + "Added spin preamble" + ) + + # Check return value + expect_equal(result, script) + + announce_snapshot_file(name = "spin_preamble.R") + expect_snapshot_file(script, "spin_preamble.R") + + skip_on_cran() + skip_if_no_quarto("1.4.511") + expect_no_error( + quarto_render(script, quiet = TRUE) + ) + expect_true(file.exists(xfun::with_ext(script, "html"))) +}) + +test_that("add_spin_preamble doesn't modify file with existing preamble", { + # Create file with existing preamble + tmp_file <- withr::local_tempfile(fileext = ".R") + original_content <- c( + "#' ---", + "#' title: \"Existing Title\"", + "#' ---", + "#' ", + "x <- 1" + ) + xfun::write_utf8(original_content, tmp_file) + + # Try to add preamble + expect_message( + result <- add_spin_preamble(tmp_file), + "already has a spin preamble" + ) + + # Check return value is invisible + expect_invisible(expect_null(result)) + + # Check content unchanged + new_content <- xfun::read_utf8(tmp_file) + expect_equal(new_content, original_content) +}) + +test_that("add_spin_preamble detects preamble with leading whitespace", { + # Create file with preamble that has leading whitespace + tmp_file <- withr::local_tempfile(fileext = ".R") + original_content <- c( + " #' This is a comment", + "x <- 1" + ) + xfun::write_utf8(original_content, tmp_file) + + expect_message( + add_spin_preamble(tmp_file), + "already has a spin preamble" + ) + + # Content should be unchanged + new_content <- xfun::read_utf8(tmp_file) + expect_equal(new_content, original_content) +}) + +test_that("add_spin_preamble works with empty file", { + tmp_dir <- withr::local_tempdir() + withr::local_dir(tmp_dir) + script <- "report.R" + writeLines("", script) # Empty file + + # Add preamble + expect_message( + result <- add_spin_preamble(script), + "Added spin preamble" + ) + + # Check return value + expect_equal(result, script) + + announce_snapshot_file(name = "spin_preamble-empty.R") + expect_snapshot_file(script, "spin_preamble-empty.R") +}) From 4fd3debea4e1b22a7a95d21b3a4213c369cb2d74 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Wed, 2 Jul 2025 15:34:02 +0200 Subject: [PATCH 3/7] Add to pkgdown --- _pkgdown.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/_pkgdown.yml b/_pkgdown.yml index c2ada90b..58b7c61d 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -68,3 +68,4 @@ reference: These functions are used to help with Quarto documents and projects: contents: - write_yaml_metadata_block + - add_spin_preamble From 8195c424e8c3e29eb7f46b87ce19eb57dda9f664 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Wed, 2 Jul 2025 15:53:05 +0200 Subject: [PATCH 4/7] support setting preamble content --- R/spin.R | 64 +++++++++++-- man/add_spin_preamble.Rd | 36 ++++++- tests/testthat/_snaps/spin.md | 8 ++ .../spin/spin_preamble-custom-preamble.R | 9 ++ .../_snaps/spin/spin_preamble-custom-title.R | 7 ++ .../spin/spin_preamble-preamble-title.R | 7 ++ .../spin/spin_preamble-title-override.R | 7 ++ tests/testthat/test-spin.R | 93 +++++++++++++++++++ 8 files changed, 222 insertions(+), 9 deletions(-) create mode 100644 tests/testthat/_snaps/spin/spin_preamble-custom-preamble.R create mode 100644 tests/testthat/_snaps/spin/spin_preamble-custom-title.R create mode 100644 tests/testthat/_snaps/spin/spin_preamble-preamble-title.R create mode 100644 tests/testthat/_snaps/spin/spin_preamble-title-override.R diff --git a/R/spin.R b/R/spin.R index 5912304d..c78f964e 100644 --- a/R/spin.R +++ b/R/spin.R @@ -9,7 +9,7 @@ #' See #' #' @section Preamble format: -#' For a script named `analysis.R`, the function adds this preamble: +#' For a script named `analysis.R`, the function adds this preamble by default: #' ``` #' #' --- #' #' title: analysis @@ -23,15 +23,46 @@ #' [Engine Bindings](https://quarto.org/docs/computations/execution-options.html#engine-binding) works. #' #' @param script Path to the R script file +#' @param title Custom title for the preamble. If provided, overrides any title +#' in the `preamble` list. If NULL, uses `preamble$title` or filename as fallback. +#' @param preamble Named list of YAML metadata to include in preamble. +#' The `title` parameter takes precedence over `preamble$title` if both are provided. #' @return Invisibly returns the script path if modified, otherwise invisible NULL +#' +#' @examples +#' \dontrun{ +#' # Basic usage with default title +#' add_spin_preamble("analysis.R") +#' +#' # Custom title +#' add_spin_preamble("analysis.R", title = "My Analysis") +#' +#' # Custom preamble with multiple fields +#' add_spin_preamble("analysis.R", preamble = list( +#' title = "Advanced Analysis", +#' author = "John Doe", +#' date = Sys.Date(), +#' format = "html" +#' )) +#' +#' # Title parameter overrides preamble title +#' add_spin_preamble("analysis.R", +#' title = "Final Title", # This takes precedence +#' preamble = list( +#' title = "Ignored Title", +#' author = "John Doe" +#' ) +#' ) +#' } #' @export -add_spin_preamble <- function(script) { +add_spin_preamble <- function(script, title = NULL, preamble = NULL) { if (!fs::file_exists(script)) { cli::cli_abort(c( "File {.file {script}} does not exist.", "Please provide a valid file path." )) } + content <- xfun::read_utf8(script) # if files starts with a spin preamble, do nothing @@ -42,13 +73,32 @@ add_spin_preamble <- function(script) { )) return(invisible()) } - # prepend the spin preamble - filename <- fs::path_file(fs::path_ext_remove(script)) - preamble <- paste( + + # Build preamble metadata + metadata <- list() + + # Start with preamble list if provided + if (!is.null(preamble)) { + if (!is.list(preamble)) { + cli::cli_abort("`preamble` must be a named list.") + } + metadata <- preamble + } + + # Add or override title + if (!is.null(title)) { + metadata$title <- title + } else if (is.null(metadata$title)) { + # Use filename as default title if none provided + metadata$title <- fs::path_file(fs::path_ext_remove(script)) + } + + preamble_text <- paste( "#'", - xfun::split_lines(as_yaml_block(list(title = filename))) + xfun::split_lines(as_yaml_block(metadata)) ) - new_content <- c(preamble, "", content) # Changed "\n" to "" + + new_content <- c(preamble_text, "", content) xfun::write_utf8(new_content, con = script) cli::cli_inform("Added spin preamble to {.file {script}}") diff --git a/man/add_spin_preamble.Rd b/man/add_spin_preamble.Rd index 4d3af7cf..3fadbdeb 100644 --- a/man/add_spin_preamble.Rd +++ b/man/add_spin_preamble.Rd @@ -4,10 +4,16 @@ \alias{add_spin_preamble} \title{Add spin preamble to R script} \usage{ -add_spin_preamble(script) +add_spin_preamble(script, title = NULL, preamble = NULL) } \arguments{ \item{script}{Path to the R script file} + +\item{title}{Custom title for the preamble. If provided, overrides any title +in the \code{preamble} list. If NULL, uses \code{preamble$title} or filename as fallback.} + +\item{preamble}{Named list of YAML metadata to include in preamble. +The \code{title} parameter takes precedence over \code{preamble$title} if both are provided.} } \value{ Invisibly returns the script path if modified, otherwise invisible NULL @@ -24,7 +30,7 @@ See \url{https://quarto.org/docs/computations/render-scripts.html#knitr} } \section{Preamble format}{ -For a script named \code{analysis.R}, the function adds this preamble: +For a script named \code{analysis.R}, the function adds this preamble by default: \if{html}{\out{
}}\preformatted{#' --- #' title: analysis @@ -38,3 +44,29 @@ This is the minimal preamble required for Quarto Script rendering, so that \href{https://quarto.org/docs/computations/execution-options.html#engine-binding}{Engine Bindings} works. } +\examples{ +\dontrun{ +# Basic usage with default title +add_spin_preamble("analysis.R") + +# Custom title +add_spin_preamble("analysis.R", title = "My Analysis") + +# Custom preamble with multiple fields +add_spin_preamble("analysis.R", preamble = list( + title = "Advanced Analysis", + author = "John Doe", + date = Sys.Date(), + format = "html" +)) + +# Title parameter overrides preamble title +add_spin_preamble("analysis.R", + title = "Final Title", # This takes precedence + preamble = list( + title = "Ignored Title", + author = "John Doe" + ) +) +} +} diff --git a/tests/testthat/_snaps/spin.md b/tests/testthat/_snaps/spin.md index 86b896ee..4825b61e 100644 --- a/tests/testthat/_snaps/spin.md +++ b/tests/testthat/_snaps/spin.md @@ -7,3 +7,11 @@ ! File 'non_existent_file.R' does not exist. Please provide a valid file path. +# add_spin_preamble validates preamble argument + + Code + add_spin_preamble(tmp_file, preamble = "not a list") + Condition + Error in `add_spin_preamble()`: + ! `preamble` must be a named list. + diff --git a/tests/testthat/_snaps/spin/spin_preamble-custom-preamble.R b/tests/testthat/_snaps/spin/spin_preamble-custom-preamble.R new file mode 100644 index 00000000..57b52142 --- /dev/null +++ b/tests/testthat/_snaps/spin/spin_preamble-custom-preamble.R @@ -0,0 +1,9 @@ +#' --- +#' title: My Report +#' author: John Doe +#' format: html +#' --- +#' + +library(ggplot2) +plot(1:10) diff --git a/tests/testthat/_snaps/spin/spin_preamble-custom-title.R b/tests/testthat/_snaps/spin/spin_preamble-custom-title.R new file mode 100644 index 00000000..26d6fcaf --- /dev/null +++ b/tests/testthat/_snaps/spin/spin_preamble-custom-title.R @@ -0,0 +1,7 @@ +#' --- +#' title: Custom Analysis +#' --- +#' + +x <- 1 +y <- 2 diff --git a/tests/testthat/_snaps/spin/spin_preamble-preamble-title.R b/tests/testthat/_snaps/spin/spin_preamble-preamble-title.R new file mode 100644 index 00000000..e3054fae --- /dev/null +++ b/tests/testthat/_snaps/spin/spin_preamble-preamble-title.R @@ -0,0 +1,7 @@ +#' --- +#' title: Preamble Title +#' author: Jane Doe +#' --- +#' + +x <- 1 diff --git a/tests/testthat/_snaps/spin/spin_preamble-title-override.R b/tests/testthat/_snaps/spin/spin_preamble-title-override.R new file mode 100644 index 00000000..81757d40 --- /dev/null +++ b/tests/testthat/_snaps/spin/spin_preamble-title-override.R @@ -0,0 +1,7 @@ +#' --- +#' title: Override Title +#' author: John Doe +#' --- +#' + +x <- 1 diff --git a/tests/testthat/test-spin.R b/tests/testthat/test-spin.R index 4bba1200..0577bfa7 100644 --- a/tests/testthat/test-spin.R +++ b/tests/testthat/test-spin.R @@ -95,3 +95,96 @@ test_that("add_spin_preamble works with empty file", { announce_snapshot_file(name = "spin_preamble-empty.R") expect_snapshot_file(script, "spin_preamble-empty.R") }) + +test_that("add_spin_preamble works with custom title", { + tmp_dir <- withr::local_tempdir() + withr::local_dir(tmp_dir) + script <- "analysis.R" + xfun::write_utf8(c("x <- 1", "y <- 2"), script) + + expect_message( + result <- add_spin_preamble(script, title = "Custom Analysis"), + "Added spin preamble" + ) + + expect_equal(result, script) + + announce_snapshot_file(name = "spin_preamble-custom-title.R") + expect_snapshot_file(script, "spin_preamble-custom-title.R") +}) + +test_that("add_spin_preamble works with custom preamble", { + tmp_dir <- withr::local_tempdir() + withr::local_dir(tmp_dir) + script <- "report.R" + xfun::write_utf8(c("library(ggplot2)", "plot(1:10)"), script) + + expect_message( + result <- add_spin_preamble( + script, + preamble = list( + title = "My Report", + author = "John Doe", + format = "html" + ) + ), + "Added spin preamble" + ) + + expect_equal(result, script) + + announce_snapshot_file(name = "spin_preamble-custom-preamble.R") + expect_snapshot_file(script, "spin_preamble-custom-preamble.R") +}) + +test_that("title parameter overrides preamble title", { + tmp_dir <- withr::local_tempdir() + withr::local_dir(tmp_dir) + script <- "override.R" + writeLines("x <- 1", script) + + expect_message( + result <- add_spin_preamble( + script, + title = "Override Title", + preamble = list(title = "Original Title", author = "John Doe") + ), + "Added spin preamble" + ) + + expect_equal(result, script) + + announce_snapshot_file(name = "spin_preamble-title-override.R") + expect_snapshot_file(script, "spin_preamble-title-override.R") +}) + +test_that("preamble title is used when title parameter is NULL", { + tmp_dir <- withr::local_tempdir() + withr::local_dir(tmp_dir) + script <- "preamble_title.R" + writeLines("x <- 1", script) + + expect_message( + result <- add_spin_preamble( + script, + title = NULL, + preamble = list(title = "Preamble Title", author = "Jane Doe") + ), + "Added spin preamble" + ) + + expect_equal(result, script) + + announce_snapshot_file(name = "spin_preamble-preamble-title.R") + expect_snapshot_file(script, "spin_preamble-preamble-title.R") +}) + +test_that("add_spin_preamble validates preamble argument", { + tmp_file <- withr::local_tempfile(fileext = ".R") + writeLines("x <- 1", tmp_file) + + expect_snapshot( + error = TRUE, + add_spin_preamble(tmp_file, preamble = "not a list") + ) +}) From 3c9460871859270c96b0255cb8fc34ee328190cc Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Wed, 2 Jul 2025 16:06:35 +0200 Subject: [PATCH 5/7] clarify tests structure --- tests/testthat/test-spin.R | 91 +++++++++++++++----------------------- 1 file changed, 35 insertions(+), 56 deletions(-) diff --git a/tests/testthat/test-spin.R b/tests/testthat/test-spin.R index 0577bfa7..83aea6f1 100644 --- a/tests/testthat/test-spin.R +++ b/tests/testthat/test-spin.R @@ -1,3 +1,29 @@ +# file specific helpers ---- +create_test_script <- function( + name, + content = c("x <- 1", "y <- 2"), + envir = rlang::caller_env() +) { + tmp_dir <- withr::local_tempdir(.local_envir = envir) + withr::local_dir(tmp_dir, .local_envir = envir) + script <- name + xfun::write_utf8(content, script) + script +} + +expect_preamble_added <- function(script, snapshot_name) { + expect_message( + result <- add_spin_preamble(script), + "Added spin preamble" + ) + expect_equal(result, script) + + announce_snapshot_file(name = snapshot_name) + expect_snapshot_file(script, snapshot_name) +} + +# Tests ---- + test_that("add_spin_preamble checks for file existence", { expect_snapshot( error = TRUE, @@ -6,29 +32,12 @@ test_that("add_spin_preamble checks for file existence", { }) test_that("add_spin_preamble adds preamble to file without one", { - # Create temporary file - tmp_dir <- withr::local_tempdir() - withr::local_dir(tmp_dir) - script <- "report.R" - writeLines(c("x <- 1", "y <- 2"), script) - - # Add preamble - expect_message( - result <- add_spin_preamble(script), - "Added spin preamble" - ) - - # Check return value - expect_equal(result, script) - - announce_snapshot_file(name = "spin_preamble.R") - expect_snapshot_file(script, "spin_preamble.R") + script <- create_test_script("report.R") + expect_preamble_added(script, "spin_preamble.R") skip_on_cran() skip_if_no_quarto("1.4.511") - expect_no_error( - quarto_render(script, quiet = TRUE) - ) + expect_no_error(quarto_render(script, quiet = TRUE)) expect_true(file.exists(xfun::with_ext(script, "html"))) }) @@ -78,35 +87,17 @@ test_that("add_spin_preamble detects preamble with leading whitespace", { }) test_that("add_spin_preamble works with empty file", { - tmp_dir <- withr::local_tempdir() - withr::local_dir(tmp_dir) - script <- "report.R" - writeLines("", script) # Empty file - - # Add preamble - expect_message( - result <- add_spin_preamble(script), - "Added spin preamble" - ) - - # Check return value - expect_equal(result, script) - - announce_snapshot_file(name = "spin_preamble-empty.R") - expect_snapshot_file(script, "spin_preamble-empty.R") + script <- create_test_script("report.R", "") + expect_preamble_added(script, "spin_preamble-empty.R") }) test_that("add_spin_preamble works with custom title", { - tmp_dir <- withr::local_tempdir() - withr::local_dir(tmp_dir) - script <- "analysis.R" - xfun::write_utf8(c("x <- 1", "y <- 2"), script) + script <- create_test_script("analysis.R") expect_message( result <- add_spin_preamble(script, title = "Custom Analysis"), "Added spin preamble" ) - expect_equal(result, script) announce_snapshot_file(name = "spin_preamble-custom-title.R") @@ -114,10 +105,7 @@ test_that("add_spin_preamble works with custom title", { }) test_that("add_spin_preamble works with custom preamble", { - tmp_dir <- withr::local_tempdir() - withr::local_dir(tmp_dir) - script <- "report.R" - xfun::write_utf8(c("library(ggplot2)", "plot(1:10)"), script) + script <- create_test_script("report.R", c("library(ggplot2)", "plot(1:10)")) expect_message( result <- add_spin_preamble( @@ -130,7 +118,6 @@ test_that("add_spin_preamble works with custom preamble", { ), "Added spin preamble" ) - expect_equal(result, script) announce_snapshot_file(name = "spin_preamble-custom-preamble.R") @@ -138,10 +125,7 @@ test_that("add_spin_preamble works with custom preamble", { }) test_that("title parameter overrides preamble title", { - tmp_dir <- withr::local_tempdir() - withr::local_dir(tmp_dir) - script <- "override.R" - writeLines("x <- 1", script) + script <- create_test_script("override.R", "x <- 1") expect_message( result <- add_spin_preamble( @@ -151,7 +135,6 @@ test_that("title parameter overrides preamble title", { ), "Added spin preamble" ) - expect_equal(result, script) announce_snapshot_file(name = "spin_preamble-title-override.R") @@ -159,10 +142,7 @@ test_that("title parameter overrides preamble title", { }) test_that("preamble title is used when title parameter is NULL", { - tmp_dir <- withr::local_tempdir() - withr::local_dir(tmp_dir) - script <- "preamble_title.R" - writeLines("x <- 1", script) + script <- create_test_script("preamble_title.R", "x <- 1") expect_message( result <- add_spin_preamble( @@ -172,7 +152,6 @@ test_that("preamble title is used when title parameter is NULL", { ), "Added spin preamble" ) - expect_equal(result, script) announce_snapshot_file(name = "spin_preamble-preamble-title.R") From 7bdc967bfb9b2e55cbca5144f987d0d0b25d2fff Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Wed, 2 Jul 2025 16:07:47 +0200 Subject: [PATCH 6/7] bump version --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 99b20fa5..63745a35 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: quarto Title: R Interface to 'Quarto' Markdown Publishing System -Version: 1.4.4.9021 +Version: 1.4.4.9022 Authors@R: c( person("JJ", "Allaire", , "jj@posit.co", role = "aut", comment = c(ORCID = "0000-0003-0174-9868")), From 663ece3aa4ee4405645b8ab8bdf061cb2bc55c70 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Wed, 2 Jul 2025 16:10:38 +0200 Subject: [PATCH 7/7] Add to NEWS --- NEWS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS.md b/NEWS.md index 84ef6bcf..7d53c37f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,7 @@ # quarto (development version) +- Added `add_spin_preamble()` function to add YAML preambles to R scripts for use with Quarto Script rendering support. The function automatically detects existing preambles and provides flexible customization options through `title` and `preamble` parameters (#164). + - `quarto_create_project()` gains a `title` argument to set the project title independently from the directory name. This allows creating projects with custom titles, including when using `name = "."` to create a project in the current directory (thanks, @davidkane9, #148). This matches with `--title` addition for `quarto create project` in Quarto CLI v1.5.15. - `quarto_use_template()` now supports using templates in another empty directory via the `dir` argument. However, the function will fail with a clear error message when used in non-empty directories, as interactive prompting is required and handled by Quarto CLI directly (requires Quarto 1.5.15+). Follow for [quarto-dev/quarto-cli#11127](https://github.com/quarto-dev/quarto-cli/issues/11127) for change with `--no-prompt` behavior in future Quarto versions.