diff --git a/.Rbuildignore b/.Rbuildignore index 55da845..3b16042 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -4,3 +4,7 @@ ^\.Rproj\.user$ ^LICENSE\.md$ ^updateme-demo\.gif$ +^\.github$ +^_pkgdown\.yml$ +^docs$ +^pkgdown$ diff --git a/.github/.gitignore b/.github/.gitignore new file mode 100644 index 0000000..2d19fc7 --- /dev/null +++ b/.github/.gitignore @@ -0,0 +1 @@ +*.html diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml new file mode 100644 index 0000000..74d8c97 --- /dev/null +++ b/.github/workflows/R-CMD-check.yaml @@ -0,0 +1,49 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] + +name: R-CMD-check + +jobs: + R-CMD-check: + runs-on: ${{ matrix.config.os }} + + name: ${{ matrix.config.os }} (${{ matrix.config.r }}) + + strategy: + fail-fast: false + matrix: + config: + - {os: macos-latest, r: 'release'} + - {os: windows-latest, r: 'release'} + - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} + - {os: ubuntu-latest, r: 'release'} + - {os: ubuntu-latest, r: 'oldrel-1'} + + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + R_KEEP_PKG_SOURCE: yes + + steps: + - uses: actions/checkout@v4 + + - uses: r-lib/actions/setup-pandoc@v2 + + - uses: r-lib/actions/setup-r@v2 + with: + r-version: ${{ matrix.config.r }} + http-user-agent: ${{ matrix.config.http-user-agent }} + use-public-rspm: true + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::rcmdcheck + needs: check + + - uses: r-lib/actions/check-r-package@v2 + with: + upload-snapshots: true diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml new file mode 100644 index 0000000..a7276e8 --- /dev/null +++ b/.github/workflows/pkgdown.yaml @@ -0,0 +1,48 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] + release: + types: [published] + workflow_dispatch: + +name: pkgdown + +jobs: + pkgdown: + runs-on: ubuntu-latest + # Only restrict concurrency for non-PR jobs + concurrency: + group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + + - uses: r-lib/actions/setup-pandoc@v2 + + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::pkgdown, local::. + needs: website + + - name: Build site + run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) + shell: Rscript {0} + + - name: Deploy to GitHub pages 🚀 + if: github.event_name != 'pull_request' + uses: JamesIves/github-pages-deploy-action@v4.5.0 + with: + clean: false + branch: gh-pages + folder: docs diff --git a/.gitignore b/.gitignore index e75435c..a397fae 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +**/.DS_Store + # History files .Rhistory .Rapp.history @@ -47,3 +49,4 @@ po/*~ # RStudio Connect folder rsconnect/ +docs diff --git a/DESCRIPTION b/DESCRIPTION index b92eedb..0b32ff8 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -9,21 +9,22 @@ Description: When a package is loaded, the source repository is checked for License: MIT + file LICENSE Encoding: UTF-8 LazyData: true -URL: https://github.com/wurli/updateme +URL: https://github.com/wurli/updateme, https://wurli.github.io/updateme/ BugReports: https://github.com/wurli/updateme/issues Imports: cachem, cli, + curl, memoise, rlang, utils RoxygenNote: 7.2.3 Suggests: - curl, gitcreds, knitr, rmarkdown, - testthat (>= 3.0.0) + testthat (>= 3.0.0), + withr VignetteBuilder: knitr Roxygen: list(markdown = TRUE) Config/testthat/edition: 3 diff --git a/R/available_version.R b/R/available_version.R index b4d68c7..631370e 100644 --- a/R/available_version.R +++ b/R/available_version.R @@ -118,51 +118,11 @@ available_version_impl_repo <- function(pkg, repo = NULL) { } -available_version_impl_github <- function(pkg, username, repo, use_curl = TRUE) { - file_url <- paste0( - "https://raw.githubusercontent.com/", - username, "/", repo, - "/HEAD/DESCRIPTION" - ) - - if (use_curl %||% is_installed("curl")) { - handle <- curl::new_handle() - pat <- get_github_pat() - if (!is.null(pat)) - curl::handle_setheaders(handle, Authorization = paste("token", get_github_pat())) - con <- curl::curl(file_url, handle = handle) - } else { - con <- url(file_url) - } - - response <- tryCatch( - readLines(con, warn = FALSE), - error = function(e) { - msg <- e$message - - private_repo_msg <- if (is.null(get_github_pat())) - "If the repo is private, consider setting a PAT using {.fun gitcreds::gitcreds_set}" - - no_curl_msg <- if (!is_installed("curl")) - "Using private repos requires {.pkg curl} to be installed" - - warning_msg <- if (grepl("404", msg)) - c("{.val 404} error: DESCRIPTION not found", i = private_repo_msg, i = no_curl_msg) - else if (grepl("302", msg)) - "{.val 302} response: not yet implemented" # TODO - else if (grepl("403", msg)) - "{.val 403} error: access forbidden" - else - "DESCRIPTION file not accessible: {msg}" - - cli::cli_warn(c(warning_msg, i = "Error occurred accessing URL {.url {file_url}}")) - - NULL - } - ) +available_version_impl_github <- function(pkg, username, repo) { + response <- desc_from_github(username, repo, pkg) if (is.null(response)) - return(NULL) + return(response) desc <- parse_description(response) github_name <- desc[["Package"]] %||% pkg @@ -187,9 +147,72 @@ available_version_impl_github <- function(pkg, username, repo, use_curl = TRUE) } +desc_from_github <- function(username, repo, pkg = repo, use_curl = TRUE) { + file_url <- paste0( + "https://raw.githubusercontent.com/", + username, "/", repo, + "/HEAD/DESCRIPTION" + ) + + handle <- curl::new_handle() + pat <- get_github_pat() + + if (!is.null(pat)) + curl::handle_setheaders(handle, Authorization = paste("token", get_github_pat())) + + con <- curl::curl(file_url, handle = handle) + + tryCatch( + readLines(con, warn = FALSE), + error = function(e) { + msg <- e$message + + private_repo_msg <- if (is.null(get_github_pat())) + "If the repo is private, consider setting a PAT using {.fun gitcreds::gitcreds_set}" + + warning_msg <- if (grepl("404", msg)) { + c( + i = "{.val 404} error: DESCRIPTION not found", + i = private_repo_msg, + i = paste( + "Is the repo private? Perhaps you need to configure", + "an {.topic [access token](updateme::`private-repos`)}." + ) + ) + } else if (grepl("302", msg)) { + c(i = "{.val 302} response: not yet implemented") # TODO + } else if (grepl("403", msg)) { + c(i = "{.val 403} error: access forbidden") + } else { + c(i = "DESCRIPTION file not accessible: {msg}") + } + + cli::cli_warn(c( + "Failed attempting to get a package version for {.pkg {pkg}} from GitHub", + warning_msg, + i = "Error occurred accessing URL {.url {file_url}}" + )) + + NULL + } + ) +} + get_github_pat <- function() { - if (is_installed("gitcreds")) - return(gitcreds::gitcreds_get()[["password"]]) + # 1. check special updateme env var + updateme_github_pat <- env_var("UPDATEME_GITHUB_PAT") + if (!is.null(updateme_github_pat)) + return(updateme_github_pat) + + # 2. check w/{gitcreds} pkg + if (is_installed("gitcreds")) { + # gitcreds may error if no git installed, no creds set, etc + try(silent = TRUE, { + pat <- gitcreds::gitcreds_get()[["password"]] + return(pat) + }) + } + # 3. Check standard env vars env_var("GITHUB_PAT") %||% env_var("GITHUB_TOKEN") } diff --git a/R/inform_load.R b/R/inform_load.R index d18adfa..c2b3514 100644 --- a/R/inform_load.R +++ b/R/inform_load.R @@ -66,76 +66,6 @@ available_packages_impl <- function(repo) { subset(select = c(Repository, Package, Version)) } -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"]] - repo <- desc[["Repository"]] - remote_type <- desc[["RemoteType"]] - gh_username <- desc[["GithubUsername"]] %||% desc[["RemoteUsername"]] - gh_repo <- desc[["GithubRepo"]] %||% desc[["RemoteRepo"]] - remote_url <- desc[["RemoteUrl"]] - bioc_views <- desc[["biocViews"]] - pkg_urls <- desc[["URL"]] - - if (is.null(desc[["Version"]])) - return(NULL) - - # 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)) { - pkg_urls <- strsplit(pkg_urls, ",\\s*")[[1]] - github_url <- pkg_urls[is_valid_github_url(pkg_urls)] - if (length(github_url) > 0) { - github_url <- github_url[1] - gh_username <- github_username_from_url(github_url) - gh_repo <- github_repo_from_url(github_url) - } - } - - available_sources <- c( - if (!is.null(repo)) "repo", - if (!is.null(gh_repo) && !is.null(gh_username)) "github", - 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, - Remote_URL = remote_url, - Bioc_Views = bioc_views - ) - -} - - - -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}" - )) - } - ) -} maybe_as_version <- function(x) { try(x <- package_version(x), silent = TRUE) diff --git a/R/package_installation_info.R b/R/package_installation_info.R new file mode 100644 index 0000000..ae8fec2 --- /dev/null +++ b/R/package_installation_info.R @@ -0,0 +1,70 @@ +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"]] + repo <- desc[["Repository"]] + remote_type <- desc[["RemoteType"]] + gh_username <- desc[["GithubUsername"]] %||% desc[["RemoteUsername"]] + gh_repo <- desc[["GithubRepo"]] %||% desc[["RemoteRepo"]] + remote_url <- desc[["RemoteUrl"]] + bioc_views <- desc[["biocViews"]] + pkg_urls <- desc[["URL"]] + + if (is.null(desc[["Version"]])) + return(NULL) + + # 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)) { + pkg_urls <- strsplit(pkg_urls, ",\\s*")[[1]] + github_url <- pkg_urls[is_valid_github_url(pkg_urls)] + if (length(github_url) > 0) { + github_url <- github_url[1] + gh_username <- github_username_from_url(github_url) + gh_repo <- github_repo_from_url(github_url) + } + } + + available_sources <- c( + if (!is.null(repo)) "repo", + if (!is.null(gh_repo) && !is.null(gh_username)) "github", + 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, + Remote_URL = remote_url, + Bioc_Views = bioc_views + ) + +} + + + +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/updateme_set_sources.R b/R/updateme_set_sources.R index d9cce03..ed6208d 100644 --- a/R/updateme_set_sources.R +++ b/R/updateme_set_sources.R @@ -58,12 +58,16 @@ #' # TODO: Add .append arg? updateme_sources_set <- function(...) { - out <- imap(list(...), updateme_sources_validate) + options(updateme.sources = updateme_sources_set_impl(...)) +} + +updateme_sources_set_impl <- function(...) { + # 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"]] %||% "") - out <- set_names(sources, packages) - - options(updateme.sources = out) + set_names(sources, packages) } preferred_sources <- function(pkg) { @@ -80,10 +84,10 @@ preferred_sources <- function(pkg) { updateme_sources_validate <- function(src, pkg = NULL, throw = cli::cli_abort) { - invalid <- function() { + handle_no_sources <- function() { if (!is.null(throw)) { repos <- names(getOption("repos")) - throw(c( + throw(call = caller_call(6), c( "Invalid package source {.val {src}}.", "i" = "Package sources must be either:", " " = "1. {.val github}, to check the version on GitHub if possible", @@ -98,7 +102,7 @@ updateme_sources_validate <- function(src, pkg = NULL, throw = cli::cli_abort) { pkg_ok <- is.character(pkg) || is.null(pkg) if (!src_ok || !pkg_ok) { - return(invalid()) + return(handle_no_sources()) } if (pkg == "") @@ -118,31 +122,35 @@ updateme_sources_validate <- function(src, pkg = NULL, throw = cli::cli_abort) { if (is_valid_repo(src)) { out[["Preferred_Source"]] <- "repo" out[["Repository"]] <- src + return(out) + } - } else if (identical(src, "github")) { + if (identical(src, "github")) { out[["Preferred_Source"]] <- "github" + return(out) + } - } else if (is_valid_github_url(src)) { + if (is_valid_github_url(src)) { out[["Preferred_Source"]] <- "github" out[["Github_Username"]] <- github_username_from_url(src) out[["Github_Repository"]] <- github_repo_from_url(src) - pkg <- pkg %||% out[["Github_Repository"]] + out[["Package"]] <- out[["Package"]] %||% out[["Github_Repository"]] + return(out) } - if (length(out[["Preferred_Source"]]) == 0L) { - return(invalid()) - } + handle_no_sources() - out } updateme_sources_get <- function(check = FALSE) { - opt <- getOption("updateme.sources") + opt <- as.list(getOption("updateme.sources")) - if (is.null(opt)) + if (length(opt) == 0L) return(NULL) - as.list(opt) |> + names(opt) <- names(opt) %||% rep("", length(opt)) + + opt |> imap(updateme_sources_validate, throw = if (check) cli::cli_abort) |> compact() |> unname() diff --git a/R/utils.R b/R/utils.R index eb878e3..eae73fa 100644 --- a/R/utils.R +++ b/R/utils.R @@ -7,20 +7,9 @@ env_var <- function(x) { if (identical(out, "")) NULL else out } -is_online <- function(use_curl = NULL) { - if (use_curl %||% is_installed("curl")) - return(!is.null(curl::nslookup("google.com", error = FALSE))) - - tryCatch( - { - con <- url("https://google.com") - on.exit(close(con)) - open(con) - TRUE - }, - error = function(e) FALSE, - warning = function(w) FALSE - ) +# See testthat::skip_if_offline +is_online <- function(host = "captive.apple.com") { + !is.null(curl::nslookup(host, error = FALSE)) } list_replace <- function(x, ...) { diff --git a/README.md b/README.md index 40f184b..29f04a6 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,14 @@ # updateme + +[![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-demo.gif) + +![](https://raw.githubusercontent.com/wurli/updateme/main/updateme-demo.gif) ## Installation diff --git a/_pkgdown.yml b/_pkgdown.yml new file mode 100644 index 0000000..9193bf7 --- /dev/null +++ b/_pkgdown.yml @@ -0,0 +1,6 @@ +url: https://wurli.github.io/updateme/ +template: + bootstrap: 5 + + + diff --git a/tests/testthat/test-available_version.R b/tests/testthat/test-available_version.R new file mode 100644 index 0000000..5dd0e68 --- /dev/null +++ b/tests/testthat/test-available_version.R @@ -0,0 +1,44 @@ +test_that("Description files can be read from GitHub public repos", { + + skip_if_offline() + + desc <- desc_from_github("wurli", "updateme.testpkg") + + expect_identical(desc, c( + "Package: updateme.testpkg", + "Title: For 'updateme' Testing", + "Version: 0.2.0", + "Authors@R: person(\"Jacob\", \"Scott\", email = \"jscott2718@gmail.com\", role = c(\"aut\", \"cre\"))", + "Description: This package is used in the {updateme} test suite.", + "License: MIT + file LICENSE", "Encoding: UTF-8", "Roxygen: list(markdown = TRUE)", + "RoxygenNote: 7.2.3" + )) + +}) + +test_that("Description files can be read from GitHub private repos", { + + skip_if_offline() + + # NOTE: This PAT is 'fine-grained' and gives read-only access for a single + # repo containing an empty package {updateme.testpkg.private}. + withr::local_envvar(list( + UPDATEME_GITHUB_PAT = paste0( + "github_pat_11AEFKREY0bCtmUEn37oc7_MhIhcuvThFPzQ9Nmd74Bf", + "kydKkvUgKx2NvYw9DEYtw7WUJ7VPW6UnvQVppv" + ) + )) + + desc <- desc_from_github("wurli", "updateme.testpkg.private") + + expect_identical(desc, c( + "Package: updateme.testpkg.private", + "Title: For 'updateme' Testing", + "Version: 0.2.0", + "Authors@R: person(\"Jacob\", \"Scott\", email = \"jscott2718@gmail.com\", role = c(\"aut\", \"cre\"))", + "Description: This package is used in the {updateme} test suite.", + "License: MIT + file LICENSE", "Encoding: UTF-8", "Roxygen: list(markdown = TRUE)", + "RoxygenNote: 7.2.3" + )) + +}) diff --git a/tests/testthat/test-list_replace.R b/tests/testthat/test-list_replace.R new file mode 100644 index 0000000..115ac63 --- /dev/null +++ b/tests/testthat/test-list_replace.R @@ -0,0 +1,8 @@ +test_that("list_replace works", { + + expect_identical(list_replace(list(a = 1), a = 2), list(a = 2)) + expect_identical(list_replace(list(a = 1), b = 2), list(a = 1)) + expect_identical(list_replace(list(), a = 1), list()) + expect_identical(list_replace(list(a = 1, b = 2), b = 1, a = 2, c = 3), list(a = 2, b = 1)) + +}) diff --git a/tests/testthat/test-package_installation_info.R b/tests/testthat/test-package_installation_info.R new file mode 100644 index 0000000..8d7b568 --- /dev/null +++ b/tests/testthat/test-package_installation_info.R @@ -0,0 +1,240 @@ +test_that("package_installation_info works with no available sources", { + + withr::local_temp_libpaths() + libpath <- .libPaths()[1] + + pkg_dir <- file.path(libpath, "foo") + dir.create(pkg_dir) + + writeLines( + con = file.path(pkg_dir, "DESCRIPTION"), + paste( + sep = "\n", + 'Package: foo', + 'Type: Package', + 'Version: 0.1.0' + ) + ) + + info <- package_installation_info("foo") + + expect_identical(info, list( + Available_Sources = NULL, + Package = "foo", + Version_Installed = "0.1.0", + Repository = NULL, + Github_Username = NULL, + Github_Repository = NULL, + Remote_URL = NULL, + Bioc_Views = NULL + )) + +}) + +test_that("package_installation_info works with repos packages", { + + withr::local_temp_libpaths() + libpath <- .libPaths()[1] + + pkg_dir <- file.path(libpath, "foo") + dir.create(pkg_dir) + + writeLines( + con = file.path(pkg_dir, "DESCRIPTION"), + paste( + sep = "\n", + 'Package: foo', + 'Type: Package', + 'Version: 0.1.0', + "Repository: CRAN" # This is the important bit + ) + ) + + info <- package_installation_info("foo") + + expect_identical(info, list( + Available_Sources = "repo", + Package = "foo", + Version_Installed = "0.1.0", + Repository = "CRAN", + Github_Username = NULL, + Github_Repository = NULL, + Remote_URL = NULL, + Bioc_Views = NULL + )) + +}) + +test_that("package_installation_info works with desc URL field", { + + withr::local_temp_libpaths() + libpath <- .libPaths()[1] + + pkg_dir <- file.path(libpath, "foo") + dir.create(pkg_dir) + + writeLines( + con = file.path(pkg_dir, "DESCRIPTION"), + paste( + sep = "\n", + 'Package: foo', + 'Type: Package', + 'Version: 0.1.0', + 'URL: https://github.com/foofyfooson/foo' # This is the important bit + ) + ) + + info <- package_installation_info("foo") + + expect_identical(info, list( + Available_Sources = "github", + Package = "foo", + Version_Installed = "0.1.0", + Repository = NULL, + Github_Username = "foofyfooson", + Github_Repository = "foo", + Remote_URL = NULL, + Bioc_Views = NULL + )) + +}) + +test_that("package_installation_info works with desc GithubRepo/GithubUsername fields", { + + withr::local_temp_libpaths() + libpath <- .libPaths()[1] + + pkg_dir <- file.path(libpath, "foo") + dir.create(pkg_dir) + + writeLines( + con = file.path(pkg_dir, "DESCRIPTION"), + paste( + sep = "\n", + 'Package: foo', + 'Type: Package', + 'Version: 0.1.0', + 'GithubUsername: foofyfooson', + 'GithubRepo: foo' + ) + ) + + info <- package_installation_info("foo") + + expect_identical(info, list( + Available_Sources = "github", + Package = "foo", + Version_Installed = "0.1.0", + Repository = NULL, + Github_Username = "foofyfooson", + Github_Repository = "foo", + Remote_URL = NULL, + Bioc_Views = NULL + )) + +}) + +test_that("package_installation_info works with desc RemoteRepo/RemoteUsername fields", { + + withr::local_temp_libpaths() + libpath <- .libPaths()[1] + + pkg_dir <- file.path(libpath, "foo") + dir.create(pkg_dir) + + writeLines( + con = file.path(pkg_dir, "DESCRIPTION"), + paste( + sep = "\n", + 'Package: foo', + 'Type: Package', + 'Version: 0.1.0', + 'RemoteUsername: foofyfooson', + 'RemoteRepo: foo' + ) + ) + + info <- package_installation_info("foo") + + expect_identical(info, list( + Available_Sources = "github", + Package = "foo", + Version_Installed = "0.1.0", + Repository = NULL, + Github_Username = "foofyfooson", + Github_Repository = "foo", + Remote_URL = NULL, + Bioc_Views = NULL + )) + +}) + +test_that("package_installation_info works with desc biocViews fields", { + + withr::local_temp_libpaths() + libpath <- .libPaths()[1] + + pkg_dir <- file.path(libpath, "foo") + dir.create(pkg_dir) + + writeLines( + con = file.path(pkg_dir, "DESCRIPTION"), + paste( + sep = "\n", + 'Package: foo', + 'Type: Package', + 'Version: 0.1.0', + "biocViews: Infrastructure" + ) + ) + + info <- package_installation_info("foo") + + expect_identical(info, list( + Available_Sources = "bioc", + Package = "foo", + Version_Installed = "0.1.0", + Repository = NULL, + Github_Username = NULL, + Github_Repository = NULL, + Remote_URL = NULL, + Bioc_Views = "Infrastructure" + )) + +}) + +test_that("package_installation_info works with multiple sources", { + + withr::local_temp_libpaths() + libpath <- .libPaths()[1] + + pkg_dir <- file.path(libpath, "foo") + dir.create(pkg_dir) + + writeLines( + con = file.path(pkg_dir, "DESCRIPTION"), + paste( + sep = "\n", + 'Package: foo', + 'Type: Package', + 'Version: 0.1.0', + "Repository: CRAN", + "biocViews: Infrastructure", + 'URL: https://github.com/foofyfooson/foo' + ) + ) + + info <- package_installation_info("foo") + + expect_identical(info, list( + Available_Sources = c("repo", "github", "bioc"), + Package = "foo", + Version_Installed = "0.1.0", + Repository = "CRAN", + Github_Username = "foofyfooson", + Github_Repository = "foo", + Remote_URL = NULL, + Bioc_Views = "Infrastructure" + )) + +}) diff --git a/tests/testthat/test-updateme_sources.R b/tests/testthat/test-updateme_sources.R new file mode 100644 index 0000000..2901742 --- /dev/null +++ b/tests/testthat/test-updateme_sources.R @@ -0,0 +1,156 @@ +test_that("updateme_sources_set() fails correctly", { + old_opts <- options() + withr::defer(options(old_opts)) + + options(repos = c( + CRAN = "https://cloud.r-project.org", + tidyverse = "https://tidyverse.r-universe.dev", + rlib = "https://r-lib.r-universe.dev" + )) + + 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')) + +}) + +test_that("updateme_sources_set() sets options correctly", { + old_opts <- options() + withr::defer(options(old_opts)) + + options(repos = c( + CRAN = "https://cloud.r-project.org", + tidyverse = "https://tidyverse.r-universe.dev", + rlib = "https://r-lib.r-universe.dev" + )) + + expect_equal( + updateme_sources_set_impl("CRAN"), + dots_list("CRAN") + ) + expect_equal( + updateme_sources_set_impl(dplyr = "CRAN"), + list(dplyr = "CRAN") + ) + expect_equal( + updateme_sources_set_impl("CRAN", dplyr = "github", "rlib"), + list("CRAN", dplyr = "github", "rlib") + ) + expect_equal( + updateme_sources_set_impl("rlib", "CRAN"), + dots_list("rlib", "CRAN") + ) + + expect_equal( + updateme_sources_set_impl(dplyr = "https://github.com/tidyverse/dplyr"), + list(dplyr = "https://github.com/tidyverse/dplyr") + ) + + # 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", { + + old_opts <- options() + withr::defer(options(old_opts)) + + options( + repos = c( + CRAN = "https://cloud.r-project.org", + tidyverse = "https://tidyverse.r-universe.dev", + rlib = "https://r-lib.r-universe.dev" + ) + ) + + options(updateme.sources = NULL) + expect_identical(updateme_sources_get(), NULL) + + options(updateme.sources = list()) + expect_identical(updateme_sources_get(), NULL) + + options(updateme.sources = "tidyverse") + expect_identical(updateme_sources_get(), list( + list( + Preferred_Source = "repo", + Package = NULL, + Source_Name = "tidyverse", + Repository = "tidyverse", + Github_Username = NULL, + Github_Repository = NULL, + Remote_URL = NULL, + Bioc_Views = NULL + ) + )) + + # Same as above, but using list() + options(updateme.sources = list("tidyverse")) + expect_identical(updateme_sources_get(), list( + list( + Preferred_Source = "repo", + Package = NULL, + Source_Name = "tidyverse", + Repository = "tidyverse", + Github_Username = NULL, + Github_Repository = NULL, + Remote_URL = NULL, + Bioc_Views = NULL + ) + )) + + options(updateme.sources = list("github")) + expect_identical(updateme_sources_get(), list( + list( + Preferred_Source = "github", + Package = NULL, + Source_Name = "github", + Repository = NULL, + Github_Username = NULL, + Github_Repository = NULL, + Remote_URL = NULL, + Bioc_Views = NULL + ) + )) + + options(updateme.sources = list(dplyr = "https://github.com/tidyverse/dplyr")) + expect_identical(updateme_sources_get(), list( + list( + Preferred_Source = "github", + Package = "dplyr", + Source_Name = "https://github.com/tidyverse/dplyr", + Repository = NULL, + Github_Username = "tidyverse", + Github_Repository = "dplyr", + Remote_URL = NULL, + Bioc_Views = NULL + ) + )) + + options(updateme.sources = list("CRAN", "https://github.com/tidyverse/dplyr")) + expect_identical(updateme_sources_get(), list( + list( + Preferred_Source = "repo", + Package = NULL, + Source_Name = "CRAN", + Repository = "CRAN", + Github_Username = NULL, + Github_Repository = NULL, + Remote_URL = NULL, + Bioc_Views = NULL + ), + list( + Preferred_Source = "github", + Package = "dplyr", + Source_Name = "https://github.com/tidyverse/dplyr", + Repository = NULL, + Github_Username = "tidyverse", + Github_Repository = "dplyr", + Remote_URL = NULL, + Bioc_Views = NULL + ) + )) + +}) diff --git a/updateme.Rproj b/updateme.Rproj index 9031c9c..56b73ef 100644 --- a/updateme.Rproj +++ b/updateme.Rproj @@ -1,22 +1,22 @@ -Version: 1.0 - -RestoreWorkspace: Default -SaveWorkspace: Default -AlwaysSaveHistory: Default - -EnableCodeIndexing: Yes -UseSpacesForTab: Yes -NumSpacesForTab: 2 -Encoding: UTF-8 - -RnwWeave: Sweave -LaTeX: pdfLaTeX - -AutoAppendNewline: Yes -StripTrailingWhitespace: Yes - -BuildType: Package -PackageUseDevtools: Yes -PackageInstallArgs: --no-multiarch --with-keep.source - -UseNativePipeOperator: Yes +Version: 1.0 + +RestoreWorkspace: Default +SaveWorkspace: Default +AlwaysSaveHistory: Default + +EnableCodeIndexing: Yes +UseSpacesForTab: Yes +NumSpacesForTab: 2 +Encoding: UTF-8 + +RnwWeave: Sweave +LaTeX: pdfLaTeX + +AutoAppendNewline: Yes +StripTrailingWhitespace: Yes + +BuildType: Package +PackageUseDevtools: Yes +PackageInstallArgs: --no-multiarch --with-keep.source + +UseNativePipeOperator: Yes