From 5a82ff0a532a47234e9be2b552de62c66ddcecf1 Mon Sep 17 00:00:00 2001 From: Miles McBain Date: Wed, 22 Jul 2020 13:23:58 +1000 Subject: [PATCH 1/4] add ability for using to detect calls to using::pkg in .R and .Rmd + function detect_dependencies that walks the syntax tree of parsed R code looking for calls to using::pkg. It returns a data.frame summarising found calls including columns for package, min_version, and repo. + knitr as dependency for knitr::purl() which extracts code from chunks in rmd. + stats as dependency (core R package) + testthat as suggested dependency and tests for detect_dependencies + test source files for parsing in tests/testthat/test_inputs --- DESCRIPTION | 6 +- NAMESPACE | 1 + R/detect_dependencies.R | 127 ++++++++++++++++++ man/detect_dependencies.Rd | 42 ++++++ man/pkg.Rd | 4 +- man/using.Rd | 2 +- tests/testthat.R | 4 + .../testthat/test-detect-using-dependencies.R | 60 +++++++++ tests/testthat/test_inputs/deps_md.md | 8 ++ tests/testthat/test_inputs/deps_parse_fail.R | 5 + tests/testthat/test_inputs/deps_using.R | 5 + tests/testthat/test_inputs/deps_using_dupes.R | 12 ++ tests/testthat/test_inputs/deps_using_rmd.Rmd | 41 ++++++ tests/testthat/test_inputs/deps_vanilla.R | 3 + 14 files changed, 316 insertions(+), 4 deletions(-) create mode 100644 R/detect_dependencies.R create mode 100644 man/detect_dependencies.Rd create mode 100644 tests/testthat.R create mode 100644 tests/testthat/test-detect-using-dependencies.R create mode 100644 tests/testthat/test_inputs/deps_md.md create mode 100644 tests/testthat/test_inputs/deps_parse_fail.R create mode 100644 tests/testthat/test_inputs/deps_using.R create mode 100644 tests/testthat/test_inputs/deps_using_dupes.R create mode 100644 tests/testthat/test_inputs/deps_using_rmd.Rmd create mode 100644 tests/testthat/test_inputs/deps_vanilla.R diff --git a/DESCRIPTION b/DESCRIPTION index 90f505f..4358c1f 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -19,4 +19,8 @@ Depends: R (>= 3.3.0) Imports: utils, uuid, - remotes + remotes, + knitr, + stats +Suggests: + testthat (>= 2.1.0) diff --git a/NAMESPACE b/NAMESPACE index 3a11159..0a7842e 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,4 +1,5 @@ # Generated by roxygen2: do not edit by hand +export(detect_dependencies) export(pkg) export(using) diff --git a/R/detect_dependencies.R b/R/detect_dependencies.R new file mode 100644 index 0000000..09e7e95 --- /dev/null +++ b/R/detect_dependencies.R @@ -0,0 +1,127 @@ +##' detect usage of using::pkg in a source file +##' +##' Intended for use when building lock files (e.g. for {renv}) from source +##' files. +##' +##' returns a data.frame of all information in using::pkg calls with columns: +##' `package`, `min_version`, `repo`. The value of a row may be NA if the +##' argument was not supplied. +##' +##' Each result row is guaranteed to be unique, however the result data frame may +##' contain near duplicates, e.g. using::pkg(janitor, min_version = "2.0.1") and +##' using::pkg(janitor, min_version = "1.0.0") would each create a row in the +##' result data frame if they appeared in the same file, due to differing +##' min_version. +##' +##' Each using::pkg call is parsed based on it's literal expression and no variable +##' substitution is done. So using(janitor, min_version = janitor_ver) would +##' place "janitor_ver" in the min_row column of the result for this dependency. +##' +##' With .Rmd files, source code declared inline with single backticks and +##' "r" prefix is not parsed. Call using::pkg inside a code chunk if you want it +##' to be detected. +##' +##' @title detect_dependencies +##' @param file_path a length 1 character vector file path to an .R or .Rmd file +##' @return a data.frame summarising found using::pkg calls in the supplied file. +##' @author Miles McBain +##' @export +detect_dependencies <- function(file_path) { + if (length(file_path) > 1) stop("file_path must be single file path not a vector of length > 1") + + if (!file.exists(file_path)) stop("could not find file ", file_path) + + file_type <- + tolower( + regmatches( + file_path, + regexpr("\\.[A-Za-z0-9]{1,3}$", file_path))) + + if (!(file_type %in% c(".r", ".rmd"))) stop("detect_dependencies only supported for .R and .Rmd") + + deps <- switch(file_type, + .r = parse_detect_deps(file_path, parse), + .rmd = parse_detect_deps(file_path, parse_rmd), + NULL) + + deps[!duplicated(deps), ] +} + +parse_detect_deps <- function(file_path, file_parser) { + + syntax_tree <- tryCatch(file_parser(file = file_path), + error = function(e) stop("Could not detect usage of using::pkg in due to invalid R code. The parser returned: \n", e$message)) + + get_using(syntax_tree) +} + +parse_rmd <- function(file_path) { + + R_temp <- tempfile(fileext=".R") + on.exit(unlink(R_temp)) + + knitr::purl(file_path, + output = R_temp, + quiet = TRUE) + + parse(file = R_temp) + +} + + +is_using_node <- function(ast_node) { + node_list <- as.list(ast_node) + name_node <- as.character(node_list[[1]]) + + length(name_node) == 3 && + name_node[[1]] == "::" && + name_node[[2]] == "using" && + name_node[[3]] == "pkg" +} + +extract_using_data <- function(ast_node) { + node_list <- as.list(ast_node) + node_list <- node_list[-1] + char_nodes <- stats::setNames(lapply(node_list, as.character), + names(node_list)) + + + do.call( + function(package = NA_character_, + min_version = NA_character_, + repo = NA_character_) { + data.frame(package = package, + min_version = min_version, + repo = repo, + stringsAsFactors = FALSE) + }, + char_nodes) + +} + +get_using <- function(syntax_tree) { + + get_using_recurse <- function(syntax_tree_expr) { + if (is.call(syntax_tree_expr)) { + if (is_using_node(syntax_tree_expr)) + return(extract_using_data(syntax_tree_expr)) + else + make_data_frame(lapply(syntax_tree_expr, get_using_recurse)) + } else { + return(NULL) + } + } + + make_data_frame(lapply(syntax_tree, get_using_recurse)) + +} + +make_data_frame <- function(x) +{ + result_dfs <- x[!unlist(lapply(x,is.null))] + + if (length(result_dfs) > 0) do.call(rbind, c(result_dfs, stringsAsFactors = FALSE)) + else data.frame(package = character(0), + min_version = character(0), + repo = character(0), stringsAsFactors = FALSE) +} diff --git a/man/detect_dependencies.Rd b/man/detect_dependencies.Rd new file mode 100644 index 0000000..19b9b63 --- /dev/null +++ b/man/detect_dependencies.Rd @@ -0,0 +1,42 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/detect_dependencies.R +\name{detect_dependencies} +\alias{detect_dependencies} +\title{detect_dependencies} +\usage{ +detect_dependencies(file_path) +} +\arguments{ +\item{file_path}{a length 1 character vector file path to an .R or .Rmd file} +} +\value{ +a data.frame summarising found using::pkg calls in the supplied file. +} +\description{ +detect usage of using::pkg in a source file +} +\details{ +Intended for use when building lock files (e.g. for {renv}) from source +files. + +returns a data.frame of all information in using::pkg calls with columns: +\code{package}, \code{min_version}, \code{repo}. The value of a row may be NA if the +argument was not supplied. + +Each result row is guaranteed to be unique, however the result data frame may +contain near duplicates, e.g. using::pkg(janitor, min_version = "2.0.1") and +using::pkg(janitor, min_version = "1.0.0") would each create a row in the +result data frame if they appeared in the same file, due to differing +min_version. + +Each using::pkg call is parsed based on it's literal expression and no variable +substitution is done. So using(janitor, min_version = janitor_ver) would +place "janitor_ver" in the min_row column of the result for this dependency. + +With .Rmd files, source code declared inline with single backticks and +"r" prefix is not parsed. Call using::pkg inside a code chunk if you want it +to be detected. +} +\author{ +Miles McBain +} diff --git a/man/pkg.Rd b/man/pkg.Rd index 5a555bd..451a2e5 100644 --- a/man/pkg.Rd +++ b/man/pkg.Rd @@ -17,13 +17,13 @@ pkg(package, min_version = NULL, repo = NULL, ...) } \value{ \code{{character | logical}} -attached packages, or boolean if \verb{logical.return is TRUE} +attached packages, or boolean if \code{logical.return is TRUE} } \description{ Optionally add version constraints to library calls } \details{ -This function is intended to always be called with the \verb{using::} prefix. A +This function is intended to always be called with the \code{using::} prefix. A call without this prefix will throw a warning. This simplifies detecting dependencies declared by using::pkg for use with \code{{renv}} and \code{{capsule}} } diff --git a/man/using.Rd b/man/using.Rd index 3579179..4b4253a 100644 --- a/man/using.Rd +++ b/man/using.Rd @@ -11,7 +11,7 @@ using(...) } \value{ \code{{character | logical}} -attached packages, or boolean if \verb{logical.return is TRUE} +attached packages, or boolean if \code{logical.return is TRUE} } \description{ Optionally add version constraints to library calls diff --git a/tests/testthat.R b/tests/testthat.R new file mode 100644 index 0000000..1d082ed --- /dev/null +++ b/tests/testthat.R @@ -0,0 +1,4 @@ +library(testthat) +library(using) + +test_check("using") diff --git a/tests/testthat/test-detect-using-dependencies.R b/tests/testthat/test-detect-using-dependencies.R new file mode 100644 index 0000000..eb3e416 --- /dev/null +++ b/tests/testthat/test-detect-using-dependencies.R @@ -0,0 +1,60 @@ +test_that("detecting using works", { + + ## test rmd + rmd_deps <- detect_dependencies(test_path("test_inputs/deps_using_rmd.Rmd")) + + expect_equal( + rmd_deps$package, + c("qfes", "ffdi", "datapasta", "slippymath")) + + expect_equal( + rmd_deps$min_version, + c("0.2.1", "0.1.3", NA, "0.1.0")) + + expect_equal( + rmd_deps$repo, + c("https://github.com/qfes/qfes.git", + "https://qfes@dev.azure.com/qfes/packages/_git/ffdi", + "https://github.com/milesmcbain/datapasta", + NA)) + + ## test R + r_deps <- detect_dependencies(test_path("test_inputs/deps_using.R")) + + expect_equal( + r_deps$package, + c("qfes", "ffdi")) + + ## test dupes + dupe_deps <- detect_dependencies(test_path("test_inputs/deps_using_dupes.R")) + + expect_equal( + dupe_deps$package, + c("qfes", "ffdi", "rdeck", "ffdi", "rdeck")) + + + ## test none + none_deps <- detect_dependencies(test_path("test_inputs/deps_vanilla.R")) + + expect_true(nrow(none_deps) == 0) + expect_equal(names(none_deps), + c("package", "min_version", "repo")) + + ## test parse fail + expect_error(detect_dependencies(test_path("test_inputs/deps_parse_fail.R")), + "Could not detect usage of using::pkg in due to invalid R code.") + + ## test unsupported file + expect_error(detect_dependencies(test_path("test_inputs/deps_md.md")), + "detect_dependencies only supported for .R and .Rmd") + + ## test only 1 file path supported + expect_error(detect_dependencies(c(test_path("test_inputs/deps_using.R"), + "test_inputs/deps_parse_fail.R")), + "file_path must be single file path not a vector of length > 1") + + ## test file not found + expect_error(detect_dependencies(test_path("test_inputs/does_not_exist.R")), + "could not find file") + +}) diff --git a/tests/testthat/test_inputs/deps_md.md b/tests/testthat/test_inputs/deps_md.md new file mode 100644 index 0000000..9f15a2e --- /dev/null +++ b/tests/testthat/test_inputs/deps_md.md @@ -0,0 +1,8 @@ +using::pkg(qfes, min_version = "0.2.1", repo = "https://github.com/qfes/qfes.git") +using::pkg(ffdi, min_version = "0.1.3", repo = "https://qfes@dev.azure.com/qfes/packages/_git/ffdi") +using::pkg(datapasta, repo = "https://github.com/milesmcbain/datapasta") +withr::with_libpaths(new = "foo/path", + code = using::pkg(slippymath, min_version = "0.1.0")) +library(geosphere) # to get the distance between stations +library(concaveman) +library(english) diff --git a/tests/testthat/test_inputs/deps_parse_fail.R b/tests/testthat/test_inputs/deps_parse_fail.R new file mode 100644 index 0000000..7214e51 --- /dev/null +++ b/tests/testthat/test_inputs/deps_parse_fail.R @@ -0,0 +1,5 @@ +using(qfes min_version = "0.2.1", repo = "https://github.com/qfes/qfes.git") +using(ffdi, min_version = "0.1.3", repo = "https://qfes@dev.azure.com/qfes/packages/_git/ffdi") +library(geosphere) # to get the distance between stations +library(concaveman) +library(english) diff --git a/tests/testthat/test_inputs/deps_using.R b/tests/testthat/test_inputs/deps_using.R new file mode 100644 index 0000000..c30d4da --- /dev/null +++ b/tests/testthat/test_inputs/deps_using.R @@ -0,0 +1,5 @@ +using::pkg(qfes, min_version = "0.2.1", repo = "https://github.com/qfes/qfes.git") +using::pkg(ffdi, min_version = "0.1.3", repo = "https://qfes@dev.azure.com/qfes/packages/_git/ffdi") +library(geosphere) # to get the distance between stations +library(concaveman) +library(english) diff --git a/tests/testthat/test_inputs/deps_using_dupes.R b/tests/testthat/test_inputs/deps_using_dupes.R new file mode 100644 index 0000000..f89dce7 --- /dev/null +++ b/tests/testthat/test_inputs/deps_using_dupes.R @@ -0,0 +1,12 @@ +using::pkg(qfes, min_version = "0.2.1", repo = "https://github.com/qfes/qfes.git") +using::pkg(ffdi, min_version = "0.1.3", repo = "https://qfes@dev.azure.com/qfes/packages/_git/ffdi") +using::pkg(rdeck, min_version = "0.2.3", repo = "https://github.com/anthonynorth/rdeck.git") + +## as above genuine dupe +using::pkg(qfes, min_version = "0.2.1", repo = "https://github.com/qfes/qfes.git") + +## different version +using::pkg(ffdi, min_version = "0.1.4", repo = "https://qfes@dev.azure.com/qfes/packages/_git/ffdi") + +## different repo +using::pkg(rdeck, min_version = "0.2.3", repo = "c:/rdeck/") diff --git a/tests/testthat/test_inputs/deps_using_rmd.Rmd b/tests/testthat/test_inputs/deps_using_rmd.Rmd new file mode 100644 index 0000000..ca185ba --- /dev/null +++ b/tests/testthat/test_inputs/deps_using_rmd.Rmd @@ -0,0 +1,41 @@ +--- +title: "Untitled Draft" +author: "Report Author" +date: "`r format(Sys.time(), '%d %B, %Y')`" +output: html_document +--- + +```{r setup, include=FALSE} +knitr::opts_chunk$set(echo = FALSE) +``` + +## Analysis + +```{r} +using::pkg(qfes, min_version = "0.2.1", repo = "https://github.com/qfes/qfes.git") +using::pkg(ffdi, min_version = "0.1.3", repo = "https://qfes@dev.azure.com/qfes/packages/_git/ffdi") +using::pkg(datapasta, repo = "https://github.com/milesmcbain/datapasta") +withr::with_libpaths(new = "foo/path", + code = using::pkg(slippymath, min_version = "0.1.0")) +library(geosphere) # to get the distance between stations +library(concaveman) +library(english) +``` + + +`r using(rdeck, min_version = "0.2.5", repo = "https://github.com/anthonynorth/rdeck")` + +## Reproducibility + +
Reproducibility receipt + +```{r} +## datetime +Sys.time() +## repository +git2r::repository() +## session info +sessionInfo(package = ) +``` + +
diff --git a/tests/testthat/test_inputs/deps_vanilla.R b/tests/testthat/test_inputs/deps_vanilla.R new file mode 100644 index 0000000..9930fc9 --- /dev/null +++ b/tests/testthat/test_inputs/deps_vanilla.R @@ -0,0 +1,3 @@ +library(rmarkdown) +library(here) # to get project root folder in Rmd +library(knitr) From 4820b190fdac36092286a4dd4065b3ce3d3a50e4 Mon Sep 17 00:00:00 2001 From: Miles McBain Date: Wed, 22 Jul 2020 15:39:24 +1000 Subject: [PATCH 2/4] can now parse inline code from rmd + dependency on withr to call with_options --- DESCRIPTION | 3 ++- R/detect_dependencies.R | 13 +++++-------- tests/testthat/test-detect-using-dependencies.R | 7 ++++--- tests/testthat/test_inputs/deps_using_rmd.Rmd | 2 +- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 4358c1f..47fd450 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -21,6 +21,7 @@ Imports: uuid, remotes, knitr, - stats + stats, + withr Suggests: testthat (>= 2.1.0) diff --git a/R/detect_dependencies.R b/R/detect_dependencies.R index 09e7e95..4e824ba 100644 --- a/R/detect_dependencies.R +++ b/R/detect_dependencies.R @@ -17,10 +17,6 @@ ##' substitution is done. So using(janitor, min_version = janitor_ver) would ##' place "janitor_ver" in the min_row column of the result for this dependency. ##' -##' With .Rmd files, source code declared inline with single backticks and -##' "r" prefix is not parsed. Call using::pkg inside a code chunk if you want it -##' to be detected. -##' ##' @title detect_dependencies ##' @param file_path a length 1 character vector file path to an .R or .Rmd file ##' @return a data.frame summarising found using::pkg calls in the supplied file. @@ -60,10 +56,11 @@ parse_rmd <- function(file_path) { R_temp <- tempfile(fileext=".R") on.exit(unlink(R_temp)) - knitr::purl(file_path, - output = R_temp, - quiet = TRUE) - + withr::with_options(list(knitr.purl.inline = TRUE), + knitr::purl(file_path, + output = R_temp, + quiet = TRUE)) + parse(file = R_temp) } diff --git a/tests/testthat/test-detect-using-dependencies.R b/tests/testthat/test-detect-using-dependencies.R index eb3e416..4de23df 100644 --- a/tests/testthat/test-detect-using-dependencies.R +++ b/tests/testthat/test-detect-using-dependencies.R @@ -5,18 +5,19 @@ test_that("detecting using works", { expect_equal( rmd_deps$package, - c("qfes", "ffdi", "datapasta", "slippymath")) + c("qfes", "ffdi", "datapasta", "slippymath", "rdeck")) expect_equal( rmd_deps$min_version, - c("0.2.1", "0.1.3", NA, "0.1.0")) + c("0.2.1", "0.1.3", NA, "0.1.0", "0.2.5")) expect_equal( rmd_deps$repo, c("https://github.com/qfes/qfes.git", "https://qfes@dev.azure.com/qfes/packages/_git/ffdi", "https://github.com/milesmcbain/datapasta", - NA)) + NA, + "https://github.com/anthonynorth/rdeck")) ## test R r_deps <- detect_dependencies(test_path("test_inputs/deps_using.R")) diff --git a/tests/testthat/test_inputs/deps_using_rmd.Rmd b/tests/testthat/test_inputs/deps_using_rmd.Rmd index ca185ba..c9dc961 100644 --- a/tests/testthat/test_inputs/deps_using_rmd.Rmd +++ b/tests/testthat/test_inputs/deps_using_rmd.Rmd @@ -23,7 +23,7 @@ library(english) ``` -`r using(rdeck, min_version = "0.2.5", repo = "https://github.com/anthonynorth/rdeck")` +`r using::pkg(rdeck, min_version = "0.2.5", repo = "https://github.com/anthonynorth/rdeck")` ## Reproducibility From 88cb717aa5bb8b4398f75d1409266f0d7def7524 Mon Sep 17 00:00:00 2001 From: Miles McBain Date: Wed, 22 Jul 2020 15:42:05 +1000 Subject: [PATCH 3/4] redoc --- man/detect_dependencies.Rd | 4 ---- 1 file changed, 4 deletions(-) diff --git a/man/detect_dependencies.Rd b/man/detect_dependencies.Rd index 19b9b63..f85221e 100644 --- a/man/detect_dependencies.Rd +++ b/man/detect_dependencies.Rd @@ -32,10 +32,6 @@ min_version. Each using::pkg call is parsed based on it's literal expression and no variable substitution is done. So using(janitor, min_version = janitor_ver) would place "janitor_ver" in the min_row column of the result for this dependency. - -With .Rmd files, source code declared inline with single backticks and -"r" prefix is not parsed. Call using::pkg inside a code chunk if you want it -to be detected. } \author{ Miles McBain From b030052ba0f01f7f01a75c8e6fd957fc3e72a00a Mon Sep 17 00:00:00 2001 From: Miles McBain Date: Wed, 22 Jul 2020 15:56:15 +1000 Subject: [PATCH 4/4] bump version and style code --- DESCRIPTION | 2 +- R/detect_dependencies.R | 79 +++++++++++++++++++++++------------------ 2 files changed, 46 insertions(+), 35 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 47fd450..9a0610b 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: using Type: Package Title: Add version constraints to library() calls -Version: 0.3.0 +Version: 0.4.0 Authors@R: c( person(given = "Anthony", family = "North", role = c("aut", "cre"), email = "anthony.jl.north@gmail.com"), person(given = "Miles", family = "McBain", role = c("aut"), email = "miles.mcbain@gmail.com"), diff --git a/R/detect_dependencies.R b/R/detect_dependencies.R index 4e824ba..b215dd2 100644 --- a/R/detect_dependencies.R +++ b/R/detect_dependencies.R @@ -31,38 +31,42 @@ detect_dependencies <- function(file_path) { tolower( regmatches( file_path, - regexpr("\\.[A-Za-z0-9]{1,3}$", file_path))) + regexpr("\\.[A-Za-z0-9]{1,3}$", file_path) + ) + ) if (!(file_type %in% c(".r", ".rmd"))) stop("detect_dependencies only supported for .R and .Rmd") deps <- switch(file_type, - .r = parse_detect_deps(file_path, parse), - .rmd = parse_detect_deps(file_path, parse_rmd), - NULL) + .r = parse_detect_deps(file_path, parse), + .rmd = parse_detect_deps(file_path, parse_rmd), + NULL + ) deps[!duplicated(deps), ] } parse_detect_deps <- function(file_path, file_parser) { - syntax_tree <- tryCatch(file_parser(file = file_path), - error = function(e) stop("Could not detect usage of using::pkg in due to invalid R code. The parser returned: \n", e$message)) + error = function(e) stop("Could not detect usage of using::pkg in due to invalid R code. The parser returned: \n", e$message) + ) get_using(syntax_tree) } parse_rmd <- function(file_path) { - - R_temp <- tempfile(fileext=".R") + R_temp <- tempfile(fileext = ".R") on.exit(unlink(R_temp)) - withr::with_options(list(knitr.purl.inline = TRUE), - knitr::purl(file_path, - output = R_temp, - quiet = TRUE)) - - parse(file = R_temp) + withr::with_options( + list(knitr.purl.inline = TRUE), + knitr::purl(file_path, + output = R_temp, + quiet = TRUE + ) + ) + parse(file = R_temp) } @@ -79,46 +83,53 @@ is_using_node <- function(ast_node) { extract_using_data <- function(ast_node) { node_list <- as.list(ast_node) node_list <- node_list[-1] - char_nodes <- stats::setNames(lapply(node_list, as.character), - names(node_list)) + char_nodes <- stats::setNames( + lapply(node_list, as.character), + names(node_list) + ) do.call( function(package = NA_character_, min_version = NA_character_, repo = NA_character_) { - data.frame(package = package, - min_version = min_version, - repo = repo, - stringsAsFactors = FALSE) + data.frame( + package = package, + min_version = min_version, + repo = repo, + stringsAsFactors = FALSE + ) }, - char_nodes) - + char_nodes + ) } get_using <- function(syntax_tree) { - get_using_recurse <- function(syntax_tree_expr) { if (is.call(syntax_tree_expr)) { - if (is_using_node(syntax_tree_expr)) + if (is_using_node(syntax_tree_expr)) { return(extract_using_data(syntax_tree_expr)) - else + } else { make_data_frame(lapply(syntax_tree_expr, get_using_recurse)) + } } else { return(NULL) } } make_data_frame(lapply(syntax_tree, get_using_recurse)) - } -make_data_frame <- function(x) -{ - result_dfs <- x[!unlist(lapply(x,is.null))] - - if (length(result_dfs) > 0) do.call(rbind, c(result_dfs, stringsAsFactors = FALSE)) - else data.frame(package = character(0), - min_version = character(0), - repo = character(0), stringsAsFactors = FALSE) +make_data_frame <- function(x) { + result_dfs <- x[!unlist(lapply(x, is.null))] + + if (length(result_dfs) > 0) { + do.call(rbind, c(result_dfs, stringsAsFactors = FALSE)) + } else { + data.frame( + package = character(0), + min_version = character(0), + repo = character(0), stringsAsFactors = FALSE + ) + } }