├── inst └── extdata │ └── promptUser ├── LICENSE ├── .travis.yml ├── NAMESPACE ├── bin ├── render.R ├── build.R ├── release.R └── pre-commit ├── .Rbuildignore ├── DESCRIPTION ├── R ├── template.R ├── autoload.R ├── toProfile.R ├── prioritize.R ├── needs-package.R └── needs.R ├── man ├── needs-package.Rd ├── toProfile.Rd ├── prioritize.Rd └── needs.Rd ├── Makefile ├── README.md ├── readme-src.Rmd └── needs.R /inst/extdata/promptUser: -------------------------------------------------------------------------------- 1 | 1 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2016 2 | COPYRIGHT HOLDER: Josh Katz 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: r 2 | cache: packages 3 | warnings_are_errors: true 4 | sudo: required 5 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(needs) 4 | export(prioritize) 5 | export(toProfile) 6 | -------------------------------------------------------------------------------- /bin/render.R: -------------------------------------------------------------------------------- 1 | library(rmarkdown) 2 | 3 | render("readme-src.Rmd", output_file = "README.md", 4 | html_document(keep_md = T, variant = "markdown_github"), 5 | quiet = T) 6 | -------------------------------------------------------------------------------- /bin/build.R: -------------------------------------------------------------------------------- 1 | 2 | ### write needs.R ### 3 | source("R/needs.R") 4 | source("R/toProfile.R") 5 | source("R/template.R") 6 | 7 | toProfile(append = F) 8 | invisible(file.rename(".Rprofile", "needs.R")) 9 | -------------------------------------------------------------------------------- /bin/release.R: -------------------------------------------------------------------------------- 1 | library(devtools) 2 | 3 | options(repos = "https://cran.rstudio.com/") 4 | check(cran = TRUE, check_version = TRUE, manual = TRUE) 5 | write(1, file = "inst/extdata/promptUser") 6 | release(check = F) 7 | -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^\.Rprofile$ 4 | ^.*ignore$ 5 | 6 | ^node_modules/ 7 | ^_id$ 8 | ^bin$ 9 | ^Makefile$ 10 | ^needs\.R$ 11 | ^package\.json$ 12 | ^working* 13 | ^cran-comments 14 | ^NEWS 15 | ^README 16 | ^cran-comments\.md$ 17 | ^revdep$ 18 | ^\.travis\.yml$ 19 | -------------------------------------------------------------------------------- /bin/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if make -q; then 4 | if grep -q 1 inst/extdata/promptUser; then 5 | exit 0 6 | else 7 | echo -e "\033[31merror\033[0m: promptUser flag reset. Run make." 8 | exit 1 9 | fi 10 | else 11 | echo -e "\033[31merror\033[0m: You must run \033[1mmake\033[0m before committing!" 12 | exit 1 13 | fi 14 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: needs 2 | Type: Package 3 | Title: Attaches and Installs Packages 4 | Version: 0.0.3 5 | Authors@R: person("Josh", "Katz", email = "josh.katz@nytimes.com", role = c("aut", "cre")) 6 | Description: A simple function for easier package loading and auto-installation. 7 | URL: https://github.com/joshkatz/needs 8 | BugReports: https://github.com/joshkatz/needs/issues 9 | Depends: 10 | R (>= 3.2.0) 11 | Imports: 12 | utils 13 | License: MIT + file LICENSE 14 | LazyData: TRUE 15 | RoxygenNote: 5.0.1 16 | -------------------------------------------------------------------------------- /R/template.R: -------------------------------------------------------------------------------- 1 | template <- list( 2 | 3 | render = function(name, contents) { 4 | insert <- function(text, key) { 5 | target <- sprintf("\\{\\{#%s\\}\\}", key) 6 | out <<- gsub(target, text, out) 7 | } 8 | out <- template[[as.character(name)]] 9 | mapply(insert, contents, names(contents)) 10 | out 11 | }, 12 | 13 | parser = 'parser <- function(...) { 14 | {{#parse}} 15 | grouped\n}', 16 | 17 | profile = 'tryCatch(needs(), error = function(e) { 18 | while (".needs" %in% search()) detach(.needs) 19 | .needs <- new.env(parent = .GlobalEnv) 20 | .needs$needs <- function(...) {{#body}} 21 | 22 | # attach to the search path 23 | attach(.needs)\n})\n' 24 | 25 | ) 26 | -------------------------------------------------------------------------------- /man/needs-package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/needs-package.R 3 | \docType{package} 4 | \name{needs-package} 5 | \alias{needs-package} 6 | \title{Easier package loading / auto-installation} 7 | \description{ 8 | \strong{needs} is a simple R function to make package loading / 9 | installation hassle-free --- use it in place of \code{library} to attach 10 | packages and automatically install any that are missing. You can also supply 11 | a minimum version number, and it will update old packages as needed. No more 12 | changing your code to reinstall packages every time you update R --- 13 | \code{needs} does it for you. 14 | } 15 | \author{ 16 | Josh Katz 17 | } 18 | \references{ 19 | Source repo: \url{http://www.github.com/joshkatz/needs} 20 | } 21 | 22 | -------------------------------------------------------------------------------- /man/toProfile.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/toProfile.R 3 | \name{toProfile} 4 | \alias{toProfile} 5 | \title{Append to Rprofile in current working directory.} 6 | \usage{ 7 | toProfile(dir = NULL, append = T) 8 | } 9 | \arguments{ 10 | \item{dir}{Target directory for Rprofile. Defaults to working directory.} 11 | 12 | \item{append}{Whether to append to the current Rprofile, if one exists. Set 13 | to false to overwrite.} 14 | } 15 | \description{ 16 | Exports the package functionality to .Rprofile in the current working 17 | directory. Now the same code you run can be run on another system without 18 | requiring any extra installation or throwing errors for uninstalled packages. 19 | } 20 | \examples{ 21 | \dontrun{ 22 | toProfile() 23 | } 24 | 25 | } 26 | 27 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GENERATED_FILES = \ 2 | .git/hooks/pre-commit \ 3 | .git/hooks/post-rewrite \ 4 | man \ 5 | needs.R \ 6 | README.md 7 | 8 | SOURCE_FILES = R/*.R 9 | 10 | all: $(GENERATED_FILES) $(SOURCE_FILES) 11 | 12 | clean: 13 | rm -rf $(GENERATED_FILES) 14 | 15 | MAKEFLAGS += --no-builtin-rules 16 | .SUFFIXES: 17 | 18 | .PHONY: all clean check 19 | 20 | .SECONDARY: 21 | 22 | .git/hooks/pre-commit .git/hooks/post-rewrite: 23 | ln -sf ../../bin/pre-commit $@ 24 | chmod u+x $@ 25 | 26 | check: 27 | Rscript -e "devtools::check(check_version = T, cleanup = F)" 28 | echo 1 > inst/extdata/promptUser 29 | 30 | man: $(SOURCE_FILES) 31 | mkdir -p man 32 | Rscript --vanilla -e "devtools::document()" 33 | 34 | man/%.Rd: 35 | make man 36 | 37 | README.md: readme-src.Rmd 38 | Rscript --vanilla bin/render.R 39 | 40 | needs.R: $(SOURCE_FILES) 41 | Rscript --vanilla bin/build.R 42 | -------------------------------------------------------------------------------- /R/autoload.R: -------------------------------------------------------------------------------- 1 | autoload <- function(flag) { 2 | sysfile <- system.file("extdata", "promptUser", package = "needs") 3 | siteProfile <- if (is.na(Sys.getenv("R_PROFILE", unset = NA))) { 4 | file.path(Sys.getenv("R_HOME"), "etc", "Rprofile.site") 5 | } else { 6 | Sys.getenv("R_PROFILE") 7 | } 8 | 9 | if (!file.exists(siteProfile)) { 10 | file.create(siteProfile) 11 | } 12 | cxn <- file(siteProfile) 13 | lines <- readLines(cxn) 14 | 15 | if (flag) { 16 | if (!any(grepl("^[:blank:]*autoload\\(\"needs\", \"needs\"\\)", lines))) { 17 | write('\n\nautoload("needs", "needs")\n\n', file = siteProfile, append = T) 18 | } 19 | } else { 20 | lines[grepl("^[:blank:]*autoload\\(\"needs\", \"needs\"\\)", lines)] <- "" 21 | k <- write(paste(lines, collapse = "\n"), file = siteProfile, append = F) 22 | } 23 | 24 | close(cxn) 25 | options(needs.promptUser = F) 26 | write(0, file = sysfile) 27 | 28 | return(flag) 29 | } 30 | -------------------------------------------------------------------------------- /R/toProfile.R: -------------------------------------------------------------------------------- 1 | #' Append to Rprofile in current working directory. 2 | #' 3 | #' Exports the package functionality to .Rprofile in the current working 4 | #' directory. Now the same code you run can be run on another system without 5 | #' requiring any extra installation or throwing errors for uninstalled packages. 6 | #' 7 | #' @param dir Target directory for Rprofile. Defaults to working directory. 8 | #' 9 | #' @param append Whether to append to the current Rprofile, if one exists. Set 10 | #' to false to overwrite. 11 | #' 12 | #' @export 13 | #' 14 | #' @examples 15 | #' \dontrun{ 16 | #' toProfile() 17 | #' } 18 | #' 19 | 20 | toProfile <- function(dir = NULL, append = T) { 21 | contents <- list(body = paste(c("", deparse(body(needs))), collapse = "\n")) 22 | profileText <- template$render("profile", contents) 23 | dir <- ifelse(is.null(dir), getwd(), dir) 24 | path <- file.path(dirname(dir), basename(dir), ".Rprofile") 25 | cat(profileText, file = path, append = append) 26 | } 27 | -------------------------------------------------------------------------------- /man/prioritize.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/prioritize.R 3 | \name{prioritize} 4 | \alias{prioritize} 5 | \title{Re-attach packages to prevent masking} 6 | \usage{ 7 | prioritize(...) 8 | } 9 | \arguments{ 10 | \item{...}{Packages, given as unquoted names or character strings. Earlier 11 | arguments will be attached later (and therefore get a higher priority).} 12 | } 13 | \description{ 14 | \code{prioritize} detaches packages from the search path, then 15 | re-attaches them, placing them at the beginning of the search path to 16 | prevent masking. This allows for the loading of packages with conflicting 17 | function names in any order. 18 | } 19 | \details{ 20 | If you find yourself calling this function a lot, you're probably 21 | doing something wrong. 22 | } 23 | \examples{ 24 | \dontrun{ 25 | 26 | # loading plyr after dplyr causes badness 27 | needs(dplyr, plyr) 28 | 29 | # prioritize the functions in dplyr 30 | prioritize(dplyr) 31 | 32 | } 33 | 34 | } 35 | 36 | -------------------------------------------------------------------------------- /R/prioritize.R: -------------------------------------------------------------------------------- 1 | #' Re-attach packages to prevent masking 2 | #' 3 | #' @description \code{prioritize} detaches packages from the search path, then 4 | #' re-attaches them, placing them at the beginning of the search path to 5 | #' prevent masking. This allows for the loading of packages with conflicting 6 | #' function names in any order. 7 | #' 8 | #' @param ... Packages, given as unquoted names or character strings. Earlier 9 | #' arguments will be attached later (and therefore get a higher priority). 10 | #' 11 | #' @details If you find yourself calling this function a lot, you're probably 12 | #' doing something wrong. 13 | #' 14 | #' @examples 15 | #' \dontrun{ 16 | #' 17 | #' # loading plyr after dplyr causes badness 18 | #' needs(dplyr, plyr) 19 | #' 20 | #' # prioritize the functions in dplyr 21 | #' prioritize(dplyr) 22 | #' 23 | #' } 24 | #' 25 | #' @export 26 | #' 27 | 28 | prioritize <- function(...) { 29 | if (missing(...)) return(invisible()) 30 | pkgs <- as.list(substitute(list(...)))[-1] 31 | pos <- if (is.null(names(pkgs))) { 32 | rep(F, length(pkgs)) 33 | } else { 34 | nchar(names(pkgs)) > 0 35 | } 36 | 37 | for (pkg in paste0("package:", pkgs[!pos])) { 38 | while (pkg %in% search()) { 39 | detach(pkg, character.only = T, force = T) 40 | } 41 | } 42 | do.call(needs, rev(pkgs[!pos])) 43 | } 44 | 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | # needs 6 | [![CRAN_Status_Badge](http://www.r-pkg.org/badges/version/needs)](http://cran.r-project.org/package=needs) 7 | 8 | `needs` is a simple R package to make package loading / installation hassle-free — use it in place of `library` to attach packages and automatically install any that are missing. You can also supply a minimum version number, and it will update old packages as needed. No more changing your code to reinstall packages every time you update R — `needs` does it for you. 9 | 10 | 11 | ```r 12 | install.packages("needs") 13 | # for the dev version: 14 | # devtools::install_github("joshkatz/needs", ref = "development") 15 | library(needs) 16 | 17 | # answer "yes" when prompted, and you will never have 18 | # to type library or install.packages again. hooray. 19 | ``` 20 | 21 | ### Usage 22 | Once installed, use just as you would `library`. With the added bonus of being able to give multiple unquoted arguments in one single function call. Specify a required package version with a pairlist: 23 | 24 | 25 | ```r 26 | needs(foo, 27 | bar = "0.9.1", 28 | baz = "0.4.3") 29 | ``` 30 | 31 | 32 | ### Rprofile 33 | `needs` can help make code-sharing easier. In your project directory: 34 | 35 | ```r 36 | needs::toProfile() 37 | ``` 38 | This extracts the package contents and appends it to the Rprofile in your working directory. Now if someone else clones your project, your code runs without requiring any extra installation or throwing errors for uninstalled packages. 39 | -------------------------------------------------------------------------------- /man/needs.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/needs.R 3 | \name{needs} 4 | \alias{needs} 5 | \title{Attach/install packages} 6 | \usage{ 7 | needs(..., .printConflicts = F) 8 | } 9 | \arguments{ 10 | \item{...}{Packages, given as unquoted names or character strings. Specify a 11 | required package version as \code{package = "version"}.} 12 | 13 | \item{.printConflicts}{Logical, specifying whether to print a summary of 14 | objects that exist in multiple places on the search path along with their 15 | respective locations. Set to \code{TRUE} to identify any masked functions. 16 | Objects in the base package and the global environment are ignored. 17 | Defaults to \code{FALSE}.} 18 | } 19 | \description{ 20 | \code{needs} loads and attaches packages, automatically 21 | installing (and attaching) any it can't find in your libraries. It accepts 22 | any number of arguments, given as names or character strings. Optionally, 23 | supply a minimum version on a per-package basis to update old packages as 24 | needed. 25 | } 26 | \details{ 27 | Recommended use is to allow the function to autoload when prompted 28 | the first time the package is loaded interactively. To change this setting 29 | later, run \code{needs:::autoload(TRUE)} or \code{needs:::autoload(FALSE)} 30 | to turn autoloading on or off, respectively. 31 | } 32 | \examples{ 33 | \dontrun{ 34 | needs() # returns NULL 35 | 36 | needs(foo, bar) 37 | 38 | # require a minimum version 39 | needs(foo, 40 | bar = "0.9.1", 41 | baz = "0.4.3") 42 | 43 | 44 | } 45 | 46 | } 47 | \seealso{ 48 | \code{\link{needs-package}} 49 | } 50 | 51 | -------------------------------------------------------------------------------- /readme-src.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: 3 | html_document: 4 | keep_md: yes 5 | variant: markdown_github 6 | --- 7 | 8 | 9 | ```{r, echo = FALSE} 10 | knitr::opts_chunk$set( 11 | collapse = TRUE, 12 | comment = "# >", 13 | fig.path = "README-" 14 | ) 15 | 16 | options(repos = "https://cran.rstudio.com/") 17 | ``` 18 | 19 | # needs 20 | [![CRAN_Status_Badge](http://www.r-pkg.org/badges/version/needs)](http://cran.r-project.org/package=needs) 21 | 22 | `needs` is a simple R package to make package loading / installation hassle-free — use it in place of `library` to attach packages and automatically install any that are missing. You can also supply a minimum version number, and it will update old packages as needed. No more changing your code to reinstall packages every time you update R — `needs` does it for you. 23 | 24 | ```{r eval = F} 25 | install.packages("needs") 26 | # for the dev version: 27 | # devtools::install_github("joshkatz/needs", ref = "development") 28 | library(needs) 29 | 30 | # answer "yes" when prompted, and you will never have 31 | # to type library or install.packages again. hooray. 32 | ``` 33 | 34 | ### Usage 35 | Once installed, use just as you would `library`. With the added bonus of being able to give multiple unquoted arguments in one single function call. Specify a required package version with a pairlist: 36 | 37 | ```{r eval = F} 38 | needs(foo, 39 | bar = "0.9.1", 40 | baz = "0.4.3") 41 | ``` 42 | 43 | 44 | ### Rprofile 45 | `needs` can help make code-sharing easier. In your project directory: 46 | ```{r eval = F} 47 | needs::toProfile() 48 | ``` 49 | This extracts the package contents and appends it to the Rprofile in your working directory. Now if someone else clones your project, your code runs without requiring any extra installation or throwing errors for uninstalled packages. 50 | -------------------------------------------------------------------------------- /R/needs-package.R: -------------------------------------------------------------------------------- 1 | #' Easier package loading / auto-installation 2 | #' 3 | #' @docType package 4 | #' @name needs-package 5 | #' @description \strong{needs} is a simple R function to make package loading / 6 | #' installation hassle-free --- use it in place of \code{library} to attach 7 | #' packages and automatically install any that are missing. You can also supply 8 | #' a minimum version number, and it will update old packages as needed. No more 9 | #' changing your code to reinstall packages every time you update R --- 10 | #' \code{needs} does it for you. 11 | #' @author Josh Katz 12 | #' @references Source repo: \url{http://www.github.com/joshkatz/needs} 13 | NULL 14 | 15 | .onLoad <- function(libname, pkgname) { 16 | while (".needs" %in% search()) detach(.needs) 17 | sysfile <- system.file("extdata", "promptUser", package = "needs") 18 | promptUser <- as.logical(scan(sysfile, quiet = T)) 19 | options(needs.promptUser = promptUser) 20 | 21 | if (getOption("needs.promptUser")) { 22 | 23 | if (interactive()) { 24 | 25 | q <- "Should `needs` load itself when it's... needed?\n (this is recommended)" 26 | choices <- sample(c("Yes", "No")) 27 | yes <- choices[utils::menu(choices, title = q)] == "Yes" 28 | 29 | if (isTRUE(yes)) { 30 | 31 | siteProfile <- if (is.na(Sys.getenv("R_PROFILE", unset = NA))) { 32 | file.path(Sys.getenv("R_HOME"), "etc", "Rprofile.site") 33 | } else { 34 | Sys.getenv("R_PROFILE") 35 | } 36 | if (!file.exists(siteProfile)) { 37 | file.create(siteProfile) 38 | } 39 | cxn <- file(siteProfile) 40 | lines <- readLines(cxn) 41 | if (!any(grepl("^[:blank:]*autoload\\(\"needs\", \"needs\"\\)", lines))) { 42 | write('\n\nautoload("needs", "needs")\n\n', file = siteProfile, append = T) 43 | } 44 | close(cxn) 45 | 46 | } 47 | 48 | options(needs.promptUser = F) 49 | write(0, file = sysfile) 50 | 51 | } 52 | } 53 | } 54 | 55 | .onAttach <- function(libname, pkgname) { 56 | if (getOption("needs.promptUser") && !interactive()) { 57 | packageStartupMessage("\nLoad `package:needs` in an interactive session to set auto-load flag\n") 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /needs.R: -------------------------------------------------------------------------------- 1 | tryCatch(needs(), error = function(e) { 2 | while (".needs" %in% search()) detach(.needs) 3 | .needs <- new.env(parent = .GlobalEnv) 4 | .needs$needs <- function(...) 5 | { 6 | needs_ <- function(...) { 7 | pkgs <- unlist(...) 8 | if (length(pkgs)) { 9 | loaded <- suppressWarnings(suppressMessages(sapply(pkgs, 10 | library, character = T, logical = T))) 11 | if (any(!loaded)) { 12 | missing <- pkgs[!loaded] 13 | cat("installing packages:n") 14 | cat(missing, sep = "n") 15 | utils::install.packages(missing, repos = "http://cran.rstudio.com/", 16 | quiet = T) 17 | } 18 | suppressWarnings(suppressMessages(sapply(pkgs, library, 19 | character = T))) 20 | } 21 | } 22 | packageInfo <- utils::installed.packages() 23 | if (!missing(...)) { 24 | pkgs <- as.list(substitute(list(...)))[-1] 25 | parsed <- if (is.null(names(pkgs))) { 26 | as.character(pkgs) 27 | } 28 | else { 29 | mapply(paste, names(pkgs), as.character(pkgs), MoreArgs = list(sep = ":")) 30 | } 31 | parts <- lapply(strsplit(parsed, "[:=(, ]+"), function(d) { 32 | d[d != ""] 33 | }) 34 | grouped <- split(parts, sapply(parts, length)) 35 | needs_(grouped$`1`) 36 | toCheck <- grouped$`2` 37 | if (length(toCheck)) { 38 | installedPackages <- packageInfo[, "Package"] 39 | needsPackage <- sapply(toCheck, `[`, 1) 40 | needsVersion <- sapply(toCheck, function(x) { 41 | gsub("[^0-9.-]+", "", x[2]) 42 | }) 43 | installed <- needsPackage %in% installedPackages 44 | needs_(needsPackage[!installed]) 45 | compared <- mapply(utils::compareVersion, needsVersion[installed], 46 | packageInfo[needsPackage[installed], "Version"]) 47 | if (any(compared == 1)) { 48 | toUpdate <- needsPackage[installed][compared == 49 | 1] 50 | cat("updating packages:n") 51 | cat(toUpdate, sep = "n") 52 | utils::update.packages(oldPkgs = toUpdate, ask = F) 53 | } 54 | needs_(needsPackage[installed]) 55 | } 56 | } 57 | if (.printConflicts) { 58 | s <- search() 59 | conflict <- conflicts(detail = T) 60 | conflict[names(conflict) %in% c("package:base", "Autoloads", 61 | ".GlobalEnv")] <- NULL 62 | tab <- table(unlist(sapply(conflict, unique))) 63 | fxns <- names(tab[tab > 1]) 64 | where <- sapply(fxns, function(f) { 65 | i <- 1 66 | while (!length(ls(pos = i, pattern = sprintf("^%s$", 67 | f)))) { 68 | i <- i + 1 69 | if (i > length(s)) 70 | break 71 | } 72 | s[i] 73 | }) 74 | if (length(where)) { 75 | df <- data.frame(FUNCTION = names(where), LOCATION = paste0(" ", 76 | where[order(names(where))]), stringsAsFactors = F) 77 | print(df, row.names = F) 78 | } 79 | } 80 | invisible() 81 | } 82 | 83 | # attach to the search path 84 | attach(.needs) 85 | }) 86 | -------------------------------------------------------------------------------- /R/needs.R: -------------------------------------------------------------------------------- 1 | #' Attach/install packages 2 | #' 3 | #' @description \code{needs} loads and attaches packages, automatically 4 | #' installing (and attaching) any it can't find in your libraries. It accepts 5 | #' any number of arguments, given as names or character strings. Optionally, 6 | #' supply a minimum version on a per-package basis to update old packages as 7 | #' needed. 8 | #' 9 | #' @param ... Packages, given as unquoted names or character strings. Specify a 10 | #' required package version as \code{package = "version"}. 11 | #' 12 | #' @param .printConflicts Logical, specifying whether to print a summary of 13 | #' objects that exist in multiple places on the search path along with their 14 | #' respective locations. Set to \code{TRUE} to identify any masked functions. 15 | #' Objects in the base package and the global environment are ignored. 16 | #' Defaults to \code{FALSE}. 17 | #' 18 | #' @details Recommended use is to allow the function to autoload when prompted 19 | #' the first time the package is loaded interactively. To change this setting 20 | #' later, run \code{needs:::autoload(TRUE)} or \code{needs:::autoload(FALSE)} 21 | #' to turn autoloading on or off, respectively. 22 | #' 23 | #' @seealso \code{\link{needs-package}} 24 | #' @export 25 | #' 26 | #' @examples 27 | #' \dontrun{ 28 | #' needs() # returns NULL 29 | #' 30 | #' needs(foo, bar) 31 | #' 32 | #' # require a minimum version 33 | #' needs(foo, 34 | #' bar = "0.9.1", 35 | #' baz = "0.4.3") 36 | #' 37 | #' 38 | #' } 39 | #' 40 | 41 | 42 | needs <- function(..., .printConflicts = F) { 43 | needs_ <- function(...) { 44 | pkgs <- unlist(...) 45 | if (length(pkgs)) { 46 | loaded <- suppressWarnings(suppressMessages( 47 | sapply(pkgs, library, character = T, logical = T))) 48 | if (any(!loaded)) { 49 | missing <- pkgs[!loaded] 50 | cat("installing packages:\n") 51 | cat(missing, sep = "\n") 52 | utils::install.packages(missing, repos = "http://cran.rstudio.com/", 53 | quiet = T) 54 | } 55 | # attach packages 56 | suppressWarnings(suppressMessages(sapply(pkgs, library, character = T))) 57 | } 58 | } 59 | 60 | packageInfo <- utils::installed.packages() 61 | 62 | #{{parse}} 63 | if (!missing(...)) { 64 | 65 | pkgs <- as.list(substitute(list(...)))[-1] 66 | parsed <- if (is.null(names(pkgs))) { 67 | as.character(pkgs) 68 | } else { 69 | mapply(paste, names(pkgs), as.character(pkgs), 70 | MoreArgs = list(sep = ":")) 71 | } 72 | parts <- lapply(strsplit(parsed, "[:=(, ]+"), function(d) { d[d != ""] }) 73 | grouped <- split(parts, sapply(parts, length)) 74 | #{{/parse}} 75 | 76 | # load latest/current version of packages 77 | needs_(grouped$`1`) 78 | 79 | # if version specified... 80 | toCheck <- grouped$`2` 81 | 82 | if (length(toCheck)) { 83 | installedPackages <- packageInfo[, "Package"] 84 | needsPackage <- sapply(toCheck, `[`, 1) 85 | needsVersion <- sapply(toCheck, function(x) { 86 | gsub("[^0-9.-]+", "", x[2]) 87 | }) 88 | 89 | installed <- needsPackage %in% installedPackages 90 | needs_(needsPackage[!installed]) 91 | 92 | compared <- mapply(utils::compareVersion, needsVersion[installed], 93 | packageInfo[needsPackage[installed], "Version"]) 94 | if (any(compared == 1)) { 95 | toUpdate <- needsPackage[installed][compared == 1] 96 | cat("updating packages:\n") 97 | cat(toUpdate, sep = "\n") 98 | utils::update.packages(oldPkgs = toUpdate, ask = F) 99 | } 100 | needs_(needsPackage[installed]) 101 | } 102 | 103 | } 104 | 105 | if (.printConflicts) { 106 | s <- search() 107 | conflict <- conflicts(detail = T) 108 | conflict[names(conflict) %in% c("package:base", "Autoloads", ".GlobalEnv")] <- NULL 109 | tab <- table(unlist(sapply(conflict, unique))) 110 | fxns <- names(tab[tab > 1]) 111 | where <- sapply(fxns, function(f) { 112 | i <- 1 113 | while (!length(ls(pos = i, pattern = sprintf("^%s$", f)))) { 114 | i <- i + 1 115 | if (i > length(s)) break 116 | } 117 | s[i] 118 | }) 119 | if (length(where)) { 120 | df <- data.frame(FUNCTION = names(where), 121 | LOCATION = paste0(" ", where[order(names(where))]), 122 | stringsAsFactors = F) 123 | print(df, row.names = F) 124 | } 125 | } 126 | 127 | invisible() 128 | } 129 | --------------------------------------------------------------------------------