Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CLI wrapper for extensions : list, remove, update #192

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
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
4 changes: 4 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@ export(quarto_add_extension)
export(quarto_binary_sitrep)
export(quarto_create_project)
export(quarto_inspect)
export(quarto_list_extensions)
export(quarto_path)
export(quarto_preview)
export(quarto_preview_stop)
export(quarto_publish_app)
export(quarto_publish_doc)
export(quarto_publish_site)
export(quarto_remove_extension)
export(quarto_render)
export(quarto_serve)
export(quarto_update_extension)
export(quarto_use_template)
export(quarto_version)
import(rlang)
Expand All @@ -28,4 +31,5 @@ importFrom(rstudioapi,isAvailable)
importFrom(rstudioapi,viewer)
importFrom(tools,vignetteEngine)
importFrom(utils,browseURL)
importFrom(utils,read.table)
importFrom(yaml,write_yaml)
33 changes: 33 additions & 0 deletions R/list.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#' List Installed Quarto extensions
#'
#' List Quarto Extensions in this folder or project by running `quarto list`
#'
#' @inheritParams quarto_render
#'
#' @examples
#' \dontrun{
#' # List Quarto Extensions in this folder or project
#' quarto_list_extensions()
#' }
#'
#' @importFrom rlang is_interactive
#' @importFrom cli cli_abort
#' @importFrom utils read.table
#' @export
quarto_list_extensions <- function(quiet = FALSE, quarto_args = NULL){
quarto_bin <- find_quarto()

args <- c("extensions", if (quiet) cli_arg_quiet(), quarto_args)
x <- quarto_list(args, quarto_bin = quarto_bin, echo = TRUE)
# Clean the stderr output to remove extra spaces and ensure consistent formatting
stderr_cleaned <- gsub("\\s+$", "", x$stderr)
if (grepl("No extensions are installed", stderr_cleaned)) {
invisible()
} else{
invisible(utils::read.table(text = stderr_cleaned, header = TRUE, fill = TRUE, sep = "", stringsAsFactors = FALSE))
}
}

quarto_list <- function(args = character(), ...){
quarto_run_what("list", args = args, ...)
}
48 changes: 48 additions & 0 deletions R/remove.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#' Remove a Quarto extensions
#'
#' Remove an extension in this folder or project by running `quarto remove`
#'
#' # Extension Trust
#'
#' Quarto extensions may execute code when documents are rendered. Therefore, if
#' you do not trust the author of an extension, we recommend that you do not
#' install or use the extension.
#' By default `no_prompt = FALSE` which means that
#' the function will ask for explicit approval when used interactively, or
#' disallow installation.
#'
#' @inheritParams quarto_render
#'
#' @param extension The extension to remove, either an archive or a GitHub
#' repository as described in the documentation
#' <https://quarto.org/docs/extensions/managing.html>.
#'
#' @param no_prompt Do not prompt to confirm approval to download external extension.
#'
#' @examples
#' \dontrun{
#' # Remove an already installed extension
#' quarto_remove_extension("quarto-ext/fontawesome")
#' }
#' @importFrom rlang is_interactive
#' @importFrom cli cli_abort
#' @export
quarto_remove_extension <- function(extension = NULL, no_prompt = FALSE, quiet = FALSE, quarto_args = NULL) {
rlang::check_required(extension)

quarto_bin <- find_quarto()

# This will ask for approval or stop installation
approval <- check_removal_approval(no_prompt, extension, "https://quarto.org/docs/extensions/managing.html")

if (approval) {
args <- c(extension, "--no-prompt", if (quiet) cli_arg_quiet(), quarto_args)
quarto_remove(args, quarto_bin = quarto_bin, echo = TRUE)
}

invisible()
}

quarto_remove <- function(args = character(), ...) {
quarto_run_what("remove", args = args, ...)
}
52 changes: 52 additions & 0 deletions R/update.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#' Update a Quarto extensions
#'
#' Update an extension to this folder or project by running `quarto update`
#'
#' # Extension Trust
#'
#' Quarto extensions may execute code when documents are rendered. Therefore, if
#' you do not trust the author of an extension, we recommend that you do not
#' install or use the extension.
#' By default `no_prompt = FALSE` which means that
#' the function will ask for explicit approval when used interactively, or
#' disallow installation.
#'
#' @inheritParams quarto_render
#'
#' @param extension The extension to update, either an archive or a GitHub
#' repository as described in the documentation
#' <https://quarto.org/docs/extensions/managing.html>.
#'
#' @param no_prompt Do not prompt to confirm approval to download external extension.
#'
#' @examples
#' \dontrun{
#' # Update a template and set up a draft document from a GitHub repository
#' quarto_update_extension("quarto-ext/fontawesome")
#'
#' # Update a template and set up a draft document from a ZIP archive
#' quarto_update_extension("https://github.com/quarto-ext/fontawesome/archive/refs/heads/main.zip")
#' }
#'
#' @importFrom rlang is_interactive
#' @importFrom cli cli_abort
#' @export
quarto_update_extension <- function(extension = NULL, no_prompt = FALSE, quiet = FALSE, quarto_args = NULL) {
rlang::check_required(extension)

quarto_bin <- find_quarto()

# This will ask for approval or stop installation
approval <- check_extension_approval(no_prompt, "Quarto extensions", "https://quarto.org/docs/extensions/managing.html")

if (approval) {
args <- c(extension, "--no-prompt", if (quiet) cli_arg_quiet(), quarto_args)
quarto_update(args, quarto_bin = quarto_bin, echo = TRUE)
}

invisible()
}

quarto_update <- function(args = character(), ...) {
quarto_run_what("update", args = args, ...)
}
30 changes: 29 additions & 1 deletion R/utils-prompt.R
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
check_extension_approval <- function(no_prompt = FALSE, what = "Something", see_more_at = NULL) {
if (no_prompt) return(TRUE)

if (!rlang::is_interactive()) {
if (!is_interactive()) {
cli::cli_abort(c(
"{ what } requires explicit approval.",
">" = "Set {.arg no_prompt = TRUE} if you agree.",
Expand All @@ -19,5 +19,33 @@ check_extension_approval <- function(no_prompt = FALSE, what = "Something", see_
cli::cli_inform("{what} not installed.")
return(invisible(FALSE))
}
return(invisible(TRUE))
}
}

check_removal_approval <- function(no_prompt = FALSE, what = "Something", see_more_at = NULL) {
if (no_prompt) return(TRUE)

if (!is_interactive()) {
cli::cli_abort(c(
"{ what } requires explicit approval.",
">" = "Set {.arg no_prompt = TRUE} if you agree.",
if (!is.null(see_more_at)) {
c(i = "See more at {.url {see_more_at}}")
}
))
} else {
prompt_value <- tolower(readline(sprintf("? Are you sure you'd like to remove %s (Y/n)? ", what)))
if (!prompt_value %in% "y") {
return(invisible(FALSE))
}
return(invisible(TRUE))
}
}

# Add binding to base R function for testthat mocking
readline <- NULL
# Add binding to function from other package for mocking later on
is_interactive <- function(...) {
rlang::is_interactive(...)
}
26 changes: 26 additions & 0 deletions man/quarto_list_extensions.Rd

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

14 changes: 6 additions & 8 deletions man/quarto_publish_doc.Rd

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

45 changes: 45 additions & 0 deletions man/quarto_remove_extension.Rd

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

49 changes: 49 additions & 0 deletions man/quarto_update_extension.Rd

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

13 changes: 13 additions & 0 deletions tests/testthat/test-list.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
test_that("Listing extensions", {
skip_if_no_quarto()
skip_if_offline("github.com")
qmd <- local_qmd_file(c("content"))
withr::local_dir(dirname(qmd))
expect_null(quarto_list_extensions())
quarto_add_extension("quarto-ext/fontawesome", no_prompt = TRUE, quiet = TRUE)
expect_true(dir.exists("_extensions/quarto-ext/fontawesome"))
expect_equal(nrow(quarto_list_extensions()), 1)
quarto_add_extension("quarto-ext/lightbox", no_prompt = TRUE, quiet = TRUE)
expect_true(dir.exists("_extensions/quarto-ext/lightbox"))
expect_equal(nrow(quarto_list_extensions()), 2)
})
11 changes: 11 additions & 0 deletions tests/testthat/test-remove.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
test_that("Removing an extension", {
skip_if_no_quarto()
skip_if_offline("github.com")
qmd <- local_qmd_file(c("content"))
withr::local_dir(dirname(qmd))
expect_null(quarto_remove_extension("quarto-ext/fontawesome", no_prompt = TRUE, quiet = TRUE))
quarto_add_extension("quarto-ext/fontawesome", no_prompt = TRUE, quiet = TRUE)
expect_true(dir.exists("_extensions/quarto-ext/fontawesome"))
quarto_remove_extension("quarto-ext/fontawesome", no_prompt = TRUE, quiet = TRUE)
expect_true(!dir.exists("_extensions/quarto-ext/fontawesome"))
})
11 changes: 11 additions & 0 deletions tests/testthat/test-update.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
test_that("Updating an extension", {
skip_if_no_quarto()
skip_if_offline("github.com")
qmd <- local_qmd_file(c("content"))
withr::local_dir(dirname(qmd))
expect_error(quarto_add_extension("quarto-ext/[email protected]"), "explicit approval")
quarto_update_extension("quarto-ext/fontawesome", no_prompt = TRUE, quiet = TRUE)
expect_true(dir.exists("_extensions/quarto-ext/fontawesome"))
current_version <- yaml::read_yaml("_extensions/quarto-ext/fontawesome/_extension.yml")$version
expect_false(identical(current_version, "v0.0.1"))
})
Loading
Loading