diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bb4035eed..c865a8f53 100755 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,7 @@ default_stages: ["commit"] repos: - repo: https://github.com/lorenzwalthert/precommit - rev: acc8657498d5bfb9e9891098ba00b36e82c7ebd6 + rev: 1435aba4f2c91753517512d90fe9b4995db59874 hooks: - id: style-files args: [--style_pkg=styler, --style_fun=tidyverse_style] @@ -13,7 +13,34 @@ repos: renv/.* )$ - id: roxygenize - # codemeta must be above use-tidy-description when both are used + additional_dependencies: + - desc@1.3.0 + - digest@0.6.27 + - docopt@0.7.1 + - fs@1.5.0 + - git2r@0.28.0 + - glue@1.4.2 + - here@1.0.1 + - knitr@1.33 + - lintr@2.0.1 + - magrittr@2.0.1 + - mockery@0.4.2 + - pkgload@1.2.1 + - purrr@0.3.4 + - R.cache@0.15.0 + - reticulate@1.20 + - rlang@0.4.11 + - r-lib/pkgapi@35226e9 + - rmarkdown@2.8 + - roxygen2@7.1.1 + - rprojroot@2.0.2 + - rstudioapi@0.13 + - spelling@2.2 + - styler@1.4.1 + - testthat@3.0.2 + - usethis@2.0.1 + - withr@2.4.2 + - yaml@2.2.1 # codemeta must be above use-tidy-description when both are used # - id: codemeta-description-updated - id: use-tidy-description - id: spell-check diff --git a/API b/API index b43386039..be1c95186 100644 --- a/API +++ b/API @@ -4,12 +4,16 @@ autoupdate(root = here::here()) diff_requires_run_roxygenize(root = here::here()) +dirs_R.cache(hook_id) install_precommit(force = FALSE) may_require_permanent_cache(temp_cache_is_enough = FALSE) open_config(root = here::here()) open_wordlist(root = here::here()) path_pre_commit_exec(check_if_exists = TRUE) path_precommit_exec(check_if_exists = TRUE) +roxygen_assert_additional_dependencies() +roxygenize_with_cache(key, dirs) +snippet_generate(snippet, root = here::here()) uninstall_precommit(scope = "repo", ask = "user", root = here::here()) update_precommit() use_precommit(config_source = getOption("precommit.config_source"), force = FALSE, legacy_hooks = "forbid", open = rstudioapi::isAvailable(), install_hooks = TRUE, root = here::here()) diff --git a/DESCRIPTION b/DESCRIPTION index 3b43f61db..9876f9c56 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: precommit Title: Pre-Commit Hooks -Version: 0.1.3.9000 +Version: 0.1.3.9002 Author: Lorenz Walthert Maintainer: Lorenz Walthert Description: Useful git hooks for R building on top of the multi-language @@ -26,20 +26,21 @@ Imports: yaml Suggests: desc, + digest, git2r, glue, knitr, lintr, + mockery, + pkgload, reticulate (>= 1.16), rmarkdown, roxygen2, spelling, - styler (>= 1.3.2.9000), + styler, testthat (>= 2.1.0) VignetteBuilder: knitr -Remotes: - r-lib/styler Encoding: UTF-8 Roxygen: list(markdown = TRUE, roclets = c( "rd", "namespace", "collate", if (rlang::is_installed("pkgapi")) "pkgapi::api_roclet" else { diff --git a/NAMESPACE b/NAMESPACE index 311d27914..b98fb7593 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -2,15 +2,20 @@ export(autoupdate) export(diff_requires_run_roxygenize) +export(dirs_R.cache) export(install_precommit) export(may_require_permanent_cache) export(open_config) export(open_wordlist) export(path_pre_commit_exec) export(path_precommit_exec) +export(roxygen_assert_additional_dependencies) +export(roxygenize_with_cache) +export(snippet_generate) export(uninstall_precommit) export(update_precommit) export(use_precommit) export(use_precommit_config) export(version_precommit) +importFrom(R.cache,saveCache) importFrom(magrittr,"%>%") diff --git a/NEWS.md b/NEWS.md index 843036831..8fc419502 100644 --- a/NEWS.md +++ b/NEWS.md @@ -18,7 +18,21 @@ `language: script` from the [pre-commit framework](https://pre-commit.com). This requires `pre-commit >= 2.11.1`. All hooks and dependencies are now contained in a virtual environment with [`{renv}`](https://rstudio.github.io/renv/) - (#233). + (#233). Thanks to {renv}'s excellent + [caching](https://rstudio.github.io/renv/articles/renv.html#cache-1), this + does not consume much space and is fast. This makes output + of hooks more consistent across different local setups, make manual dependency + management redundant and will facilitate running R hooks as part of CI/CD, + e.g. https://pre-commit.ci or [GitHub Actions](https://github.com/pre-commit/action) + along with arbitrary other hooks. + +- Because hooks run in a virtual environment and the `roxygenize` hook runs + `pkgload::load_all()`, you need to list all dependencies of your package in + `additional_dependencies` field in `.pre-commit-config.yaml`. You will be + prompted to add them if they are missing, + `precommit::snippet_generate("additional-deps-roxygenize")` generates + the code you can copy/paste (). + **Minor changes** diff --git a/R/aaa.R b/R/aaa.R new file mode 100644 index 000000000..330837885 --- /dev/null +++ b/R/aaa.R @@ -0,0 +1 @@ +hooks_repo <- "https://github.com/lorenzwalthert/precommit" diff --git a/R/assert.R b/R/assert.R index de0d41786..fc53003cf 100644 --- a/R/assert.R +++ b/R/assert.R @@ -35,7 +35,7 @@ assert_correct_upstream_repo_url <- function() { if (upstream_repo_url_is_outdated()) { usethis::ui_info(c( "The repo https://github.com/lorenzwalthert/pre-commit-hooks ", - "has moved to https://github.com/lorenzwalthert/precommit. ", + "has moved to ", hooks_repo, ". ", "Please fix the URL in .pre-commit-config.yaml, ", "most confortably with `precommit::open_config()`." )) diff --git a/R/config.R b/R/config.R index f6a771123..eabedd4aa 100644 --- a/R/config.R +++ b/R/config.R @@ -15,8 +15,8 @@ #' pre-commit with [use_precommit()] using the argument `config_source` to #' copy an existing config file into your repo. This argument defaults to the R #' option `precommit.config_source`, so you may want to set this option in -#' your `.Rprofile` for convenience. Note that this is **not** equivalent -#' to the `--config` option in the CLI command `pre-commit install` and similar, +#' your `.Rprofile` for convenience. Note that this is **not** equivalent to the +#' `--config` option in the CLI command `pre-commit install` and similar, #' which do *not* copy a config file into a project root (and allow to put it #' under version control), but rather link it in some more or less transparent #' way. @@ -63,7 +63,7 @@ use_precommit_config <- function(config_source = getOption("precommit.config_sou "All available hooks: ", "https://pre-commit.com/hooks.html", "R specific hooks:", - "https://github.com/lorenzwalthert/precommit." + hooks_repo, "." )) config_source } diff --git a/R/release.R b/R/release.R index 62d027be9..3364aeb11 100644 --- a/R/release.R +++ b/R/release.R @@ -9,8 +9,8 @@ #' - update default config in inst/ #' - commit #' - git tag -#' - run `inst/consistent-release-tag` hook with --release-mode (passing args to hooks -#' not possible interactively, hence we run in advance). +#' - run `inst/consistent-release-tag` hook with --release-mode (passing args to +#' hooks not possible interactively, hence we run in advance). #' - commit and push with skipping `inst/consistent-release-tag`. #' - autoupdate own config file #' - bump description with dev @@ -135,7 +135,7 @@ release_prechecks <- function(bump, is_cran) { update_rev_in_config <- function(new_version, path = "inst/pre-commit-config.yaml") { config <- readLines(path) - ours <- grep("- repo: https://github.com/lorenzwalthert/precommit", config, fixed = TRUE) + ours <- grep(paste0("- repo: ", hooks_repo), config, fixed = TRUE) others <- setdiff(grep("- repo:", config, fixed = TRUE), ours) next_after_ours <- others[others > ours][1] rev <- grep("rev:", config) diff --git a/R/roxygen2.R b/R/roxygen2.R index 76c17dd0a..7db12da02 100644 --- a/R/roxygen2.R +++ b/R/roxygen2.R @@ -20,8 +20,8 @@ extract_diff_files <- function(files) { #' Extract old and new lines from `git diff --cached` #' -#' This is useful to detect within a hook script if the core function -#' from a hook such as [roxygen2::roxygenize()] must run at all or not. +#' This is useful to detect within a hook script if the core function from a +#' hook such as [roxygen2::roxygenize()] must run at all or not. #' @param root The root of project. #' @keywords internal extract_diff_root <- function(root = here::here()) { @@ -73,3 +73,80 @@ diff_requires_run_roxygenize <- function(root = here::here()) { any(grep("function(", without_comments, fixed = TRUE)) } } + +#' Assert if all dependencies are installed +#' +#' This function is only exported for use in hook scripts, but it's not intended +#' to be called by the end-user directly. +#' @family hook script helpers +#' @export +roxygen_assert_additional_dependencies <- function() { + out <- rlang::with_handlers( + # roxygen2 will load: https://github.com/r-lib/roxygen2/issues/771 + pkgload::load_all(quiet = TRUE), + error = function(e) { + e + } + ) + if (inherits(out, "packageNotFoundError")) { + # case used in package but not installed + rlang::abort(paste0( + "The roxygenize hook requires all* dependencies of your package to be listed in ", + "the file `.pre-commit-config.yaml` under `id: roxygenize` -> ", + "`additional_dependencies:`, like this:\n\n", + " - id: roxygenize", + " + additional_dependencies: + - tidyr + - dplyr\n\n", + "Call ", + "`precommit::snippet_generate('additional-deps-roxygenize')`", + "and paste the ", + "output into the file `.pre-commit-config.yaml`. This requires precommit", + " > 0.1.3 and assumes you declared all dependencies in `DESCRIPTION`.", + "\n\nContext: https://github.com/lorenzwalthert/precommit/issues/243", + "\n\nThe initial error (from `pkgload::load_all()`) was: ", + conditionMessage(out), ".\n\n===================================\n", + "*Some packages are already installed in the renv to run the hook, so ", + "these technically don't have to be listed as additional dependencies, ", + "but we recommend listing all for simplicity and consistency." + )) + } +} + +#' Roxygen depending on cache state +#' +#' This function is only exported for use in hook scripts, but it's not intended +#' to be called by the end-user directly. +#' @inheritParams R.cache::saveCache +#' @family hook script helpers +#' @export +#' @importFrom R.cache saveCache +# fails if accessed with R.cache::saveCache()! +roxygenize_with_cache <- function(key, dirs) { + if (diff_requires_run_roxygenize()) { + out <- rlang::with_handlers( + roxygen2::roxygenise(), + error = function(e) e + ) + if (inherits(out, "packageNotFoundError")) { + rlang::abort(paste0( + conditionMessage(out), + ". Please add the package as a dependency to ", + "`.pre-commit-config.yaml` -> `id: roxygenize` -> ", + "`additional_dependencies` and try again. The package must be ", + "specified so `renv::install()` understands it, e.g. like this:\n\n", + " - id: roxygenize", + " + additional_dependencies: + - r-lib/pkgapi + - dplyr@1.0.0\n\n" + )) + } else if (inherits(out, "error")) { + rlang::abort(conditionMessage(out)) + } else if (inherits(out, "warning")) { + rlang::warn(conditionMessage(out)) + } + saveCache(object = Sys.time(), key = key, dirs = dirs) + } +} diff --git a/R/setup.R b/R/setup.R index 6dcb10fba..91df8ae7d 100644 --- a/R/setup.R +++ b/R/setup.R @@ -91,3 +91,56 @@ upstream_repo_url_is_outdated <- function() { grepl("https://github.com/lorenzwalthert/pre-commit-hooks", ., fixed = TRUE) %>% any() } + +#' Generate code snippets +#' +#' Utility function to generate code snippets: +#' +#' @details +#' Currently supported: +#' +#' * additional-deps-roxygenize: Code to paste into +#' `.pre-commit-config.yaml` for the additional dependencies required by +#' roxygen2. +#' @param snippet Name of the snippet. +#' @inheritParams fallback_doc +#' @export +snippet_generate <- function(snippet = "", root = here::here()) { + rlang::arg_match(snippet, c("additional-deps-roxygenize")) + if (snippet == "additional-deps-roxygenize") { + rlang::inform( + "Generating snippet using installed versions of all dependencies.\n" + ) + deps <- desc::desc_get_deps() + deps <- deps[order(deps$package), ] + paste0( + " - ", deps$package, "@", + purrr::map_chr(deps$package, ~ as.character(packageVersion(.x))), "\n", + collapse = "" + ) %>% + sort() %>% + cat(sep = "") + remote_deps <- rlang::with_handlers( + desc::desc_get_field("Remotes"), + error = function(e) character() + ) + if (length(remote_deps) > 0) { + rlang::warn(paste0( + "It seems you have remote dependencies in your `DESCRIPTION`. You ", + "need to edit the above list manually to match the syntax `renv::install()` ", + "understands, i.e. if you have in your `DESCRIPTION`", " + +Imports: + tidyr +Remotes: + tidyverse/tidyr@2fd80d5 + +You need in your `.pre-commit-config.yaml` + + additional_dependencies: + - tidyverse/tidyr@2fd80d5 + " + )) + } + } +} diff --git a/R/testing.R b/R/testing.R index 351213ceb..a8add87b4 100644 --- a/R/testing.R +++ b/R/testing.R @@ -179,17 +179,50 @@ not_conda <- function() { #' @param git Whether or not to init git in the local directory. #' @param use_precommmit Whether or not to [use_precommit()]. #' @keywords internal -local_test_setup <- function(.local_envir = parent.frame(), - git = TRUE, +local_test_setup <- function(git = TRUE, use_precommit = FALSE, - ...) { + package = FALSE, + quiet = TRUE, + ..., + .local_envir = parent.frame()) { dir <- withr::local_tempdir(.local_envir = .local_envir) + withr::local_dir(dir, .local_envir = .local_envir) + if (quiet) { + withr::local_options("usethis.quiet" = TRUE, .local_envir = .local_envir) + } if (git) { - git2r::init(path = dir) + git2r::init() withr::defer(fs::dir_delete(fs::path(dir, ".git")), envir = .local_envir) } if (use_precommit) { - suppressMessages(use_precommit(...)) + suppressMessages(use_precommit(..., root = dir)) + } + if (package) { + usethis::create_package(dir) + withr::local_dir(dir) + usethis::proj_set(dir) + usethis::use_testthat() } + dir } + +#' Generate a random package name that is not installed +#' @param n The number of times we should try +#' @keywords internal +generate_uninstalled_pkg_name <- function(n = 10) { + additional_pkg <- paste0("package", digest::digest(Sys.time())) + if (rlang::is_installed(additional_pkg)) { + if (n > 0) { + generate_uninstalled_pkg_name(n - 1) + } else { + rlang::abort("could not find a package name that was not yet installed") + } + } else { + additional_pkg + } +} + +generate_uninstalled_pkg_call <- function(n = 10) { + paste0(generate_uninstalled_pkg_name(n), "::x") +} diff --git a/R/utils.R b/R/utils.R index 0d3884970..3f57cae33 100644 --- a/R/utils.R +++ b/R/utils.R @@ -37,3 +37,16 @@ is_package <- function(root = here::here()) { add_trailing_linebreak <- function(x) { paste0(x, "\n") } + + +#' Create the path to the precommit R.cache cache +#' +#' This function is only exported for use in hook scripts, but it's not intended +#' to be called by the end-user directly. +#' @param hook_id The id of the hook for which we want the relative cache +#' directory. +#' @family hook script helpers +#' @export +dirs_R.cache <- function(hook_id) { + file.path("precommit", hook_id) +} diff --git a/_pkgdown.yml b/_pkgdown.yml index 5f66c26d8..4be9d1587 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -13,6 +13,7 @@ reference: - open_config - open_wordlist - autoupdate + - snippet_generate - title: "Manage the pre-commit executable" - contents: - uninstall_precommit diff --git a/inst/WORDLIST b/inst/WORDLIST index 31c752e62..13624d0ec 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -40,6 +40,7 @@ gh github gitignore gsub +Homebrew href http https @@ -77,6 +78,7 @@ params parsable pkgapi pkgdown +pkgload pre precommit precommithooks @@ -90,6 +92,7 @@ rds Rds readLines readme +recognised renv Renviron repo diff --git a/inst/bin/roxygenize b/inst/bin/roxygenize index ae114b7a9..c88515227 100755 --- a/inst/bin/roxygenize +++ b/inst/bin/roxygenize @@ -2,15 +2,15 @@ "Run roxygen2::roxygenize() -Obviously, this hook is only activated when any staged file passes the filter +Obviously, this hook is only activated when any staged file passes the filter specified in .pre-commit-hooks.yaml. Then, we check the time stamp of the last time we ran the hook. If any of our R files is younger than that, we consider running the hook. We next use {git2r} to inspect the cached diff of all .R files -in the R/ directory, and not the files passed to this hook. If we find any -roxygen2 comment in the diff, we run `roxygen2::roxygenize(). The preliminary -use case for this is when we previously attempted to commit but check failed, so -on the second try without any other files changed, it will succeed. -This check should run *after* check that modify the files that are passed to +in the R/ directory, and not the files passed to this hook. If we find any +roxygen2 comment in the diff, we run `roxygen2::roxygenize(). The preliminary +use case for this is when we previously attempted to commit but check failed, so +on the second try without any other files changed, it will succeed. +This check should run *after* check that modify the files that are passed to them (like styler) because they will never modify their input .R files. Usage: @@ -21,27 +21,19 @@ Options: " -> doc arguments <- docopt::docopt(doc) - -if (packageVersion("precommit") < "0.0.0.9039") { +if (packageVersion("precommit") < "0.1.3.9001") { rlang::abort(paste( - "This hooks only works with the R package {precommit} >= 0.0.0.9039", + "This hooks only works with the R package {precommit} >= 0.1.3.9001", 'Please upgrade with `remotes::install_github("lorenzwalthert/precommit")`.' )) } else { precommit::may_require_permanent_cache(arguments$no_warn_cache) + precommit::roxygen_assert_additional_dependencies() } -roxygenize_with_cache <- function(key, dirs) { - if (precommit::diff_requires_run_roxygenize()) { - roxygen2::roxygenise() - saveCache(object = Sys.time(), key = key, dirs = dirs) - } -} - -library("R.cache") -path_relative_cache <- file.path("precommit", "roxygenize") +path_relative_cache <- precommit::dirs_R.cache('roxygenize') wd <- list(getwd()) -cache <- loadCache(key = wd, dirs = path_relative_cache) +cache <- R.cache::loadCache(key = wd, dirs = path_relative_cache) if (!is.null(cache)) { candidates <- intersect( @@ -51,8 +43,8 @@ if (!is.null(cache)) { all_files <- file.info(candidates) last_modified <- max(all_files$mtime) if (last_modified > cache[[1]]) { - roxygenize_with_cache(key = wd, dirs = path_relative_cache) + precommit::roxygenize_with_cache(key = wd, dirs = path_relative_cache) } } else { - roxygenize_with_cache(key = wd, dirs = path_relative_cache) + precommit::roxygenize_with_cache(key = wd, dirs = path_relative_cache) } diff --git a/inst/consistent-release-tag b/inst/consistent-release-tag index d1dde6ed7..f27f2c786 100755 --- a/inst/consistent-release-tag +++ b/inst/consistent-release-tag @@ -19,8 +19,7 @@ arguments <- docopt::docopt(doc) path_config <- c( fs::path("inst", "pre-commit-config-pkg.yaml"), - fs::path("inst", "pre-commit-config-proj.yaml"), - if (!arguments$release_mode) fs::path(".pre-commit-config.yaml") + fs::path("inst", "pre-commit-config-proj.yaml") ) diff --git a/man/diff_requires_run_roxygenize.Rd b/man/diff_requires_run_roxygenize.Rd index fe7cdc7a3..700eb62ba 100644 --- a/man/diff_requires_run_roxygenize.Rd +++ b/man/diff_requires_run_roxygenize.Rd @@ -25,6 +25,9 @@ diff_requires_run_roxygenize() } \seealso{ Other hook script helpers: -\code{\link{may_require_permanent_cache}()} +\code{\link{dirs_R.cache}()}, +\code{\link{may_require_permanent_cache}()}, +\code{\link{roxygen_assert_additional_dependencies}()}, +\code{\link{roxygenize_with_cache}()} } \concept{hook script helpers} diff --git a/man/dirs_R.cache.Rd b/man/dirs_R.cache.Rd new file mode 100644 index 000000000..106e8d7c7 --- /dev/null +++ b/man/dirs_R.cache.Rd @@ -0,0 +1,24 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils.R +\name{dirs_R.cache} +\alias{dirs_R.cache} +\title{Create the path to the precommit R.cache cache} +\usage{ +dirs_R.cache(hook_id) +} +\arguments{ +\item{hook_id}{The id of the hook for which we want the relative cache +directory.} +} +\description{ +This function is only exported for use in hook scripts, but it's not intended +to be called by the end-user directly. +} +\seealso{ +Other hook script helpers: +\code{\link{diff_requires_run_roxygenize}()}, +\code{\link{may_require_permanent_cache}()}, +\code{\link{roxygen_assert_additional_dependencies}()}, +\code{\link{roxygenize_with_cache}()} +} +\concept{hook script helpers} diff --git a/man/extract_diff_root.Rd b/man/extract_diff_root.Rd index 1a825e428..c9138fe81 100644 --- a/man/extract_diff_root.Rd +++ b/man/extract_diff_root.Rd @@ -10,7 +10,7 @@ extract_diff_root(root = here::here()) \item{root}{The root of project.} } \description{ -This is useful to detect within a hook script if the core function -from a hook such as \code{\link[roxygen2:roxygenize]{roxygen2::roxygenize()}} must run at all or not. +This is useful to detect within a hook script if the core function from a +hook such as \code{\link[roxygen2:roxygenize]{roxygen2::roxygenize()}} must run at all or not. } \keyword{internal} diff --git a/man/generate_uninstalled_pkg_name.Rd b/man/generate_uninstalled_pkg_name.Rd new file mode 100644 index 000000000..da82e4dc1 --- /dev/null +++ b/man/generate_uninstalled_pkg_name.Rd @@ -0,0 +1,15 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/testing.R +\name{generate_uninstalled_pkg_name} +\alias{generate_uninstalled_pkg_name} +\title{Generate a random package name that is not installed} +\usage{ +generate_uninstalled_pkg_name(n = 10) +} +\arguments{ +\item{n}{The number of times we should try} +} +\description{ +Generate a random package name that is not installed +} +\keyword{internal} diff --git a/man/local_test_setup.Rd b/man/local_test_setup.Rd index 12861a270..ecbe198f8 100644 --- a/man/local_test_setup.Rd +++ b/man/local_test_setup.Rd @@ -5,17 +5,19 @@ \title{Testing utilities} \usage{ local_test_setup( - .local_envir = parent.frame(), git = TRUE, use_precommit = FALSE, - ... + package = FALSE, + quiet = TRUE, + ..., + .local_envir = parent.frame() ) } \arguments{ -\item{.local_envir}{\verb{[environment]}\cr The environment to use for scoping.} - \item{git}{Whether or not to init git in the local directory.} +\item{.local_envir}{\verb{[environment]}\cr The environment to use for scoping.} + \item{use_precommmit}{Whether or not to \code{\link[=use_precommit]{use_precommit()}}.} } \description{ diff --git a/man/may_require_permanent_cache.Rd b/man/may_require_permanent_cache.Rd index 449f22c40..189bb7995 100644 --- a/man/may_require_permanent_cache.Rd +++ b/man/may_require_permanent_cache.Rd @@ -17,6 +17,9 @@ to be called by the end-user directly. } \seealso{ Other hook script helpers: -\code{\link{diff_requires_run_roxygenize}()} +\code{\link{diff_requires_run_roxygenize}()}, +\code{\link{dirs_R.cache}()}, +\code{\link{roxygen_assert_additional_dependencies}()}, +\code{\link{roxygenize_with_cache}()} } \concept{hook script helpers} diff --git a/man/release_gh.Rd b/man/release_gh.Rd index 570055800..0eeadd814 100644 --- a/man/release_gh.Rd +++ b/man/release_gh.Rd @@ -21,8 +21,8 @@ This function does the following: \item update default config in inst/ \item commit \item git tag -\item run \code{inst/consistent-release-tag} hook with --release-mode (passing args to hooks -not possible interactively, hence we run in advance). +\item run \code{inst/consistent-release-tag} hook with --release-mode (passing args to +hooks not possible interactively, hence we run in advance). \item commit and push with skipping \code{inst/consistent-release-tag}. \item autoupdate own config file \item bump description with dev diff --git a/man/roxygen_assert_additional_dependencies.Rd b/man/roxygen_assert_additional_dependencies.Rd new file mode 100644 index 000000000..53b4b140b --- /dev/null +++ b/man/roxygen_assert_additional_dependencies.Rd @@ -0,0 +1,20 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/roxygen2.R +\name{roxygen_assert_additional_dependencies} +\alias{roxygen_assert_additional_dependencies} +\title{Assert if all dependencies are installed} +\usage{ +roxygen_assert_additional_dependencies() +} +\description{ +This function is only exported for use in hook scripts, but it's not intended +to be called by the end-user directly. +} +\seealso{ +Other hook script helpers: +\code{\link{diff_requires_run_roxygenize}()}, +\code{\link{dirs_R.cache}()}, +\code{\link{may_require_permanent_cache}()}, +\code{\link{roxygenize_with_cache}()} +} +\concept{hook script helpers} diff --git a/man/roxygenize_with_cache.Rd b/man/roxygenize_with_cache.Rd new file mode 100644 index 000000000..85272ccfd --- /dev/null +++ b/man/roxygenize_with_cache.Rd @@ -0,0 +1,29 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/roxygen2.R +\name{roxygenize_with_cache} +\alias{roxygenize_with_cache} +\title{Roxygen depending on cache state} +\usage{ +roxygenize_with_cache(key, dirs) +} +\arguments{ +\item{key}{An optional object from which a hexadecimal hash + code will be generated and appended to the filename.} + +\item{dirs}{A \code{\link[base]{character}} \code{\link[base]{vector}} constituting the path to the + cache subdirectory (of the \emph{cache root directory} + as returned by \code{\link[R.cache]{getCacheRootPath}}()) to be used. + If \code{\link[base]{NULL}}, the path will be the cache root path.} +} +\description{ +This function is only exported for use in hook scripts, but it's not intended +to be called by the end-user directly. +} +\seealso{ +Other hook script helpers: +\code{\link{diff_requires_run_roxygenize}()}, +\code{\link{dirs_R.cache}()}, +\code{\link{may_require_permanent_cache}()}, +\code{\link{roxygen_assert_additional_dependencies}()} +} +\concept{hook script helpers} diff --git a/man/snippet_generate.Rd b/man/snippet_generate.Rd new file mode 100644 index 000000000..36950f7ca --- /dev/null +++ b/man/snippet_generate.Rd @@ -0,0 +1,24 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/setup.R +\name{snippet_generate} +\alias{snippet_generate} +\title{Generate code snippets} +\usage{ +snippet_generate(snippet = "", root = here::here()) +} +\arguments{ +\item{snippet}{Name of the snippet.} + +\item{root}{The path to the root directory of your project.} +} +\description{ +Utility function to generate code snippets: +} +\details{ +Currently supported: +\itemize{ +\item additional-deps-roxygenize: Code to paste into +\code{.pre-commit-config.yaml} for the additional dependencies required by +roxygen2. +} +} diff --git a/man/use_precommit.Rd b/man/use_precommit.Rd index 666137da4..a7cdac1b0 100644 --- a/man/use_precommit.Rd +++ b/man/use_precommit.Rd @@ -72,8 +72,8 @@ You can use an existing \code{.pre-commit-config.yaml} file when initializing pre-commit with \code{\link[=use_precommit]{use_precommit()}} using the argument \code{config_source} to copy an existing config file into your repo. This argument defaults to the R option \code{precommit.config_source}, so you may want to set this option in -your \code{.Rprofile} for convenience. Note that this is \strong{not} equivalent -to the \code{--config} option in the CLI command \verb{pre-commit install} and similar, +your \code{.Rprofile} for convenience. Note that this is \strong{not} equivalent to the +\code{--config} option in the CLI command \verb{pre-commit install} and similar, which do \emph{not} copy a config file into a project root (and allow to put it under version control), but rather link it in some more or less transparent way. diff --git a/man/use_precommit_config.Rd b/man/use_precommit_config.Rd index 6baf6cb2e..003c26d91 100644 --- a/man/use_precommit_config.Rd +++ b/man/use_precommit_config.Rd @@ -41,8 +41,8 @@ You can use an existing \code{.pre-commit-config.yaml} file when initializing pre-commit with \code{\link[=use_precommit]{use_precommit()}} using the argument \code{config_source} to copy an existing config file into your repo. This argument defaults to the R option \code{precommit.config_source}, so you may want to set this option in -your \code{.Rprofile} for convenience. Note that this is \strong{not} equivalent -to the \code{--config} option in the CLI command \verb{pre-commit install} and similar, +your \code{.Rprofile} for convenience. Note that this is \strong{not} equivalent to the +\code{--config} option in the CLI command \verb{pre-commit install} and similar, which do \emph{not} copy a config file into a project root (and allow to put it under version control), but rather link it in some more or less transparent way. diff --git a/renv.lock b/renv.lock index 2fb36fdb8..86dd75577 100644 --- a/renv.lock +++ b/renv.lock @@ -18,10 +18,10 @@ }, "R.cache": { "Package": "R.cache", - "Version": "0.14.0", + "Version": "0.15.0", "Source": "Repository", "Repository": "CRAN", - "Hash": "1ca02d43e1a4d49e616bd23bb39b17e6" + "Hash": "e92a8ea8388c47c82ed8aa435ed3be50" }, "R.methodsS3": { "Package": "R.methodsS3", diff --git a/tests/testthat/test-conda.R b/tests/testthat/test-conda.R index 9815ea232..364c5c522 100644 --- a/tests/testthat/test-conda.R +++ b/tests/testthat/test-conda.R @@ -6,7 +6,7 @@ if (!on_cran()) { }) test_that("can use pre-commit", { - tempdir <- local_test_setup() + tempdir <- local_test_setup(quiet = FALSE) expect_message( use_precommit(open = FALSE, force = TRUE, root = tempdir), "to get the latest" @@ -18,7 +18,7 @@ if (!on_cran()) { }) test_that("fails early if repo is not a git repo ", { - tempdir <- local_test_setup(git = FALSE) + tempdir <- local_test_setup(git = FALSE, quiet = FALSE) expect_error( use_precommit(open = FALSE, root = tempdir), @@ -45,7 +45,7 @@ if (!on_cran()) { }) test_that("existing hooks are recognized", { - tempdir <- local_test_setup() + tempdir <- local_test_setup(quiet = FALSE) withr::with_dir(tempdir, { git2r::init() usethis::proj_set(".") @@ -80,7 +80,7 @@ if (!on_cran()) { test_that("Can uninstall pre-commit (repo scope)", { # with all files there - tempdir <- local_test_setup(use_precommit = TRUE) + tempdir <- local_test_setup(use_precommit = TRUE, quiet = FALSE) expect_message( uninstall_precommit(scope = "repo", root = tempdir), "Uninstalled pre-commit from repo scope.*" @@ -104,13 +104,13 @@ if (!on_cran()) { test_that("Can uninstall (userly)", { if (not_conda()) { - tempdir <- local_test_setup(use_precommit = TRUE) + tempdir <- local_test_setup(use_precommit = TRUE, quiet = FALSE) expect_error( uninstall_precommit(scope = "user", ask = "none", root = tempdir), "installed with conda" ) } else { - tempdir <- local_test_setup(use_precommit = FALSE) + tempdir <- local_test_setup(use_precommit = FALSE, quiet = FALSE) expect_message( uninstall_precommit(scope = "user", ask = "none", root = tempdir), "Removed pre-commit from" @@ -131,7 +131,7 @@ if (!on_cran()) { if (!not_conda()) { expect_error(install_precommit(), NA) } - tempdir <- local_test_setup() + tempdir <- local_test_setup(quiet = FALSE) expect_message( use_precommit( example_remote_config(), @@ -145,7 +145,7 @@ if (!on_cran()) { if (!not_conda()) { expect_message(install_precommit(), "already installed") } - tempdir <- local_test_setup(use_precommit = FALSE) + tempdir <- local_test_setup(use_precommit = FALSE, quiet = FALSE) withr::with_dir( tempdir, { diff --git a/tests/testthat/test-hook-roxygenize.R b/tests/testthat/test-hook-roxygenize.R index a75167f06..e4904b46e 100644 --- a/tests/testthat/test-hook-roxygenize.R +++ b/tests/testthat/test-hook-roxygenize.R @@ -74,3 +74,68 @@ test_that("change in formals alone triggers invalidation", { git2r::commit(".", "clear case 5") }) }) + + +test_that("asserting installed dependencies", { + local_test_setup(git = FALSE, use_precommit = FALSE, package = TRUE) + installed <- c("pkgload", "rlang", "testthat") + purrr::walk(installed, usethis::use_package) + writeLines(c("utils::adist", "rlang::is_installed"), "R/blur.R") + testthat::expect_silent(roxygen_assert_additional_dependencies()) + writeLines(generate_uninstalled_pkg_call(), "R/core.R") + testthat::expect_error( + roxygen_assert_additional_dependencies(), + "requires all\\* dependencies of your package" + ) +}) + +test_that("roxygenize works in general", { + local_test_setup(git = FALSE, use_precommit = FALSE, package = TRUE) + writeLines(c("#' This is a title", "#'", "#' More", "#' @name test", "NULL"), "R/blur.R") + # works + mockery::stub(roxygenize_with_cache, "diff_requires_run_roxygenize", TRUE) + expect_output( + roxygenize_with_cache(list(getwd()), dirs = dirs_R.cache("roxygenize")), + "test.R" + ) +}) + + +test_that("fails when package is called but not installed", { + local_test_setup(git = FALSE, use_precommit = FALSE, package = TRUE) + writeLines(c("NULL"), "R/blur.R") + # works + mockery::stub(roxygenize_with_cache, "diff_requires_run_roxygenize", TRUE) + # when there is a missing package + roxygen_field <- paste0( + 'list(markdown = TRUE, roclets = c("rd", "namespace", "collate", "', + generate_uninstalled_pkg_call(), '"))' + ) + desc::desc_set(Roxygen = roxygen_field) + expect_error( + roxygenize_with_cache(list(getwd()), dirs = dirs_R.cache("roxygenize")), + "Please add the package as a dependency" + ) +}) + +test_that("fails when there is invalid code", { + local_test_setup(git = FALSE, use_precommit = FALSE, package = TRUE) + mockery::stub(roxygenize_with_cache, "diff_requires_run_roxygenize", TRUE) + # when there is a missing package + writeLines(c("invalid code stuff /3kj"), "R/more.R") + expect_error( + roxygenize_with_cache(list(getwd()), dirs = dirs_R.cache("roxygenize")), + "[Uu]nexpected symbol" + ) +}) + +test_that("warns if there is any other warning", { + local_test_setup(git = FALSE, use_precommit = FALSE, package = TRUE) + mockery::stub(roxygenize_with_cache, "diff_requires_run_roxygenize", TRUE) + writeLines(c("#' This is a title", "#'", "#' More", "NULL"), "R/blur.R") + + expect_warning( + roxygenize_with_cache(list(getwd()), dirs = dirs_R.cache("roxygenize")), + "Missing name" + ) +}) diff --git a/tests/testthat/test-setup.R b/tests/testthat/test-setup.R new file mode 100644 index 000000000..b6f0bbdd9 --- /dev/null +++ b/tests/testthat/test-setup.R @@ -0,0 +1,19 @@ +test_that("snippet generation works", { + local_test_setup(git = FALSE, use_precommit = FALSE, package = TRUE) + usethis::use_package("styler") + expect_warning( + out <- capture_output(snippet_generate("additional-deps-roxygenize")), + NA + ) + expect_match( + out, "^ - styler@.+\n - testthat@.+$", + ) + desc::desc_set("Remotes", "r-lib/styler") + expect_warning( + out <- capture_output(snippet_generate("additional-deps-roxygenize")), + "you have remote dependencies " + ) + expect_match( + out, "^ - styler@.+\n - testthat@.+$", + ) +})