diff --git a/DESCRIPTION b/DESCRIPTION index 8a0e6e8..da2d4fb 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: Vmisc Title: Various functions for personal use -Version: 0.1.0 +Version: 0.1.5 Authors@R: person("Vencislav", "Popov", , "vencislav.popov@gmail.com", role = c("aut", "cre"), comment = c(ORCID = "0000-0002-8073-4199")) @@ -11,7 +11,11 @@ BugReports: https://github.com/venpopov/Vmisc/issues Imports: stringr, tools, - utils + utils, + xfun, + rlang, + devtools, + remotes Suggests: testthat (>= 3.0.0) Config/testthat/edition: 3 diff --git a/NAMESPACE b/NAMESPACE index 8b62392..7467a3d 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -8,9 +8,17 @@ export("%A%") export("%A%<-") export("%a%") export("%a%<-") +export(arg2string) +export(available_packages) export(collapse) export(extract_pkg_fun_calls) +export(is_dir_empty) +export(nlist) export(packageOptions) +export(parse_pkg_version) +export(pkg_vavailable) +export(pkg_vload) +export(require_pkg) export(stop2) export(str_extract_nested_balanced) export(strip_attributes) diff --git a/NEWS.md b/NEWS.md index 12b252b..d7938df 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,16 @@ +# Vmisc 0.1.5 + +### New features +* add nlist() function to create a named list +* add is_dir_empty() function to check if a directory is empty +* add arg2string() to defuse a function's arguments into strings +* add pkg_vload() function which can load and/or install a specific version of multiple packages. This function takes calls to packages of the form pkg(version), e.g. dplyr('1.0.0'). +* add parse_pkg_version() function that parses calls such as dplyr('1.0.0') into a list with package names and versions +* add require_pkg() function which checks if one or more packages are installed and if their versions are at least the specified one. If not, it gives an error message and stops the execution. +* add pkg_vavailable() function which is is an alternative to [xfun::pkg_available()] that checks for a specific version of the package rather than a minimal version. +* add available_packages() function which returns a simple character vector of all installed packages, including specific multiple versions created by pkg_vload() + + # Vmisc 0.1.0 ### New features diff --git a/R/data_objects.R b/R/data_objects.R new file mode 100644 index 0000000..615e7fb --- /dev/null +++ b/R/data_objects.R @@ -0,0 +1,38 @@ +#' Create Named List from Arguments +#' +#' This function creates a named list from its arguments. If the arguments are named, +#' those names are used in the resulting list. If some arguments are unnamed, the variable +#' names themselves are used as names in the list. This can be useful for creating lists +#' where the names are important for later indexing or manipulation, and ensures all +#' elements in the list have names. +#' +#' @param ... Arbitrary arguments to be included in the list. These can be named or unnamed. +#' Unnamed arguments will be named based on their variable names. +#' +#' @return A list where each element corresponds to an argument passed to the function. +#' Elements of the list are named based on either their original names or the names of +#' the variables passed as arguments. +#' +#' @export +#' +#' @examples +#' var1 <- 1 +#' var2 <- 1:10 +#' # This will return a list with names: c("a", "b", "var1", "var2") +#' nlist(a = 1, b = 2, var1, var2) + +nlist <- function(...) { + # adapted from brms + m <- match.call() + dots <- list(...) + no_names <- is.null(names(dots)) + has_name <- if (no_names) FALSE else nzchar(names(dots)) + if (all(has_name)) return(dots) + nms <- as.character(m)[-1] + if (no_names) { + names(dots) <- nms + } else { + names(dots)[!has_name] <- nms[!has_name] + } + dots +} diff --git a/R/file_system.R b/R/file_system.R new file mode 100644 index 0000000..36ddc33 --- /dev/null +++ b/R/file_system.R @@ -0,0 +1,40 @@ +#' Check if Directories are Empty +#' +#' This function checks whether one or more directories are empty. +#' An empty directory is one that contains no files or subdirectories. +#' If the directory does not exist, it is considered as empty. +#' +#' @param paths A character vector containing one or more file paths. +#' Each path is checked to determine if the corresponding directory is empty. +#' +#' @return A logical vector where each element corresponds to a directory +#' specified in `paths`. `TRUE` indicates that the directory is empty, +#' and `FALSE` indicates that it is not. +#' +#' @export +#' +#' @examples +#' \dontrun{ +#' # Create two temporary directories one of which is empty +#' library(fs) +#' dir1 <- tempfile() +#' dir2 <- tempfile() +#' dir_create(c(dir1, dir2)) +#' dir_create(file.path(dir1, "subdir")) +#' +#' # Check if the directories are empty (should return FALSE, TRUE) +#' is_dir_empty(c(dir1, dir2)) +#' +#' # Clean up +#' dir_delete(c(dir1, dir2)) +#' } +is_dir_empty <- function(paths) { + .is_dir_empty <- function(path) { + if (dir.exists(path)) { + files <- list.files(path) + return(length(files) == 0) + } + return(TRUE) + } + sapply(paths, .is_dir_empty) +} diff --git a/R/functions.R b/R/functions.R new file mode 100644 index 0000000..a18494e --- /dev/null +++ b/R/functions.R @@ -0,0 +1,33 @@ +#' Convert Function Arguments to Strings +#' +#' This function takes any number of R language objects and converts their names into strings. +#' This is particularly useful for programming where variable names or symbols need to be used +#' as strings without evaluating them. It leverages `rlang`'s tidy evaluation framework. +#' +#' @param ... Arbitrary arguments representing R language objects or symbols. +#' +#' @return A character vector where each element is the string representation of the corresponding +#' argument passed to the function. The order of the strings in the output matches the order of +#' the arguments. +#' +#' @export +#' +#' @examples +#' # returns the arguments as strings even though functions bmm() and brms() are not defined +#' arg2string(bmm('0.4.0'), brms('2.20.4')) +arg2string <- function(...) { + args <- rlang::enquos(...) + labels <- as.character(sapply(args, rlang::as_label)) + # if the argument was already a string, it will be returned as is + labels <- sapply(labels, function(x) + tryCatch({ + eval(parse(text = x)) + }, + error = function(e) { + x + } + ) + ) + names(labels) <- NULL + labels +} diff --git a/R/packages.R b/R/packages.R index 7325190..7bf61ee 100644 --- a/R/packages.R +++ b/R/packages.R @@ -1,3 +1,276 @@ +#' Load and/or install packages with specific versions +#' +#' pkg_vload() attempts to load a package and, if it is not available, installs +#' it. It can also install a specific version of a package. If the package is +#' already installed, it will check if the version is the same as the one +#' specified in the call. If the version is different, it will attempt to unload +#' the package and install the specified version in a separate library, allowing +#' the user to have multiple versions of the same package installed at the same +#' time. +#' +#' @param ... One or more calls to the package name with version (if desired). +#' The calls should be of the form `pkg('version')` where `pkg` is the package +#' name and `version` is the version number. If the version is not specified, +#' the function will check for the default version of the package. +#' @param reload Logical. If `TRUE`, the function will attempt to unload the +#' package and load it again, regardless of whether the version is the same as +#' the one specified in the call. Default is `FALSE`. If the package is +#' already loaded, it will be reloaded even if reload is `FALSE`, if the +#' specified version is different from the one currently loaded. +#' @param path A character vector of paths to search for the package. Default is +#' the default library paths. +#' @param repos A character vector of repository URLs to use for installing the +#' package. Default is the value of `getOption("repos")`. +#' @param install_args A list of additional arguments to be passed to +#' `install.packages()` or `remotes::install_version()`. Default is `NULL`. +#' @return This function does not return a value. Instead, it will stop the +#' execution and display a message if the requirements are not met. +#' @export +#' +#' @examples +#' \dontrun{ +#' # Load the 'brms' package and install version 2.0.0 if it is not available +#' pkg_vload(brms("2.0.0")) +#' +#' # Load multiple packages and install specific versions if they are not available +#' pkg_vload(brms("2.0.0"), utils) +#' } +pkg_vload <- function(..., reload = FALSE, path = .libPaths(), repos = getOption("repos"), install_args = NULL) { + pkgs <- pkg_vavailable(..., path = path) + if (any(!is.na(pkgs$pkg_version))) { + require_pkg("remotes", + message_prefix = "Installing specific versions of packages" %+% + " requires you to first install:") + } + + + npkgs <- length(pkgs$pkg_name) + for (i in 1:npkgs) { + if (pkgs$pkg_folder[i] == pkgs$pkg_name[i]) { + install_path <- path + } else { + install_path <- pkgs$path[i] + } + if (reload | !pkgs$available[i] | pkgs$pkg_version[i] != packageVersion(pkgs$pkg_name[i])) { + tryCatch( + { + devtools::unload(pkgs$pkg_name[i]) + }, + error = function(e) { + invisible(NULL) + }, + warning = function(w) { + invisible(NULL) + } + ) + } + + if (pkgs$available[i]) { + require(pkgs$pkg_name[i], lib.loc = install_path, character.only = TRUE) + } else { + tryCatch( + { + xfun::dir_create(install_path) + if (is.na(pkgs$pkg_version[i])) { + args <- c(list(pkgs = pkgs$pkg_name[i], + lib = install_path, + dependencies = TRUE, + repos = repos), + install_args) + do.call('install.packages', args) + } else { + args <- c(list(package = pkgs$pkg_name[i], + lib = install_path, + dependencies = TRUE, + repos = repos, + version = pkgs$pkg_version[i]), + install_args) + do.call(remotes::install_version, args) + } + require(pkgs$pkg_name[i], lib.loc = install_path, character.only = TRUE) + }, + error = function(e) { + warning2( + "\nPackage ", pkgs$pkg_name[i], " could not be installed. The attempt", + " returned the following error: \n", e + ) + if (is_dir_empty(install_path)) { + unlink(install_path, recursive = TRUE, force = TRUE) + } + } + ) + } + } + return(invisible(NULL)) +} + + +#' Parse package name and version from a pkg('verions') call +#' +#' @param ... a number of calls to objects of type pkg('version') where pkg is +#' the package name and version is the version number +#' +#' @return A list with two elements: names and versions. The names are the package +#' names and the versions are of class 'package_version'. If the version is not +#' specified, the version will be NA. +#' @export +#' +#' @examples +#' parse_pkg_version(brms("2.20.4"), bmm("0.4-0"), utils) +parse_pkg_version <- function(...) { + x <- arg2string(...) + names <- gsub("\\(.*", "", x) + versions_str <- stringr::str_extract(x, "\\d+(\\.|\\-)\\d+(\\.|\\-)\\d+") + versions <- package_version(versions_str, strict = FALSE) + nchar_ver <- ifelse(is.na(versions_str), 0, nchar(versions_str)+4) + cant_parse <- (nchar(names) + nchar_ver) != nchar(x) + if (any(cant_parse)) { + stop2("Invalid version format: ", paste0((x[cant_parse]), collapse=", ")) + } + nlist(names, versions) +} + +is.named <- function(x) { + !is.null(names(x)) +} + + +#' Check Required Packages and Their Versions +#' +#' This function checks if the required R packages are available and if their +#' versions meet the specified minimum requirements. It will stop the execution +#' and display a message if any required package is missing or does not meet +#' the version requirement. +#' +#' @param ... Variable arguments representing required package names and, +#' optionally, their minimum versions. The versions should be specified +#' immediately after the package names, in the format `packageName(version)`. +#' @param message_prefix A character string to be displayed before the message +#' if the requirements are not met. +#' +#' @return This function does not return a value. Instead, it will stop +#' the execution and display a message if the requirements are not met. +#' @export +#' +#' @examples +#' \dontrun{ +#' # Check if 'dplyr' and 'ggplot2' are installed (any versions): +#' require_pkg(dplyr, ggplot2) +#' +#' # Check if 'dplyr' (version 1.0.0 or higher) and 'ggplot2' (version 8.3.0 or higher) are installed: +#' require_pkg(dplyr('1.0.0'), ggplot2('8.3.0')) +#' } +require_pkg <- function(..., message_prefix = "Please install the following packages:") { + pkgs <- pkg_vavailable(..., exact = FALSE) + available <- pkgs$available + min_available <- + required_version <- pkgs$pkg_version_specified + m <- c() + for (i in 1:length(available)) { + if (!available[i] || isTRUE(required_version[i] > packageVersion(pkgs$pkg_name[i]))) { + if (is.na(required_version[i])) { + m <- c(m, paste0(" ", pkgs$pkg_name[i], "\n")) + } else { + m <- c(m, paste0(" ", pkgs$pkg_name[i], " (version ", required_version[i], " or higher)\n")) + } + } + } + if (length(m) > 0) { + stop2(message_prefix, "\n", collapse(m)) + } +} + +#' Check if a specific package version is available in the library +#' +#' [pkg_vavailable()] is an alternative to [xfun::pkg_available()] that checks +#' for a specific version of the package rather than a minimal version. If the +#' version is not specified, the function will check for the default version of +#' the package. +#' +#' @param ... One or more calls to the package name with version (if desired) +#' specified in parantheses. E.g. brms("2.14.4") or brms or "brms" +#' +#' @param path A character vector of paths to search for the package. Default is +#' the default library paths. +#' @param exact Logical. If `TRUE`, the function will only return `TRUE` if the +#' exact version is available. If `FALSE`, the function will return `TRUE` if +#' the version is available or if a higher version is available. Default is `TRUE`. +#' @return a named list with the following elements: +#' - available: A logical vector indicating whether the package is available +#' - pkg_name: The name of the package +#' - pkg_version: The version of the package in the library +#' - pkg_version_specified: The version of the package specified in the call to pkg('version') +#' - pkg_folder: The folder name of the package in the library +#' @details To check for a specific version, the function assumes that this +#' version was installed using pkg_load(pkg(version)), which has +#' created a folder named "pkg-version" in the library. +#' @export +#' @examples +#' \dontrun{ +#' pkg_vavailable(utils) +#' pkg_vavailable(xfun("0.1.0")) +#' pkg_vavailable(utils, brms("2.14.4"), xfun("0.1.0")) +#' +#' # compare with xfun::pkg_available() +#' xfun::pkg_available("xfun", "0.1.0") # returns TRUE +#' } +pkg_vavailable <- function(..., path = .libPaths(), exact = TRUE) { + pkgs <- parse_pkg_version(...) + names <- pkgs[["names"]] + versions <- pkgs[["versions"]] + default_versions <- sapply(seq_along(names), function(i) { + tryCatch( + { + packageVersion(names[i], lib.loc = path) + }, + error = function(e) { + package_version(NA, strict = FALSE) + } + ) + }) + class(default_versions) <- c("package_version", "numeric_version") + is_default <- is.na(versions) | is.na(default_versions) | default_versions == versions + if (!exact) { + is_default <- is.na(versions) | is.na(default_versions) | versions <= default_versions + } + affix <- sapply(seq_along(versions), function(x) ifelse(is_default[x], "", paste0("-", versions[x]))) + pkg_folder <- paste0(names, affix) + paths <- c() + for (pkg in pkg_folder) { + where <- path[dir.exists(file.path(path, pkg))] + if (length(where) == 0) { + where <- path[1] + } + paths <- c(paths, file.path(where, pkg)) + } + available <- pkg_folder %in% available_packages(path) + + versions[is_default] <- default_versions[is_default] + out <- list(available = available, + pkg_name = names, + pkg_version = versions, + pkg_version_specified = pkgs[["versions"]], + pkg_folder = pkg_folder, + path = paths) + out +} + +#' A character vector of available packages in the library +#' +#' @param path A character vector of paths to search for the package. Default is +#' the default library paths. +#' +#' @return A character vector of available package names in the library. If any +#' package versions were installed via pkg_vload(), the version will be shown as +#' "pkg-version" +#' @export +available_packages <- function(path = .libPaths()) { + libs_full <- dir(path, full.names = TRUE) + libs <- basename(libs_full) + libs[!is_dir_empty(libs_full) & !(libs %in% c(".", ".."))] +} + + #' View the current or default global options for a package #' #' [packageOptions()] scrapes the source code of a package to find all calls to @@ -25,7 +298,7 @@ #' @export #' @import utils #' @examples -#' packageOptions('utils') +#' packageOptions("utils") packageOptions <- function(pkg, own_only = FALSE, max_length = 50, show_defaults = FALSE) { opts <- extract_pkg_fun_calls(pkg, "getOption") # assuming options without defaults are not user-facing but only used internally @@ -98,7 +371,7 @@ extract_pkg_fun_calls <- function(pkg, fun) { funs <- utils::lsf.str(nmsp) fcode <- c() for (f in funs) { - code <- deparse1(get(f, envir = nmsp),collapse = "\n") + code <- deparse1(get(f, envir = nmsp), collapse = "\n") fcode <- c(fcode, code) } attr(fcode, "source") <- funs diff --git a/man/arg2string.Rd b/man/arg2string.Rd new file mode 100644 index 0000000..8c1bcab --- /dev/null +++ b/man/arg2string.Rd @@ -0,0 +1,25 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/functions.R +\name{arg2string} +\alias{arg2string} +\title{Convert Function Arguments to Strings} +\usage{ +arg2string(...) +} +\arguments{ +\item{...}{Arbitrary arguments representing R language objects or symbols.} +} +\value{ +A character vector where each element is the string representation of the corresponding +argument passed to the function. The order of the strings in the output matches the order of +the arguments. +} +\description{ +This function takes any number of R language objects and converts their names into strings. +This is particularly useful for programming where variable names or symbols need to be used +as strings without evaluating them. It leverages \code{rlang}'s tidy evaluation framework. +} +\examples{ +# returns the arguments as strings even though functions bmm() and brms() are not defined +arg2string(bmm('0.4.0'), brms('2.20.4')) +} diff --git a/man/available_packages.Rd b/man/available_packages.Rd new file mode 100644 index 0000000..92581f7 --- /dev/null +++ b/man/available_packages.Rd @@ -0,0 +1,20 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/packages.R +\name{available_packages} +\alias{available_packages} +\title{A character vector of available packages in the library} +\usage{ +available_packages(path = .libPaths()) +} +\arguments{ +\item{path}{A character vector of paths to search for the package. Default is +the default library paths.} +} +\value{ +A character vector of available package names in the library. If any +package versions were installed via pkg_vload(), the version will be shown as +"pkg-version" +} +\description{ +A character vector of available packages in the library +} diff --git a/man/is_dir_empty.Rd b/man/is_dir_empty.Rd new file mode 100644 index 0000000..bf085f2 --- /dev/null +++ b/man/is_dir_empty.Rd @@ -0,0 +1,38 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/file_system.R +\name{is_dir_empty} +\alias{is_dir_empty} +\title{Check if Directories are Empty} +\usage{ +is_dir_empty(paths) +} +\arguments{ +\item{paths}{A character vector containing one or more file paths. +Each path is checked to determine if the corresponding directory is empty.} +} +\value{ +A logical vector where each element corresponds to a directory +specified in \code{paths}. \code{TRUE} indicates that the directory is empty, +and \code{FALSE} indicates that it is not. +} +\description{ +This function checks whether one or more directories are empty. +An empty directory is one that contains no files or subdirectories. +If the directory does not exist, it is considered as empty. +} +\examples{ +\dontrun{ +# Create two temporary directories one of which is empty +library(fs) +dir1 <- tempfile() +dir2 <- tempfile() +dir_create(c(dir1, dir2)) +dir_create(file.path(dir1, "subdir")) + +# Check if the directories are empty (should return FALSE, TRUE) +is_dir_empty(c(dir1, dir2)) + +# Clean up +dir_delete(c(dir1, dir2)) +} +} diff --git a/man/nlist.Rd b/man/nlist.Rd new file mode 100644 index 0000000..4b7354b --- /dev/null +++ b/man/nlist.Rd @@ -0,0 +1,30 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/data_objects.R +\name{nlist} +\alias{nlist} +\title{Create Named List from Arguments} +\usage{ +nlist(...) +} +\arguments{ +\item{...}{Arbitrary arguments to be included in the list. These can be named or unnamed. +Unnamed arguments will be named based on their variable names.} +} +\value{ +A list where each element corresponds to an argument passed to the function. +Elements of the list are named based on either their original names or the names of +the variables passed as arguments. +} +\description{ +This function creates a named list from its arguments. If the arguments are named, +those names are used in the resulting list. If some arguments are unnamed, the variable +names themselves are used as names in the list. This can be useful for creating lists +where the names are important for later indexing or manipulation, and ensures all +elements in the list have names. +} +\examples{ +var1 <- 1 +var2 <- 1:10 +# This will return a list with names: c("a", "b", "var1", "var2") +nlist(a = 1, b = 2, var1, var2) +} diff --git a/man/packageOptions.Rd b/man/packageOptions.Rd index 18bbf2a..65102e2 100644 --- a/man/packageOptions.Rd +++ b/man/packageOptions.Rd @@ -36,5 +36,5 @@ Initial running time might be slow if a package contains a large amount of code. Repeated calls to the function will be significantly faster. } \examples{ -packageOptions('utils') +packageOptions("utils") } diff --git a/man/parse_pkg_version.Rd b/man/parse_pkg_version.Rd new file mode 100644 index 0000000..54fcc69 --- /dev/null +++ b/man/parse_pkg_version.Rd @@ -0,0 +1,23 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/packages.R +\name{parse_pkg_version} +\alias{parse_pkg_version} +\title{Parse package name and version from a pkg('verions') call} +\usage{ +parse_pkg_version(...) +} +\arguments{ +\item{...}{a number of calls to objects of type pkg('version') where pkg is +the package name and version is the version number} +} +\value{ +A list with two elements: names and versions. The names are the package +names and the versions are of class 'package_version'. If the version is not +specified, the version will be NA. +} +\description{ +Parse package name and version from a pkg('verions') call +} +\examples{ +parse_pkg_version(brms("2.20.4"), bmm("0.4-0"), utils) +} diff --git a/man/pkg_vavailable.Rd b/man/pkg_vavailable.Rd new file mode 100644 index 0000000..9bd816e --- /dev/null +++ b/man/pkg_vavailable.Rd @@ -0,0 +1,50 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/packages.R +\name{pkg_vavailable} +\alias{pkg_vavailable} +\title{Check if a specific package version is available in the library} +\usage{ +pkg_vavailable(..., path = .libPaths(), exact = TRUE) +} +\arguments{ +\item{...}{One or more calls to the package name with version (if desired) +specified in parantheses. E.g. brms("2.14.4") or brms or "brms"} + +\item{path}{A character vector of paths to search for the package. Default is +the default library paths.} + +\item{exact}{Logical. If \code{TRUE}, the function will only return \code{TRUE} if the +exact version is available. If \code{FALSE}, the function will return \code{TRUE} if +the version is available or if a higher version is available. Default is \code{TRUE}.} +} +\value{ +a named list with the following elements: +\itemize{ +\item available: A logical vector indicating whether the package is available +\item pkg_name: The name of the package +\item pkg_version: The version of the package in the library +\item pkg_version_specified: The version of the package specified in the call to pkg('version') +\item pkg_folder: The folder name of the package in the library +} +} +\description{ +\code{\link[=pkg_vavailable]{pkg_vavailable()}} is an alternative to \code{\link[xfun:pkg_attach]{xfun::pkg_available()}} that checks +for a specific version of the package rather than a minimal version. If the +version is not specified, the function will check for the default version of +the package. +} +\details{ +To check for a specific version, the function assumes that this +version was installed using pkg_load(pkg(version)), which has +created a folder named "pkg-version" in the library. +} +\examples{ +\dontrun{ +pkg_vavailable(utils) +pkg_vavailable(xfun("0.1.0")) +pkg_vavailable(utils, brms("2.14.4"), xfun("0.1.0")) + +# compare with xfun::pkg_available() +xfun::pkg_available("xfun", "0.1.0") # returns TRUE +} +} diff --git a/man/pkg_vload.Rd b/man/pkg_vload.Rd new file mode 100644 index 0000000..9e84409 --- /dev/null +++ b/man/pkg_vload.Rd @@ -0,0 +1,57 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/packages.R +\name{pkg_vload} +\alias{pkg_vload} +\title{Load and/or install packages with specific versions} +\usage{ +pkg_vload( + ..., + reload = FALSE, + path = .libPaths(), + repos = getOption("repos"), + install_args = NULL +) +} +\arguments{ +\item{...}{One or more calls to the package name with version (if desired). +The calls should be of the form \code{pkg('version')} where \code{pkg} is the package +name and \code{version} is the version number. If the version is not specified, +the function will check for the default version of the package.} + +\item{reload}{Logical. If \code{TRUE}, the function will attempt to unload the +package and load it again, regardless of whether the version is the same as +the one specified in the call. Default is \code{FALSE}. If the package is +already loaded, it will be reloaded even if reload is \code{FALSE}, if the +specified version is different from the one currently loaded.} + +\item{path}{A character vector of paths to search for the package. Default is +the default library paths.} + +\item{repos}{A character vector of repository URLs to use for installing the +package. Default is the value of \code{getOption("repos")}.} + +\item{install_args}{A list of additional arguments to be passed to +\code{install.packages()} or \code{remotes::install_version()}. Default is \code{NULL}.} +} +\value{ +This function does not return a value. Instead, it will stop the +execution and display a message if the requirements are not met. +} +\description{ +pkg_vload() attempts to load a package and, if it is not available, installs +it. It can also install a specific version of a package. If the package is +already installed, it will check if the version is the same as the one +specified in the call. If the version is different, it will attempt to unload +the package and install the specified version in a separate library, allowing +the user to have multiple versions of the same package installed at the same +time. +} +\examples{ +\dontrun{ +# Load the 'brms' package and install version 2.0.0 if it is not available +pkg_vload(brms("2.0.0")) + +# Load multiple packages and install specific versions if they are not available +pkg_vload(brms("2.0.0"), utils) +} +} diff --git a/man/require_pkg.Rd b/man/require_pkg.Rd new file mode 100644 index 0000000..f5d771a --- /dev/null +++ b/man/require_pkg.Rd @@ -0,0 +1,35 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/packages.R +\name{require_pkg} +\alias{require_pkg} +\title{Check Required Packages and Their Versions} +\usage{ +require_pkg(..., message_prefix = "Please install the following packages:") +} +\arguments{ +\item{...}{Variable arguments representing required package names and, +optionally, their minimum versions. The versions should be specified +immediately after the package names, in the format \code{packageName(version)}.} + +\item{message_prefix}{A character string to be displayed before the message +if the requirements are not met.} +} +\value{ +This function does not return a value. Instead, it will stop +the execution and display a message if the requirements are not met. +} +\description{ +This function checks if the required R packages are available and if their +versions meet the specified minimum requirements. It will stop the execution +and display a message if any required package is missing or does not meet +the version requirement. +} +\examples{ +\dontrun{ +# Check if 'dplyr' and 'ggplot2' are installed (any versions): +require_pkg(dplyr, ggplot2) + +# Check if 'dplyr' (version 1.0.0 or higher) and 'ggplot2' (version 8.3.0 or higher) are installed: +require_pkg(dplyr('1.0.0'), ggplot2('8.3.0')) +} +} diff --git a/tests/testthat/test-packages.R b/tests/testthat/test-packages.R index 43db452..319d1c8 100644 --- a/tests/testthat/test-packages.R +++ b/tests/testthat/test-packages.R @@ -1,4 +1,59 @@ test_that("extract_pkg_fun_calls works", { - res <- extract_pkg_fun_calls('utils', 'getOption') - expect_snapshot_value(res, style="json2") + res <- extract_pkg_fun_calls("utils", "getOption") + expect_snapshot_value(res, style = "json2") +}) + +test_that("parse_pkg_version works", { + expect_error(parse_pkg_version(brms("2.20.0a")), "Invalid version") + expect_error(parse_pkg_version(brms("2.20")), "Invalid version") + expect_error(parse_pkg_version(brms("2.20.0-a")), "Invalid version") + expect_error(parse_pkg_version(brms("2.20.0.")), "Invalid version") + expect_equal(parse_pkg_version(utils), list( + names = "utils", + versions = package_version(NA, strict = FALSE) + )) + expect_equal(parse_pkg_version(brms("2.20.4")), list( + names = "brms", + versions = package_version("2.20.4") + )) + expect_equal( + parse_pkg_version(brms("2.20.4"), bmm("0.4-0")), + list( + names = c("brms", "bmm"), + versions = package_version(c("2.20.4", "0.4.0")) + ) + ) +}) + +test_that("pkg_vavailable works", { + utilsver <- packageVersion("utils") + statsver <- packageVersion("stats") + expect_equal(pkg_vavailable(stats, utils, xfun("7.0.0")), list( + available = c(TRUE, TRUE, FALSE), + pkg_name = c("stats", "utils", "xfun"), + pkg_version = c(statsver, utilsver, package_version("7.0.0")), + pkg_version_specified = c( + package_version(NA, strict = FALSE), + package_version(NA, strict = FALSE), + package_version("7.0.0") + ), + pkg_folder = c("stats", "utils", "xfun-7.0.0"), + path = c( + file.path(.libPaths()[2], "stats"), + file.path(.libPaths()[2], "utils"), + file.path(.libPaths()[1], "xfun-7.0.0") + ) + )) +}) + +test_that("require_pkg works", { + expect_error(require_pkg(brms("10.1.1")), "version 10.1.1 or higher") + expect_error(require_pkg(bgdfrms("10.1.1")), "version 10.1.1 or higher") + expect_error(require_pkg(brms("10.1.1"), bmm("8.4.0")), "version 8.4.0 or higher") + expect_error(require_pkg(brgsms("10.1.1"), bmgfdm("8.4.0")), "version 8.4.0 or higher") + expect_silent(require_pkg(stats)) + expect_silent(require_pkg("stats")) + expect_silent(require_pkg(stats, utils)) + expect_silent(require_pkg(stats("0.0.1"))) + expect_error(require_pkg(stats("10.0.1"))) })