Skip to content

Commit d06e627

Browse files
authored
Add a function to create spin preamble (#262)
* refactor for a as_yaml_block helper * Add a function to add a knit spin preamble to a script * Add to pkgdown * support setting preamble content * clarify tests structure * bump version * Add to NEWS
1 parent 2555225 commit d06e627

17 files changed

+420
-5
lines changed

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Package: quarto
22
Title: R Interface to 'Quarto' Markdown Publishing System
3-
Version: 1.4.4.9021
3+
Version: 1.4.4.9022
44
Authors@R: c(
55
person("JJ", "Allaire", , "[email protected]", role = "aut",
66
comment = c(ORCID = "0000-0003-0174-9868")),

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)

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# quarto (development version)
22

3+
- 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).
4+
35
- `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.
46

57
- `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.

R/blog.R

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,7 @@ make_post_yaml <- function(title, ...) {
8989
if (length(yml_values$categories) == 0) {
9090
yml_values <- yml_values[names(yml_values) != "categories"]
9191
}
92-
yml_values <- as_yaml(yml_values)
93-
yml_values <- paste0("---\n", yml_values, "---\n")
92+
yml_values <- as_yaml_block(yml_values)
9493
yml_values
9594
}
9695

R/metadata.R

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@ write_yaml_metadata_block <- function(..., .list = NULL) {
8989
if (length(meta) == 0) {
9090
return()
9191
}
92-
res <- as_yaml(meta)
93-
yaml_block <- paste0("---\n", res, "---\n")
92+
yaml_block <- as_yaml_block(meta)
9493
knitr::asis_output(yaml_block)
9594
}

R/spin.R

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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 by default:
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+
#' @param title Custom title for the preamble. If provided, overrides any title
27+
#' in the `preamble` list. If NULL, uses `preamble$title` or filename as fallback.
28+
#' @param preamble Named list of YAML metadata to include in preamble.
29+
#' The `title` parameter takes precedence over `preamble$title` if both are provided.
30+
#' @return Invisibly returns the script path if modified, otherwise invisible NULL
31+
#'
32+
#' @examples
33+
#' \dontrun{
34+
#' # Basic usage with default title
35+
#' add_spin_preamble("analysis.R")
36+
#'
37+
#' # Custom title
38+
#' add_spin_preamble("analysis.R", title = "My Analysis")
39+
#'
40+
#' # Custom preamble with multiple fields
41+
#' add_spin_preamble("analysis.R", preamble = list(
42+
#' title = "Advanced Analysis",
43+
#' author = "John Doe",
44+
#' date = Sys.Date(),
45+
#' format = "html"
46+
#' ))
47+
#'
48+
#' # Title parameter overrides preamble title
49+
#' add_spin_preamble("analysis.R",
50+
#' title = "Final Title", # This takes precedence
51+
#' preamble = list(
52+
#' title = "Ignored Title",
53+
#' author = "John Doe"
54+
#' )
55+
#' )
56+
#' }
57+
#' @export
58+
add_spin_preamble <- function(script, title = NULL, preamble = NULL) {
59+
if (!fs::file_exists(script)) {
60+
cli::cli_abort(c(
61+
"File {.file {script}} does not exist.",
62+
"Please provide a valid file path."
63+
))
64+
}
65+
66+
content <- xfun::read_utf8(script)
67+
68+
# if files starts with a spin preamble, do nothing
69+
if (grepl("^\\s*#'", content[1])) {
70+
cli::cli_inform(c(
71+
"File {.file {script}} already has a spin preamble.",
72+
"No changes made. Edit manually if needed."
73+
))
74+
return(invisible())
75+
}
76+
77+
# Build preamble metadata
78+
metadata <- list()
79+
80+
# Start with preamble list if provided
81+
if (!is.null(preamble)) {
82+
if (!is.list(preamble)) {
83+
cli::cli_abort("`preamble` must be a named list.")
84+
}
85+
metadata <- preamble
86+
}
87+
88+
# Add or override title
89+
if (!is.null(title)) {
90+
metadata$title <- title
91+
} else if (is.null(metadata$title)) {
92+
# Use filename as default title if none provided
93+
metadata$title <- fs::path_file(fs::path_ext_remove(script))
94+
}
95+
96+
preamble_text <- paste(
97+
"#'",
98+
xfun::split_lines(as_yaml_block(metadata))
99+
)
100+
101+
new_content <- c(preamble_text, "", content)
102+
xfun::write_utf8(new_content, con = script)
103+
104+
cli::cli_inform("Added spin preamble to {.file {script}}")
105+
return(invisible(script))
106+
}

R/utils.R

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ write_yaml <- function(x, file) {
2424
yaml::write_yaml(x, file, handlers = yaml_handlers)
2525
}
2626

27+
as_yaml_block <- function(x) {
28+
# Convert to YAML and wrap in a block
29+
yaml_content <- as_yaml(x)
30+
paste0("---\n", yaml_content, "---\n")
31+
}
32+
2733

2834
# inline knitr:::merge_list()
2935
merge_list <- function(x, y) {

_pkgdown.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,4 @@ reference:
6868
These functions are used to help with Quarto documents and projects:
6969
contents:
7070
- write_yaml_metadata_block
71+
- add_spin_preamble

man/add_spin_preamble.Rd

Lines changed: 72 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: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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+
10+
# add_spin_preamble validates preamble argument
11+
12+
Code
13+
add_spin_preamble(tmp_file, preamble = "not a list")
14+
Condition
15+
Error in `add_spin_preamble()`:
16+
! `preamble` must be a named list.
17+

0 commit comments

Comments
 (0)