diff --git a/DESCRIPTION b/DESCRIPTION index 45b0516e..40fd6023 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: quarto Title: R Interface to 'Quarto' Markdown Publishing System -Version: 1.4.4.9009 +Version: 1.4.4.9010 Authors@R: c( person("JJ", "Allaire", , "jj@posit.co", role = "aut", comment = c(ORCID = "0000-0003-0174-9868")), diff --git a/NAMESPACE b/NAMESPACE index c68157b1..9fdfb6be 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -2,6 +2,7 @@ export(is_using_quarto) export(quarto_add_extension) +export(quarto_available) export(quarto_binary_sitrep) export(quarto_create_project) export(quarto_inspect) diff --git a/NEWS.md b/NEWS.md index 67437fdb..7ff552d2 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,7 @@ # quarto (development version) +- Add `quarto_available()` function to check if Quarto CLI is found (thanks, @hadley, #187). + - `quarto_render()` now correctly set `as_job` when not inside RStudio IDE and required **rstudioapi** functions are not available (#203). - Add several new wrapper function (thanks, @parmsam, #192): diff --git a/R/quarto.R b/R/quarto.R index 0100e64f..9b0e44fc 100644 --- a/R/quarto.R +++ b/R/quarto.R @@ -7,6 +7,8 @@ #' #' @return Path to quarto binary (or `NULL` if not found) #' +#' @seealso [quarto_version()] to check the version of the binary found, [quarto_available()] to check if Quarto CLI is available and meets some requirements. +#' #' @export quarto_path <- function(normalize = TRUE) { path_env <- get_quarto_path_env() @@ -46,12 +48,77 @@ find_quarto <- function() { #' If it returns `99.9.9` then it means you are using a dev version. #' #' @return a [`numeric_version`][base::numeric_version] with the quarto version found +#' @seealso [quarto_available()] to check if the version meets some requirements. #' @export quarto_version <- function() { quarto_bin <- find_quarto() as.numeric_version(system2(quarto_bin, "--version", stdout = TRUE)) } + +#' Check if quarto is available and version meet some requirements +#' +#' This function allows to test if Quarto is available and meets version requirement, a min, max or +#' in between requirement. +#' +#' If `min` and `max` are provided, this will check if Quarto version is +#' in-between two versions. If non is provided (keeping the default `NULL` for +#' both), it will just check for Quarto availability version and return `FALSE` if not found. +#' +#' @param min Minimum version expected. +#' @param max Maximum version expected +#' @param error If `TRUE`, will throw an error if Quarto is not available or does not meet the requirement. Default is `FALSE`. +#' +#' @return logical. `TRUE` if requirement is met, `FALSE` otherwise. +#' +#' @examples +#' # Is there an active version available ? +#' quarto_available() +#' # check for a minimum requirement +#' quarto_available(min = "1.5") +#' # check for a maximum version +#' quarto_available(max = "1.6") +#' # only returns TRUE if Pandoc version is between two bounds +#' quarto_available(min = "1.4", max = "1.6") +#' +#' @export +quarto_available <- function(min = NULL, max = NULL, error = FALSE) { + found <- FALSE + is_above <- is_below <- TRUE + if (!is.null(min) && !is.null(max)) { + if (min > max) { + cli::cli_abort(c( + "Minimum version {.strong {min}} cannot be greater than maximum version {.strong {max}}." + )) + } + } + quarto_version <- tryCatch( + quarto_version(), + error = function(e) NULL + ) + if (!is.null(quarto_version)) { + if (!is.null(min)) is_above <- quarto_version >= min + if (!is.null(max)) is_below <- quarto_version <= max + found <- is_above && is_below + } + if (!found && error) { + cli::cli_abort(c( + if (is.null(min) && is.null(max)) { + "Quarto is not available." + } else { + "Quarto version requirement not met." + }, + "*" = if (!is_above) { + paste0(" Minimum version expected is ", min, ".") + }, + "*" = if (!is_below) { + paste0(" Maximum version expected is ", max, ".") + } + )) + } + return(found) +} + #' @importFrom processx run quarto_run <- function( args = character(), diff --git a/_pkgdown.yml b/_pkgdown.yml index 9436a263..11e5dfa7 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -43,6 +43,7 @@ reference: - quarto_inspect - quarto_path - quarto_version + - quarto_available - is_using_quarto - quarto_binary_sitrep diff --git a/man/quarto_available.Rd b/man/quarto_available.Rd new file mode 100644 index 00000000..c9544949 --- /dev/null +++ b/man/quarto_available.Rd @@ -0,0 +1,38 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/quarto.R +\name{quarto_available} +\alias{quarto_available} +\title{Check if quarto is available and version meet some requirements} +\usage{ +quarto_available(min = NULL, max = NULL, error = FALSE) +} +\arguments{ +\item{min}{Minimum version expected.} + +\item{max}{Maximum version expected} + +\item{error}{If \code{TRUE}, will throw an error if Quarto is not available or does not meet the requirement. Default is \code{FALSE}.} +} +\value{ +logical. \code{TRUE} if requirement is met, \code{FALSE} otherwise. +} +\description{ +This function allows to test if Quarto is available and meets version requirement, a min, max or +in between requirement. +} +\details{ +If \code{min} and \code{max} are provided, this will check if Quarto version is +in-between two versions. If non is provided (keeping the default \code{NULL} for +both), it will just check for Quarto availability version and return \code{FALSE} if not found. +} +\examples{ +# Is there an active version available ? +quarto_available() +# check for a minimum requirement +quarto_available(min = "1.5") +# check for a maximum version +quarto_available(max = "1.6") +# only returns TRUE if Pandoc version is between two bounds +quarto_available(min = "1.4", max = "1.6") + +} diff --git a/man/quarto_path.Rd b/man/quarto_path.Rd index bf1cf3c7..89671e4d 100644 --- a/man/quarto_path.Rd +++ b/man/quarto_path.Rd @@ -16,3 +16,6 @@ Path to quarto binary (or \code{NULL} if not found) Determine the path to the quarto binary. Uses \code{QUARTO_PATH} environment variable if defined, otherwise uses \code{Sys.which()}. } +\seealso{ +\code{\link[=quarto_version]{quarto_version()}} to check the version of the binary found, \code{\link[=quarto_available]{quarto_available()}} to check if Quarto CLI is available and meets some requirements. +} diff --git a/man/quarto_version.Rd b/man/quarto_version.Rd index bba8c544..74c57d02 100644 --- a/man/quarto_version.Rd +++ b/man/quarto_version.Rd @@ -13,3 +13,6 @@ a \code{\link[base:numeric_version]{numeric_version}} with the quarto version fo Determine the specific version of quarto binary found by \code{\link[=quarto_path]{quarto_path()}}. If it returns \verb{99.9.9} then it means you are using a dev version. } +\seealso{ +\code{\link[=quarto_available]{quarto_available()}} to check if the version meets some requirements. +} diff --git a/tests/testthat/test-quarto.R b/tests/testthat/test-quarto.R index c900fb65..d6e276ee 100644 --- a/tests/testthat/test-quarto.R +++ b/tests/testthat/test-quarto.R @@ -122,3 +122,37 @@ test_that("quarto.quiet options controls echo and overwrite function argument", expect_output(quarto_render(qmd, quiet = TRUE)) }) }) + +test_that("quarto_available()", { + expect_error( + quarto_available("1.5", "1.4"), + regexp = "Minimum version .* cannot be greater than maximum version .*" + ) + + # Mock no quarto found + with_mocked_bindings( + quarto_version = function(...) NULL, + { + expect_false(quarto_available()) + expect_error(quarto_available(error = TRUE)) + } + ) + + local_mocked_bindings( + quarto_version = function(...) as.numeric_version("1.8.4") + ) + + expect_true(quarto_available()) + expect_true(quarto_available("1", "2")) + expect_false(quarto_available(max = "1.5")) + expect_error( + quarto_available(max = "1.6", error = TRUE), + regexp = "Maximum version expected is 1.6" + ) + expect_true(quarto_available(min = "1.8.4")) + expect_false(quarto_available(min = "1.9.5")) + expect_error( + quarto_available(min = "1.9.5", error = TRUE), + regexp = "Minimum version expected is 1.9.5" + ) +})