简体   繁体   中英

List all the packages required in a script assuming package::function() in R

Let´s assume that all the non-base R functions in a script are called by package::function() . Therefore, it don´t have any full package load during the script run. Let´s assume that we have the a R script called run.R with the following contents.

"data.table::fread(file)"

In this example, the script would need the data.table package. I am looking for a R function that reading this external run.R script would give the code to install all the requested packages ie:

install.packages("data.table") 

Any idea about existent functions or strategies?

You could try something like this using regex:

f <- file("/path/to/here/file.R") # set up connection to file

file_lines <- readLines(con = f) # read file into list

close(f)

pckgs <- lapply(file_lines, function(l) { 
  if(grepl("::", l)){
    gsub(".*?([[:alnum:]\\.]+)::.*","\\1", l) 
  } else {
    return(NULL)
  }

})  
unique(unlist(pckgs))

I worked under the assumption that package names only contains letters and numbers. you may need to change the regex pattern if that isn't the case.

Update: changed the assumption to include a . as per the data.table example

The other solutions using regular expressions will also match :: if it occurs in a comment or string literal. It's better to parse the script and look for things that parse to the pkg::fn operation. For example:

src <- "data.table::fread(file)"
# Use src <- readLines("source.R") in the real case, or parse the file directly

parsed <- parse(text = src)
parseData <- getParseData(parsed)
parseData$text[parseData$token == "SYMBOL_PACKAGE"]
#> [1] "data.table"

Edited to add: You can put this in a function to install necessary packages before running a script. For example, if these lines are in ~/temp/run.R :

file <- "not::a::package"
data.table::fread(file)
foobar::notafunction()

then you get these results:

installThenSource <- function(file, ...) {
  parsed <- parse(file)
  parseData <- getParseData(parsed)
  packages <- unique(parseData$text[parseData$token == "SYMBOL_PACKAGE"])
  for (p in packages) {
    if (!requireNamespace(p, quietly = TRUE)) {
      message("Installing ", p)
      install.packages(p)
      if (!requireNamespace(p, quietly = TRUE))
        stop("Install of ", p, " failed.")
    } else
      message("Package ", p, " already installed.")
  }
  source(file, ...)
}

installThenSource("~/temp/run.R")
#> Package data.table already installed.
#> Installing foobar
#> Warning: package 'foobar' is not available (for R version 3.6.1)
#> Error in installThenSource("~/temp/run.R"): Install of foobar failed.

Here is a function that finds strings like package::function .

findPackages <- function(file){
  txt <- readLines(file)
  inx <- grep('::', txt)
  txt <- txt[inx]
  m <- regexpr('[[:alnum:]]+::', txt)
  pkg <- regmatches(txt, m)
  unique(sub('::', '', pkg))
}

This Ubuntu bash command gets all files *.R in the working directory with :: in them.

fls <- system2('grep', args = c('-l', '::', '*.R'), stdout = TRUE)

Now apply the function to a file that calls functions like that.

findPackages(fls[1])

And to all such files found with the bash command.

pkgs <- lapply(fls, findPackages)
unique(unlist(pkgs))

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM