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.9027
Version: 1.4.4.9028
Authors@R: c(
person("JJ", "Allaire", , "jj@posit.co", role = "aut",
comment = c(ORCID = "0000-0003-0174-9868")),
Expand Down
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export(check_newer_version)
export(detect_bookdown_crossrefs)
export(find_project_root)
export(get_running_project_root)
export(has_parameters)
export(is_using_quarto)
export(new_blog_post)
export(project_path)
Expand Down
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

- Package is now licenced MIT like Quarto CLI.

- 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).

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

- 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).
Expand Down
107 changes: 107 additions & 0 deletions R/parameters.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#' Check if a Quarto document uses parameters
#'
#' Determines whether a Quarto document uses parameters by examining the document
#' structure and metadata. This function works with both knitr and Jupyter engines,
#' using different detection methods for each:
#'
#' - **Knitr engine (.qmd files)**: Checks for a "params" field in the document's
#' YAML metadata using `quarto_inspect()`
#' - **Jupyter engine (.ipynb files)**: Looks for code cells tagged with "parameters"
#' following the papermill convention. For .ipynb files, the function parses the
#' notebook JSON directly due to limitations in `quarto inspect`.
#'
#' @param input Path to the Quarto document (.qmd or .ipynb file) to inspect.
#'
#' @return Logical. `TRUE` if the document uses parameters, `FALSE` otherwise.
#'
#' @details
#' Parameters in Quarto enable creating dynamic, reusable documents. This function
#' helps identify parameterized documents programmatically, which is useful for:
#'
#' - Document processing workflows
#' - Automated report generation
#' - Parameter validation before rendering
#' - Project analysis and organization
#'
#' For more information about using parameters in Quarto, see
#' <https://quarto.org/docs/computations/parameters.html>
#'
#' @examples
#' \dontrun{
#' # Check if a document uses parameters
#' has_parameters("my-document.qmd")
#'
#' # Check a parameterized report
#' has_parameters("parameterized-report.qmd")
#'
#' # Check a Jupyter notebook
#' has_parameters("analysis.ipynb")
#'
#' # Use in a workflow
#' if (has_parameters("report.qmd")) {
#' message("This document accepts parameters")
#' }
#' }
#'
#' @export
has_parameters <- function(input) {
if (!file.exists(input)) {
cli::cli_abort(
c(
"File {.file {input}} does not exist.",
">" = "Please provide a valid Quarto document."
),
call = rlang::caller_env()
)
}

# Check for Jupyter engine: look for cells with "parameters" tag
# Note: quarto_inspect() has limitations with Jupyter notebooks and may not
# detect code cells properly, so we fall back to direct JSON parsing for .ipynb files
if (identical(fs::path_ext(input), "ipynb")) {
return(has_parameters_jupyter_direct(input))
}

inspect <- quarto::quarto_inspect(input)

if (identical(inspect$engines, "jupyter")) {
return(
"parameters" %in% inspect$fileInformation[[input]]$codeCells$metadata$tags
)
} else if (identical(inspect$engines, "knitr")) {
return(
"params" %in% names(inspect$fileInformation[[input]]$metadata)
)
} else {
return(FALSE)
}
}

# Helper function to directly parse Jupyter notebook JSON for parameters
# This is needed because quarto_inspect() has limitations with detecting
# code cells in Jupyter notebooks
has_parameters_jupyter_direct <- function(notebook_path) {
tryCatch(
{
# Read and parse the notebook JSON with simplifyDataFrame = FALSE
# to preserve the original structure
notebook_json <- jsonlite::fromJSON(
notebook_path,
simplifyDataFrame = FALSE
)

# Check if there are cells
if (length(notebook_json$cells) == 0) {
return(FALSE)
}
# Look through cells for parameters tag
any(sapply(notebook_json$cells, function(cell) {
"parameters" %in% cell$metadata$tags
}))
},
error = function(e) {
# If JSON parsing fails, return FALSE
return(FALSE)
}
)
}
1 change: 1 addition & 0 deletions _pkgdown.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ reference:
for Quarto documents and projects:
contents:
- quarto_inspect
- has_parameters
- quarto_path
- quarto_version
- quarto_available
Expand Down
58 changes: 58 additions & 0 deletions man/has_parameters.Rd

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

47 changes: 47 additions & 0 deletions tests/testthat/resources/test-no-parameters.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"cells": [
{
"cell_type": "raw",
"id": "cell-0",
"metadata": {},
"source": [
"---\n",
"title: \"Test Notebook without Parameters\"\n",
"format: html\n",
"---"
]
},
{
"cell_type": "markdown",
"id": "cell-1",
"metadata": {},
"source": [
"## Test Notebook\n",
"\n",
"This notebook does not use parameters."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cell-2",
"metadata": {},
"outputs": [],
"source": [
"print(\"Hello World!\")"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
63 changes: 63 additions & 0 deletions tests/testthat/resources/test-with-parameters.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{
"cells": [
{
"cell_type": "raw",
"id": "cell-0",
"metadata": {},
"source": [
"---\n",
"title: \"Test Notebook with Parameters\"\n",
"format: html\n",
"---"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cell-1",
"metadata": {
"tags": [
"parameters"
]
},
"outputs": [],
"source": [
"# Parameters cell\n",
"name = \"John\"\n",
"age = 30"
]
},
{
"cell_type": "markdown",
"id": "cell-2",
"metadata": {},
"source": [
"## Test Notebook\n",
"\n",
"This notebook uses parameters defined in the cell above."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cell-3",
"metadata": {},
"outputs": [],
"source": [
"print(f\"Hello {name}, you are {age} years old!\")"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
58 changes: 58 additions & 0 deletions tests/testthat/test-parameters.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
test_that("has_parameters() works with knitr engine", {
skip_if_no_quarto()

# Create a .qmd file with params in metadata
qmd_with_params <- local_qmd_file(
"---",
"title: Test Document",
"params:",
" name: John",
"---",
"",
"# Test Document",
"",
"This is a test document with parameters.",
"```{r}",
"params$name",
"```"
)

# Create a .qmd file without params
qmd_no_params <- local_qmd_file(
"---",
"title: Test Document",
"---",
"",
"# Test Document",
"",
"This is a test document without parameters."
)

expect_true(has_parameters(qmd_with_params))
expect_false(has_parameters(qmd_no_params))
})

test_that("has_parameters() works with Jupyter engine", {
skip_if_no_quarto()

# Test with notebook that has parameters cell
expect_true(has_parameters(resources_path("test-with-parameters.ipynb")))

# Test with notebook that doesn't have parameters
expect_false(has_parameters(resources_path("test-no-parameters.ipynb")))
})

test_that("has_parameters() handles non-existent files", {
expect_error(
has_parameters("non-existent-file.qmd"),
class = "rlang_error"
)
})

test_that("has_parameters() works with existing test files", {
skip_if_no_quarto()

# Test with existing test files that don't have parameters
expect_false(has_parameters(test_path("test.qmd")))
expect_false(has_parameters(test_path("test.ipynb")))
})