Skip to content

Commit 89c1d14

Browse files
implement tutorial_package_dependencies() (#329)
* implement tutorial_dependencies() * add unit test * rename file * Move files into one place and unify renv call to a single function * allow for tutorial_package_dependencies to work like available_packages * document available tutorials. Add new column package_dependencies. Have tutorial_package_dependencies call available_tutorials if needed. Skip tests if learnr isn't installed * return NULL for test and older R versions * Correct the news item and add the PR * Remove unneeded skip check * Trim white space * Reflow tutorial_package_dependencies() docs. Fix incorrect word * Preempt if there are no deps found. R <= 3.4 can not `sort(NULL)` Co-authored-by: Barret Schloerke <barret@rstudio.com>
1 parent 6620c67 commit 89c1d14

9 files changed

+120
-46
lines changed

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export(safe_env)
4242
export(tutorial)
4343
export(tutorial_html_dependency)
4444
export(tutorial_options)
45+
export(tutorial_package_dependencies)
4546
import(rmarkdown)
4647
import(shiny)
4748
importFrom(evaluate,evaluate)

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ learnr 0.10.0.9000 (unreleased)
55

66
## Minor new features and improvements
77

8+
* `learnr` gained the function `learnr::tutorial_package_dependencies()`, used to enumerate a tutorial's R package dependencies. Front-ends can use this to ensure a tutorial's dependencies are satisfied before attempting to run that tutorial. `learnr::available_tutorials()` gained the column `package_dependencies` containing the required packages to run the document. ([#329](https://github.com/rstudio/learnr/pull/329))
9+
810
* Include vignette about publishing learnr tutorials on shinyapps.io
911

1012
* `learnr`'s built-in tutorials now come with a description as part of the YAML header, with the intention of this being used in front-end software that catalogues available `learnr` tutorials on the system. ([#312](https://github.com/rstudio/learnr/issues/312))

R/available_tutorials.R

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
#' development of the package (i.e. the corresponding tutorial .html file for
1111
#' the .Rmd file must exist).
1212
#'
13-
#' @return \code{available_tutorials} will return a \code{data.frame} containing "package", "name", and "title".
13+
#' @return \code{available_tutorials} will return a \code{data.frame} containing "package", "name", "title", "description", "package_dependencies", "private", and "yaml_front_matter".
1414
#' @rdname available_tutorials
1515
#' @export
1616
available_tutorials <- function(package = NULL) {
@@ -39,6 +39,7 @@ available_tutorials <- function(package = NULL) {
3939
#' "name": Tutorial directory. (can be passed in as `run_tutorial(NAME, PKG)`; string
4040
#' "title": Tutorial title from yaml header; [NA]
4141
#' "description": Tutorial description from yaml header; [NA]
42+
#' "package_dependencies": Packages needed to run tutorial; [lsit()]
4243
#' "private": Boolean describing if tutorial should be indexed / displayed; [FALSE]
4344
#' "yaml_front_matter": list column of all yaml header info; [list()]
4445
#' @noRd
@@ -87,6 +88,7 @@ available_tutorials_for_package <- function(package) {
8788
title = yaml_front_matter$title %||% NA,
8889
description = yaml_front_matter$description %||% NA,
8990
private = yaml_front_matter$private %||% FALSE,
91+
package_dependencies = I(list(tutorial_dir_package_dependencies(tut_dir))),
9092
yaml_front_matter = I(list(yaml_front_matter)),
9193
stringsAsFactors = FALSE,
9294
row.names = FALSE

R/install_tutorial_dependencies.R

Lines changed: 0 additions & 43 deletions
This file was deleted.

R/tutorial_package_dependencies.R

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
get_needed_pkgs <- function(dir) {
2+
3+
pkgs <- tutorial_dir_package_dependencies(dir)
4+
5+
pkgs[!pkgs %in% utils::installed.packages()]
6+
}
7+
8+
format_needed_pkgs <- function(needed_pkgs) {
9+
paste(" -", needed_pkgs, collapse = "\n")
10+
}
11+
12+
ask_pkgs_install <- function(needed_pkgs) {
13+
question <- sprintf("Would you like to install the following packages?\n%s",
14+
format_needed_pkgs(needed_pkgs))
15+
16+
utils::menu(choices = c("yes", "no"),
17+
title = question)
18+
}
19+
20+
install_tutorial_dependencies <- function(dir) {
21+
needed_pkgs <- get_needed_pkgs(dir)
22+
23+
if(length(needed_pkgs) == 0) {
24+
return(invisible(NULL))
25+
}
26+
27+
if(!interactive()) {
28+
stop("The following packages need to be installed:\n",
29+
format_needed_pkgs(needed_pkgs))
30+
}
31+
32+
answer <- ask_pkgs_install(needed_pkgs)
33+
34+
if(answer == 2) {
35+
stop("The tutorial is missing required packages and cannot be rendered.")
36+
}
37+
38+
utils::install.packages(needed_pkgs)
39+
}
40+
41+
42+
43+
44+
#' List Tutorial Dependencies
45+
#'
46+
#' List the \R packages required to run a particular tutorial.
47+
#'
48+
#' @param name The tutorial name. If \code{name} is \code{NULL}, then all
49+
#' tutorials within \code{package} will be searched.
50+
#' @param package The \R package providing the tutorial. If \code{package} is
51+
#' \code{NULL}, then all tutorials will be searched.
52+
#'
53+
#' @export
54+
#' @return A character vector of package names that are required for execution.
55+
#' @examples
56+
#' tutorial_package_dependencies(package = "learnr")
57+
tutorial_package_dependencies <- function(name = NULL, package = NULL) {
58+
if (identical(name, NULL)) {
59+
info <- available_tutorials(package = package)
60+
return(
61+
sort(unique(unlist(info$package_dependencies)))
62+
)
63+
}
64+
65+
tutorial_dir_package_dependencies(
66+
get_tutorial_path(name = name, package = package)
67+
)
68+
}
69+
70+
tutorial_dir_package_dependencies <- function(dir) {
71+
# enumerate tutorial package dependencies
72+
deps <- renv::dependencies(dir, quiet = TRUE)
73+
74+
# R <= 3.4 can not sort(NULL)
75+
# if no deps are found, renv::dependencies returns NULL
76+
if (is.null(deps)) {
77+
return(NULL)
78+
}
79+
80+
sort(unique(deps$Package))
81+
}

man/available_tutorials.Rd

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/tutorial_package_dependencies.Rd

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/test-dependency.R

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,11 @@ test_that("tutor html dependencies can be retreived", {
66
expect_equal(dep$name, "tutorial")
77
})
88

9+
test_that("tutorial package dependencies can be enumerated", {
10+
packages <- tutorial_package_dependencies("ex-data-summarise", "learnr")
11+
expect_true("tidyverse" %in% packages)
12+
})
13+
test_that("Per package, tutorial package dependencies can be enumerated", {
14+
packages <- tutorial_package_dependencies(package = "learnr")
15+
expect_true("tidyverse" %in% packages)
16+
})

tests/testthat/test-install-dependencies.R

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,3 @@ test_that("tutorial dependency check works (not interactive)", {
5151

5252
expect_error(install_tutorial_dependencies(tutorial_dir))
5353
})
54-

0 commit comments

Comments
 (0)