diff --git a/R/render_docs.R b/R/render_docs.R index f9dd74e5..00156fc6 100644 --- a/R/render_docs.R +++ b/R/render_docs.R @@ -5,8 +5,14 @@ #' content of the 'docs/' folder. See details below. #' #' @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. -#' @param freeze Logical. If TRUE and a man page or vignette has not changed since the last call to `render_docs()`, that file is skipped. File hashes are stored in `altdoc/freeze.rds`. If that file is deleted, all man pages and vignettes will be rendered anew. +#' @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. +#' @param freeze Logical. If TRUE and a man page or vignette has not changed +#' since the last call to `render_docs()`, that file is skipped. File hashes +#' are stored in `altdoc/freeze.rds`. If that file is deleted, all man pages +#' and vignettes will be rendered anew. +#' @param output_path Destination path for the documentation. #' @param ... Additional arguments are ignored. #' @inheritParams setup_docs #' @export @@ -37,18 +43,21 @@ #' #' @examples #' if (interactive()) { -#' #' render_docs() #' #' # parallel rendering #' library(future) #' plan(multicore) #' render_docs(parallel = TRUE) -#' #' } -render_docs <- function(path = ".", verbose = FALSE, parallel = FALSE, freeze = FALSE, ...) { - - # Quarto sometimes raises errors encouraging users to set `quiet=FALSE` to get more information. +render_docs <- function( + path = ".", + verbose = FALSE, + parallel = FALSE, + freeze = FALSE, + output_path = ".", + ...) { + # Quarto sometimes raises errors encouraging users to set `quiet=FALSE` to get more information. # This is a convenience check to match Quarto's `quiet` and `altdoc`'s `verbose` arguments. dots <- list(...) if ("quiet" %in% names(dots) && is.logical(dots[["quiet"]]) && isTRUE(length(dots[["quiet"]]) == 1)) { @@ -56,6 +65,7 @@ render_docs <- function(path = ".", verbose = FALSE, parallel = FALSE, freeze = } path <- .convert_path(path) + output_path <- .convert_path(output_path) tool <- .doc_type(path) dir_altdoc <- fs::path_join(c(path, "altdoc")) @@ -73,18 +83,16 @@ render_docs <- function(path = ".", verbose = FALSE, parallel = FALSE, freeze = docs_files <- fs::dir_ls(docs_dir) if (freeze == TRUE) { docs_files <- Filter(function(f) basename(f) != "_freeze", docs_files) - } + } fs::file_delete(docs_files) } - } else { docs_dir <- fs::path_join(c(path, "docs")) } - # create `docs_dir/` fs::dir_create(docs_dir) - cli::cli_h1("Basic files") + cli::cli_h1("Basic files") basics <- c("NEWS", "CHANGELOG", "ChangeLog", "CODE_OF_CONDUCT", "LICENSE", "LICENCE") for (b in basics) { .import_basic(src_dir = path, tar_dir = docs_dir, name = b) @@ -92,16 +100,12 @@ render_docs <- function(path = ".", verbose = FALSE, parallel = FALSE, freeze = .import_readme(src_dir = path, tar_dir = docs_dir, tool = tool, freeze = freeze) .import_citation(src_dir = path, tar_dir = docs_dir) - - # Update functions reference cli::cli_h1("Man pages") fail_man <- .import_man(src_dir = path, tar_dir = docs_dir, tool = tool, verbose = verbose, parallel = parallel, freeze = freeze) - # Update vignettes cli::cli_h1("Vignettes") fail_vignettes <- .import_vignettes(src_dir = path, tar_dir = docs_dir, tool = tool, verbose = verbose, parallel = parallel, freeze = freeze) - # Error so that CI fails if (length(fail_vignettes) > 0 & length(fail_man) > 0) { cli::cli_abort("There were some failures when rendering vignettes and man pages.") @@ -114,7 +118,12 @@ render_docs <- function(path = ".", verbose = FALSE, parallel = FALSE, freeze = cli::cli_h1("Update HTML") .import_settings(path = path, tool = tool, verbose = verbose, freeze = freeze) + if (output_path != path) { + files_to_copy <- list.files(fs::path(path, "docs"), full.names = TRUE) + file.copy(files_to_copy, to = fs::path(output_path), recursive = TRUE) + fs::dir_delete(fs::path(path, "docs")) + } + cli::cli_h1("Complete") cli::cli_alert_success("Documentation updated.") } - diff --git a/R/settings.R b/R/settings.R index e4b25585..8332c7e6 100644 --- a/R/settings.R +++ b/R/settings.R @@ -1,61 +1,64 @@ .import_settings <- function(path = ".", tool = "docsify", verbose = FALSE, freeze = FALSE) { + # copy all files from altdoc/ into docs/ + # this allows users to store arbitrary and settings static files in altdoc/ + src <- fs::path_abs(fs::path_join(c(path, "altdoc"))) + if (fs::dir_exists(src)) { + files <- fs::dir_ls(src) - # copy all files from altdoc/ into docs/ - # this allows users to store arbitrary and settings static files in altdoc/ - src <- fs::path_abs(fs::path_join(c(path, "altdoc"))) - if (fs::dir_exists(src)) { - files <- fs::dir_ls(src) + files <- files[!grepl("freeze.rds$", files)] - files <- files[!grepl("freeze.rds$", files)] + # hidden files not detected + fn <- fs::path_join(c(path, "altdoc/.nojekyll")) + if (fs::file_exists(fn)) { + files <- c(files, fn) + } + + files <- files[!grepl("docute.html$|docsify.md$|mkdocs.yml$", files)] - # hidden files not detected - fn <- fs::path_join(c(path, "altdoc/.nojekyll")) - if (fs::file_exists(fn)) { - files <- c(files, fn) - } + # docs/* files are mutable and should be overwritten + if (grepl("^quarto", tool)) { + tar_dir <- fs::path_join(c(path, "_quarto")) + } else { + tar_dir <- .doc_path(path) + } - files <- files[!grepl("docute.html$|docsify.md$|mkdocs.yml$", files)] + fs::dir_copy(src, tar_dir, overwrite = TRUE) + } - # docs/* files are mutable and should be overwritten - if (grepl("^quarto", tool)) { - tar_dir <- fs::path_join(c(path, "_quarto")) - } else { - tar_dir <- .doc_path(path) - } + fn <- switch(tool, + docsify = "docsify.md", + docute = "docute.html", + mkdocs = "mkdocs.yml", + quarto_website = "quarto_website.yml" + ) + fn <- fs::path_join(c(path, "altdoc", fn)) + settings <- .readlines(fn) - fs::dir_copy(src, tar_dir, overwrite = TRUE) - } + settings <- .substitute_altdoc_variables(settings, path = path, tool = tool) + + vignettes <- switch(tool, + docsify = .sidebar_vignettes_docsify, + docute = .sidebar_vignettes_docute, + mkdocs = .sidebar_vignettes_mkdocs, + quarto_website = .sidebar_vignettes_quarto_website + ) + settings <- vignettes(sidebar = settings, path = path) + + man <- switch(tool, + docsify = .sidebar_man_docsify, + docute = .sidebar_man_docute, + mkdocs = .sidebar_man_mkdocs, + quarto_website = .sidebar_man_quarto_website + ) + settings <- man(settings, path) + + finalize <- switch(tool, + docsify = .finalize_docsify, + docute = .finalize_docute, + mkdocs = .finalize_mkdocs, + quarto_website = .finalize_quarto_website + ) + settings <- finalize(settings, path, verbose, freeze) - fn <- switch(tool, - docsify = "docsify.md", - docute = "docute.html", - mkdocs = "mkdocs.yml", - quarto_website = "quarto_website.yml") - fn <- fs::path_join(c(path, "altdoc", fn)) - settings <- .readlines(fn) - - settings <- .substitute_altdoc_variables(settings, path = path, tool = tool) - - vignettes <- switch(tool, - docsify = .sidebar_vignettes_docsify, - docute = .sidebar_vignettes_docute, - mkdocs = .sidebar_vignettes_mkdocs, - quarto_website = .sidebar_vignettes_quarto_website) - settings <- vignettes(sidebar = settings, path = path) - - man <- switch(tool, - docsify = .sidebar_man_docsify, - docute = .sidebar_man_docute, - mkdocs = .sidebar_man_mkdocs, - quarto_website = .sidebar_man_quarto_website) - settings <- man(settings, path) - - finalize <- switch(tool, - docsify = .finalize_docsify, - docute = .finalize_docute, - mkdocs = .finalize_mkdocs, - quarto_website = .finalize_quarto_website) - settings <- finalize(settings, path, verbose, freeze) - - cli::cli_alert_success("HTML updated.") + cli::cli_alert_success("HTML updated.") } diff --git a/R/settings_docsify.R b/R/settings_docsify.R index 1b67b0c0..e0fde8e8 100644 --- a/R/settings_docsify.R +++ b/R/settings_docsify.R @@ -1,88 +1,88 @@ .finalize_docsify <- function(settings, path, ...) { + tool <- .doc_type(path) - tool <- .doc_type(path) + # drop missing links + settings <- settings[!grepl("\\]\\(\\)", settings)] + settings <- stats::na.omit(settings) - # drop missing links - settings <- settings[!grepl("\\]\\(\\)", settings)] - settings <- stats::na.omit(settings) + fn_man <- fs::path_join(c(.doc_path(path), "reference.md")) + dn_man <- fs::path_join(c(.doc_path(path), "man")) - fn_man <- fs::path_join(c(.doc_path(path), "reference.md")) - dn_man <- fs::path_join(c(.doc_path(path), "man")) + fn <- fs::path_join(c(.doc_path(path), "_sidebar.md")) + writeLines(settings, fn) - fn <- fs::path_join(c(.doc_path(path), "_sidebar.md")) - writeLines(settings, fn) - - # relative links - dn <- fs::path_join(c(path, "docs", "vignettes")) - if (fs::dir_exists(dn)) { - md_files <- fs::dir_ls(dn, regexp = "\\.md$") - for (md in md_files) { - src <- sprintf('src="%s.markdown_strict_files', gsub("\\.md$|\\.pdf$", "", basename(md))) - tar <- sprintf('src="vignettes/%s.markdown_strict_files', gsub("\\.md$|\\.pdf$", "", basename(md))) - content <- gsub(src, tar, .readlines(md), fixed = TRUE) - writeLines(content, md) - } + # relative links + dn <- fs::path_join(c(path, "docs", "vignettes")) + if (fs::dir_exists(dn)) { + md_files <- fs::dir_ls(dn, regexp = "\\.md$") + for (md in md_files) { + src <- sprintf('src="%s.markdown_strict_files', gsub("\\.md$|\\.pdf$", "", basename(md))) + tar <- sprintf('src="vignettes/%s.markdown_strict_files', gsub("\\.md$|\\.pdf$", "", basename(md))) + content <- gsub(src, tar, .readlines(md), fixed = TRUE) + writeLines(content, md) } + } - # body also includes altdoc variables - fn <- fs::path_join(c(path, "altdoc", "docsify.html")) - body <- .readlines(fn) - body <- .substitute_altdoc_variables(body, path = path, tool = tool) - fn <- fs::path_join(c(.doc_path(path), "index.html")) - writeLines(body, fn) + # body also includes altdoc variables + fn <- fs::path_join(c(path, "altdoc", "docsify.html")) + body <- .readlines(fn) + body <- .substitute_altdoc_variables(body, path = path, tool = tool) + fn <- fs::path_join(c(.doc_path(path), "index.html")) + writeLines(body, fn) } .sidebar_vignettes_docsify <- function(sidebar, path) { - dn <- fs::path_join(c(.doc_path(path), "vignettes")) - fn_vignettes <- list.files(dn, pattern = "\\.md$|\\.pdf$", full.names = TRUE) - # before gsub on files - titles <- sapply(fn_vignettes, .get_vignettes_titles) - fn_vignettes <- sapply(fn_vignettes, function(x) { - fs::path_join(c("vignettes", basename(x))) - }) - if (length(fn_vignettes) > 0) { - idx <- grep("\\$ALTDOC_VIGNETTE_BLOCK", sidebar) - if (length(idx) == 1) { - sidebar <- gsub("\\$ALTDOC_VIGNETTE_BLOCK", "", sidebar) - indent <- gsub("^(\\w*).*", "\\1", sidebar[idx]) - tmp <- ifelse( - tools::file_ext(fn_vignettes) == "pdf", - sprintf("%s - [%s](%s ':ignore')", indent, titles, fn_vignettes), - sprintf("%s - [%s](%s)", indent, titles, fn_vignettes)) - sidebar <- c(sidebar[1:idx], tmp, sidebar[(idx + 1):length(sidebar)]) - } - } else { - sidebar <- sidebar[!grepl("\\$ALTDOC_VIGNETTE_BLOCK", sidebar)] + dn <- fs::path_join(c(.doc_path(path), "vignettes")) + fn_vignettes <- list.files(dn, pattern = "\\.md$|\\.pdf$", full.names = TRUE) + # before gsub on files + titles <- sapply(fn_vignettes, .get_vignettes_titles) + fn_vignettes <- sapply(fn_vignettes, function(x) { + fs::path_join(c("vignettes", basename(x))) + }) + if (length(fn_vignettes) > 0) { + idx <- grep("\\$ALTDOC_VIGNETTE_BLOCK", sidebar) + if (length(idx) == 1) { + sidebar <- gsub("\\$ALTDOC_VIGNETTE_BLOCK", "", sidebar) + indent <- gsub("^(\\w*).*", "\\1", sidebar[idx]) + tmp <- ifelse( + tools::file_ext(fn_vignettes) == "pdf", + sprintf("%s - [%s](%s ':ignore')", indent, titles, fn_vignettes), + sprintf("%s - [%s](%s)", indent, titles, fn_vignettes) + ) + sidebar <- c(sidebar[1:idx], tmp, sidebar[(idx + 1):length(sidebar)]) } - return(sidebar) + } else { + sidebar <- sidebar[!grepl("\\$ALTDOC_VIGNETTE_BLOCK", sidebar)] + } + return(sidebar) } .sidebar_man_docsify <- function(sidebar, path) { - dn <- fs::path_join(c(.doc_path(path), "man")) - if (fs::dir_exists(dn)) { - fn <- list.files(dn, pattern = "\\.md$", full.names = TRUE) - fn <- sapply(fn, function(x) fs::path_join(c("man", basename(x)))) - fn <- sapply(fn, fs::path_ext_remove) + dn <- fs::path_join(c(.doc_path(path), "man")) + if (fs::dir_exists(dn)) { + fn <- list.files(dn, pattern = "\\.md$", full.names = TRUE) + fn <- sapply(fn, function(x) fs::path_join(c("man", basename(x)))) + fn <- sapply(fn, fs::path_ext_remove) - if (length(fn) > 0) { - titles <- fs::path_ext_remove(basename(fn)) - idx <- grep("\\$ALTDOC_MAN_BLOCK", sidebar) - if (length(idx) == 1) { - sidebar <- gsub("\\$ALTDOC_MAN_BLOCK", "", sidebar) - indent <- gsub("^(\\w*).*", "\\1", sidebar[idx]) - tmp <- sprintf("%s - [%s](%s)", indent, titles, fn) - sidebar <- c(sidebar[1:idx], tmp, sidebar[(idx + 1):length(sidebar)]) - } else { - sidebar <- sidebar[!grepl("\\$ALTDOC_MAN_BLOCK", sidebar)] - } - } else { - sidebar <- sidebar[!grepl("\\$ALTDOC_MAN_BLOCK", sidebar)] - } - } else { + if (length(fn) > 0) { + titles <- fs::path_ext_remove(basename(fn)) + idx <- grep("\\$ALTDOC_MAN_BLOCK", sidebar) + if (length(idx) == 1) { + sidebar <- gsub("\\$ALTDOC_MAN_BLOCK", "", sidebar) + indent <- gsub("^(\\w*).*", "\\1", sidebar[idx]) + tmp <- sprintf("%s - [%s](%s)", indent, titles, fn) + sidebar <- c(sidebar[1:idx], tmp, sidebar[(idx + 1):length(sidebar)]) + } else { sidebar <- sidebar[!grepl("\\$ALTDOC_MAN_BLOCK", sidebar)] + } + } else { + sidebar <- sidebar[!grepl("\\$ALTDOC_MAN_BLOCK", sidebar)] } - return(sidebar) + } else { + sidebar <- sidebar[!grepl("\\$ALTDOC_MAN_BLOCK", sidebar)] + } + return(sidebar) } diff --git a/R/utils.R b/R/utils.R index 5e9543a1..e14829ff 100644 --- a/R/utils.R +++ b/R/utils.R @@ -1,7 +1,6 @@ # https://github.com/ropenscilabs/r2readthedocs/blob/main/R/utils.R .convert_path <- function(path = ".") { path <- fs::path_abs(path) - .check_is_package(path = path) path <- normalizePath(path) return(path) } @@ -89,10 +88,18 @@ ) } - if (mkdocs) return("mkdocs") - if (docsify) return("docsify") - if (docute) return("docute") - if (quarto_website) return("quarto_website") + if (mkdocs) { + return("mkdocs") + } + if (docsify) { + return("docsify") + } + if (docute) { + return("docute") + } + if (quarto_website) { + return("quarto_website") + } return(NULL) } @@ -141,7 +148,8 @@ "urls:", paste(" reference:", man), paste(" article:", vig), - "") + "" + ) cli::cli_alert_info("Adding altdoc/pkgdown.yml file.") writeLines(content, fn) } diff --git a/man/render_docs.Rd b/man/render_docs.Rd index ee164693..3f2ee29c 100644 --- a/man/render_docs.Rd +++ b/man/render_docs.Rd @@ -4,16 +4,30 @@ \alias{render_docs} \title{Update documentation} \usage{ -render_docs(path = ".", verbose = FALSE, parallel = FALSE, freeze = FALSE, ...) +render_docs( + path = ".", + verbose = FALSE, + parallel = FALSE, + freeze = FALSE, + output_path = ".", + ... +) } \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.} +\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.} -\item{freeze}{Logical. If TRUE and a man page or vignette has not changed since the last call to \code{render_docs()}, that file is skipped. File hashes are stored in \code{altdoc/freeze.rds}. If that file is deleted, all man pages and vignettes will be rendered anew.} +\item{freeze}{Logical. If TRUE and a man page or vignette has not changed +since the last call to \code{render_docs()}, that file is skipped. File hashes +are stored in \code{altdoc/freeze.rds}. If that file is deleted, all man pages +and vignettes will be rendered anew.} + +\item{output_path}{Destination path for the documentation.} \item{...}{Additional arguments are ignored.} } @@ -110,13 +124,11 @@ Importantly, \code{downlit} requires the \code{pkgdown.yml} file to be live on t \examples{ if (interactive()) { - render_docs() # parallel rendering library(future) plan(multicore) render_docs(parallel = TRUE) - } } diff --git a/tests/testthat/helper.R b/tests/testthat/helper.R index 1ccc05ae..9d262013 100644 --- a/tests/testthat/helper.R +++ b/tests/testthat/helper.R @@ -50,7 +50,7 @@ create_local_thing <- function( thing = c("package", "project")) { thing <- match.arg(thing) if (fs::dir_exists(dir)) { - ui_stop("Target {ui_code('dir')} {.file {dir}} already exists.") + ui_stop("Target {ui_code('dir')} '{dir}' already exists.") } old_project <- proj_get_() # this could be `NULL`, i.e. no active project diff --git a/tests/testthat/test-render_docs.R b/tests/testthat/test-render_docs.R index e818552b..9eefc483 100644 --- a/tests/testthat/test-render_docs.R +++ b/tests/testthat/test-render_docs.R @@ -164,6 +164,23 @@ test_that("quarto: autolink", { expect_true(any(grepl("https://rdrr.io/r/base/library.html", tmp, fixed = TRUE))) }) +test_that("arg 'output_path' works", { + parent_dir <- withr::local_tempfile() + create_local_package(dir = parent_dir) + fs::dir_create(fs::path("..", "foobar")) + fs::dir_create("man") + install.packages(".", repos = NULL, type = "source") + cat("\\name{hi}\n\\title{hi}\n\\usage{\nhi()\n}\n\\examples{\n1 + 1\n}\n", file = "man/foo.Rd") + + setup_docs("docute") + render_docs(output_path = "../foobar", verbose = .on_ci()) + + expect_false(dir_exists("docs")) + expect_true(dir_exists("../foobar")) + expect_true(file_exists("../foobar/docute.html")) + expect_true(file_exists("../foobar/man/foo.md")) +}) + # Test failures ------------------------------