├── automagic.png ├── .gitignore ├── tests ├── testthat.R └── testthat │ ├── test-get_package_details.R │ ├── test-parse_packages.R │ └── samples │ └── Simple.R ├── R ├── pipe.R ├── zzz.R ├── get_dependent_packages.R ├── automagic.R ├── update_description_file.R ├── get_package_details.R ├── install_package_guess.R ├── deps_file.R └── parse_packages.R ├── .Rbuildignore ├── man ├── pipe.Rd ├── get_dependent_packages.Rd ├── install_deps_file.Rd ├── update_description_file.Rd ├── get_package_details.Rd ├── automagic.Rd ├── make_deps_file.Rd ├── parse_packages.Rd └── install_package_guess.Rd ├── .travis.yml ├── NAMESPACE ├── automagic.Rproj ├── cran-comments.md ├── NEWS.md ├── DESCRIPTION └── README.md /automagic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nevrome/automagic/master/automagic.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | hello_shiny_tester/ 6 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(automagic) 3 | 4 | test_check("automagic") 5 | -------------------------------------------------------------------------------- /R/pipe.R: -------------------------------------------------------------------------------- 1 | #' Pipe imported from magrittr 2 | #' 3 | #' @importFrom magrittr %>% 4 | #' @name %>% 5 | #' @rdname pipe 6 | #' @export 7 | NULL 8 | -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^CRAN-RELEASE$ 2 | ^.*\.Rproj$ 3 | ^\.Rproj\.user$ 4 | .travis.yml 5 | .gitignore 6 | cran-comments.md 7 | automagic.png 8 | README.md 9 | -------------------------------------------------------------------------------- /man/pipe.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pipe.R 3 | \name{\%>\%} 4 | \alias{\%>\%} 5 | \title{Pipe imported from magrittr} 6 | \description{ 7 | Pipe imported from magrittr 8 | } 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # R for travis: see documentation at https://docs.travis-ci.com/user/languages/r 2 | 3 | language: R 4 | r: 5 | - oldrel 6 | - release 7 | cache: packages 8 | warnings_are_errors: false 9 | sudo: true 10 | os: 11 | - linux 12 | -------------------------------------------------------------------------------- /tests/testthat/test-get_package_details.R: -------------------------------------------------------------------------------- 1 | context("get_package_details") 2 | 3 | test_that("returns NULL if package is not installed",{ 4 | expect_equal( 5 | get_package_details("package.that.not.here"), 6 | NULL 7 | ) 8 | }) 9 | -------------------------------------------------------------------------------- /R/zzz.R: -------------------------------------------------------------------------------- 1 | .onLoad <- function(libname = find.package('automagic'), pkgname = 'automagic') { 2 | # CRAN Note avoidance 3 | if(getRversion() >= "2.15.1") 4 | utils::globalVariables(c('GithubUsername','GithubRepo','GithubSHA1','Repository')) 5 | invisible() 6 | } 7 | -------------------------------------------------------------------------------- /tests/testthat/test-parse_packages.R: -------------------------------------------------------------------------------- 1 | context("parse_packages") 2 | 3 | test_that("Identifies packages correctly in .R files",{ 4 | expect_equal( 5 | parse_packages("samples/Simple.R"), 6 | paste0('pkg', 1:11) 7 | ) 8 | }) 9 | 10 | test_that("Errors for non-exisiting R file",{ 11 | expect_error(parse_packages("samples/not_here.R")) 12 | }) 13 | -------------------------------------------------------------------------------- /tests/testthat/samples/Simple.R: -------------------------------------------------------------------------------- 1 | library(pkg1) 2 | library("pkg2") 3 | library('pkg3') 4 | library(pkg4); library(pkg5) 5 | library(pkg6, quietly = TRUE) 6 | 7 | require(pkg7) 8 | require("pkg8") 9 | require('pkg9', quietly = TRUE) 10 | 11 | pkg10::some_function 12 | pkg11:::some_unexported_function 13 | 14 | ## xfun::pkg_attach(c('pkg12', 'pkg13', 'pkg14')) 15 | ## pacman::p_load('pkg15') 16 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export("%>%") 4 | export(automagic) 5 | export(get_dependent_packages) 6 | export(get_package_details) 7 | export(install_deps_file) 8 | export(install_package_guess) 9 | export(make_deps_file) 10 | export(parse_packages) 11 | export(update_description_file) 12 | importFrom(magrittr,"%>%") 13 | importFrom(utils,packageDescription) 14 | -------------------------------------------------------------------------------- /automagic.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: knitr 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | 18 | BuildType: Package 19 | PackageUseDevtools: Yes 20 | PackageInstallArgs: --no-multiarch --with-keep.source 21 | PackageRoxygenize: rd,collate,namespace 22 | -------------------------------------------------------------------------------- /cran-comments.md: -------------------------------------------------------------------------------- 1 | ## Test environments 2 | 3 | - local macOS install, R 3.5.2 4 | - ubuntu 14.05.5 (on travis), R 3.5.2 and R 3.4.4 5 | - Windows Server 2008 R2 SP1 (on r-hub), R-devel, 32/64 bit 6 | - Ubuntu 16.04 (on r-hub) R-release 7 | - Fedora (on r-hub) R-devel 8 | 9 | ## R CMD check results 10 | 11 | There were no ERRORs or WARNINGs or NOTEs 12 | 13 | ## Downstream dependencies 14 | 15 | I have also run R CMD check on downstream dependencies of autoamgic and all 16 | packages passed 17 | 18 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | ## automagic v0.5.1 2 | 3 | - added back `install_package_guess`, but removed ability to installed packages from GitHub based on a guess 4 | 5 | 6 | ## automagic v0.5 7 | 8 | - Deprecated the ability to install github packages with `install_package_guess` as it was causing installation problems on older versions of R due to `githubinstall` 9 | - Fixed bug where duplicate packages would be returned if parsing more than one file in a folder 10 | - Fixed bug where deps.yaml file with no github packages would fail 11 | - Fixed bug that would duplicate packages in deps.yaml when parsing more than one file in a folder 12 | -------------------------------------------------------------------------------- /man/get_dependent_packages.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/get_dependent_packages.R 3 | \name{get_dependent_packages} 4 | \alias{get_dependent_packages} 5 | \title{get packages required to run R code} 6 | \usage{ 7 | get_dependent_packages(directory = getwd()) 8 | } 9 | \arguments{ 10 | \item{directory}{folder to search for R and Rmd files} 11 | } 12 | \value{ 13 | a vector of package names 14 | } 15 | \description{ 16 | get packages required to run R code 17 | } 18 | \details{ 19 | parses all R and Rmd files in a directory and uses \code{\link{parse_packages}} 20 | to find all R packages required for the code to run 21 | } 22 | -------------------------------------------------------------------------------- /man/install_deps_file.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/deps_file.R 3 | \name{install_deps_file} 4 | \alias{install_deps_file} 5 | \title{Install R packages from a package dependencies (\code{deps.yaml}) file} 6 | \usage{ 7 | install_deps_file(directory = getwd()) 8 | } 9 | \arguments{ 10 | \item{directory}{directory containing \code{deps.yaml} file} 11 | } 12 | \description{ 13 | Installs packages from GitHub and CRAN based on Sha1 key and version number 14 | respectively, as defined in a \code{deps.yaml} file created by 15 | \code{\link{make_deps_file}} 16 | } 17 | \seealso{ 18 | \code{\link{make_deps_file}}, \code{\link{automagic}} 19 | } 20 | -------------------------------------------------------------------------------- /man/update_description_file.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/update_description_file.R 3 | \name{update_description_file} 4 | \alias{update_description_file} 5 | \title{add missing packages to package DESCRIPTION file} 6 | \usage{ 7 | update_description_file(directory = getwd()) 8 | } 9 | \arguments{ 10 | \item{directory}{folder to search for R and Rmd files. 11 | The DESCRIPTION file is expected to be in this directory.} 12 | } 13 | \description{ 14 | add missing packages to package DESCRIPTION file 15 | } 16 | \details{ 17 | uses \code{get_dependent_packages()} to find dependencies and 18 | adds them to the description file with the desc package 19 | } 20 | -------------------------------------------------------------------------------- /man/get_package_details.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/get_package_details.R 3 | \name{get_package_details} 4 | \alias{get_package_details} 5 | \title{get package details} 6 | \usage{ 7 | get_package_details(pkg_name) 8 | } 9 | \arguments{ 10 | \item{pkg_name}{package name} 11 | } 12 | \value{ 13 | A list of package characteristics. 14 | "Package", "Repository", and "Version" for CRAN packages. 15 | "Package", "GithubUsername", "GithubRepo", "GithubRef", and "GithubSHA1" for Github packages. 16 | } 17 | \description{ 18 | Uses \code{packageDescription} to get details about given package from R library on local machine. 19 | Currently only supports CRAN and GitHub packages 20 | } 21 | -------------------------------------------------------------------------------- /man/automagic.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/automagic.R 3 | \name{automagic} 4 | \alias{automagic} 5 | \title{Automagically install all required R packages} 6 | \usage{ 7 | automagic(directory = getwd()) 8 | } 9 | \arguments{ 10 | \item{directory}{folder to search for R and Rmd files} 11 | } 12 | \description{ 13 | Searches a given directory for all R and R Markdown files, parses them for 14 | required packages and attempts to install them from CRAN. More importantly, 15 | if a `deps.yaml` file was made using \code{\link{make_deps_file}}, automagic 16 | will use this rather than try to install based on a best guess. 17 | } 18 | \seealso{ 19 | \code{\link{install_package_guess}}, \code{\link{parse_packages}} 20 | } 21 | -------------------------------------------------------------------------------- /man/make_deps_file.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/deps_file.R 3 | \name{make_deps_file} 4 | \alias{make_deps_file} 5 | \title{Make a package dependencies (\code{deps.yaml}) file} 6 | \usage{ 7 | make_deps_file(directory = getwd()) 8 | } 9 | \arguments{ 10 | \item{directory}{directory containing R code to parse} 11 | } 12 | \description{ 13 | This function parses R code for required packages using 14 | \code{\link{parse_packages}} and then queries the R package library to 15 | determine the exact source and version of each package to install. 16 | Currently, only CRAN and GitHub packages are supported. 17 | Install packages from the `deps.yaml` file using 18 | \code{\link{automagic}{install_deps_file}} 19 | } 20 | \seealso{ 21 | \code{\link{automagic}} 22 | } 23 | -------------------------------------------------------------------------------- /R/get_dependent_packages.R: -------------------------------------------------------------------------------- 1 | #' get packages required to run R code 2 | #' 3 | #' @details parses all R and Rmd files in a directory and uses \code{\link{parse_packages}} 4 | #' to find all R packages required for the code to run 5 | #' 6 | #' @param directory folder to search for R and Rmd files 7 | #' 8 | #' @return a vector of package names 9 | #' @export 10 | get_dependent_packages <- function(directory = getwd()) { 11 | fls <- list.files(path=directory,pattern='^.*\\.R$|^.*\\.Rmd$', 12 | full.names=TRUE,recursive=TRUE) 13 | pkg_names <- unlist(sapply(fls,parse_packages)) 14 | pkg_names <- unique(pkg_names) 15 | if (length(pkg_names)==0) { 16 | message('warning: no packages found in specified directory') 17 | return(invisible(NULL)) 18 | } 19 | return(unname(pkg_names)) 20 | } 21 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: automagic 2 | Type: Package 3 | Title: Automagically Document and Install Packages Necessary to Run R Code 4 | Version: 0.5.1 5 | Authors@R: c( 6 | person("Cole", "Brokamp", 7 | email = "cole.brokamp@gmail.com", role = c("aut", "cre")), 8 | person("Steph", "Locke", role = "ctb")) 9 | Description: Parse R code in a given directory for R packages and attempt to install them from CRAN or GitHub. Optionally use a dependencies file for tighter control over which package versions to install. 10 | License: GPL 11 | Encoding: UTF-8 12 | LazyData: true 13 | RoxygenNote: 6.1.1 14 | Depends: 15 | R (>= 3.1.0) 16 | Imports: 17 | desc, 18 | dplyr, 19 | formatR, 20 | knitr, 21 | magrittr, 22 | purrr, 23 | remotes, 24 | yaml 25 | URL: https://github.com/cole-brokamp/automagic 26 | BugReports: https://github.com/cole-brokamp/automagic/issues 27 | Suggests: 28 | testthat 29 | -------------------------------------------------------------------------------- /R/automagic.R: -------------------------------------------------------------------------------- 1 | #' Automagically install all required R packages 2 | #' 3 | #' Searches a given directory for all R and R Markdown files, parses them for 4 | #' required packages and attempts to install them from CRAN. More importantly, 5 | #' if a `deps.yaml` file was made using \code{\link{make_deps_file}}, automagic 6 | #' will use this rather than try to install based on a best guess. 7 | #' 8 | #' @param directory folder to search for R and Rmd files 9 | #' 10 | #' @export 11 | #' @seealso \code{\link{install_package_guess}}, \code{\link{parse_packages}} 12 | 13 | 14 | 15 | automagic <- function(directory = getwd()) { 16 | if (file.exists(file.path(directory,'deps.yaml'))) { 17 | message('\n\ninstalling from deps.yaml file at\n', 18 | paste0(file.path(directory,'deps.yaml')),'\n\n') 19 | automagic::install_deps_file(directory=directory) 20 | } else { 21 | message('no deps.yaml file found in specified directory') 22 | message('parsing code and attempting to install packages from CRAN') 23 | get_dependent_packages(directory) %>% install_package_guess() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /R/update_description_file.R: -------------------------------------------------------------------------------- 1 | #' add missing packages to package DESCRIPTION file 2 | #' 3 | #' @details uses \code{get_dependent_packages()} to find dependencies and 4 | #' adds them to the description file with the desc package 5 | #' 6 | #' @param directory folder to search for R and Rmd files. 7 | #' The DESCRIPTION file is expected to be in this directory. 8 | #' 9 | #' @export 10 | update_description_file <- function(directory = getwd()) { 11 | description_file_path <- file.path(directory, "DESCRIPTION") 12 | # check if a descripton file is available 13 | if (!file.exists(description_file_path)) { 14 | stop("There is no DESCRIPTION file in this directory.") 15 | } 16 | # get dependent packages 17 | dependent_packages <- get_dependent_packages(directory) 18 | # get description object 19 | desc <- desc::description$new(description_file_path) 20 | # add packages to description object 21 | purrr::walk( 22 | .x = dependent_packages, 23 | .f = function(x, y) { 24 | y$set_dep(x) 25 | }, 26 | desc 27 | ) 28 | # write description object to file system 29 | desc$write(description_file_path) 30 | } 31 | -------------------------------------------------------------------------------- /R/get_package_details.R: -------------------------------------------------------------------------------- 1 | #' get package details 2 | #' 3 | #' Uses \code{packageDescription} to get details about given package from R library on local machine. 4 | #' Currently only supports CRAN and GitHub packages 5 | #' 6 | #' @param pkg_name package name 7 | #' 8 | #' @return A list of package characteristics. 9 | #' "Package", "Repository", and "Version" for CRAN packages. 10 | #' "Package", "GithubUsername", "GithubRepo", "GithubRef", and "GithubSHA1" for Github packages. 11 | #' @importFrom utils packageDescription 12 | #' @export 13 | 14 | get_package_details <- function(pkg_name) { 15 | if (!requireNamespace(pkg_name, quietly = TRUE)){ 16 | warning('skipping ', pkg_name, ' because it is not installed') 17 | return(invisible(NULL)) 18 | } 19 | pkg_d <- packageDescription(pkg_name) 20 | is.cran <- !is.null(pkg_d$Repository) && pkg_d$Repository == 'CRAN' 21 | is.github <- !is.null(pkg_d$GithubRepo) 22 | is.base <- !is.null(pkg_d$Priority) && pkg_d$Priority == 'base' 23 | if (!is.cran & !is.github & !is.base) stop('CRAN or GitHub info for ',pkg_name, 24 | ' not found. Other packages repos are not supported.', 25 | call.=FALSE) 26 | if (is.cran) return(pkg_d[c('Package','Repository','Version')]) 27 | if (is.github) return(pkg_d[c('Package','GithubUsername','GithubRepo','GithubRef','GithubSHA1')]) 28 | } 29 | 30 | -------------------------------------------------------------------------------- /man/parse_packages.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/parse_packages.R 3 | \name{parse_packages} 4 | \alias{parse_packages} 5 | \title{Parse R code for required packages} 6 | \usage{ 7 | parse_packages(fl) 8 | } 9 | \arguments{ 10 | \item{fl}{file to parse for required package names} 11 | } 12 | \value{ 13 | a vector of package names as character strings 14 | } 15 | \description{ 16 | Parses an R or R Markdown file for the package names that would be required to run the code. 17 | } 18 | \details{ 19 | This function uses regular expressions to search through a file 20 | containing R code to find required package names. It extracts not only 21 | package names denoted by \code{\link[base]{library}} and \code{\link[base]{require}}, but also 22 | packages not attached to the global namespace, but are still called with 23 | \code{\link[base]{::}} or \code{\link[base]{:::}}. 24 | 25 | Because it relies on regular expressions, it assumes all packages adhere to 26 | the valid CRAN package name rules (contain only ASCII letters, numbers, and 27 | dot; have at least two characters and start with a letter and not end it a 28 | dot). Code is also tidying internally, making the code more predictable and 29 | easier to parse (removes comments, adds whitespace around operators, etc). 30 | R Markdown files are also supported by extracting only R code using 31 | \code{\link[knitr]{purl}}. 32 | } 33 | \examples{ 34 | \dontrun{ 35 | cat('library(ggplot2)\\n # library(curl)\\n require(leaflet)\\n CB::date_print()\\n',file='temp.R') 36 | parse_packages('temp.R') 37 | unlink('temp.R') 38 | } 39 | } 40 | \seealso{ 41 | \code{\link{install_package_guess}}, \code{\link{automagic}} 42 | } 43 | -------------------------------------------------------------------------------- /man/install_package_guess.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/install_package_guess.R 3 | \name{install_package_guess} 4 | \alias{install_package_guess} 5 | \title{Install latest version of package from CRAN} 6 | \usage{ 7 | install_package_guess(pkg, force_install = FALSE, 8 | prompt = interactive()) 9 | } 10 | \arguments{ 11 | \item{pkg}{a character vector with the names of packages to install from CRAN} 12 | 13 | \item{force_install}{install even if package is in library (warning! this 14 | could install a newer or older version of an already installed package)} 15 | 16 | \item{prompt}{prompt the user to install a package (defaults to yes if the R session is interactive)} 17 | } 18 | \description{ 19 | If a package is not available in the R library, attempt to install it 20 | from CRAN. Unlike previous versions of automagic, if the packages is not available on CRAN, 21 | the function will return an error (instead of trying to install from GitHub). 22 | If R is running interactively, then the user will be prompted before installing. 23 | } 24 | \details{ 25 | @details This function does not check package versions. Specify 26 | \code{force_install=TRUE} to force installation of the package, updating it 27 | to the latest available version. Note that this function attempts to 28 | install its packages based on a best guess and is meant for use in 29 | automatically setting up an R programming environment. Do not use for 30 | installing packages if you have the option to install from a \code{deps.yaml} file. 31 | See \code{\link{make_deps_file}} and \code{\link{install_deps_file}} for 32 | installing version specific packages based on a local R library. 33 | } 34 | -------------------------------------------------------------------------------- /R/install_package_guess.R: -------------------------------------------------------------------------------- 1 | #' Install latest version of package from CRAN 2 | #' 3 | #' If a package is not available in the R library, attempt to install it 4 | #' from CRAN. Unlike previous versions of automagic, if the packages is not available on CRAN, 5 | #' the function will return an error (instead of trying to install from GitHub). 6 | #' If R is running interactively, then the user will be prompted before installing. 7 | #' 8 | #' @details This function does not check package versions. Specify 9 | #' \code{force_install=TRUE} to force installation of the package, updating it 10 | #' to the latest available version. Note that this function attempts to 11 | #' install its packages based on a best guess and is meant for use in 12 | #' automatically setting up an R programming environment. Do not use for 13 | #' installing packages if you have the option to install from a \code{deps.yaml} file. 14 | #' See \code{\link{make_deps_file}} and \code{\link{install_deps_file}} for 15 | #' installing version specific packages based on a local R library. 16 | #' 17 | #' @param pkg a character vector with the names of packages to install from CRAN 18 | #' @param force_install install even if package is in library (warning! this 19 | #' could install a newer or older version of an already installed package) 20 | #' @param prompt prompt the user to install a package (defaults to yes if the R session is interactive) 21 | #' 22 | #' @export 23 | #' 24 | install_package_guess <- function(pkg, force_install = FALSE, prompt = interactive()) { 25 | message("unlike earlier releases, the current version of automagic only supports installing 'best guess' packages from CRAN") 26 | cran_pkgs <- row.names(utils::available.packages()) 27 | if (pkg %in% cran_pkgs) { 28 | remotes::install_cran(pkgs = pkg, force = force_install) 29 | } else { 30 | stop(pkg, 'not found on CRAN; cannot install it', call. = FALSE) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /R/deps_file.R: -------------------------------------------------------------------------------- 1 | #' Make a package dependencies (\code{deps.yaml}) file 2 | #' 3 | #' This function parses R code for required packages using 4 | #' \code{\link{parse_packages}} and then queries the R package library to 5 | #' determine the exact source and version of each package to install. 6 | #' Currently, only CRAN and GitHub packages are supported. 7 | #' Install packages from the `deps.yaml` file using 8 | #' \code{\link{automagic}{install_deps_file}} 9 | #' 10 | #' @param directory directory containing R code to parse 11 | #' 12 | #' @export 13 | #' @seealso \code{\link{automagic}} 14 | make_deps_file <- function(directory=getwd()) { 15 | pkg_names <- get_dependent_packages(directory) %>% unique() 16 | lapply(pkg_names,get_package_details) %>% 17 | yaml::as.yaml() %>% 18 | cat(file=file.path(directory,'deps.yaml')) 19 | } 20 | 21 | 22 | #' Install R packages from a package dependencies (\code{deps.yaml}) file 23 | #' 24 | #' Installs packages from GitHub and CRAN based on Sha1 key and version number 25 | #' respectively, as defined in a \code{deps.yaml} file created by 26 | #' \code{\link{make_deps_file}} 27 | #' 28 | #' @param directory directory containing \code{deps.yaml} file 29 | #' 30 | #' @export 31 | #' @seealso \code{\link{make_deps_file}}, \code{\link{automagic}} 32 | install_deps_file <- function(directory=getwd()) { 33 | 34 | deps_file <- file.path(directory,'deps.yaml') 35 | if (! file.exists(deps_file)) stop('deps.yaml not found',call.=FALSE) 36 | 37 | deps <- yaml::yaml.load_file(deps_file) %>% 38 | dplyr::bind_rows() 39 | 40 | if ('GithubRepo' %in% names(deps)) { 41 | gh_deps <- deps %>% dplyr::filter(!is.na(GithubRepo)) 42 | if (!nrow(gh_deps) == 0) { 43 | gh_deps <- gh_deps %>% 44 | dplyr::mutate(install_calls = paste0(GithubUsername,'/',GithubRepo,'@',GithubSHA1)) 45 | remotes::install_github(gh_deps$install_calls) 46 | } 47 | } 48 | 49 | cran_deps <- deps %>% dplyr::filter(Repository == 'CRAN') 50 | # install CRAN package given version number 51 | if (!nrow(cran_deps) == 0) { 52 | purrr::walk2(cran_deps$Package,cran_deps$Version, remotes::install_version,type='source') 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /R/parse_packages.R: -------------------------------------------------------------------------------- 1 | #' Parse R code for required packages 2 | #' 3 | #' Parses an R or R Markdown file for the package names that would be required to run the code. 4 | #' 5 | #' @param fl file to parse for required package names 6 | #' 7 | #' @return a vector of package names as character strings 8 | #' @export 9 | #' @seealso \code{\link{install_package_guess}}, \code{\link{automagic}} 10 | #' 11 | #' @details This function uses regular expressions to search through a file 12 | #' containing R code to find required package names. It extracts not only 13 | #' package names denoted by \code{\link[base]{library}} and \code{\link[base]{require}}, but also 14 | #' packages not attached to the global namespace, but are still called with 15 | #' \code{\link[base]{::}} or \code{\link[base]{:::}}. 16 | #' 17 | #' Because it relies on regular expressions, it assumes all packages adhere to 18 | #' the valid CRAN package name rules (contain only ASCII letters, numbers, and 19 | #' dot; have at least two characters and start with a letter and not end it a 20 | #' dot). Code is also tidying internally, making the code more predictable and 21 | #' easier to parse (removes comments, adds whitespace around operators, etc). 22 | #' R Markdown files are also supported by extracting only R code using 23 | #' \code{\link[knitr]{purl}}. 24 | #' 25 | #' @examples \dontrun{ 26 | #' cat('library(ggplot2)\n # library(curl)\n require(leaflet)\n CB::date_print()\n',file='temp.R') 27 | #' parse_packages('temp.R') 28 | #' unlink('temp.R') 29 | #' } 30 | 31 | parse_packages <- function(fl){ 32 | lns <- get_lines(fl) 33 | rgxs <- list(library = '(?<=(library\\()|(library\\(["\']{1}))[[:alnum:]|.]+', 34 | require = '(?<=(require\\()|(require\\(["\']{1}))[[:alnum:]|.]+', 35 | colon = "[[:alnum:]|.]*(?=:{2,3})") 36 | 37 | found_pkgs <- purrr::map(rgxs, finder, lns = lns) %>% unlist() %>% unique() 38 | found_pkgs <- found_pkgs[! found_pkgs %in% c('', ' ')] 39 | return(found_pkgs) 40 | } 41 | 42 | finder <- function(rgx, lns) regmatches(lns, gregexpr(rgx, lns, perl = TRUE)) %>% unlist() 43 | 44 | get_lines <- function(file_name) { 45 | if (grepl('.Rmd', file_name, fixed=TRUE)) { 46 | tmp.file <- tempfile() 47 | knitr::purl(input=file_name, output=tmp.file, quiet=TRUE) 48 | file_name <- tmp.file 49 | } 50 | lns <- tryCatch(formatR::tidy_source(file_name, comment = FALSE, blank = FALSE, arrow = TRUE, 51 | brace.newline = TRUE, output = FALSE)$text.mask, 52 | error = function(e) { 53 | message(paste('Could not parse R code in:', file_name)) 54 | message(' Make sure you are specifying the right file name') 55 | message(' and check for syntax errors') 56 | stop("", call. = FALSE) 57 | }) 58 | if (is.null(lns)) stop('No parsed text available', call. = FALSE) 59 | return(lns) 60 | } 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

automagic

2 | 3 | [![CRAN_Status_Badge](http://www.r-pkg.org/badges/version/automagic)](https://cran.r-project.org/package=automagic) 4 | [![DOI](https://zenodo.org/badge/65520853.svg)](https://zenodo.org/badge/latestdoi/65520853) 5 | [![Build Status](https://travis-ci.org/cole-brokamp/automagic.svg?branch=master)](https://travis-ci.org/cole-brokamp/automagic) 6 | ![CRAN Downloads](http://cranlogs.r-pkg.org/badges/grand-total/automagic?color=orange) 7 | 8 | Parse R code for required packages and install them. Optionally control which package versions to install using a `deps.yaml` file that can be created manually or automatically created based on a directory of R source files and the versions and sources of packages found in the local R library. 9 | 10 | ## Installation 11 | 12 | Install the latest stable version from CRAN with `install.packages('automagic')`. 13 | Install the latest development version from GitHub with `remotes::install_github('cole-brokamp/automagic')`. 14 | 15 | ## Using 16 | 17 | ### Without A Dependencies File 18 | 19 | To make sure R has all the required packages before running any R code, run `automagic::automagic()`. If there is no `deps.yaml` file present, `automagic` searches all `.R` and `.Rmd` files in the current working directory for necessary packages and attempts to install them from CRAN. Unlike previous versions of the `automagic` package, if a package is not available on CRAN, it will *not* attempt to install it from GitHub based on a best guess. 20 | 21 | ### With A Dependencies File 22 | 23 | It is possible that `automagic` might mistakenly install the wrong package from GitHub or you might need a different version of an R package for the code to work as intended. In this case, you can create a `deps.yaml` file with `automagic::make_deps_file()`. This function parses R code and then queries the local R package libraries to determine the exact source and version of each package to install. Currently, only CRAN and GitHub packages are supported. 24 | 25 | An example `deps.yaml` file looks like 26 | 27 | ```yaml 28 | - Package: CB 29 | GithubUsername: cole-brokamp 30 | GithubRepo: CB 31 | GithubRef: master 32 | GithubSHA1: 0a56eadaf4282678c949bc8eedaacb5d6e0777fe 33 | - Package: remotes 34 | Repository: CRAN 35 | Version: 1.0.0 36 | ``` 37 | 38 | The dependencies file could also be created or changed manually if necessary. For example, you could create a list of packages that you frequently depend on and when moving to a new machine or server, run `automagic::automagic()` to install them all. 39 | 40 | ## Details 41 | 42 | ### GitHub Installation 43 | 44 | GitHub packages are installed using the `remotes` package. Set the environment variable `GITHUB_PAT` to supply a personal access token (PAT) to install a package from a private repository or to increase the limit of calls to the GitHub API during installation of a large number of packages. 45 | 46 | ### Shiny Applications 47 | 48 | See [https://github.com/cole-brokamp/rize](https://github.com/cole-brokamp/rize) for robust method of dockerizing Shiny applications within R. `rize` utilizes `automagic` for documenting and installing the packages that are needed to run the R Shiny app in a Docker container. 49 | --------------------------------------------------------------------------------