From 79279da0932059a52188f63831c4a98b9147e6a8 Mon Sep 17 00:00:00 2001 From: Dawid Kaledkowski Date: Wed, 18 Jun 2025 15:43:07 +0200 Subject: [PATCH 01/14] evaluate caches outputs --- .lintr | 1 - DESCRIPTION | 1 + NEWS.md | 1 + R/qenv-eval_code.R | 68 +++++++++++++--------------- R/utils.R | 19 ++++++-- man/get_warn_message_util.Rd | 2 +- tests/testthat/test-qenv_concat.R | 38 ++++++++++++---- tests/testthat/test-qenv_eval_code.R | 15 ++++++ tests/testthat/test-qenv_join.R | 38 ++++++++++++---- 9 files changed, 122 insertions(+), 61 deletions(-) diff --git a/.lintr b/.lintr index 34473d273..3377b6466 100644 --- a/.lintr +++ b/.lintr @@ -1,5 +1,4 @@ linters: linters_with_defaults( line_length_linter = line_length_linter(120), - cyclocomp_linter = NULL, object_usage_linter = NULL ) diff --git a/DESCRIPTION b/DESCRIPTION index a98b06fb7..3fc30cdd6 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -29,6 +29,7 @@ Depends: Imports: checkmate (>= 2.1.0), cli (>= 3.4.0), + evaluate (>= 1.0.0), grDevices, lifecycle (>= 0.2.0), rlang (>= 1.1.0), diff --git a/NEWS.md b/NEWS.md index 0ac600535..4cda48fec 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,6 +6,7 @@ ### Miscellaneous +* `eval_code` uses `evaluate::evaluate` and stores returned outputs in the code's attribute. * Refactor `eval_code` method signature to allow for more flexibility when extending the `eval_code`/`within` functions. # teal.code 0.6.1 diff --git a/R/qenv-eval_code.R b/R/qenv-eval_code.R index 878f5ecad..ca1da3639 100644 --- a/R/qenv-eval_code.R +++ b/R/qenv-eval_code.R @@ -53,48 +53,44 @@ setMethod("eval_code", signature = c(object = "qenv.error"), function(object, co return(object) } code_split <- split_code(paste(code, collapse = "\n")) + for (i in seq_along(code_split)) { current_code <- code_split[[i]] current_call <- parse(text = current_code, keep.source = TRUE) - # Using withCallingHandlers to capture warnings and messages. - # Using tryCatch to capture the error and abort further evaluation. - x <- withCallingHandlers( - tryCatch( - { - eval(current_call, envir = object@.xData) - if (!identical(parent.env(object@.xData), parent.env(.GlobalEnv))) { - # needed to make sure that @.xData is always a sibling of .GlobalEnv - # could be changed when any new package is added to search path (through library or require call) - parent.env(object@.xData) <- parent.env(.GlobalEnv) - } - NULL - }, - error = function(e) { - errorCondition( - message = sprintf( - "%s \n when evaluating qenv code:\n%s", - cli::ansi_strip(conditionMessage(e)), - current_code - ), - class = c("qenv.error", "try-error", "simpleError"), - trace = unlist(c(object@code, list(current_code))) - ) - } - ), - warning = function(w) { - attr(current_code, "warning") <<- cli::ansi_strip(sprintf("> %s\n", conditionMessage(w))) - invokeRestart("muffleWarning") - }, - message = function(m) { - attr(current_code, "message") <<- cli::ansi_strip(sprintf("> %s", conditionMessage(m))) - invokeRestart("muffleMessage") - } + x <- evaluate::evaluate( + current_code, + envir = object@.xData, + stop_on_error = 1, + output_handler = evaluate::new_output_handler(value = identity) ) - if (!is.null(x)) { - return(x) + e <- Filter(function(e) inherits(e, "error"), x) + if (length(e)) { + return( + errorCondition( + message = sprintf( + "%s \n when evaluating qenv code:\n%s", + cli::ansi_strip(conditionMessage(e[[1]])), + current_code + ), + class = c("qenv.error", "try-error", "simpleError"), + trace = unlist(c(object@code, list(current_code))) + ) + ) + } + if (!identical(parent.env(object@.xData), parent.env(.GlobalEnv))) { + # needed to make sure that @.xData is always a sibling of .GlobalEnv + # could be changed when any new package is added to search path (through library or require call) + parent.env(object@.xData) <- parent.env(.GlobalEnv) } - attr(current_code, "dependency") <- extract_dependency(current_call) + + attributes(current_code) <- Filter( + length, + list( + dependency = extract_dependency(current_call), + outputs = x[-1] + ) + ) object@code <- c(object@code, stats::setNames(list(current_code), sample.int(.Machine$integer.max, size = 1))) } diff --git a/R/utils.R b/R/utils.R index 0028c708c..bec2b9e42 100644 --- a/R/utils.R +++ b/R/utils.R @@ -57,12 +57,25 @@ lang2calls <- function(x) { #' Obtain warnings or messages from code slot #' #' @param object (`qenv`) -#' @param what (`"warning"` or `"message"`) +#' @param what (`warning` or `message`) #' @return `character(1)` containing combined message or `NULL` when no warnings/messages #' @keywords internal get_warn_message_util <- function(object, what) { checkmate::matchArg(what, choices = c("warning", "message")) - messages <- lapply(object@code, "attr", what) + messages <- lapply( + object@code, + function(x) { + unlist(lapply( + attr(x, "outputs"), + function(el) { + if (inherits(el, what)) { + sprintf("> %s", conditionMessage(el)) + } + } + )) + } + ) + idx_warn <- which(sapply(messages, function(x) !is.null(x) && !identical(x, ""))) if (!any(idx_warn)) { return(NULL) @@ -74,7 +87,7 @@ get_warn_message_util <- function(object, what) { warn = messages, expr = code, function(warn, expr) { - sprintf("%swhen running code:\n%s", warn, expr) + sprintf("%s\nwhen running code:\n%s", trimws(warn), trimws(expr)) } ) diff --git a/man/get_warn_message_util.Rd b/man/get_warn_message_util.Rd index 18a54dfc7..e6bd06ca5 100644 --- a/man/get_warn_message_util.Rd +++ b/man/get_warn_message_util.Rd @@ -9,7 +9,7 @@ get_warn_message_util(object, what) \arguments{ \item{object}{(\code{qenv})} -\item{what}{(\code{"warning"} or \code{"message"})} +\item{what}{(\code{warning} or \code{message})} } \value{ \code{character(1)} containing combined message or \code{NULL} when no warnings/messages diff --git a/tests/testthat/test-qenv_concat.R b/tests/testthat/test-qenv_concat.R index 7feb0200a..32761f885 100644 --- a/tests/testthat/test-qenv_concat.R +++ b/tests/testthat/test-qenv_concat.R @@ -50,11 +50,20 @@ testthat::test_that("Concatenate two independent qenvs with warnings results in q12 <- concat(q1, q2) - testthat::expect_equal( - unlist(lapply(q12@code, attr, "warning"), use.names = FALSE), - c( - "> This is warning 1\n", - "> This is warning 2\n" + testthat::expect_identical( + get_warnings(q12), + paste( + "~~~ Warnings ~~~", + "\n> This is warning 1", + "when running code:", + "warning('This is warning 1')", + "\n> This is warning 2", + "when running code:", + "warning('This is warning 2')", + "\n~~~ Trace ~~~\n", + "warning('This is warning 1')", + "warning('This is warning 2')", + sep = "\n" ) ) }) @@ -65,11 +74,20 @@ testthat::test_that("Concatenate two independent qenvs with messages results in q12 <- concat(q1, q2) - testthat::expect_equal( - unlist(lapply(q12@code, attr, "message"), use.names = FALSE), - c( - "> This is message 1\n", - "> This is message 2\n" + testthat::expect_identical( + get_messages(q12), + paste( + "~~~ Messages ~~~", + "\n> This is message 1", + "when running code:", + "message('This is message 1')", + "\n> This is message 2", + "when running code:", + "message('This is message 2')", + "\n~~~ Trace ~~~\n", + "message('This is message 1')", + "message('This is message 2')", + sep = "\n" ) ) }) diff --git a/tests/testthat/test-qenv_eval_code.R b/tests/testthat/test-qenv_eval_code.R index b16ddbdf6..cf769a009 100644 --- a/tests/testthat/test-qenv_eval_code.R +++ b/tests/testthat/test-qenv_eval_code.R @@ -186,3 +186,18 @@ testthat::test_that("comments passed alone to eval_code that contain @linksto ta "x" ) }) + +testthat::test_that("object printed (explicitly) is stored as string in the 'outputs' attribute of a code element", { + q <- eval_code(qenv(), "print('whatever')") + testthat::expect_identical(attr(q@code[[1]], "outputs")[[1]], '[1] "whatever"\n') +}) + +testthat::test_that("object printed (implicitly) is stored asis in the 'outputs' attribute of a code element", { + q <- eval_code(qenv(), "head(letters)") + testthat::expect_identical(attr(q@code[[1]], "outputs")[[1]], head(letters)) +}) + +testthat::test_that("plot output is stored as recordedplot in the 'outputs' attribute of a code element", { + q <- eval_code(qenv(), "plot(1)") + testthat::expect_s3_class(attr(q@code[[1]], "outputs")[[1]], "recordedplot") +}) diff --git a/tests/testthat/test-qenv_join.R b/tests/testthat/test-qenv_join.R index a234fced6..ccb23284e 100644 --- a/tests/testthat/test-qenv_join.R +++ b/tests/testthat/test-qenv_join.R @@ -130,11 +130,20 @@ testthat::test_that("Joining two independent qenvs with warnings results in obje q <- c(q1, q2) - testthat::expect_equal( - vapply(q@code, attr, which = "warning", character(1L), USE.NAMES = FALSE), - c( - "> This is warning 1\n", - "> This is warning 2\n" + testthat::expect_identical( + get_warnings(q), + paste( + "~~~ Warnings ~~~", + "\n> This is warning 1", + "when running code:", + "warning('This is warning 1')", + "\n> This is warning 2", + "when running code:", + "warning('This is warning 2')", + "\n~~~ Trace ~~~\n", + "warning('This is warning 1')", + "warning('This is warning 2')", + sep = "\n" ) ) }) @@ -145,11 +154,20 @@ testthat::test_that("Joining two independent qenvs with messages results in obje q <- c(q1, q2) - testthat::expect_equal( - vapply(q@code, attr, which = "message", character(1L), USE.NAMES = FALSE), - c( - "> This is message 1\n", - "> This is message 2\n" + testthat::expect_identical( + get_messages(q), + paste( + "~~~ Messages ~~~", + "\n> This is message 1", + "when running code:", + "message('This is message 1')", + "\n> This is message 2", + "when running code:", + "message('This is message 2')", + "\n~~~ Trace ~~~\n", + "message('This is message 1')", + "message('This is message 2')", + sep = "\n" ) ) }) From 9c1f3951e949192fb217c75ad8d73fd36ca18c11 Mon Sep 17 00:00:00 2001 From: Dawid Kaledkowski Date: Wed, 18 Jun 2025 16:24:00 +0200 Subject: [PATCH 02/14] WIP - adding get_outputs function --- DESCRIPTION | 1 + NAMESPACE | 1 + NEWS.md | 4 ++++ R/qenv-get_outputs.R | 29 +++++++++++++++++++++++++++++ man/get_outputs.Rd | 29 +++++++++++++++++++++++++++++ tests/testthat/test-get_outputs.R | 8 ++++++++ 6 files changed, 72 insertions(+) create mode 100644 R/qenv-get_outputs.R create mode 100644 man/get_outputs.Rd create mode 100644 tests/testthat/test-get_outputs.R diff --git a/DESCRIPTION b/DESCRIPTION index 3fc30cdd6..411671821 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -65,6 +65,7 @@ Collate: 'qenv-get_code.R' 'qenv-get_env.R' 'qenv-get_messages.r' + 'qenv-get_outputs.R' 'qenv-get_var.R' 'qenv-get_warnings.R' 'qenv-join.R' diff --git a/NAMESPACE b/NAMESPACE index a57ca1d2c..1ab986cb3 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -17,6 +17,7 @@ export(eval_code) export(get_code) export(get_env) export(get_messages) +export(get_outputs) export(get_var) export(get_warnings) export(join) diff --git a/NEWS.md b/NEWS.md index 4cda48fec..cc3f8e91a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,9 @@ # teal.code 0.6.1.9003 +### Enhancements + +* Introduced `get_outputs` function to fetch objects which have been printed or plotted in the `qenv` code. + ### Bug fixes * Fix a problem detecting co-occurrences when expression has multiple lines. diff --git a/R/qenv-get_outputs.R b/R/qenv-get_outputs.R new file mode 100644 index 000000000..112dabdbd --- /dev/null +++ b/R/qenv-get_outputs.R @@ -0,0 +1,29 @@ +#' Get outputs +#' +#' `eval_code` evaluates code silently so plots and prints don't show up in the console or graphic devices. +#' If one wants to use an output outside of the qenv (e.g. use a graph in `renderPlot`) then use `get_outputs`. +#' @param object (`qenv`) +#' @return list of outputs generated in a qenv +#' @examples +#' q <- eval_code( +#' qenv(), +#' quote({ +#' a <- 1 +#' print("I'm an output") +#' plot(1) +#' }) +#' ) +#' get_outputs(q) +#' @export +setGeneric("get_outputs", function(object) standardGeneric("get_outputs")) + + +setMethod("get_outputs", signature = "qenv", function(object) { + Reduce( + function(x, y) { + c(x, attr(y, "outputs")) + }, + init = list(), + x = object@code + ) +}) diff --git a/man/get_outputs.Rd b/man/get_outputs.Rd new file mode 100644 index 000000000..69bc47cb2 --- /dev/null +++ b/man/get_outputs.Rd @@ -0,0 +1,29 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/qenv-get_outputs.R +\name{get_outputs} +\alias{get_outputs} +\title{Get outputs} +\usage{ +get_outputs(object) +} +\arguments{ +\item{object}{(\code{qenv})} +} +\value{ +list of outputs generated in a qenv +} +\description{ +\code{eval_code} evaluates code silently so plots and prints don't show up in the console or graphic devices. +If one wants to use an output outside of the qenv (e.g. use a graph in \code{renderPlot}) then use \code{get_outputs}. +} +\examples{ +q <- eval_code( + qenv(), + quote({ + a <- 1 + print("I'm an output") + plot(1) + }) +) +get_outputs(q) +} diff --git a/tests/testthat/test-get_outputs.R b/tests/testthat/test-get_outputs.R new file mode 100644 index 000000000..6ce18f49c --- /dev/null +++ b/tests/testthat/test-get_outputs.R @@ -0,0 +1,8 @@ +testthat::test_that("", { + q <- qenv() + q1 <- eval_code(q, expression(iris, mtcars)) + testthat::expect_identical( + get_outputs(q1), + list(iris, mtcars) + ) +}) From ec49b8d205ca946a8572130b89f047f0d0be8e49 Mon Sep 17 00:00:00 2001 From: Dawid Kaledkowski Date: Thu, 19 Jun 2025 07:51:00 +0200 Subject: [PATCH 03/14] fix R CMD check --- R/qenv-get_outputs.R | 9 ++++-- _pkgdown.yml | 1 + man/get_outputs.Rd | 6 ++-- tests/testthat/test-get_outputs.R | 46 ++++++++++++++++++++++++++----- 4 files changed, 51 insertions(+), 11 deletions(-) diff --git a/R/qenv-get_outputs.R b/R/qenv-get_outputs.R index 112dabdbd..479ad7772 100644 --- a/R/qenv-get_outputs.R +++ b/R/qenv-get_outputs.R @@ -1,9 +1,10 @@ #' Get outputs #' +#' @description #' `eval_code` evaluates code silently so plots and prints don't show up in the console or graphic devices. -#' If one wants to use an output outside of the qenv (e.g. use a graph in `renderPlot`) then use `get_outputs`. +#' If one wants to use an output outside of the `qenv` (e.g. use a graph in `renderPlot`) then use `get_outputs`. #' @param object (`qenv`) -#' @return list of outputs generated in a qenv +#' @return list of outputs generated in a `qenv`` #' @examples #' q <- eval_code( #' qenv(), @@ -14,10 +15,14 @@ #' }) #' ) #' get_outputs(q) +#' +#' @aliases get_outputs,qenv-method +#' #' @export setGeneric("get_outputs", function(object) standardGeneric("get_outputs")) + setMethod("get_outputs", signature = "qenv", function(object) { Reduce( function(x, y) { diff --git a/_pkgdown.yml b/_pkgdown.yml index e4a8c973c..6be321a65 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -31,6 +31,7 @@ reference: - eval_code - get_code - get_env + - get_outputs - get_var - get_messages - get_warnings diff --git a/man/get_outputs.Rd b/man/get_outputs.Rd index 69bc47cb2..e838cecbd 100644 --- a/man/get_outputs.Rd +++ b/man/get_outputs.Rd @@ -2,6 +2,7 @@ % Please edit documentation in R/qenv-get_outputs.R \name{get_outputs} \alias{get_outputs} +\alias{get_outputs,qenv-method} \title{Get outputs} \usage{ get_outputs(object) @@ -10,11 +11,11 @@ get_outputs(object) \item{object}{(\code{qenv})} } \value{ -list of outputs generated in a qenv +list of outputs generated in a `qenv`` } \description{ \code{eval_code} evaluates code silently so plots and prints don't show up in the console or graphic devices. -If one wants to use an output outside of the qenv (e.g. use a graph in \code{renderPlot}) then use \code{get_outputs}. +If one wants to use an output outside of the \code{qenv} (e.g. use a graph in \code{renderPlot}) then use \code{get_outputs}. } \examples{ q <- eval_code( @@ -26,4 +27,5 @@ q <- eval_code( }) ) get_outputs(q) + } diff --git a/tests/testthat/test-get_outputs.R b/tests/testthat/test-get_outputs.R index 6ce18f49c..b4140d383 100644 --- a/tests/testthat/test-get_outputs.R +++ b/tests/testthat/test-get_outputs.R @@ -1,8 +1,40 @@ -testthat::test_that("", { - q <- qenv() - q1 <- eval_code(q, expression(iris, mtcars)) - testthat::expect_identical( - get_outputs(q1), - list(iris, mtcars) - ) +testthat::describe("get_output", { + testthat::it("returns an empty list if nothing is printed", { + q <- qenv() + q1 <- eval_code(q, expression(a <- 1L, b <- 2L)) + testthat::expect_identical(get_outputs(q1), list()) + }) + + testthat::it("implicitly printed objects are returned asis in a list", { + q <- qenv() + q1 <- eval_code(q, expression(a <- 1L, a, b <- 2L, b)) + testthat::expect_identical(get_outputs(q1), list(1L, 2L)) + }) + + testthat::it("explicitly printed objects are returned as console-output-string in a list", { + q <- qenv() + q1 <- eval_code(q, expression(a <- 1L, print(a), b <- 2L, print(b))) + testthat::expect_identical(get_outputs(q1), list("[1] 1\n", "[1] 2\n")) + }) + + testthat::it("printed plots are returned as recordedplot in a list", { + q <- qenv() + q1 <- eval_code(q, expression(a <- 1L, plot(a))) + testthat::expect_true(inherits(get_outputs(q1)[[1]], "recordedplot")) + }) + + testthat::it("warnings are returned asis in a list", { + q <- qenv() + q1 <- eval_code(q, expression(warning("test"))) + expected <- simpleWarning("test") + expected["call"] <- NULL + testthat::expect_identical(get_outputs(q1), list(expected)) + }) + + testthat::it("messages are returned asis in a list", { + q <- qenv() + q1 <- eval_code(q, expression(message("test"))) + expected <- simpleMessage("test\n", call = quote(message("test"))) + testthat::expect_identical(get_outputs(q1), list(expected)) + }) }) From 004364fdba693ccd2f42ad07093babe9ada68f68 Mon Sep 17 00:00:00 2001 From: Dawid Kaledkowski Date: Tue, 24 Jun 2025 11:26:46 +0200 Subject: [PATCH 04/14] - single evaluate::evaluate - mask environment for qenv - remove semicolon from the code --- R/qenv-class.R | 12 ++++++-- R/qenv-eval_code.R | 75 ++++++++++++++++++++-------------------------- 2 files changed, 42 insertions(+), 45 deletions(-) diff --git a/R/qenv-class.R b/R/qenv-class.R index b91146927..4b2a24ccf 100644 --- a/R/qenv-class.R +++ b/R/qenv-class.R @@ -31,11 +31,19 @@ setMethod( "initialize", "qenv", function(.Object, .xData, code = list(), ...) { # nolint: object_name. + mask_env <- new.env(parent = parent.env(.GlobalEnv)) + mask_env$library <- function(...) { + x <- library(...) + if (!identical(parent.env(mask_env), parent.env(.GlobalEnv))) { + parent.env(mask_env) <- parent.env(.GlobalEnv) + } + invisible(x) + } new_xdata <- if (rlang::is_missing(.xData)) { - new.env(parent = parent.env(.GlobalEnv)) + new.env(parent = mask_env) } else { checkmate::assert_environment(.xData) - rlang::env_clone(.xData, parent = parent.env(.GlobalEnv)) + rlang::env_clone(.xData, parent = mask_env) } lockEnvironment(new_xdata, bindings = TRUE) diff --git a/R/qenv-eval_code.R b/R/qenv-eval_code.R index ca1da3639..387fe3491 100644 --- a/R/qenv-eval_code.R +++ b/R/qenv-eval_code.R @@ -44,56 +44,45 @@ setMethod("eval_code", signature = c(object = "qenv.error"), function(object, co if (identical(trimws(code), "") || length(code) == 0) { return(object) } + code <- paste(split_code(code), collapse = "\n") + + object@.xData <- rlang::env_clone(object@.xData, parent = parent.env(object@.xData)) parsed_code <- parse(text = code, keep.source = TRUE) - object@.xData <- rlang::env_clone(object@.xData, parent = parent.env(.GlobalEnv)) - if (length(parsed_code) == 0) { - # empty code, or just comments - attr(code, "dependency") <- extract_dependency(parsed_code) # in case comment contains @linksto tag - object@code <- c(object@code, stats::setNames(list(code), sample.int(.Machine$integer.max, size = 1))) - return(object) - } - code_split <- split_code(paste(code, collapse = "\n")) - for (i in seq_along(code_split)) { - current_code <- code_split[[i]] - current_call <- parse(text = current_code, keep.source = TRUE) - x <- evaluate::evaluate( - current_code, - envir = object@.xData, - stop_on_error = 1, - output_handler = evaluate::new_output_handler(value = identity) - ) + out <- evaluate::evaluate( + code, + envir = object@.xData, + stop_on_error = 1, + output_handler = evaluate::new_output_handler(value = identity) + ) - e <- Filter(function(e) inherits(e, "error"), x) - if (length(e)) { - return( - errorCondition( - message = sprintf( - "%s \n when evaluating qenv code:\n%s", - cli::ansi_strip(conditionMessage(e[[1]])), - current_code - ), - class = c("qenv.error", "try-error", "simpleError"), - trace = unlist(c(object@code, list(current_code))) + new_code <- list() + for (this in out) { + if (inherits(this, "source")) { + this_code <- gsub("\n$", "", this$src) + attr(this_code, "dependency") <- extract_dependency(parse(text = this_code)) + new_code <- c(new_code, stats::setNames(list(this_code), sample.int(.Machine$integer.max, size = 1))) + } else { + last_code <- new_code[[length(new_code)]] + if (inherits(this, "error")) { + return( + errorCondition( + message = sprintf( + "%s \n when evaluating qenv code:\n%s", + cli::ansi_strip(conditionMessage(this)), + last_code + ), + class = c("qenv.error", "try-error", "simpleError"), + trace = unlist(c(object@code, list(new_code))) + ) ) - ) + } + attr(last_code, "outputs") <- c(attr(last_code, "outputs"), list(this)) + new_code[[length(new_code)]] <- last_code } - if (!identical(parent.env(object@.xData), parent.env(.GlobalEnv))) { - # needed to make sure that @.xData is always a sibling of .GlobalEnv - # could be changed when any new package is added to search path (through library or require call) - parent.env(object@.xData) <- parent.env(.GlobalEnv) - } - - attributes(current_code) <- Filter( - length, - list( - dependency = extract_dependency(current_call), - outputs = x[-1] - ) - ) - object@code <- c(object@code, stats::setNames(list(current_code), sample.int(.Machine$integer.max, size = 1))) } + object@code <- c(object@code, new_code) lockEnvironment(object@.xData, bindings = TRUE) object } From 208db3bee54875907c031a0ab245d67958a5a550 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Wed, 25 Jun 2025 10:39:16 +0100 Subject: [PATCH 05/14] fix: problem with tests during R CMD check --- R/qenv-eval_code.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/qenv-eval_code.R b/R/qenv-eval_code.R index 387fe3491..94136cc53 100644 --- a/R/qenv-eval_code.R +++ b/R/qenv-eval_code.R @@ -60,7 +60,7 @@ setMethod("eval_code", signature = c(object = "qenv.error"), function(object, co for (this in out) { if (inherits(this, "source")) { this_code <- gsub("\n$", "", this$src) - attr(this_code, "dependency") <- extract_dependency(parse(text = this_code)) + attr(this_code, "dependency") <- extract_dependency(parse(text = this_code, keep.source = TRUE)) new_code <- c(new_code, stats::setNames(list(this_code), sample.int(.Machine$integer.max, size = 1))) } else { last_code <- new_code[[length(new_code)]] From 8587ca783cc5ddfbfa1a59519310d6c93da10302 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Wed, 25 Jun 2025 11:40:28 +0200 Subject: [PATCH 06/14] Fix tests in `use_evaluate` branch (#259) targets: #258 --- R/utils-get_code_dependency.R | 3 +++ tests/testthat/test-qenv_constructor.R | 6 ++--- tests/testthat/test-qenv_eval_code.R | 33 +------------------------- tests/testthat/test-qenv_get_code.R | 10 ++++---- 4 files changed, 12 insertions(+), 40 deletions(-) diff --git a/R/utils-get_code_dependency.R b/R/utils-get_code_dependency.R index 7f555e4c1..8596ae420 100644 --- a/R/utils-get_code_dependency.R +++ b/R/utils-get_code_dependency.R @@ -521,6 +521,9 @@ get_call_breaks <- function(code) { } )) call_breaks <- call_breaks[-nrow(call_breaks), , drop = FALSE] # breaks in between needed only + if (nrow(call_breaks) == 0L) { + call_breaks <- matrix(numeric(0), ncol = 2) + } colnames(call_breaks) <- c("line", "col") call_breaks } diff --git a/tests/testthat/test-qenv_constructor.R b/tests/testthat/test-qenv_constructor.R index 064ef9805..992397edf 100644 --- a/tests/testthat/test-qenv_constructor.R +++ b/tests/testthat/test-qenv_constructor.R @@ -54,14 +54,14 @@ testthat::test_that("constructor returns qenv", { testthat::expect_identical(q@code, list()) }) -testthat::describe("parent of qenv environment is the parent of .GlobalEnv", { +testthat::describe("grand parent of qenv environment is the parent of .GlobalEnv", { testthat::it("via slot", { q <- qenv() - testthat::expect_identical(parent.env(q@.xData), parent.env(.GlobalEnv)) + testthat::expect_identical(parent.env(parent.env(q@.xData)), parent.env(.GlobalEnv)) }) testthat::it("via qenv directly", { q <- qenv() - testthat::expect_identical(parent.env(q), parent.env(.GlobalEnv)) + testthat::expect_identical(parent.env(parent.env(q)), parent.env(.GlobalEnv)) }) }) diff --git a/tests/testthat/test-qenv_eval_code.R b/tests/testthat/test-qenv_eval_code.R index cf769a009..9a6075a6d 100644 --- a/tests/testthat/test-qenv_eval_code.R +++ b/tests/testthat/test-qenv_eval_code.R @@ -136,44 +136,12 @@ testthat::test_that("comments fall into proper calls", { testthat::expect_identical(get_code(q), code) }) -testthat::test_that("comments alone are pasted to the next/following call element", { - code <- c("x <- 5", "# comment", "y <- 6") - q <- eval_code(qenv(), code) - testthat::expect_identical( - as.character(q@code)[2], - paste(code[2:3], collapse = "\n") - ) - testthat::expect_identical( - get_code(q), - paste(code, collapse = "\n") - ) -}) - -testthat::test_that("comments at the end of src are added to the previous call element", { - code <- c("x <- 5", "# comment") - q <- eval_code(qenv(), code) - testthat::expect_identical( - as.character(q@code), - paste(code[1:2], collapse = "\n") - ) - testthat::expect_identical( - get_code(q), - paste(code, collapse = "\n") - ) -}) - testthat::test_that("comments from the same line are associated with it's call", { code <- c("x <- 5", " y <- 4 # comment", "z <- 5") q <- eval_code(qenv(), code) testthat::expect_identical(as.character(q@code)[2], code[2]) }) -testthat::test_that("alone comments at the end of the source are considered as continuation of the last call", { - code <- c("x <- 5\n", "y <- 10\n# comment") - q <- eval_code(eval_code(qenv(), code[1]), code[2]) - testthat::expect_identical(as.character(q@code)[2], code[2]) -}) - testthat::test_that("comments passed alone to eval_code that contain @linksto tag have detected dependency", { code <- c("x <- 5", "# comment @linksto x") q <- eval_code(eval_code(qenv(), code[1]), code[2]) @@ -201,3 +169,4 @@ testthat::test_that("plot output is stored as recordedplot in the 'outputs' attr q <- eval_code(qenv(), "plot(1)") testthat::expect_s3_class(attr(q@code[[1]], "outputs")[[1]], "recordedplot") }) + diff --git a/tests/testthat/test-qenv_get_code.R b/tests/testthat/test-qenv_get_code.R index 825d9769b..a5dc2975f 100644 --- a/tests/testthat/test-qenv_get_code.R +++ b/tests/testthat/test-qenv_get_code.R @@ -581,7 +581,7 @@ testthat::test_that("detects occurrence of a function definition when a formal i }) testthat::test_that("detects occurrence of a function definition with a @linksto usage", { - code <- c( + code <- trimws(c( " foo <- function() { env <- parent.frame() @@ -589,7 +589,7 @@ testthat::test_that("detects occurrence of a function definition with a @linksto }", "foo() # @linksto x", "y <- x" - ) + )) q <- eval_code(qenv(), code) testthat::expect_identical( get_code(q, names = "x"), @@ -601,7 +601,7 @@ testthat::test_that("detects occurrence of a function definition with a @linksto # for loop -------------------------------------------------------------------------------------------------------- testthat::test_that("objects in for loop are extracted if passed as one character", { - code <- " + code <- trimws(" some_other_dataset <- mtcars original_dataset <- iris[, 1:4] count <- 1 @@ -610,11 +610,11 @@ testthat::test_that("objects in for loop are extracted if passed as one characte count <- count + 1 } output <- rlang::list2(x = original_dataset) - " + ") q <- eval_code(qenv(), code) testthat::expect_identical( get_code(q, names = "output"), - gsub("\n some_other_dataset <- mtcars\n", "", code, fixed = TRUE) + gsub("some_other_dataset <- mtcars\n", "", code, fixed = TRUE) ) }) From 0f2861b2bcfe73a988ef271822d266c31a20b755 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Wed, 25 Jun 2025 11:40:52 +0200 Subject: [PATCH 07/14] Move `library()` override back to `eval_code()` (#260) @gogonzo This might be a better solution that keeps the override under `evaluate()`, while avoiding changing who the `parent.env` is. That is, we don't need to check the "`grand.parent.env()`" --- R/qenv-class.R | 13 +++---------- R/qenv-eval_code.R | 10 ++++++++++ 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/R/qenv-class.R b/R/qenv-class.R index 4b2a24ccf..552082850 100644 --- a/R/qenv-class.R +++ b/R/qenv-class.R @@ -31,19 +31,12 @@ setMethod( "initialize", "qenv", function(.Object, .xData, code = list(), ...) { # nolint: object_name. - mask_env <- new.env(parent = parent.env(.GlobalEnv)) - mask_env$library <- function(...) { - x <- library(...) - if (!identical(parent.env(mask_env), parent.env(.GlobalEnv))) { - parent.env(mask_env) <- parent.env(.GlobalEnv) - } - invisible(x) - } + parent <- parent.env(.GlobalEnv) new_xdata <- if (rlang::is_missing(.xData)) { - new.env(parent = mask_env) + new.env(parent = parent) } else { checkmate::assert_environment(.xData) - rlang::env_clone(.xData, parent = mask_env) + rlang::env_clone(.xData, parent = parent) } lockEnvironment(new_xdata, bindings = TRUE) diff --git a/R/qenv-eval_code.R b/R/qenv-eval_code.R index 94136cc53..49b90abae 100644 --- a/R/qenv-eval_code.R +++ b/R/qenv-eval_code.R @@ -49,12 +49,22 @@ setMethod("eval_code", signature = c(object = "qenv.error"), function(object, co object@.xData <- rlang::env_clone(object@.xData, parent = parent.env(object@.xData)) parsed_code <- parse(text = code, keep.source = TRUE) + old <- evaluate::inject_funs( + library = function(...) { + x <- library(...) + if (!identical(parent.env(object@.xData), parent.env(.GlobalEnv))) { + parent.env(object@.xData) <- parent.env(.GlobalEnv) + } + invisible(x) + } + ) out <- evaluate::evaluate( code, envir = object@.xData, stop_on_error = 1, output_handler = evaluate::new_output_handler(value = identity) ) + evaluate::inject_funs(old) # remove library() override new_code <- list() for (this in out) { From ab12555687a441895a96e684836420ce2117446a Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 25 Jun 2025 09:43:03 +0000 Subject: [PATCH 08/14] [skip style] [skip vbump] Restyle files --- tests/testthat/test-qenv_eval_code.R | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/testthat/test-qenv_eval_code.R b/tests/testthat/test-qenv_eval_code.R index 9a6075a6d..c6c40798c 100644 --- a/tests/testthat/test-qenv_eval_code.R +++ b/tests/testthat/test-qenv_eval_code.R @@ -169,4 +169,3 @@ testthat::test_that("plot output is stored as recordedplot in the 'outputs' attr q <- eval_code(qenv(), "plot(1)") testthat::expect_s3_class(attr(q@code[[1]], "outputs")[[1]], "recordedplot") }) - From 6995ae54efcd094f933f3c17b5c71358543ec9db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Wed, 25 Jun 2025 10:43:11 +0100 Subject: [PATCH 09/14] chore: fix test from PR merge --- tests/testthat/test-qenv_constructor.R | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/testthat/test-qenv_constructor.R b/tests/testthat/test-qenv_constructor.R index 992397edf..064ef9805 100644 --- a/tests/testthat/test-qenv_constructor.R +++ b/tests/testthat/test-qenv_constructor.R @@ -54,14 +54,14 @@ testthat::test_that("constructor returns qenv", { testthat::expect_identical(q@code, list()) }) -testthat::describe("grand parent of qenv environment is the parent of .GlobalEnv", { +testthat::describe("parent of qenv environment is the parent of .GlobalEnv", { testthat::it("via slot", { q <- qenv() - testthat::expect_identical(parent.env(parent.env(q@.xData)), parent.env(.GlobalEnv)) + testthat::expect_identical(parent.env(q@.xData), parent.env(.GlobalEnv)) }) testthat::it("via qenv directly", { q <- qenv() - testthat::expect_identical(parent.env(parent.env(q)), parent.env(.GlobalEnv)) + testthat::expect_identical(parent.env(q), parent.env(.GlobalEnv)) }) }) From f878fed19a6e5e3f8852c6087e6569ac1d51847b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Ka=C5=82=C4=99dkowski?= Date: Wed, 25 Jun 2025 13:01:19 +0200 Subject: [PATCH 10/14] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: André Veríssimo <211358+averissimo@users.noreply.github.com> Signed-off-by: Dawid Kałędkowski --- R/qenv-get_outputs.R | 6 +----- tests/testthat/test-get_outputs.R | 4 ++++ 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/R/qenv-get_outputs.R b/R/qenv-get_outputs.R index 479ad7772..2aa626165 100644 --- a/R/qenv-get_outputs.R +++ b/R/qenv-get_outputs.R @@ -21,13 +21,9 @@ #' @export setGeneric("get_outputs", function(object) standardGeneric("get_outputs")) - - setMethod("get_outputs", signature = "qenv", function(object) { Reduce( - function(x, y) { - c(x, attr(y, "outputs")) - }, + function(x, y) c(x, attr(y, "outputs")), init = list(), x = object@code ) diff --git a/tests/testthat/test-get_outputs.R b/tests/testthat/test-get_outputs.R index b4140d383..fa54c3427 100644 --- a/tests/testthat/test-get_outputs.R +++ b/tests/testthat/test-get_outputs.R @@ -37,4 +37,8 @@ testthat::describe("get_output", { expected <- simpleMessage("test\n", call = quote(message("test"))) testthat::expect_identical(get_outputs(q1), list(expected)) }) + testthat::it("prints inside for are bundled together", { + q <- within(qenv(), for (i in 1:3) print(i)) + testthat::expect_identical(get_outputs(q)[[1]], "[1] 1\n[1] 2\n[1] 3\n") + }) }) From c26c322151fc761b4a6cc99014874d702db05c53 Mon Sep 17 00:00:00 2001 From: Dawid Kaledkowski Date: Wed, 25 Jun 2025 17:02:44 +0200 Subject: [PATCH 11/14] testing - make sure get_output is returning the same reference - check addition of print.class --- tests/testthat/test-get_outputs.R | 46 +++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/tests/testthat/test-get_outputs.R b/tests/testthat/test-get_outputs.R index fa54c3427..0a4b64196 100644 --- a/tests/testthat/test-get_outputs.R +++ b/tests/testthat/test-get_outputs.R @@ -5,10 +5,34 @@ testthat::describe("get_output", { testthat::expect_identical(get_outputs(q1), list()) }) - testthat::it("implicitly printed objects are returned asis in a list", { + testthat::it("implicitly printed objects are returned asis in a list and are identical to ones in the environment", { q <- qenv() - q1 <- eval_code(q, expression(a <- 1L, a, b <- 2L, b)) - testthat::expect_identical(get_outputs(q1), list(1L, 2L)) + q1 <- eval_code( + q, + expression( + a <- 1L, a, + b <- structure(list(aa = list(aaa = "aaa")), class = "class_to_break"), b + ) + ) + testthat::expect_identical(get_outputs(q1), unname(as.list(q1))) + testthat::expect_reference(get_outputs(q1)[[1]], q1$a) + testthat::expect_reference(get_outputs(q1)[[2]], q1$b) + }) + + testthat::it("implicitly printed list is returned asis even if its print is overridden", { + q <- qenv() + q1 <- eval_code( + q, + expression( + print.test_class <- function(x, ...) { + print("test_print") + invisible(NULL) + }, + b <- structure(list("test"), class = "test_class"), + b + ) + ) + testthat::expect_identical(get_outputs(q1), list(q1$b)) }) testthat::it("explicitly printed objects are returned as console-output-string in a list", { @@ -17,6 +41,22 @@ testthat::describe("get_output", { testthat::expect_identical(get_outputs(q1), list("[1] 1\n", "[1] 2\n")) }) + testthat::it("explicitly printed object uses newly registered print method and returned as console-output-string", { + q <- qenv() + q1 <- eval_code( + q, + expression( + print.test_class <- function(x, ...) { + print("test_print") + invisible(NULL) + }, + b <- structure(list("test"), class = "test_class"), + print(b) + ) + ) + testthat::expect_identical(get_outputs(q1), list("[1] \"test_print\"\n")) + }) + testthat::it("printed plots are returned as recordedplot in a list", { q <- qenv() q1 <- eval_code(q, expression(a <- 1L, plot(a))) From 4858a087405830a93a8c8333cd55691135028ec8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Thu, 26 Jun 2025 13:41:29 +0200 Subject: [PATCH 12/14] Adds some tests to `use_evaluate` feature branch and avoids deprecated function (#261) ### Changes description - Remove intermediate plots by default - Avoids deprecated function in testthat 3rd edition - Adds some tests - Q: Does the S4 test make sense? --- R/qenv-eval_code.R | 2 ++ tests/testthat/test-get_outputs.R | 35 +++++++++++++++++++++++++--- tests/testthat/test-qenv_eval_code.R | 8 +++++++ 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/R/qenv-eval_code.R b/R/qenv-eval_code.R index 49b90abae..cb37d14a9 100644 --- a/R/qenv-eval_code.R +++ b/R/qenv-eval_code.R @@ -64,6 +64,8 @@ setMethod("eval_code", signature = c(object = "qenv.error"), function(object, co stop_on_error = 1, output_handler = evaluate::new_output_handler(value = identity) ) + out <- evaluate::trim_intermediate_plots(out) + evaluate::inject_funs(old) # remove library() override new_code <- list() diff --git a/tests/testthat/test-get_outputs.R b/tests/testthat/test-get_outputs.R index 0a4b64196..c9ef388e0 100644 --- a/tests/testthat/test-get_outputs.R +++ b/tests/testthat/test-get_outputs.R @@ -15,8 +15,24 @@ testthat::describe("get_output", { ) ) testthat::expect_identical(get_outputs(q1), unname(as.list(q1))) - testthat::expect_reference(get_outputs(q1)[[1]], q1$a) - testthat::expect_reference(get_outputs(q1)[[2]], q1$b) + testthat::expect_true(rlang::is_reference(get_outputs(q1)[[1]], q1$a)) + testthat::expect_true(rlang::is_reference(get_outputs(q1)[[2]], q1$b)) + }) + + testthat::it("implicitly printed S4 object is returned asis in a list and identical to the one in the environment", { + q <- qenv() + q1 <- eval_code( + q, + expression( + methods::setClass("NewS4Class", slots = list(value = "numeric")), + new_obj <- methods::new("NewS4Class", value = 42), + new_obj + ) + ) + withr::defer(removeClass("NewS4Class")) + testthat::expect_identical(get_outputs(q1), unname(as.list(q1))) + testthat::expect_true(rlang::is_reference(get_outputs(q1)[[1]], q1$new_obj)) + testthat::expect_s4_class(get_outputs(q1)[[1]], "NewS4Class") }) testthat::it("implicitly printed list is returned asis even if its print is overridden", { @@ -57,12 +73,18 @@ testthat::describe("get_output", { testthat::expect_identical(get_outputs(q1), list("[1] \"test_print\"\n")) }) - testthat::it("printed plots are returned as recordedplot in a list", { + testthat::it("printed plots are returned as recordedplot in a list (1)", { q <- qenv() q1 <- eval_code(q, expression(a <- 1L, plot(a))) testthat::expect_true(inherits(get_outputs(q1)[[1]], "recordedplot")) }) + testthat::it("printed plots are returned as recordedplot in a list (2)", { + q <- qenv() + q1 <- eval_code(q, expression(a <- seq_len(10L), hist(a))) + testthat::expect_true(inherits(get_outputs(q1)[[1]], "recordedplot")) + }) + testthat::it("warnings are returned asis in a list", { q <- qenv() q1 <- eval_code(q, expression(warning("test"))) @@ -77,8 +99,15 @@ testthat::describe("get_output", { expected <- simpleMessage("test\n", call = quote(message("test"))) testthat::expect_identical(get_outputs(q1), list(expected)) }) + testthat::it("prints inside for are bundled together", { q <- within(qenv(), for (i in 1:3) print(i)) testthat::expect_identical(get_outputs(q)[[1]], "[1] 1\n[1] 2\n[1] 3\n") }) + + testthat::it("intermediate plots are not kept", { + q <- qenv() + q1 <- eval_code(q, expression(plot(1:10), title("A title"))) + testthat::expect_length(get_outputs(q1), 1) + }) }) diff --git a/tests/testthat/test-qenv_eval_code.R b/tests/testthat/test-qenv_eval_code.R index c6c40798c..01b113535 100644 --- a/tests/testthat/test-qenv_eval_code.R +++ b/tests/testthat/test-qenv_eval_code.R @@ -169,3 +169,11 @@ testthat::test_that("plot output is stored as recordedplot in the 'outputs' attr q <- eval_code(qenv(), "plot(1)") testthat::expect_s3_class(attr(q@code[[1]], "outputs")[[1]], "recordedplot") }) + +testthat::test_that("plot cannot modified previous plots when calls are seperate", { + q <- qenv() + q1 <- eval_code(q, expression(plot(1:10))) + + q2 <- eval_code(q1, expression(title("A title"))) + testthat::expect_s3_class(q2, "qenv.error") +}) From a834e08a840d1b2ccf797bbfbbbf233292ab3b92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Thu, 26 Jun 2025 14:05:21 +0100 Subject: [PATCH 13/14] fix: test with S4 method registration --- tests/testthat/test-get_outputs.R | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/testthat/test-get_outputs.R b/tests/testthat/test-get_outputs.R index c9ef388e0..a10395561 100644 --- a/tests/testthat/test-get_outputs.R +++ b/tests/testthat/test-get_outputs.R @@ -19,17 +19,18 @@ testthat::describe("get_output", { testthat::expect_true(rlang::is_reference(get_outputs(q1)[[2]], q1$b)) }) - testthat::it("implicitly printed S4 object is returned asis in a list and identical to the one in the environment", { + # it cannot have a package prefix here until upstream bug in testthat is solved + it("implicitly printed S4 object is returned asis in a list and identical to the one in the environment", { + methods::setClass("NewS4Class", slots = list(value = "numeric")) + withr::defer(removeClass("NewS4Class")) q <- qenv() q1 <- eval_code( q, expression( - methods::setClass("NewS4Class", slots = list(value = "numeric")), new_obj <- methods::new("NewS4Class", value = 42), new_obj ) ) - withr::defer(removeClass("NewS4Class")) testthat::expect_identical(get_outputs(q1), unname(as.list(q1))) testthat::expect_true(rlang::is_reference(get_outputs(q1)[[1]], q1$new_obj)) testthat::expect_s4_class(get_outputs(q1)[[1]], "NewS4Class") From d17b8df87d430b70183c9bad45318f4363c7a21f Mon Sep 17 00:00:00 2001 From: Dawid Kaledkowski Date: Thu, 26 Jun 2025 16:42:05 +0200 Subject: [PATCH 14/14] vignette --- vignettes/qenv.Rmd | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/vignettes/qenv.Rmd b/vignettes/qenv.Rmd index 4859e32c5..0a16b9fd3 100644 --- a/vignettes/qenv.Rmd +++ b/vignettes/qenv.Rmd @@ -14,6 +14,7 @@ A `qenv` inherits from the `environment` class, behaves like an environment, and - It inherits from the environment and methods such as `$`, `get`, `ls`, `as.list()` work out of the box. - `qenv` is a locked environment, and data modification is only possible through the `eval_code` and `within` functions. +- It stores printed and plotted outputs (see `get_outputs`). - It stores metadata about the code used to create the data (see `get_code`). - It supports slicing by `[`. - It is immutable which means that each code evaluation does not modify the original `qenv` environment directly. @@ -56,14 +57,17 @@ The same result can be achieved with the `within` method. ```{r} q2 <- within(my_qenv, y <- x * 2) q2 <- within(q2, z <- y * 2) +q2 <- within(q2, plot(z)) print(q2) ``` -To extract objects from a `qenv`, use `[[`; this is particularly useful for displaying them in a `shiny` app. You can retrieve the code used to generate the `qenv` using the `get_code()` function. +To extract specific object from a `qenv`'s environment, use `[[`. To extract an output of a `print` or `plot` functions, use `get_outputs()` to get a `list()` of outputs captured by `qenv`. These functions are particularly useful for displaying them in a `shiny` app. You can retrieve the code used to generate the `qenv` using the `get_code()` function. ```{r} print(q2[["y"]]) +print(get_outputs(q2)[[1]]) + cat(get_code(q2)) ``` @@ -126,7 +130,8 @@ The feasibility of joining `qenv` objects hinges on the contents of the environm ### Warnings and messages in `qenv` objects -In cases where warnings or messages arise while evaluating code within a `qenv` environment, these are captured and stored within the `qenv` object. Access these messages and warnings using below +In cases where warnings or messages arise while evaluating code within a `qenv` environment, these are captured and stored within the `qenv` object. Access these messages and warnings using `get_messages()` and `get_warnings()` functions as shown below. + ```{r} q_message <- eval_code(qenv(), quote(message("this is a message"))) @@ -163,13 +168,13 @@ server <- function(input, output, session) { # create a qenv containing the reproducible output output_q <- reactive({ req(input$option) - eval_code( + within( data_q, - bquote(p <- hist(iris_data[, .(input$option)])) + p <- hist(iris_data[, .(input$option)]) ) }) - # display output + # display plot output output$plot <- renderPlot(output_q()[["p"]]) # display code output$rcode <- renderText(get_code(output_q()))