From 43698f0860ca08bc0782141b1bde390968f458fc Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 21 Mar 2024 13:07:57 -0400 Subject: [PATCH] feat: Run examples from any package (#4005) * chore: `inst/shiny` -> `inst/examples-shiny` * feat(runExamples): Find examples in any package * refactor: code style * refactor: small code style changes * docs: fix runApp typo * chore: include package name in valid examples message * chore(runExample): check that `package` is installed * chore: use braced package name * Update news --------- Co-authored-by: Carson Sievert --- NEWS.md | 2 ++ R/runapp.R | 66 +++++++++++++++++++++++++++++++---------------- man/runExample.Rd | 14 +++++++++- 3 files changed, 59 insertions(+), 23 deletions(-) diff --git a/NEWS.md b/NEWS.md index b074017cf5..ca461d1f0d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -16,6 +16,8 @@ * Added `onUnhandledError()` to register a function that will be called when an unhandled error occurs in a Shiny app. Note that this handler doesn't stop the error or prevent the session from closing, but it can be used to log the error or to clean up session-specific resources. (thanks @JohnCoene, #3993) +* `runExamples()` now uses the `{bslib}` package to build the user interface (UI). It also gains a `package` argument so that other packages can leverage this same function to run Shiny app examples. For more, see `?runExamples`. (#3963, #4005) + ## Bug fixes * Notifications are now constrained to the width of the viewport for window widths smaller the default notification panel size. (#3949) diff --git a/R/runapp.R b/R/runapp.R index 10049dd310..c6af84a2a1 100644 --- a/R/runapp.R +++ b/R/runapp.R @@ -447,6 +447,16 @@ stopApp <- function(returnValue = invisible()) { #' @param display.mode The mode in which to display the example. Defaults to #' `showcase`, but may be set to `normal` to see the example without #' code or commentary. +#' @param package The package in which to find the example (defaults to +#' `"shiny"`). +#' +#' To provide examples in your package, store examples in the +#' `inst/examples-shiny` directory of your package. Each example should be +#' in its own subdirectory and should be runnable when [runApp()] is called +#' on the subdirectory. Example apps can include a `DESCRIPTION` file and a +#' `README.md` file to provide metadata and commentary about the example. See +#' the article on [Display Modes](https://shiny.posit.co/r/articles/build/display-modes/) +#' on the Shiny website for more information. #' @inheritParams runApp #' #' @examples @@ -462,34 +472,46 @@ stopApp <- function(returnValue = invisible()) { #' system.file("examples", package="shiny") #' } #' @export -runExample <- function(example=NA, - port=getOption("shiny.port"), - launch.browser = getOption('shiny.launch.browser', interactive()), - host=getOption('shiny.host', '127.0.0.1'), - display.mode=c("auto", "normal", "showcase")) { - legacy <- getOption('shiny.legacy.examples', FALSE) - examplesDir <- if (isTRUE(legacy)) 'examples' else 'examples-shiny' - examplesDir <- system_file(examplesDir, package='shiny') +runExample <- function( + example = NA, + port = getOption("shiny.port"), + launch.browser = getOption("shiny.launch.browser", interactive()), + host = getOption("shiny.host", "127.0.0.1"), + display.mode = c("auto", "normal", "showcase"), + package = "shiny" +) { + if (!identical(package, "shiny") && !is_installed(package)) { + rlang::check_installed(package) + } + + use_legacy_shiny_examples <- + identical(package, "shiny") && + isTRUE(getOption('shiny.legacy.examples', FALSE)) + + examplesDir <- system_file( + if (use_legacy_shiny_examples) "examples" else "examples-shiny", + package = package + ) + dir <- resolve(examplesDir, example) + if (is.null(dir)) { + valid_examples <- sprintf( + 'Valid examples in {%s}: "%s"', + package, + paste(list.files(examplesDir), collapse = '", "') + ) + if (is.na(example)) { - errFun <- message - errMsg <- '' - } - else { - errFun <- stop - errMsg <- paste('Example', example, 'does not exist. ') + message(valid_examples) + return(invisible()) } - errFun(errMsg, - 'Valid examples are "', - paste(list.files(examplesDir), collapse='", "'), - '"') - } - else { - runApp(dir, port = port, host = host, launch.browser = launch.browser, - display.mode = display.mode) + stop("Example '", example, "' does not exist. ", valid_examples) } + + runApp(dir, port = port, host = host, launch.browser = launch.browser, + display.mode = display.mode) } #' Run a gadget diff --git a/man/runExample.Rd b/man/runExample.Rd index c17a40ba56..56c3937459 100644 --- a/man/runExample.Rd +++ b/man/runExample.Rd @@ -9,7 +9,8 @@ runExample( port = getOption("shiny.port"), launch.browser = getOption("shiny.launch.browser", interactive()), host = getOption("shiny.host", "127.0.0.1"), - display.mode = c("auto", "normal", "showcase") + display.mode = c("auto", "normal", "showcase"), + package = "shiny" ) } \arguments{ @@ -34,6 +35,17 @@ to the \code{shiny.host} option, if set, or \code{"127.0.0.1"} if not.} \item{display.mode}{The mode in which to display the example. Defaults to \code{showcase}, but may be set to \code{normal} to see the example without code or commentary.} + +\item{package}{The package in which to find the example (defaults to +\code{"shiny"}). + +To provide examples in your package, store examples in the +\code{inst/examples-shiny} directory of your package. Each example should be +in its own subdirectory and should be runnable when \code{\link[=runApp]{runApp()}} is called +on the subdirectory. Example apps can include a \code{DESCRIPTION} file and a +\code{README.md} file to provide metadata and commentary about the example. See +the article on \href{https://shiny.posit.co/r/articles/build/display-modes/}{Display Modes} +on the Shiny website for more information.} } \description{ Launch Shiny example applications, and optionally, your system's web browser.