add combination of `is_installed()` and `check_installed()`
I sometimes find myself in this situation:
if (rlang::is_installed("optional-package")) {
# do something more fully-featured
} else {
# do a simpler version without the opt package
}
Typically, such an optional-package will be a Suggests:.
If they're missing it, I'd like to inform my users about them missing optional-package and (once) prompt them to install it.
rlang::check_installed() is great for this except that it errors out.
So I'd like to us a "combination" (loosely speaking) of the two functions which:
- always returns as a predicate would (
TRUE/FALSE) - if interactive, prompt the user to install
- if non-interactive inform the user that they're missing
If you deem this useful, read on -- and I'll be happy to write up a PR with some guidance --, otherwise feel free to close 🙂
I've build a hacky version of this (see below), but ran into these limitations which may require/justify inclusion/deeper integration in rlang:
- just catching and re-signaling (as a message)
check_installed()does not work great, because:- the wording (~ "required package missing") of the condition doesn't quite work as a message; it sounds too alarming and may confuse users.
- I can't use
.frequencyfor how often to even prompt the user (which may be even more annoying than messages, if done too often). (related #1729)
- rebuilding based off of only
is_installed()would duplicate a lot of work already done in rlang.
In summary, it might be nice to include this feature in rlang, though to avoid code duplication, it might require some refactoring.
Also some unresolved questions:
- [ ] how should this function be named?
It's too side-effecty for
is_installed(). Something likeis_installed_after_trying()? Is there a idiomatic name for this kind of pattern? - [ ] is it ok to reuse
rlang:::needs_signal()for when to try installing? That clearly needs a.frequencynot to be annoying,rlang:::needs_signal()isn't about when/how often to prompt.
I'm running into the exact same scenario.
I couldn't for the life of me figure out how to combine try_fetch() + check_installed() in a way that would produce the desired behaviour.
@teunbrand in case that's helpful I got this to work:
#' Checks if a package is installed and *informs* the user if not
#'
#' This is wrapper around [rlang::check_installed];
#' instead of erroring out if the check fails it returns `FALSE`.
#' However, unlike [rlang::is_installed], it emits a message to the user.
#'
#' @inheritParams rlang::check_installed
#' @inheritDotParams rlang::check_installed
#' @example inst/examples/dependencies/is_installed2/missing.R
#' @example inst/examples/dependencies/is_installed2/present.R
#' @keywords dependencies helper
#' @export
is_installed2 <- function(...) {
if (rlang::is_installed(...)) {
return(TRUE)
}
rlang::try_fetch(
# TODO this should only interact with the user as per .frequency
# might get annoying otherwise
# but that is blocked by deep integration in rlang
rlang::check_installed(...),
error = function(cnd) {
inform_missing_pkgs(...)
}
)
rlang::is_installed(...)
}
you can replace inform_missing_pkgs() with your own simpler rlang::inform("blah").
Full source, largely copied from rlang itself is here: https://github.com/dataheld/elf/blob/main/R/dependencies.R
Though it's all a bit hacky, not ready for prime time.
Thanks for sharing this code, Max!
This is similar to what I attempted but I found one gripe with this approach.
If a user selects 'No' to a prompt, the function should return FALSE but instead no value is returned at all.
It is hard to provide a reprex as this only occurs in interactive sessions, but essentially:
x <- is_installed2("foobar")
# User should select 'No' as answer
print(x)
#> Error: object 'x' not found
Related https://github.com/r-lib/rlang/issues/1658. In dm there is a standalone file that basically extends the is_installed() behavior to skip in tests.