Skip to content

Commit a89f0cd

Browse files
authored
Add a has_parameter function to check if a document appears to be using parameters (#273)
* Add a has_parameter function to check if a document appears to be using parameters * Bump version [skip ci]
1 parent 84fc369 commit a89f0cd

File tree

9 files changed

+338
-1
lines changed

9 files changed

+338
-1
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.9027
3+
Version: 1.4.4.9028
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
@@ -5,6 +5,7 @@ export(check_newer_version)
55
export(detect_bookdown_crossrefs)
66
export(find_project_root)
77
export(get_running_project_root)
8+
export(has_parameters)
89
export(is_using_quarto)
910
export(new_blog_post)
1011
export(project_path)

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
- Package is now licenced MIT like Quarto CLI.
44

5+
- Added `has_parameters()` function to detect whether Quarto documents use parameters. The function works with both knitr and Jupyter engines: for knitr documents (.qmd), it checks for a "params" field in the document metadata; for Jupyter notebooks (.ipynb), it detects cells tagged with "parameters" using papermill convention. This enables programmatic identification of parameterized documents for automated workflows and document processing (#245).
6+
57
- Added `detect_bookdown_crossrefs()` function to help users migrate from bookdown to Quarto by identifying cross-references that need manual conversion. The function scans R Markdown or Quarto files to detect bookdown-specific cross-reference syntax (like `\@ref(fig:label)` and `(\#eq:label)`) and provides detailed guidance on converting them to Quarto syntax (like `@fig-label` and `{#eq-label}`). It offers both compact and verbose reporting modes, with context-aware warnings that only show syntax patterns actually found in your files.
68

79
- Added `project_path()`, `get_running_project_root()`, and `find_project_root()` functions for Quarto-aware project path construction. These functions provide a consistent way to reference files relative to the project root, working both during Quarto rendering (using `QUARTO_PROJECT_ROOT` environment variables) and in interactive sessions (using intelligent project detection). The `project_path()` function is particularly useful in Quarto document cells where you need to reference data files or scripts from the project root regardless of the document's location in subdirectories (#180).0).

R/parameters.R

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
#' Check if a Quarto document uses parameters
2+
#'
3+
#' Determines whether a Quarto document uses parameters by examining the document
4+
#' structure and metadata. This function works with both knitr and Jupyter engines,
5+
#' using different detection methods for each:
6+
#'
7+
#' - **Knitr engine (.qmd files)**: Checks for a "params" field in the document's
8+
#' YAML metadata using `quarto_inspect()`
9+
#' - **Jupyter engine (.ipynb files)**: Looks for code cells tagged with "parameters"
10+
#' following the papermill convention. For .ipynb files, the function parses the
11+
#' notebook JSON directly due to limitations in `quarto inspect`.
12+
#'
13+
#' @param input Path to the Quarto document (.qmd or .ipynb file) to inspect.
14+
#'
15+
#' @return Logical. `TRUE` if the document uses parameters, `FALSE` otherwise.
16+
#'
17+
#' @details
18+
#' Parameters in Quarto enable creating dynamic, reusable documents. This function
19+
#' helps identify parameterized documents programmatically, which is useful for:
20+
#'
21+
#' - Document processing workflows
22+
#' - Automated report generation
23+
#' - Parameter validation before rendering
24+
#' - Project analysis and organization
25+
#'
26+
#' For more information about using parameters in Quarto, see
27+
#' <https://quarto.org/docs/computations/parameters.html>
28+
#'
29+
#' @examples
30+
#' \dontrun{
31+
#' # Check if a document uses parameters
32+
#' has_parameters("my-document.qmd")
33+
#'
34+
#' # Check a parameterized report
35+
#' has_parameters("parameterized-report.qmd")
36+
#'
37+
#' # Check a Jupyter notebook
38+
#' has_parameters("analysis.ipynb")
39+
#'
40+
#' # Use in a workflow
41+
#' if (has_parameters("report.qmd")) {
42+
#' message("This document accepts parameters")
43+
#' }
44+
#' }
45+
#'
46+
#' @export
47+
has_parameters <- function(input) {
48+
if (!file.exists(input)) {
49+
cli::cli_abort(
50+
c(
51+
"File {.file {input}} does not exist.",
52+
">" = "Please provide a valid Quarto document."
53+
),
54+
call = rlang::caller_env()
55+
)
56+
}
57+
58+
# Check for Jupyter engine: look for cells with "parameters" tag
59+
# Note: quarto_inspect() has limitations with Jupyter notebooks and may not
60+
# detect code cells properly, so we fall back to direct JSON parsing for .ipynb files
61+
if (identical(fs::path_ext(input), "ipynb")) {
62+
return(has_parameters_jupyter_direct(input))
63+
}
64+
65+
inspect <- quarto::quarto_inspect(input)
66+
67+
if (identical(inspect$engines, "jupyter")) {
68+
return(
69+
"parameters" %in% inspect$fileInformation[[input]]$codeCells$metadata$tags
70+
)
71+
} else if (identical(inspect$engines, "knitr")) {
72+
return(
73+
"params" %in% names(inspect$fileInformation[[input]]$metadata)
74+
)
75+
} else {
76+
return(FALSE)
77+
}
78+
}
79+
80+
# Helper function to directly parse Jupyter notebook JSON for parameters
81+
# This is needed because quarto_inspect() has limitations with detecting
82+
# code cells in Jupyter notebooks
83+
has_parameters_jupyter_direct <- function(notebook_path) {
84+
tryCatch(
85+
{
86+
# Read and parse the notebook JSON with simplifyDataFrame = FALSE
87+
# to preserve the original structure
88+
notebook_json <- jsonlite::fromJSON(
89+
notebook_path,
90+
simplifyDataFrame = FALSE
91+
)
92+
93+
# Check if there are cells
94+
if (length(notebook_json$cells) == 0) {
95+
return(FALSE)
96+
}
97+
# Look through cells for parameters tag
98+
any(sapply(notebook_json$cells, function(cell) {
99+
"parameters" %in% cell$metadata$tags
100+
}))
101+
},
102+
error = function(e) {
103+
# If JSON parsing fails, return FALSE
104+
return(FALSE)
105+
}
106+
)
107+
}

_pkgdown.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ reference:
4545
for Quarto documents and projects:
4646
contents:
4747
- quarto_inspect
48+
- has_parameters
4849
- quarto_path
4950
- quarto_version
5051
- quarto_available

man/has_parameters.Rd

Lines changed: 58 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "raw",
5+
"id": "cell-0",
6+
"metadata": {},
7+
"source": [
8+
"---\n",
9+
"title: \"Test Notebook without Parameters\"\n",
10+
"format: html\n",
11+
"---"
12+
]
13+
},
14+
{
15+
"cell_type": "markdown",
16+
"id": "cell-1",
17+
"metadata": {},
18+
"source": [
19+
"## Test Notebook\n",
20+
"\n",
21+
"This notebook does not use parameters."
22+
]
23+
},
24+
{
25+
"cell_type": "code",
26+
"execution_count": null,
27+
"id": "cell-2",
28+
"metadata": {},
29+
"outputs": [],
30+
"source": [
31+
"print(\"Hello World!\")"
32+
]
33+
}
34+
],
35+
"metadata": {
36+
"kernelspec": {
37+
"display_name": "Python 3",
38+
"language": "python",
39+
"name": "python3"
40+
},
41+
"language_info": {
42+
"name": "python"
43+
}
44+
},
45+
"nbformat": 4,
46+
"nbformat_minor": 4
47+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "raw",
5+
"id": "cell-0",
6+
"metadata": {},
7+
"source": [
8+
"---\n",
9+
"title: \"Test Notebook with Parameters\"\n",
10+
"format: html\n",
11+
"---"
12+
]
13+
},
14+
{
15+
"cell_type": "code",
16+
"execution_count": null,
17+
"id": "cell-1",
18+
"metadata": {
19+
"tags": [
20+
"parameters"
21+
]
22+
},
23+
"outputs": [],
24+
"source": [
25+
"# Parameters cell\n",
26+
"name = \"John\"\n",
27+
"age = 30"
28+
]
29+
},
30+
{
31+
"cell_type": "markdown",
32+
"id": "cell-2",
33+
"metadata": {},
34+
"source": [
35+
"## Test Notebook\n",
36+
"\n",
37+
"This notebook uses parameters defined in the cell above."
38+
]
39+
},
40+
{
41+
"cell_type": "code",
42+
"execution_count": null,
43+
"id": "cell-3",
44+
"metadata": {},
45+
"outputs": [],
46+
"source": [
47+
"print(f\"Hello {name}, you are {age} years old!\")"
48+
]
49+
}
50+
],
51+
"metadata": {
52+
"kernelspec": {
53+
"display_name": "Python 3",
54+
"language": "python",
55+
"name": "python3"
56+
},
57+
"language_info": {
58+
"name": "python"
59+
}
60+
},
61+
"nbformat": 4,
62+
"nbformat_minor": 4
63+
}

tests/testthat/test-parameters.R

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
test_that("has_parameters() works with knitr engine", {
2+
skip_if_no_quarto()
3+
4+
# Create a .qmd file with params in metadata
5+
qmd_with_params <- local_qmd_file(
6+
"---",
7+
"title: Test Document",
8+
"params:",
9+
" name: John",
10+
"---",
11+
"",
12+
"# Test Document",
13+
"",
14+
"This is a test document with parameters.",
15+
"```{r}",
16+
"params$name",
17+
"```"
18+
)
19+
20+
# Create a .qmd file without params
21+
qmd_no_params <- local_qmd_file(
22+
"---",
23+
"title: Test Document",
24+
"---",
25+
"",
26+
"# Test Document",
27+
"",
28+
"This is a test document without parameters."
29+
)
30+
31+
expect_true(has_parameters(qmd_with_params))
32+
expect_false(has_parameters(qmd_no_params))
33+
})
34+
35+
test_that("has_parameters() works with Jupyter engine", {
36+
skip_if_no_quarto()
37+
38+
# Test with notebook that has parameters cell
39+
expect_true(has_parameters(resources_path("test-with-parameters.ipynb")))
40+
41+
# Test with notebook that doesn't have parameters
42+
expect_false(has_parameters(resources_path("test-no-parameters.ipynb")))
43+
})
44+
45+
test_that("has_parameters() handles non-existent files", {
46+
expect_error(
47+
has_parameters("non-existent-file.qmd"),
48+
class = "rlang_error"
49+
)
50+
})
51+
52+
test_that("has_parameters() works with existing test files", {
53+
skip_if_no_quarto()
54+
55+
# Test with existing test files that don't have parameters
56+
expect_false(has_parameters(test_path("test.qmd")))
57+
expect_false(has_parameters(test_path("test.ipynb")))
58+
})

0 commit comments

Comments
 (0)