Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prototype helper #1

Merged
merged 9 commits into from
Jul 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,20 @@ Description: Only exists while we manage the migration between orderly versions.
License: MIT + file LICENSE
Encoding: UTF-8
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.1.1
RoxygenNote: 7.2.3
URL: https://github.com/mrc-ide/orderly.helper
BugReports: https://github.com/mrc-ide/orderly.helper/issues
Imports:
rprojroot,
pkgload,
yaml
Suggests:
testthat (>= 3.0.0)
mockery,
orderly1,
orderly2,
testthat (>= 3.0.0),
withr
Config/testthat/edition: 3
Remotes:
orderly1=vimc/orderly@vimc-7135,
mrc-ide/orderly2
6 changes: 6 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
# Generated by roxygen2: do not edit by hand

export(activate)
export(auto)
export(deactivate)
export(sitrep)
export(use)
247 changes: 247 additions & 0 deletions R/helper.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
##' Activate orderly for the current repository. Run this from
##' anywhere within an orderly archive (i.e., a path containing
##' orderly_config.yml; or where one of its parent directories do). We
##' check what the required orderly minimum version is and set up the
##' orderly namespace for the appropriate version.
##'
##' @title Activate orderly for current reposotory
##'
##' @param verbose Be verbose about what we are doing. If `NULL` uses
##' the the value of the option `orderly.helper.verbose`
##'
##' @return Nothing, called for side effects only.
##' @export
activate <- function(verbose = NULL) {
version <- detect_orderly_version(getwd())
create_orderly_ns(version, verbose)
}


##' Set up orderly based on your global preferences. This will look at
##' the R option (`orderly.version`) and the environment variable
##' `ORDERLY_VERSION`, in that order, falling back on the second
##' version. Or pass in a version explicitly.
##'
##' @title Set orderly version
##'
##' @param version Either `NULL` (in which case we use global
##' preferences) or a number `1` or `2`.
##'
##' @inheritParams activate
##'
##' @export
##' @return Nothing, called for side effects only.
use <- function(version = NULL, verbose = NULL) {
version <- guess_orderly_version(version)
create_orderly_ns(version, verbose)
}


##' Deactivate any orderly helper
##'
##' @title Deactivate orderly helper
##' @return Nothing, called for its side effect
##' @export
deactivate <- function() {
tryCatch(pkgload::unload("orderly"), error = function(e) NULL)
current$name <- NULL
current$version <- NULL
invisible()
}


##' Return information about the state of orderly1, orderly2 and the helper
##'
##' @title Return information about packages
##' @return A list
##' @export
sitrep <- function() {
loaded <- loadedNamespaces()
attached <- sub("^package:", "", search())

f <- function(p) {
version <- tryCatch(utils::packageVersion(p), error = function(e) NULL)
list(version = version,
is_installed = !is.null(version),
is_loaded = p %in% loaded,
is_attached = p %in% attached)
}

pkg <- c("orderly", "orderly1", "orderly2")
ret <- lapply(pkg, f)
names(ret) <- pkg

if (ret$orderly$is_installed) {
is_installed <- any(
file.exists(file.path(.libPaths(), "orderly", "DESCRIPTION")))
if (!is_installed) {
ret$orderly <- list(version = NULL,
is_installed = FALSE,
is_loaded = FALSE,
is_attached = FALSE)
}
}

ret$current <- list(version = current$version, name = current$name)

ret
}


##' Automatically set up the orderly namespace depending on the
##' context in which this function is called.
##'
##' If called from within an orderly repository, it will activate the
##' required version using [activate()] otherwise it will use the
##' globally preferred version with [use()]
##'
##' @title Automatically configure orderly
##' @return Nothing, called for side effects only
##' @export
auto <- function() {
tryCatch(activate(), error = function(e) use())
}


## Persistant package state goes here
current <- new.env(parent = emptyenv())

create_orderly_ns <- function(version, verbose) {
check_sitrep()
name <- sprintf("orderly%d", version)
verbose <- orderly_helper_verbose(verbose)
if (identical(version, current$version)) {
if (verbose) {
message(sprintf("Already using %s", orderly_version_str(version)))
}
return(invisible())
}
if (verbose) {
message(sprintf("Using %s", orderly_version_str(version)))
}
path <- find.package(name)

desc_contents <- readLines(file.path(path, "DESCRIPTION"))
i <- grep("^Package:", desc_contents)
desc_contents[[i]] <- "Package: orderly"

exports <- getNamespaceExports(asNamespace(name))
ns_contents <- c(sprintf('import("%s")', name),
sprintf('export("%s")', exports))

tmp <- temp_package_dir(name)
writeLines(desc_contents, file.path(tmp, "DESCRIPTION"))
writeLines(ns_contents, file.path(tmp, "NAMESPACE"))
res <- pkgload::load_all(tmp, attach = FALSE, quiet = !verbose)

## Lastly, we might wire up the help too:
##
## pkgload:::dev_help(topic_str, package_str) ->
## utils::help(topic_str, "orderly1")
##
## also system.file and vignette need dealing with; these might be
## somewhat trickier though, and devtools/pkgload don't try and pull
## it off, so we can't rely on assistance there.
##
## Also don't support ':::' access; that's reasonable though.

current$name <- name
current$version <- version

invisible()
}


check_sitrep <- function(info = sitrep()) {
if (info$orderly$is_installed) {
stop(paste("You have 'orderly' installed; please uninstall it first and",
"install 'orderly1' and/or 'orderly2' instead"))
}
if (info$orderly1$is_attached && info$orderly2$is_attached) {
stop(paste("You have 'orderly1' and 'orderly2' attached; please",
"restart your session"))
}
invisible(info)
}


temp_package_dir <- function(name) {
if (is.null(current$path)) {
current$path <- tempfile()
}
path <- file.path(current$path, name)
dir.create(path, FALSE, TRUE)
path
}


orderly_helper_verbose <- function(verbose) {
verbose %||% getOption("orderly.helper.verbose", TRUE)
}


detect_orderly_version <- function(path) {
root <- find_orderly_root(path)
d <- yaml::yaml.load_file(file.path(root, "orderly_config.yml"))
version_str <- d$minimum_orderly_version
if (is.null(version_str)) {
stop(sprintf("Failed to read required orderly version from '%s'", path))
}
version <- numeric_version(version_str)
if (version < numeric_version("1.99.0")) 1 else 2
}


guess_orderly_version <- function(version) {
if (!is.null(version)) {
return(validate_orderly_version(version, "argument 'version'", FALSE))
}

version <- getOption("orderly.version", NULL)
if (!is.null(version)) {
return(validate_orderly_version(version, "option 'orderly.version'"))
}
version <- Sys.getenv("ORDERLY_VERSION", NA_character_)
if (!is.na(version)) {
return(validate_orderly_version(version,
"environment variable 'ORDERLY_VERSION'",
TRUE))
}
2
}


validate_orderly_version <- function(value, name, from_character = FALSE) {
if (from_character) {
if (!grepl("^[0-9]$", value)) {
stop(sprintf("Expected 'version' to be a number (from %s)", name))
}
value <- as.numeric(value)
} else {
if (!(is.numeric(value) && length(value) == 1 && !is.na(value))) {
stop(sprintf("Expected 'version' to be scalar number (from %s)", name))
}
}
if (!(value %in% 1:2)) {
stop(sprintf("Invalid version '%s', expected '1' or '2' (from %s)",
value, name))
}
value
}


orderly_version_str <- function(major) {
name <- sprintf("orderly%d", major)
version <- tryCatch(utils::packageVersion(name), error = function(e) "???")
sprintf("orderly %d (%s)", major, as.character(version))
}


find_orderly_root <- function(start) {
tryCatch(
rprojroot::find_root(rprojroot::has_file("orderly_config.yml"),
path = start),
error = function(e) {
stop(sprintf("Did not find orderly root above '%s'", start))
})
}
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,47 @@
[![codecov.io](https://codecov.io/github/mrc-ide/orderly.helper/coverage.svg?branch=main)](https://codecov.io/github/mrc-ide/orderly.helper?branch=main)
<!-- badges: end -->

This package exists to smooth over the difference between [`orderly1`](https://vaccineimpact.org/orderly) and [`orderly2`](https://mrc-ide.github.io/orderly2) while we manage the migration between the two packages. It will allow you to refer to either (but at the same time only *one*) of the packages by its namespace, so that

```
orderly::orderly_run(...)
```

will run `orderly_run` in one of the packages.

Within an orderly repo, you can then run:

```
orderly.helper::activate()
```

which will configure everything for you.

Alternatively, run

```r
orderly.helper::use()
```

which will set up orderly based on your personal preferences.

If you don't want to think about any of this, you can call

```r
orderly.helper::auto()
```

We recommend adding to your `.Rprofile`:

```{r}
options(orderly.version = 2, orderly.helper.verbose = TRUE)
if (!require("orderly.helper", quietly = TRUE)) {
orderly.helper::auto()
}
```

With the two options configured as you prefer.

## Installation

To install `orderly.helper`:
Expand Down
22 changes: 22 additions & 0 deletions man/activate.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions man/auto.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions man/deactivate.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading