From 28b9669123caecf11603a346af4eca491c55fa67 Mon Sep 17 00:00:00 2001 From: Vincent Arel-Bundock Date: Mon, 20 Nov 2023 06:48:16 -0500 Subject: [PATCH] `render_docs(parallel = TRUE)` using `future.apply` (#96) Co-authored-by: Etienne Bacher <52219252+etiennebacher@users.noreply.github.com> --- DESCRIPTION | 4 +++- NEWS.md | 1 + R/man.R | 35 +++++++++++++++++++++-------------- R/render_docs.R | 12 +++++++++--- R/vignettes.R | 26 ++++++++++++++++---------- docs/man/render_docs.md | 18 +++++++++++++++++- man/render_docs.Rd | 9 ++++++++- 7 files changed, 75 insertions(+), 30 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 8cba96a8..0f791631 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Type: Package Package: altdoc Title: Package Documentation Websites with 'Docsify.js', 'Docute', or 'Mkdocs -Version: 0.2.2.9006 +Version: 0.2.2.9007 Authors@R: c(person(given = "Etienne", family = "Bacher", @@ -29,6 +29,8 @@ Imports: xml2 Suggests: covr, + future, + future.apply, knitr, rstudioapi, servr, diff --git a/NEWS.md b/NEWS.md index ec86e8a9..3525d667 100644 --- a/NEWS.md +++ b/NEWS.md @@ -16,6 +16,7 @@ Breaking changes: New: * Support for Quarto vignettes (.qmd) stored in the `vignettes/` folder. +* `render_docs(parallel=TRUE)` uses `future` to parallelize the rendering of vignettes and man pages. * Settings files are now permanently stored in the `altdoc/` directory. These files can be edited manually to customize the website. * Major internal changes to the .Rd -> .md conversion system. We now use Quarto to convert man pages and execute examples, and the man pages are stored in separate markdown files instead of combined in a single large file. * `mkdocs` now behaves like the other documentation generators and stores its files in `docs/`. This means that `mkdocs` websites can be deployed to Github Pages. diff --git a/R/man.R b/R/man.R index b071e541..4dc5fdd1 100644 --- a/R/man.R +++ b/R/man.R @@ -1,5 +1,5 @@ # Convert and unite .Rd files to 'docs/reference.md'. -.import_man <- function(path = ".", verbose = FALSE) { +.import_man <- function(path = ".", verbose = FALSE, parallel = FALSE) { # source and target file paths # using here::here() breaks tests, so we rely on directory check higher up man_source <- list.files(path = "man", pattern = "\\.Rd$") @@ -9,26 +9,20 @@ n <- length(man_source) - # can't use message_info with {} - cli::cli_alert_info("Found {n} man page{?s} to convert.") - i <- 0 - cli::cli_progress_step("Converting {cli::qty(n)}man page{?s}: {i}/{n}", spinner = TRUE) - conversion_worked <- vector(length = n) + cli::cli_alert_info("Found {n} man page{?s} to convert.") # process man pages one by one - for (i in seq_along(man_source)) { - f <- man_source[i] + render_one_man <- function(fn) { # fs::path_ext_set breaks filenames with dots, ex: 'foo.bar.Rd' - origin_Rd <- fs::path_join(c("man", paste0(f, ".Rd"))) + origin_Rd <- fs::path_join(c("man", paste0(fn, ".Rd"))) destination_dir <- fs::path_join(c(.doc_path(path = "."), "man")) - destination_qmd <- fs::path_join(c(destination_dir, paste0(f, ".qmd"))) - destination_md <- fs::path_join(c(destination_dir, paste0(f, ".md"))) + destination_qmd <- fs::path_join(c(destination_dir, paste0(fn, ".qmd"))) + destination_md <- fs::path_join(c(destination_dir, paste0(fn, ".md"))) fs::dir_create(destination_dir) .rd2qmd(origin_Rd, destination_dir) - conversion_worked[i] <- .qmd2md(destination_qmd, destination_dir, verbose = verbose) + worked <- .qmd2md(destination_qmd, destination_dir, verbose = verbose) fs::file_delete(destination_qmd) - # section headings are too deeply nested by default # this is a hack because it may remove one # from comments. But that's # probably not the end of the world, because the line stick stays commented @@ -38,8 +32,21 @@ tmp <- gsub("^##", "#", tmp) writeLines(tmp, destination_md) } + return(worked) + } - cli::cli_progress_update(inc = 1) + if (isTRUE(parallel)) { + .assert_dependency("future.apply", install = TRUE) + conversion_worked <- future.apply::future_sapply(man_source, render_one_man, future.seed = NULL) + } else { + # can't use message_info with {} + i <- 0 + cli::cli_progress_step("Converting {cli::qty(n)}man page{?s}: {i}/{n}", spinner = TRUE) + conversion_worked <- vector(length = n) + for (i in seq_along(man_source)) { + conversion_worked[i] <- render_one_man(man_source[i]) + cli::cli_progress_update(inc = 1) + } } successes <- which(conversion_worked == TRUE) diff --git a/R/render_docs.R b/R/render_docs.R index 29abaac5..0345b682 100644 --- a/R/render_docs.R +++ b/R/render_docs.R @@ -5,6 +5,7 @@ #' overwrites the files in the 'docs/' folder. #' #' @param verbose Logical. Print Rmarkdown or Quarto rendering output. +#' @param parallel Logical. Render man pages and vignettes in parallel using the `future` framework. In addition to setting this argument to TRUE, users must define the parallelism plan in `future`. See the examples section below. #' @inheritParams setup_docs #' @export #' @@ -15,8 +16,13 @@ #' #' render_docs() #' +#' # parallel rendering +#' library(future) +#' plan(multicore) +#' render_docs(parallel = TRUE) +#' #' } -render_docs <- function(path = ".", verbose = FALSE) { +render_docs <- function(path = ".", verbose = FALSE, parallel = FALSE) { dir_altdoc <- fs::path_join(c(path, "altdoc")) if (!fs::dir_exists(dir_altdoc) || length(fs::dir_ls(dir_altdoc)) == 0) { @@ -41,11 +47,11 @@ render_docs <- function(path = ".", verbose = FALSE) { # Update functions reference cli::cli_h1("Man pages") - .import_man(path = path, verbose = verbose) + .import_man(path = path, verbose = verbose, parallel = parallel) # Update vignettes cli::cli_h1("Vignettes") - .import_vignettes(path, verbose = verbose) + .import_vignettes(path, verbose = verbose, parallel = parallel) cli::cli_h1("Update HTML") .import_settings(path = path, doctype = .doc_type(path)) diff --git a/R/vignettes.R b/R/vignettes.R index de97cda4..b48d0c1c 100644 --- a/R/vignettes.R +++ b/R/vignettes.R @@ -9,7 +9,7 @@ # that it is "md_document" instead of "html_vignette" # * render all of the modified .Rmd files (in "docs/vignettes"), which produce .md files. -.import_vignettes <- function(path = path, verbose = FALSE) { +.import_vignettes <- function(path = path, verbose = FALSE, parallel = FALSE) { # source directory src_dir <- fs::path_abs("vignettes", start = path) if (!fs::dir_exists(src_dir) || .folder_is_empty(src_dir)) { @@ -45,22 +45,15 @@ n <- length(src_files) - # can't use message_info with {} cli::cli_alert_info("Found {n} vignette{?s} to convert.") - i <- 0 - cli::cli_progress_step("Converting {cli::qty(n)}vignette{?s}: {i}/{n}", spinner = TRUE) - - conversion_worked <- vector(length = n) fs::dir_copy(src_dir, tar_dir, overwrite = TRUE) - for (i in seq_along(src_files)) { + render_one_vignette <- function(i) { # only process new or modified vignettes origin <- fs::path_join(c(src_dir, src_files[i])) destination <- fs::path_join(c(tar_dir, src_files[i])) - fs::file_copy(origin, destination, overwrite = TRUE) - if (fs::path_ext(origin) == "md") { fs::file_copy(origin, tar_dir, overwrite = TRUE) @@ -73,7 +66,20 @@ # conversion_worked[i] <- .rmd2md(origin, tar_dir, path = path, verbose = verbose) # cli::cli_progress_update() } else { - conversion_worked[i] <- .qmd2md(origin, tar_dir, verbose = verbose) + worked <- .qmd2md(origin, tar_dir, verbose = verbose) + } + return(worked) + } + + if (isTRUE(parallel)) { + .assert_dependency("future.apply", install = TRUE) + conversion_worked <- future.apply::future_sapply(seq_along(src_files), render_one_vignette, future.seed = NULL) + } else { + i <- 0 + cli::cli_progress_step("Converting {cli::qty(n)}vignette{?s}: {i}/{n}", spinner = TRUE) + conversion_worked <- vector(length = n) + for (i in seq_along(src_files)) { + conversion_worked[i] <- render_one_vignette(i) cli::cli_progress_update() } } diff --git a/docs/man/render_docs.md b/docs/man/render_docs.md index f285d941..2f4684e0 100644 --- a/docs/man/render_docs.md +++ b/docs/man/render_docs.md @@ -11,7 +11,7 @@ modifies and overwrites the files in the ‘docs/’ folder. ## Usage -
render_docs(path = ".", verbose = FALSE)
+
render_docs(path = ".", verbose = FALSE, parallel = FALSE)
 
## Arguments @@ -33,6 +33,17 @@ Path to the package root directory. Logical. Print Rmarkdown or Quarto rendering output. + + +parallel + + +Logical. Render man pages and vignettes in parallel using the +future framework. In addition to setting this argument to +TRUE, users must define the parallelism plan in future. See +the examples section below. + + ## Value @@ -48,5 +59,10 @@ if (interactive()) { render_docs() + # parallel rendering + library(future) + plan(multicore) + render_docs(parallel = TRUE) + } ``` diff --git a/man/render_docs.Rd b/man/render_docs.Rd index 2ee5a6a1..d91331af 100644 --- a/man/render_docs.Rd +++ b/man/render_docs.Rd @@ -4,12 +4,14 @@ \alias{render_docs} \title{Update documentation} \usage{ -render_docs(path = ".", verbose = FALSE) +render_docs(path = ".", verbose = FALSE, parallel = FALSE) } \arguments{ \item{path}{Path to the package root directory.} \item{verbose}{Logical. Print Rmarkdown or Quarto rendering output.} + +\item{parallel}{Logical. Render man pages and vignettes in parallel using the \code{future} framework. In addition to setting this argument to TRUE, users must define the parallelism plan in \code{future}. See the examples section below.} } \value{ No value returned. Updates and overwrites the files in folder 'docs'. @@ -24,5 +26,10 @@ if (interactive()) { render_docs() + # parallel rendering + library(future) + plan(multicore) + render_docs(parallel = TRUE) + } }