diff --git a/DESCRIPTION b/DESCRIPTION index e32271a..9914602 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -4,7 +4,7 @@ Title: Informative Messages About Outdated Packages Version: 0.1.0 Authors@R: person("Jacob", "Scott", email = "jscott2718@gmail.com", role = c("aut", "cre")) Description: When a package is loaded, the source repository is checked for - new versions. A message is show in the console indicating whether the + new versions and a message is show in the console indicating whether the package is out of date. License: MIT + file LICENSE Encoding: UTF-8 @@ -13,7 +13,7 @@ URL: https://github.com/wurli/updateme, https://wurli.github.io/updateme/ BugReports: https://github.com/wurli/updateme/issues Imports: cachem, - cli, + cli (>= 3.6.0), curl, memoise, rlang, diff --git a/NEWS.md b/NEWS.md new file mode 100644 index 0000000..23bbf5e --- /dev/null +++ b/NEWS.md @@ -0,0 +1,3 @@ +# updateme 0.1.0 + +* Added a `NEWS.md` file to track changes to the package. diff --git a/R/available_version.R b/R/available_version.R index ff82432..bc8b3c2 100644 --- a/R/available_version.R +++ b/R/available_version.R @@ -217,6 +217,7 @@ desc_from_git <- function(username, repo, pkg = repo, type = c("github", "gitlab curl::handle_setheaders(handle, .list = auth_header) con <- curl::curl(file_url, handle = handle) + on.exit(try(close(con), silent = TRUE)) tryCatch( readLines(con, warn = FALSE), @@ -232,7 +233,7 @@ desc_from_git <- function(username, repo, pkg = repo, type = c("github", "gitlab i = private_repo_msg, i = paste( "Is the repo private? Perhaps you need to configure", - "an {.topic [access token](updateme::updateme_set_sources)}." + "an {.topic [access token](updateme::updateme_sources_set)}." ) ) } else if (grepl("302", msg)) { diff --git a/R/bioc.R b/R/bioc.R index b32e464..05e803c 100644 --- a/R/bioc.R +++ b/R/bioc.R @@ -5,7 +5,7 @@ bioc_version <- function(pkg = NULL) { return(vn[, 1:2]) } - vn <- try(packageVersion("BiocVersion"), silent = TRUE) + vn <- installed_version("BiocVersion") if (inherits(vn, "numeric_version")) return(vn[, 1:2]) diff --git a/R/capture_tidyverse_startup_message.R b/R/capture_tidyverse_startup_message.R deleted file mode 100644 index e33a72c..0000000 --- a/R/capture_tidyverse_startup_message.R +++ /dev/null @@ -1,13 +0,0 @@ -# # For reference later -# capture_tidyverse_startup_message <- function() { -# out <- "" -# withCallingHandlers( -# library(tidyverse), -# packageStartupMessage = function(x) { -# out <<- paste0(out, "\n", x$message) -# tryInvokeRestart("muffleMessage") -# } -# ) -# out -# } -# diff --git a/R/inform_load.R b/R/inform_load.R index d65c39b..39e0e99 100644 --- a/R/inform_load.R +++ b/R/inform_load.R @@ -1,27 +1,52 @@ -inform_load <- function(pkg, inform_if_ahead = NULL) { +inform_load <- function(pkg, extra_attachments = NULL) { if (!is_interactive() || !updateme_is_on()) { return(invisible(NULL)) } + if (identical(pkg, "tidyverse")) { + cli::cat_line(tidyverse_attach_message(extra_attachments)) + + if (!is_attached("conflicted")) + print(get("tidyverse_conflicts", asNamespace("tidyverse"))()) + + return(invisible(NULL)) + } + + is_loaded <- paste0("package:", pkg) %in% search() + + cli::cli_alert_info(paste0( + if (is_loaded) "Using " else "Loading ", + "{.pkg {pkg}} ", + package_version_describe(pkg) + )) + + invisible(NULL) + +} + +package_version_describe <- function(pkg, inform_if_ahead = NULL, template = NULL) { + installation_info <- package_installation_info(pkg) + current <- packageVersion(pkg) + remote_vn_info <- available_version(installation_info) + available <- remote_vn_info[["Source_Version"]] + source <- remote_vn_info[["Source_Name"]] + source_url <- remote_vn_info[["Source_URL"]] - installed_version <- packageVersion(pkg) - available <- available_version(installation_info) - repo_version <- available[["Source_Version"]] - inform_if_ahead <- inform_if_ahead %||% grepl("^Bioc", available[["Source_Name"]]) + inform_if_ahead <- inform_if_ahead %||% grepl("^Bioc", source) - new_version_found <- !is.null(repo_version) + new_version_found <- !is.null(available) - currentness_unknown <- !new_version_found || - !is.package_version(repo_version) || - !is.package_version(installed_version) + currentness_is_unknown <- !new_version_found || + !is.package_version(available) || + !is.package_version(current) - currentness <- if (currentness_unknown) + currentness <- if (currentness_is_unknown) "unknown" - else if (installed_version < repo_version) + else if (current < available) "outdated" - else if (installed_version == repo_version) + else if (current == available) "up_to_date" else "ahead" @@ -33,31 +58,39 @@ inform_load <- function(pkg, inform_if_ahead = NULL) { ahead = cli::col_br_magenta ) - src_name <- available[["Source_Name"]] - src_url <- available[["Source_URL"]] + show_extra_info <- currentness %in% c( + "outdated", "unknown", if (inform_if_ahead) "ahead" + ) - extra_info <- if (new_version_found && currentness %in% c("outdated", "unknown", "ahead")) { - src_pretty <- if (is.null(src_url)) - src_name + extra_info <- if (!new_version_found || !show_extra_info) { + "" + } else { + src <- if (is.null(source_url)) + source else - cli::format_inline("{.href [{src_name}]({src_url})}") + cli::format_inline("{.href [{source}]({source_url})}") + + vn <- available cli::col_grey(cli::style_italic(cli::format_inline(switch(currentness, outdated = , - unknown = " ({repo_version} now available from {src_pretty})", - ahead = " ({repo_version} is the latest version on {src_pretty})" + unknown = template %||% "({vn} now available from {src})", + ahead = "({vn} is the latest version on {src})" )))) } - cli::cli_alert_info(paste0( - "Loading ", - cli::format_inline("{.pkg {pkg}}"), " ", - fmt_currentness(installed_version), - extra_info - )) - - invisible(NULL) + paste(fmt_currentness(style_version(current)), extra_info) +} +style_version <- function(x) { + x <- as.character(x) + is_dev <- function(x) { + x <- suppressWarnings(as.numeric(x)) + !is.na(x) & x >= 9000 + } + pieces <- strsplit(x, ".", fixed = TRUE) + pieces <- lapply(pieces, function(x) ifelse(is_dev(x), cli::style_italic(x), x)) + vapply(pieces, paste, collapse = ".", FUN.VALUE = character(1)) } available_packages <- function(repos = getOption("repos")) { diff --git a/R/package_installation_info.R b/R/package_installation_info.R index 144bbaa..b79fde9 100644 --- a/R/package_installation_info.R +++ b/R/package_installation_info.R @@ -1,111 +1,111 @@ -package_installation_info <- function(pkg, lib.loc = NULL) { - - desc <- package_description( - pkg, - lib.loc = lib.loc, - fields = c( - "Version", "URL", "Repository", "RemoteType", - "RemoteUsername", "RemoteRepo", "GithubUsername", "GithubRepo", - "RemoteUrl", "biocViews" - ) - ) - - version <- desc[["Version"]] - - if (is.null(desc[["Version"]])) - return(NULL) - - repo <- desc[["Repository"]] - remote_type <- desc[["RemoteType"]] - is_github <- identical(remote_type, "github") - is_gitlab <- identical(remote_type, "gitlab") - rmt_username <- desc[["RemoteUsername"]] - rmt_repo <- desc[["RemoteRepo"]] - gl_username <- desc[["GitlabUsername"]] %||% (if (is_gitlab) rmt_username) - gl_repo <- desc[["GitlabRepo"]] %||% (if (is_gitlab) rmt_repo) - gh_username <- desc[["GithubUsername"]] %||% (if (is_github) rmt_username) - gh_repo <- desc[["GithubRepo"]] %||% (if (is_github) rmt_repo) - remote_url <- desc[["RemoteUrl"]] - bioc_views <- desc[["biocViews"]] - pkg_urls <- desc[["URL"]] - - url_repo_details <- repo_details_from_urls(pkg_urls) - - # If no github info set, try getting it from the URL field - if ((is.null(gh_username) || is.null(gh_repo)) && !is.null(pkg_urls)) { - gh_username <- url_repo_details[["Github_Username"]] - gh_repo <- url_repo_details[["Github_Repository"]] - } - - # If no gitlab info set, try getting it from the URL field - if ((is.null(gl_username) || is.null(gl_repo)) && !is.null(pkg_urls)) { - gl_username <- url_repo_details[["Gitlab_Username"]] - gl_repo <- url_repo_details[["Gitlab_Repository"]] - } - - available_sources <- c( - if (!is.null(repo)) "repo", - if (!is.null(gh_repo) && !is.null(gh_username)) "github", - if (!is.null(gl_repo) && !is.null(gl_username)) "gitlab", - if (!is.null(remote_url)) "remote", - if (!is.null(bioc_views)) "bioc" - ) - - list( - Available_Sources = available_sources, - Package = pkg, - Version_Installed = version, - Repository = repo, - Github_Username = gh_username, - Github_Repository = gh_repo, - Gitlab_Username = gl_username, - Gitlab_Repository = gl_repo, - Remote_URL = remote_url, - Bioc_Views = bioc_views - ) - -} - -repo_details_from_urls <- function(x) { - if (is.null(x)) - return(NULL) - - pkg_urls <- strsplit(x, ",\\s*")[[1]] - - out <- list( - Github_Username = NULL, - Github_Repository = NULL, - Gitlab_Username = NULL, - Gitlab_Repository = NULL - ) - - github_urls <- pkg_urls[is_github_url(pkg_urls)] - if (length(github_urls) > 0L) { - github_url <- github_urls[1] - out[["Github_Username"]] <- github_username_from_url(github_url) - out[["Github_Repository"]] <- github_repo_from_url(github_url) - } - - gitlab_urls <- pkg_urls[is_gitlab_url(pkg_urls)] - if (length(gitlab_urls) > 0L) { - gitlab_url <- gitlab_urls[1] - out[["Gitlab_Username"]] <- gitlab_username_from_url(gitlab_url) - out[["Gitlab_Repository"]] <- gitlab_repo_from_url(gitlab_url) - } - - out -} - -package_description <- function(pkg, lib.loc = NULL, fields = NULL) { - tryCatch( - pkg |> - packageDescription(lib.loc = lib.loc, fields = fields, drop = FALSE) |> - keep(\(x) !is.na(x) && x != ""), - warning = function(w) { - cli::cli_abort(c( - "No DESCRIPTION file found for {.pkg {pkg}}", - i = "Original warning: {w$message}" - )) - } - ) -} +package_installation_info <- function(pkg, lib.loc = NULL) { + + desc <- package_description( + pkg, + lib.loc = lib.loc, + fields = c( + "Version", "URL", "Repository", "RemoteType", + "RemoteUsername", "RemoteRepo", "GithubUsername", "GithubRepo", + "RemoteUrl", "biocViews" + ) + ) + + version <- desc[["Version"]] + + if (is.null(desc[["Version"]])) + return(NULL) + + repo <- desc[["Repository"]] + remote_type <- desc[["RemoteType"]] + is_github <- identical(remote_type, "github") + is_gitlab <- identical(remote_type, "gitlab") + rmt_username <- desc[["RemoteUsername"]] + rmt_repo <- desc[["RemoteRepo"]] + gl_username <- desc[["GitlabUsername"]] %||% (if (is_gitlab) rmt_username) + gl_repo <- desc[["GitlabRepo"]] %||% (if (is_gitlab) rmt_repo) + gh_username <- desc[["GithubUsername"]] %||% (if (is_github) rmt_username) + gh_repo <- desc[["GithubRepo"]] %||% (if (is_github) rmt_repo) + remote_url <- desc[["RemoteUrl"]] + bioc_views <- desc[["biocViews"]] + pkg_urls <- desc[["URL"]] + + url_repo_details <- repo_details_from_urls(pkg_urls) + + # If no github info set, try getting it from the URL field + if ((is.null(gh_username) || is.null(gh_repo)) && !is.null(pkg_urls)) { + gh_username <- url_repo_details[["Github_Username"]] + gh_repo <- url_repo_details[["Github_Repository"]] + } + + # If no gitlab info set, try getting it from the URL field + if ((is.null(gl_username) || is.null(gl_repo)) && !is.null(pkg_urls)) { + gl_username <- url_repo_details[["Gitlab_Username"]] + gl_repo <- url_repo_details[["Gitlab_Repository"]] + } + + available_sources <- c( + if (!is.null(repo)) "repo", + if (!is.null(gh_repo) && !is.null(gh_username)) "github", + if (!is.null(gl_repo) && !is.null(gl_username)) "gitlab", + if (!is.null(remote_url)) "remote", + if (!is.null(bioc_views)) "bioc" + ) + + list( + Available_Sources = available_sources, + Package = pkg, + Version_Installed = version, + Repository = repo, + Github_Username = gh_username, + Github_Repository = gh_repo, + Gitlab_Username = gl_username, + Gitlab_Repository = gl_repo, + Remote_URL = remote_url, + Bioc_Views = bioc_views + ) + +} + +repo_details_from_urls <- function(x) { + if (is.null(x)) + return(NULL) + + pkg_urls <- strsplit(x, ",\\s*")[[1]] + + out <- list( + Github_Username = NULL, + Github_Repository = NULL, + Gitlab_Username = NULL, + Gitlab_Repository = NULL + ) + + github_urls <- pkg_urls[is_github_url(pkg_urls)] + if (length(github_urls) > 0L) { + github_url <- github_urls[1] + out[["Github_Username"]] <- github_username_from_url(github_url) + out[["Github_Repository"]] <- github_repo_from_url(github_url) + } + + gitlab_urls <- pkg_urls[is_gitlab_url(pkg_urls)] + if (length(gitlab_urls) > 0L) { + gitlab_url <- gitlab_urls[1] + out[["Gitlab_Username"]] <- gitlab_username_from_url(gitlab_url) + out[["Gitlab_Repository"]] <- gitlab_repo_from_url(gitlab_url) + } + + out +} + +package_description <- function(pkg, lib.loc = NULL, fields = NULL) { + tryCatch( + pkg |> + packageDescription(lib.loc = lib.loc, fields = fields, drop = FALSE) |> + keep(\(x) !is.na(x) && x != ""), + warning = function(w) { + cli::cli_abort(c( + "No DESCRIPTION file found for {.pkg {pkg}}", + i = "Original warning: {w$message}" + )) + } + ) +} diff --git a/R/shims.R b/R/shims.R index bca0a3a..8a8e118 100644 --- a/R/shims.R +++ b/R/shims.R @@ -37,8 +37,9 @@ shim_library_3_1 <- function(package, if (!missing(package)) { package <- package_name(enquo(package), character.only = character.only) + extra_attachments <- tidyverse_extra_attachments(package) - out <- library( + out <- maybe_muffle(package)(library( package, pos = pos, lib.loc = lib.loc, @@ -47,9 +48,9 @@ shim_library_3_1 <- function(package, warn.conflicts = FALSE, quietly = quietly, verbose = verbose - ) + )) - inform_load(package) + inform_load(package, extra_attachments) handle_conflicted() invisible(out) @@ -85,8 +86,9 @@ shim_library_3_6 <- function(package, if (!missing(package)) { package <- package_name(enquo(package), character.only = character.only) + extra_attachments <- tidyverse_extra_attachments(package) - out <- library( + out <- maybe_muffle(package)(library( package, pos = pos, lib.loc = lib.loc, @@ -99,9 +101,9 @@ shim_library_3_6 <- function(package, exclude = exclude, include.only = include.only, attach.required = attach.required - ) + )) - inform_load(package) + inform_load(package, extra_attachments) handle_conflicted() invisible(out) @@ -129,16 +131,17 @@ shim_require_3_1 <- function(package, require <- the$conflicted_shims$require %||% require package <- package_name(enquo(package), character.only = character.only) + extra_attachments <- tidyverse_extra_attachments(package) - out <- require( + out <- maybe_muffle(package)(require( package, lib.loc = lib.loc, quietly = quietly, warn.conflicts = FALSE, character.only = TRUE - ) + )) - inform_load(package) + inform_load(package, extra_attachments) handle_conflicted() invisible(out) } @@ -156,8 +159,9 @@ shim_require_3_6 <- function(package, require <- the$conflicted_shims$require %||% require package <- package_name(enquo(package), character.only = character.only) + extra_attachments <- tidyverse_extra_attachments(package) - out <- require( + out <- maybe_muffle(package)(require( package, lib.loc = lib.loc, quietly = quietly, @@ -167,9 +171,9 @@ shim_require_3_6 <- function(package, exclude = exclude, include.only = include.only, attach.required = attach.required - ) + )) - inform_load(package) + inform_load(package, extra_attachments) handle_conflicted() invisible(out) } @@ -211,6 +215,13 @@ handle_conflicted <- function() { } } +maybe_muffle <- function(pkg) { + if (pkg == "tidyverse" && updateme_is_on()) + suppressPackageStartupMessages + else + identity +} + conflicted_loaded <- function() { all(c("package:conflicted", ".conflicts") %in% search()) } diff --git a/R/tidyverse.R b/R/tidyverse.R new file mode 100644 index 0000000..ad62ed6 --- /dev/null +++ b/R/tidyverse.R @@ -0,0 +1,51 @@ +tidyverse_core_packages <- function() { + c( + "ggplot2", "tibble", "tidyr", "readr", "purrr", "dplyr", "stringr", "forcats", + if (installed_version("tidyverse") >= package_version("2.0.0")) "lubridate" + ) +} + +tidyverse_extra_attachments <- function(pkg) { + if (pkg != "tidyverse" || !is_installed("tidyverse")) + return(NULL) + + tidyverse_pkgs <- tidyverse_core_packages() + search <- paste0("package:", tidyverse_pkgs) + tidyverse_pkgs[!search %in% search()] +} + +tidyverse_attach_message <- function(to_load = tidyverse_core_packages()) { + + if (length(to_load) == 0) + return(NULL) + + header <- cli::rule( + left = cli::style_bold("Attaching core tidyverse packages"), + right = paste0( + "tidyverse ", + package_version_describe("tidyverse", template = "({src} = {vn})") + ) + ) + + to_load <- sort(to_load) + versions <- vapply(to_load, package_version_describe, character(1), template = "{src} = {vn}") + + col1 <- seq_len(ceiling(length(to_load) / 2)) + + col1_text <- paste0( + cli::col_green(cli::symbol$tick), " ", cli::col_blue(format(to_load[col1])), " ", + cli::ansi_align(style_version(versions[col1]), max(cli::ansi_nchar(versions[col1]))) + ) + + col2_text <- paste0( + cli::col_green(cli::symbol$tick), " ", cli::col_blue(format(to_load[-col1])), " ", + cli::ansi_align(style_version(versions[-col1]), max(cli::ansi_nchar(versions[-col1]))) + ) + + if (length(to_load) %% 2 == 1) + col2_text <- append(col2_text, "") + + info <- paste0(col1_text, " ", col2_text) + + paste0(header, "\n", paste(info, collapse = "\n")) +} diff --git a/R/updateme_on.R b/R/updateme_on.R index 01f152c..6ba5afc 100644 --- a/R/updateme_on.R +++ b/R/updateme_on.R @@ -6,7 +6,7 @@ #' @return `NULL`, invisibly #' @export #' -#' @seealso [updateme_set_sources()] to turn {updateme} off for individual +#' @seealso [updateme_sources_set()] to turn {updateme} off for individual #' packages #' #' @examples diff --git a/R/updateme_set_sources.R b/R/updateme_sources_set.R similarity index 78% rename from R/updateme_set_sources.R rename to R/updateme_sources_set.R index 4939c59..5e30249 100644 --- a/R/updateme_set_sources.R +++ b/R/updateme_sources_set.R @@ -6,20 +6,24 @@ #' #' @param ... Named or unnamed arguments. Values should be either: #' -#' - `"github"`/`"gitlab"`: new versions will be found from the package -#' development version on GitHub/GitLab, if a repo can be identified using -#' the package `DESCRIPTION` -#' #' - One of `names(getOption("repo"))`: latest versions will be taken from #' this source, if available #' +#' - `"bioc"`: new versions will be looked for on Bioconductor +#' +#' - `"github"`/`"gitlab"`: new versions will looked for on on +#' GitHub/GitLab, if a repo can be identified using the package +#' `DESCRIPTION` +#' #' - A URL pointing to a GitHub/GitLab repo, e.g. #' `"https://github.com/wurli/updateme"`: the latest version *for this #' particular package* will be taken from this project #' -#' - `NULL`: {updateme} will not attempt to query new versions. -#' Note that `NULL` inputs must always be named (i.e. you must specify this -#' 'per package'). +#' - `NA`: {updateme} will not attempt to query new versions. +#' Note that `NA` inputs must always be named (i.e. you must specify this +#' 'per package') +#' +#' - `NULL`: return to the default behaviour #' #' If arguments are named, names should indicate package which the option #' should apply to. If unnamed, the option will apply to all packages. See @@ -87,12 +91,19 @@ updateme_sources_set <- function(...) { } updateme_sources_set_impl <- function(...) { + + if (...length() == 0L) + cli::cli_abort(call = caller_call(1), c( + "At least one argument must be supplied", + i = "Use {.code updateme::updateme_off()} to disable updateme" + )) + # dots_list() prevents names being NULL dots <- dots_list(...) out <- imap(dots, updateme_sources_validate) sources <- map(out, function(x) x[["Source_Name"]]) packages <- map_chr(out, function(x) x[["Package"]] %||% "") - set_names(sources, packages) + compact(set_names(sources, packages)) } preferred_sources <- function(pkg) { @@ -115,17 +126,19 @@ updateme_sources_validate <- function(src, pkg = NULL, throw = cli::cli_abort) { throw(call = caller_call(6), c( "Invalid package source {.val {src}}.", "i" = "Package sources must be either:", - " " = "1. {.val github}/{.val gitlab}, to check the version on GitHub/GitLab if possible", - " " = '2. One of {.code names(getOption("repos"))}', - " " = "3. The URL of a specific GitHub repository, e.g. {.url https://github.com/wurli/updateme}", - " " = "4. The URL of a specific GitLab repository, e.g. {.url https://gitlab.com/r-packages/yum}", - " " = "5. {.val NULL} to turn {.pkg updateme} off for a package" + " " = '1. One of {.code names(getOption("repos"))}', + " " = "2. {.val bioc} to check te version on Bioconductor", + " " = "3. {.val github}/{.val gitlab} to check the version on GitHub/GitLab if possible", + " " = "4. The URL of a specific GitHub repository, e.g. {.url https://github.com/wurli/updateme}", + " " = "5. The URL of a specific GitLab repository, e.g. {.url https://gitlab.com/r-packages/yum}", + " " = "6. {.val NA} to turn {.pkg updateme} off for a package", + " " = "7. {.val NULL} to return to the default behaviour" )) } NULL } - src_ok <- is.character(src) || is.null(src) + src_ok <- is.character(src) || is.null(src) || is.na(src) pkg_ok <- is.character(pkg) || is.null(pkg) if (!src_ok || !pkg_ok) { @@ -135,17 +148,18 @@ updateme_sources_validate <- function(src, pkg = NULL, throw = cli::cli_abort) { if (pkg == "") pkg <- NULL - if (any(is.na(src))) - src <- NULL - if (is.null(src) && is.null(pkg)) { throw(call = caller_call(6), c( "Invalid package source", - i = "All {.val NULL} and {.val NA} arguments must be named" + i = "All {.val NULL} arguments must be named" )) } + if (is.null(src)) + return(NULL) + out <- list( + Available_Sources = NULL, Preferred_Source = NULL, Package = pkg, Source_Name = src, @@ -156,14 +170,21 @@ updateme_sources_validate <- function(src, pkg = NULL, throw = cli::cli_abort) { Bioc_Views = NULL ) - if (is.null(src)) { + if (is.na(src)) { out[["Preferred_Source"]] <- .updateme_skip return(out) } if (is_valid_repo(src)) { - out[["Preferred_Source"]] <- "repo" - out[["Repository"]] <- src + out[["Available_Sources"]] <- "repo" + out[["Preferred_Source"]] <- "repo" + out[["Repository"]] <- src + return(out) + } + + if (identical(src, "bioc")) { + out[["Available_Sources"]] <- "bioc" + out[["Preferred_Source"]] <- "bioc" return(out) } diff --git a/R/utils.R b/R/utils.R index eae73fa..e28c219 100644 --- a/R/utils.R +++ b/R/utils.R @@ -42,3 +42,10 @@ with_timeout <- function(expr, time = Inf){ ) } +str_extract_all <- function(x, pattern, invert = FALSE, perl = TRUE) { + regmatches(x, gregexpr(pattern, x, perl = perl), invert)[[1]] +} + +installed_version <- function(pkg) { + tryCatch(packageVersion(pkg), error = function(e) NULL) +} diff --git a/README.md b/README.md index 3993f58..1f87504 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ [![R-CMD-check](https://github.com/wurli/updateme/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/wurli/updateme/actions/workflows/R-CMD-check.yaml) -{updateme} modifies `library()` to tell you if your package is up to date when -it gets loaded: +{updateme} modifies `library()` to tell you if your packages are up to date when +you load them: ![](https://raw.githubusercontent.com/wurli/updateme/main/updateme-demo.gif) @@ -49,46 +49,13 @@ library(updateme) By default, new versions of packages will be looked up from the location where they seem to have been installed from. If you installed from CRAN (e.g. using `install.packages()`), {updateme} will check CRAN for a newer -version, and similarly with packages installed from GitHub or GitLab. +version, and similarly with packages installed from Bioconductor, GitHub, and +GitLab. -If this doesn't work for you, you can override this behaviour using -`updateme_sources_set()`: - -``` r -# ~~ Set the `repos` global option ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# If you want to check non-standard repos for new versions of packages, -# you'll first have to set the repos global option. Note that each -# option must be named for compatibility with {updateme}. -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -options(repos = c( - - # Your default repos, e.g. c(CRAN = "https://cloud.r-project.org") - getOption("repos"), - - # The tidyverse r-universe, including dev versions of tidyverse packages - tidyverse = "https://tidyverse.r-universe.dev", - - # The r-lib r-universe, including dev versions of infrastructure packages - # like {cli}, {rlang}, etc - rlib = "https://r-lib.r-universe.dev" -)) - -# ~~ Set the `updateme.sources` global option ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# 1. New versions will first be looked up in the tidyverse r-universe -# 2. If not found, they will be looked up from your usual CRAN mirror -# 3. {bslib} will always be first looked up from GitHub -# 4. {cli} will always be first looked up from the r-lib r-universe -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -updateme_sources_set( - "tidyverse", - "CRAN", - bslib = "https://github.com/rstudio/bslib", # Name is optional here - cli = "rlib" -) -``` - -Packages installed from Bioconductor, or from other remotes such as Bitbucket -are not yet supported. +Occasionally you may want more control over where {updateme} looks for new +package versions. You can configure this behaviour, including turning {updateme} +off for particular packages, using `updateme_sources_set()`. If you want to +temporarily disable {updateme} entirely you can do so using `updateme_off()`. ### Caching diff --git a/man/updateme_on.Rd b/man/updateme_on.Rd index fa287ab..9e919aa 100644 --- a/man/updateme_on.Rd +++ b/man/updateme_on.Rd @@ -23,6 +23,6 @@ if (FALSE) { } } \seealso{ -\code{\link[=updateme_set_sources]{updateme_set_sources()}} to turn {updateme} off for individual +\code{\link[=updateme_sources_set]{updateme_sources_set()}} to turn {updateme} off for individual packages } diff --git a/man/updateme_sources_set.Rd b/man/updateme_sources_set.Rd index ae9d305..a3958e1 100644 --- a/man/updateme_sources_set.Rd +++ b/man/updateme_sources_set.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/updateme_set_sources.R +% Please edit documentation in R/updateme_sources_set.R \name{updateme_sources_set} \alias{updateme_sources_set} \title{Configure {updateme} lookup of new package versions} @@ -9,17 +9,19 @@ updateme_sources_set(...) \arguments{ \item{...}{Named or unnamed arguments. Values should be either: \itemize{ -\item \code{"github"}/\code{"gitlab"}: new versions will be found from the package -development version on GitHub/GitLab, if a repo can be identified using -the package \code{DESCRIPTION} \item One of \code{names(getOption("repo"))}: latest versions will be taken from this source, if available +\item \code{"bioc"}: new versions will be looked for on Bioconductor +\item \code{"github"}/\code{"gitlab"}: new versions will looked for on on +GitHub/GitLab, if a repo can be identified using the package +\code{DESCRIPTION} \item A URL pointing to a GitHub/GitLab repo, e.g. \code{"https://github.com/wurli/updateme"}: the latest version \emph{for this particular package} will be taken from this project -\item \code{NULL}: {updateme} will not attempt to query new versions. -Note that \code{NULL} inputs must always be named (i.e. you must specify this -'per package'). +\item \code{NA}: {updateme} will not attempt to query new versions. +Note that \code{NA} inputs must always be named (i.e. you must specify this +'per package') +\item \code{NULL}: return to the default behaviour } If arguments are named, names should indicate package which the option @@ -37,9 +39,9 @@ options you set. } \section{Private Repositories}{ -{updateme} supports packages stored in private repositories on GitHub and -GitLab. To get upstream package version from either, you should only -have to configure a personal access token (PAT). +{updateme} supports packages installed from private repositories on GitHub +and GitLab. To get upstream package version from either, you should only have +to configure a personal access token (PAT): \itemize{ \item For GitHub packages, {updateme} checks, in order: \itemize{ diff --git a/tests/testthat/test-available_version.R b/tests/testthat/test-available_version.R index 9ebf786..fc46bde 100644 --- a/tests/testthat/test-available_version.R +++ b/tests/testthat/test-available_version.R @@ -39,7 +39,7 @@ test_that("available_version() respects updateme.sources", { Source_Version = package_version("0.2.0") )) - updateme_sources_set(updateme.testpkg = NULL) + updateme_sources_set(updateme.testpkg = NA) expect_identical(available_version(info), NULL) }) diff --git a/tests/testthat/test-updateme_sources.R b/tests/testthat/test-updateme_sources.R index 4a3f0ba..f2d3534 100644 --- a/tests/testthat/test-updateme_sources.R +++ b/tests/testthat/test-updateme_sources.R @@ -11,6 +11,7 @@ test_that("updateme_sources_set() fails correctly", { expect_error(updateme_sources_set_impl("foo"), 'Invalid package source "foo"') expect_error(updateme_sources_set_impl("CRAN", "foo"), 'Invalid package source "foo"') expect_error(updateme_sources_set_impl("http://github.com/wurli/updateme", 'Invalid package source "http')) + expect_error(updateme_sources_set_impl(), "At least one argument must be supplied") }) @@ -44,17 +45,29 @@ test_that("updateme_sources_set() sets options correctly", { updateme_sources_set_impl("rlib", "CRAN"), dots_list("rlib", "CRAN") ) - + expect_equal( + updateme_sources_set_impl("bioc"), + dots_list("bioc") + ) expect_equal( updateme_sources_set_impl(dplyr = "https://github.com/tidyverse/dplyr"), list(dplyr = "https://github.com/tidyverse/dplyr") ) + expect_equal( + updateme_sources_set_impl(dplyr = NA), + list(dplyr = NA) + ) + expect_equal( + updateme_sources_set_impl(dplyr = NULL), + dots_list() + ) # Note that the output is named! expect_equal( updateme_sources_set_impl("https://github.com/tidyverse/dplyr"), list(dplyr = "https://github.com/tidyverse/dplyr") ) + }) test_that("updateme_sources_get() works", { @@ -79,6 +92,7 @@ test_that("updateme_sources_get() works", { options(updateme.sources = "tidyverse") expect_identical(updateme_sources_get(), list( list( + Available_Sources = "repo", Preferred_Source = "repo", Package = NULL, Source_Name = "tidyverse", @@ -94,6 +108,7 @@ test_that("updateme_sources_get() works", { options(updateme.sources = list("tidyverse")) expect_identical(updateme_sources_get(), list( list( + Available_Sources = "repo", Preferred_Source = "repo", Package = NULL, Source_Name = "tidyverse", @@ -108,6 +123,7 @@ test_that("updateme_sources_get() works", { options(updateme.sources = list("github")) expect_identical(updateme_sources_get(), list( list( + Available_Sources = NULL, Preferred_Source = "github", Package = NULL, Source_Name = "github", @@ -122,6 +138,7 @@ test_that("updateme_sources_get() works", { options(updateme.sources = list("gitlab")) expect_identical(updateme_sources_get(), list( list( + Available_Sources = NULL, Preferred_Source = "gitlab", Package = NULL, Source_Name = "gitlab", @@ -136,6 +153,7 @@ test_that("updateme_sources_get() works", { options(updateme.sources = list(dplyr = "https://github.com/tidyverse/dplyr")) expect_identical(updateme_sources_get(), list( list( + Available_Sources = NULL, Preferred_Source = "github", Package = "dplyr", Source_Name = "https://github.com/tidyverse/dplyr", @@ -150,6 +168,7 @@ test_that("updateme_sources_get() works", { options(updateme.sources = list("CRAN", "https://github.com/tidyverse/dplyr")) expect_identical(updateme_sources_get(), list( list( + Available_Sources = "repo", Preferred_Source = "repo", Package = NULL, Source_Name = "CRAN", @@ -160,6 +179,7 @@ test_that("updateme_sources_get() works", { Bioc_Views = NULL ), list( + Available_Sources = NULL, Preferred_Source = "github", Package = "dplyr", Source_Name = "https://github.com/tidyverse/dplyr", diff --git a/updateme.Rproj b/updateme.Rproj index 9031c9c..b4de2f6 100644 --- a/updateme.Rproj +++ b/updateme.Rproj @@ -1,8 +1,8 @@ Version: 1.0 -RestoreWorkspace: Default -SaveWorkspace: Default -AlwaysSaveHistory: Default +RestoreWorkspace: No +SaveWorkspace: No +AlwaysSaveHistory: No EnableCodeIndexing: Yes UseSpacesForTab: Yes diff --git a/vignettes/.gitignore b/vignettes/.gitignore deleted file mode 100644 index 097b241..0000000 --- a/vignettes/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.html -*.R