Skip to content

Commit e653786

Browse files
parmsamcderv
andauthored
Add CLI wrapper for extensions : list, remove, update (#192)
Co-authored-by: Christophe Dervieux <christophe.dervieux@gmail.com>
1 parent 2a0cd7c commit e653786

20 files changed

+675
-42
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.9007
3+
Version: 1.4.4.9008
44
Authors@R: c(
55
person("JJ", "Allaire", , "jj@posit.co", role = "aut",
66
comment = c(ORCID = "0000-0003-0174-9868")),

NAMESPACE

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,17 @@ export(quarto_add_extension)
55
export(quarto_binary_sitrep)
66
export(quarto_create_project)
77
export(quarto_inspect)
8+
export(quarto_list_extensions)
89
export(quarto_path)
910
export(quarto_preview)
1011
export(quarto_preview_stop)
1112
export(quarto_publish_app)
1213
export(quarto_publish_doc)
1314
export(quarto_publish_site)
15+
export(quarto_remove_extension)
1416
export(quarto_render)
1517
export(quarto_serve)
18+
export(quarto_update_extension)
1619
export(quarto_use_template)
1720
export(quarto_version)
1821
export(theme_brand_flextable)

NEWS.md

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

3+
- Add several new wrapper function (thanks, @parmsam, #192):
4+
- `quarto_list_extensions()` to list installed extensions using `quarto list extensions`
5+
- `quarto_remove_extension()` to remove an installed extension using `quarto remove extensions`
6+
- `quarto_update_extension()` to update an installed extension using `quarto update extensions`
7+
38
- `quarto_create_project()` offers better user experience now (thanks, @jennybc, #206, #153).
49

510
- `quarto_preview()` gains a `quiet` argument to suppress any output from R or Quarto CLI (thanks, @cwickham, #232.)

R/list.R

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#' List Installed Quarto extensions
2+
#'
3+
#' List Quarto Extensions in this folder or project by running `quarto list`
4+
#'
5+
#' @return A data frame with the installed extensions or NULL (invisibly) if no extensions are installed.
6+
#'
7+
#' @examples
8+
#' \dontrun{
9+
#' # List Quarto Extensions in this folder or project
10+
#' quarto_list_extensions()
11+
#' }
12+
#'
13+
#' @export
14+
quarto_list_extensions <- function() {
15+
quarto_bin <- find_quarto()
16+
17+
# quarto list extensions --quiet will return nothing so we need to prevent that.
18+
args <- c("extensions")
19+
x <- quarto_list(args, quarto_bin = quarto_bin, echo = FALSE)
20+
# Clean the stderr output to remove extra spaces and ensure consistent formatting
21+
stderr_cleaned <- gsub("\\s+$", "", x$stderr)
22+
if (grepl("No extensions are installed", stderr_cleaned)) {
23+
invisible()
24+
} else {
25+
df <- utils::read.table(
26+
text = stderr_cleaned,
27+
header = TRUE,
28+
fill = TRUE,
29+
sep = "",
30+
stringsAsFactors = FALSE
31+
)
32+
df[order(df$Id), ]
33+
}
34+
}
35+
36+
quarto_list <- function(args = character(), ...) {
37+
quarto_run_what("list", args = args, ...)
38+
}

R/remove.R

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#' Remove a Quarto extensions
2+
#'
3+
#' Remove an extension in this folder or project by running `quarto remove`
4+
#'
5+
#' @inheritParams quarto_render
6+
#'
7+
#' @param extension The extension name to remove, as in `quarto remove <extension-name>`.
8+
#'
9+
#' @param no_prompt Do not prompt to confirm approval to download external extension.
10+
#'
11+
#'
12+
#' @return Returns invisibly `TRUE` if the extension was removed, `FALSE` otherwise.
13+
#'
14+
#' @seealso `quarto_add_extension()` and [Quarto Website](https://quarto.org/docs/extensions/managing.html).
15+
#'
16+
#' @examples
17+
#' \dontrun{
18+
#' # Remove an already installed extension
19+
#' quarto_remove_extension("quarto-ext/fontawesome")
20+
#' }
21+
#' @export
22+
quarto_remove_extension <- function(
23+
extension = NULL,
24+
no_prompt = FALSE,
25+
quiet = FALSE,
26+
quarto_args = NULL
27+
) {
28+
rlang::check_required(extension)
29+
30+
installed_extensions <- quarto_list_extensions()
31+
if (is.null(installed_extensions)) {
32+
if (!quiet) {
33+
cli::cli_alert_warning("No extensions installed.")
34+
}
35+
return(invisible(FALSE))
36+
}
37+
38+
quarto_bin <- find_quarto()
39+
40+
# This will ask for approval or stop installation
41+
approval <- check_removal_approval(
42+
no_prompt,
43+
extension,
44+
"https://quarto.org/docs/extensions/managing.html"
45+
)
46+
47+
if (approval) {
48+
args <- c(extension, "--no-prompt", if (quiet) cli_arg_quiet(), quarto_args)
49+
quarto_remove(args, quarto_bin = quarto_bin, echo = FALSE)
50+
if (!quiet) {
51+
cli::cli_alert_success(
52+
"Extension {.code {extension}} successfully removed."
53+
)
54+
}
55+
}
56+
57+
invisible(TRUE)
58+
}
59+
60+
quarto_remove <- function(args = character(), ...) {
61+
quarto_run_what("remove", args = args, ...)
62+
}

R/theme.R

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
#' @param fg The foreground color
1111
#' @param brand_yml The path to a brand.yml file
1212

13-
1413
#' @rdname theme_helpers
1514
#'
1615
#' @export
@@ -41,7 +40,8 @@ theme_colors_ggplot <- function(bg, fg) {
4140
if (!requireNamespace("ggplot2", quietly = TRUE)) {
4241
return(NULL)
4342
}
44-
ggplot2::`%+%`(ggplot2::theme_minimal(base_size = 11),
43+
ggplot2::`%+%`(
44+
ggplot2::theme_minimal(base_size = 11),
4545
ggplot2::theme(
4646
panel.border = ggplot2::element_blank(),
4747
panel.grid.major.y = ggplot2::element_blank(),
@@ -54,7 +54,8 @@ theme_colors_ggplot <- function(bg, fg) {
5454
plot.background = ggplot2::element_rect(fill = bg, colour = NA),
5555
axis.line = ggplot2::element_line(colour = fg),
5656
axis.ticks = ggplot2::element_line(colour = fg)
57-
))
57+
)
58+
)
5859
}
5960

6061
#' @rdname theme_helpers
@@ -92,10 +93,12 @@ theme_brand_gt <- function(brand_yml) {
9293
#' @export
9394
theme_colors_plotly <- function(bg, fg) {
9495
(function(plot) {
95-
plot |> plotly::layout(paper_bgcolor = bg,
96-
plot_bgcolor = bg,
97-
font = list(color = fg)
98-
)
96+
plot |>
97+
plotly::layout(
98+
paper_bgcolor = bg,
99+
plot_bgcolor = bg,
100+
font = list(color = fg)
101+
)
99102
})
100103
}
101104

@@ -116,7 +119,8 @@ theme_colors_thematic <- function(bg, fg) {
116119
thematic::thematic_rmd(
117120
bg = bg,
118121
fg = fg,
119-
)})
122+
)
123+
})
120124
}
121125

122126
#' @rdname theme_helpers

R/update.R

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#' Update a Quarto extensions
2+
#'
3+
#' Update an extension to this folder or project by running `quarto update`
4+
#'
5+
#' # Extension Trust
6+
#'
7+
#' Quarto extensions may execute code when documents are rendered. Therefore, if
8+
#' you do not trust the author of an extension, we recommend that you do not
9+
#' install or use the extension.
10+
#' By default `no_prompt = FALSE` which means that
11+
#' the function will ask for explicit approval when used interactively, or
12+
#' disallow installation.
13+
#'
14+
#' @inheritParams quarto_render
15+
#'
16+
#' @param extension The extension to update, either by its name (i.e ` quarto update extension <gh-org>/<gh-repo>`), an archive (` quarto update extension <path-to-zip>`) or a url (`quarto update extension <url>`).
17+
#'
18+
#' @param no_prompt Do not prompt to confirm approval to download external extension. Setting `no_prompt = FALSE` means [Extension Trust](#extension-trust) is accepted.
19+
#'
20+
#' @seealso [quarto_add_extension()], [quarto_remove_extension()], and [Quarto website](https://quarto.org/docs/extensions/managing.html).
21+
#'
22+
#' @return Returns invisibly `TRUE` if the extension was updated, `FALSE` otherwise.
23+
#'
24+
#' @examples
25+
#' \dontrun{
26+
#' # Update a template and set up a draft document from a GitHub repository
27+
#' quarto_update_extension("quarto-ext/fontawesome")
28+
#'
29+
#' # Update a template and set up a draft document from a ZIP archive
30+
#' quarto_update_extension("https://github.com/quarto-ext/fontawesome/archive/refs/heads/main.zip")
31+
#' }
32+
#' @export
33+
quarto_update_extension <- function(
34+
extension = NULL,
35+
no_prompt = FALSE,
36+
quiet = FALSE,
37+
quarto_args = NULL
38+
) {
39+
rlang::check_required(extension)
40+
41+
quarto_bin <- find_quarto()
42+
43+
# This will ask for approval or stop installation
44+
approval <- check_extension_approval(
45+
no_prompt,
46+
"Quarto extension",
47+
"https://quarto.org/docs/extensions/managing.html"
48+
)
49+
50+
if (!approval) {
51+
return(invisible(FALSE))
52+
}
53+
54+
args <- c(extension, "--no-prompt", if (quiet) cli_arg_quiet(), quarto_args)
55+
quarto_update(args, quarto_bin = quarto_bin, echo = TRUE)
56+
if (!quiet) {
57+
cli::cli_inform("Extension {.code {extension}} updated.")
58+
}
59+
invisible(TRUE)
60+
}
61+
62+
quarto_update <- function(args = character(), ...) {
63+
quarto_run_what("update", args = args, ...)
64+
}

R/utils-prompt.R

Lines changed: 72 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,82 @@
1-
check_extension_approval <- function(
1+
check_approval <- function(
22
no_prompt = FALSE,
33
what = "Something",
4-
see_more_at = NULL
4+
not_action = "approved",
5+
see_more_at = NULL,
6+
prompt_message = NULL,
7+
interactive_info = NULL, # could use `{ what }` as used in `cli_inform()`
8+
.call = rlang::caller_env()
59
) {
610
if (no_prompt) return(TRUE)
711

8-
if (!rlang::is_interactive()) {
9-
cli::cli_abort(c(
10-
"{ what } requires explicit approval.",
11-
">" = "Set {.arg no_prompt = TRUE} if you agree.",
12-
if (!is.null(see_more_at)) {
13-
c(i = "See more at {.url {see_more_at}}")
14-
}
15-
))
12+
if (!is_interactive()) {
13+
cli::cli_abort(
14+
c(
15+
"{ what } requires explicit approval.",
16+
">" = "Set {.arg no_prompt = TRUE} if you agree.",
17+
if (!is.null(see_more_at)) {
18+
c(i = "See more at {.url {see_more_at}}")
19+
}
20+
),
21+
call = .call
22+
)
1623
} else {
17-
cli::cli_inform(c(
18-
"{what} may execute code when documents are rendered. ",
19-
"*" = "If you do not trust the author(s) of this {what}, we recommend that you do not install or use this {what}."
20-
))
21-
prompt_value <- tolower(readline(sprintf(
22-
"Do you trust the authors of this %s (Y/n)? ",
23-
what
24-
)))
25-
if (!prompt_value %in% "y") {
26-
cli::cli_inform("{what} not installed.")
24+
if (!is.null(interactive_info)) {
25+
cli::cli_inform(interactive_info)
26+
}
27+
prompt_value <- tolower(readline(prompt_message))
28+
if (!prompt_value %in% c("", "y")) {
29+
cli::cli_alert_info(paste0(what, " not {not_action}"))
2730
return(invisible(FALSE))
28-
} else {
29-
return(invisible(TRUE))
3031
}
3132
}
33+
return(invisible(TRUE))
34+
}
35+
36+
check_extension_approval <- function(
37+
no_prompt = FALSE,
38+
what = "Something",
39+
see_more_at = NULL
40+
) {
41+
interactive_info <- c(
42+
"{what} may execute code when documents are rendered. ",
43+
"*" = "If you do not trust the author(s) of this {what}, we recommend that you do not install or use this {what}."
44+
)
45+
46+
prompt_message <- sprintf(
47+
"Do you trust the authors of this %s (Y/n)? ",
48+
what
49+
)
50+
51+
check_approval(
52+
no_prompt = no_prompt,
53+
what = what,
54+
not_action = "installed",
55+
see_more_at = see_more_at,
56+
prompt_message = prompt_message,
57+
interactive_info = interactive_info
58+
)
59+
}
60+
61+
check_removal_approval <- function(
62+
no_prompt = FALSE,
63+
what = "Something",
64+
see_more_at = NULL
65+
) {
66+
prompt_message <- sprintf(
67+
"Are you sure you'd like to remove %s (Y/n)? ",
68+
what
69+
)
70+
71+
check_approval(
72+
no_prompt = no_prompt,
73+
what = what,
74+
not_action = "removed",
75+
see_more_at = see_more_at,
76+
prompt_message = prompt_message,
77+
interactive_info = NULL
78+
)
3279
}
80+
81+
# Needed for testthat to mock base function
82+
readline <- NULL

_pkgdown.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,12 @@ reference:
2121

2222
- title: "Extensions"
2323
desc: >
24-
This functions enable you to install extensions and use template from extensions in your folder and projects.
24+
These functions enable you to manage Quarto extensions and use template from extensions in your folder and projects.
2525
More about Quarto extensions at <https://quarto.org/docs/extensions/>
2626
contents:
27-
- starts_with("quarto_add")
28-
- starts_with("quarto_use")
27+
- ends_with("_extension")
28+
- ends_with("_extensions")
29+
- quarto_use_template
2930

3031
- title: "Projects"
3132
desc: >

0 commit comments

Comments
 (0)