Skip to content

Commit 28055ae

Browse files
committed
Add a function to add a knit spin preamble to a script
1 parent 7472039 commit 28055ae

File tree

7 files changed

+216
-0
lines changed

7 files changed

+216
-0
lines changed

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Generated by roxygen2: do not edit by hand
22

3+
export(add_spin_preamble)
34
export(check_newer_version)
45
export(is_using_quarto)
56
export(new_blog_post)

R/spin.R

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#' Add spin preamble to R script
2+
#'
3+
#' Adds a minimal spin preamble to an R script file if one doesn't already exist.
4+
#' The preamble includes a title derived from the filename and is formatted as
5+
#' a YAML block suitable preprended with `#'` for [knitr::spin()].
6+
#'
7+
#' This is useful to prepare R scripts for use with
8+
#' Quarto Script rendering support.
9+
#' See <https://quarto.org/docs/computations/render-scripts.html#knitr>
10+
#'
11+
#' @section Preamble format:
12+
#' For a script named `analysis.R`, the function adds this preamble:
13+
#' ```
14+
#' #' ---
15+
#' #' title: analysis
16+
#' #' ---
17+
#' #'
18+
#'
19+
#' # Original script content starts here
20+
#' ```
21+
#'
22+
#' This is the minimal preamble required for Quarto Script rendering, so that
23+
#' [Engine Bindings](https://quarto.org/docs/computations/execution-options.html#engine-binding) works.
24+
#'
25+
#' @param script Path to the R script file
26+
#' @return Invisibly returns the script path if modified, otherwise invisible NULL
27+
#' @export
28+
add_spin_preamble <- function(script) {
29+
if (!fs::file_exists(script)) {
30+
cli::cli_abort(c(
31+
"File {.file {script}} does not exist.",
32+
"Please provide a valid file path."
33+
))
34+
}
35+
content <- xfun::read_utf8(script)
36+
37+
# if files starts with a spin preamble, do nothing
38+
if (grepl("^\\s*#'", content[1])) {
39+
cli::cli_inform(c(
40+
"File {.file {script}} already has a spin preamble.",
41+
"No changes made. Edit manually if needed."
42+
))
43+
return(invisible())
44+
}
45+
# prepend the spin preamble
46+
filename <- fs::path_file(fs::path_ext_remove(script))
47+
preamble <- paste(
48+
"#'",
49+
xfun::split_lines(as_yaml_block(list(title = filename)))
50+
)
51+
new_content <- c(preamble, "", content) # Changed "\n" to ""
52+
xfun::write_utf8(new_content, con = script)
53+
54+
cli::cli_inform("Added spin preamble to {.file {script}}")
55+
return(invisible(script))
56+
}

man/add_spin_preamble.Rd

Lines changed: 40 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/_snaps/spin.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# add_spin_preamble checks for file existence
2+
3+
Code
4+
add_spin_preamble("non_existent_file.R")
5+
Condition
6+
Error in `add_spin_preamble()`:
7+
! File 'non_existent_file.R' does not exist.
8+
Please provide a valid file path.
9+
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#' ---
2+
#' title: report
3+
#' ---
4+
#'
5+
6+
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#' ---
2+
#' title: report
3+
#' ---
4+
#'
5+
6+
x <- 1
7+
y <- 2

tests/testthat/test-spin.R

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
test_that("add_spin_preamble checks for file existence", {
2+
expect_snapshot(
3+
error = TRUE,
4+
add_spin_preamble("non_existent_file.R")
5+
)
6+
})
7+
8+
test_that("add_spin_preamble adds preamble to file without one", {
9+
# Create temporary file
10+
tmp_dir <- withr::local_tempdir()
11+
withr::local_dir(tmp_dir)
12+
script <- "report.R"
13+
writeLines(c("x <- 1", "y <- 2"), script)
14+
15+
# Add preamble
16+
expect_message(
17+
result <- add_spin_preamble(script),
18+
"Added spin preamble"
19+
)
20+
21+
# Check return value
22+
expect_equal(result, script)
23+
24+
announce_snapshot_file(name = "spin_preamble.R")
25+
expect_snapshot_file(script, "spin_preamble.R")
26+
27+
skip_on_cran()
28+
skip_if_no_quarto("1.4.511")
29+
expect_no_error(
30+
quarto_render(script, quiet = TRUE)
31+
)
32+
expect_true(file.exists(xfun::with_ext(script, "html")))
33+
})
34+
35+
test_that("add_spin_preamble doesn't modify file with existing preamble", {
36+
# Create file with existing preamble
37+
tmp_file <- withr::local_tempfile(fileext = ".R")
38+
original_content <- c(
39+
"#' ---",
40+
"#' title: \"Existing Title\"",
41+
"#' ---",
42+
"#' ",
43+
"x <- 1"
44+
)
45+
xfun::write_utf8(original_content, tmp_file)
46+
47+
# Try to add preamble
48+
expect_message(
49+
result <- add_spin_preamble(tmp_file),
50+
"already has a spin preamble"
51+
)
52+
53+
# Check return value is invisible
54+
expect_invisible(expect_null(result))
55+
56+
# Check content unchanged
57+
new_content <- xfun::read_utf8(tmp_file)
58+
expect_equal(new_content, original_content)
59+
})
60+
61+
test_that("add_spin_preamble detects preamble with leading whitespace", {
62+
# Create file with preamble that has leading whitespace
63+
tmp_file <- withr::local_tempfile(fileext = ".R")
64+
original_content <- c(
65+
" #' This is a comment",
66+
"x <- 1"
67+
)
68+
xfun::write_utf8(original_content, tmp_file)
69+
70+
expect_message(
71+
add_spin_preamble(tmp_file),
72+
"already has a spin preamble"
73+
)
74+
75+
# Content should be unchanged
76+
new_content <- xfun::read_utf8(tmp_file)
77+
expect_equal(new_content, original_content)
78+
})
79+
80+
test_that("add_spin_preamble works with empty file", {
81+
tmp_dir <- withr::local_tempdir()
82+
withr::local_dir(tmp_dir)
83+
script <- "report.R"
84+
writeLines("", script) # Empty file
85+
86+
# Add preamble
87+
expect_message(
88+
result <- add_spin_preamble(script),
89+
"Added spin preamble"
90+
)
91+
92+
# Check return value
93+
expect_equal(result, script)
94+
95+
announce_snapshot_file(name = "spin_preamble-empty.R")
96+
expect_snapshot_file(script, "spin_preamble-empty.R")
97+
})

0 commit comments

Comments
 (0)