Skip to content

Commit 71b8960

Browse files
averissimogogonzom7prgithub-actions[bot]
authored
{teal} module returns a teal_report object that extends from teal_data (#255)
# Pull Request Fixes: - insightsengineering/teal#1526 Built on top of: - insightsengineering/teal.reporter#307 - _(#307 will be closed once this PR is stable)_ ### Companion PRs: - insightsengineering/teal#1541 - #255 - insightsengineering/teal.data#370 - insightsengineering/teal.reporter#331 - insightsengineering/teal.modules.general#884 ### Changes description - [x] Add new parameter `cache` - Caches the result of the last evaluation in the respective `@code` slot - [ ] Decide on name - [x] Remove signature with multiple arguments to allow overriding `eval_code` in other packages without showing a note ``` r pkgload::load_all("teal.code") #> ℹ Loading teal.code q <- qenv() |> eval_code(1 + 1, cache = TRUE) |> eval_code(mtcars <- head(mtcars)) attr(q@code[[1]], "cache") #> [1] 2 ``` <sup>Created on 2025-06-03 with [reprex v2.1.1](https://reprex.tidyverse.org)</sup> --------- Co-authored-by: Dawid Kaledkowski <dawid.kaledkowski@gmail.com> Co-authored-by: Marcin <133694481+m7pr@users.noreply.github.com> Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 68f0b73 commit 71b8960

File tree

8 files changed

+132
-127
lines changed

8 files changed

+132
-127
lines changed

NEWS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44

55
* Fix a problem detecting co-occurrences when expression has multiple lines.
66

7+
### Miscellaneous
8+
9+
* Refactor `eval_code` method signature to allow for more flexibility when extending the `eval_code`/`within` functions.
10+
711
# teal.code 0.6.1
812

913
### Bug fixes

R/qenv-eval_code.R

Lines changed: 34 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#' @param code (`character`, `language` or `expression`) code to evaluate.
1010
#' It is possible to preserve original formatting of the `code` by providing a `character` or an
1111
#' `expression` being a result of `parse(keep.source = TRUE)`.
12+
#' @param ... ([`dots`]) additional arguments passed to future methods.
1213
#'
1314
#' @return
1415
#' `qenv` environment with `code/expr` evaluated or `qenv.error` if evaluation fails.
@@ -21,15 +22,28 @@
2122
#' q <- eval_code(q, quote(library(checkmate)))
2223
#' q <- eval_code(q, expression(assert_number(a)))
2324
#'
24-
#' @aliases eval_code,qenv,character-method
25-
#' @aliases eval_code,qenv,language-method
26-
#' @aliases eval_code,qenv,expression-method
27-
#' @aliases eval_code,qenv.error,ANY-method
28-
#'
25+
#' @aliases eval_code,qenv-method
26+
#' @aliases eval_code,qenv.error-method
27+
#' @seealso [within.qenv]
2928
#' @export
30-
setGeneric("eval_code", function(object, code) standardGeneric("eval_code"))
29+
setGeneric("eval_code", function(object, code, ...) standardGeneric("eval_code"))
30+
31+
setMethod("eval_code", signature = c(object = "qenv"), function(object, code, ...) {
32+
if (!is.language(code) && !is.character(code)) {
33+
stop("eval_code accepts code being language or character")
34+
}
35+
code <- .preprocess_code(code)
36+
# preprocess code to ensure it is a character vector
37+
.eval_code(object = object, code = code, ...)
38+
})
39+
40+
setMethod("eval_code", signature = c(object = "qenv.error"), function(object, code, ...) object)
3141

32-
setMethod("eval_code", signature = c("qenv", "character"), function(object, code) {
42+
#' @keywords internal
43+
.eval_code <- function(object, code, ...) {
44+
if (identical(trimws(code), "") || length(code) == 0) {
45+
return(object)
46+
}
3347
parsed_code <- parse(text = code, keep.source = TRUE)
3448
object@.xData <- rlang::env_clone(object@.xData, parent = parent.env(.GlobalEnv))
3549
if (length(parsed_code) == 0) {
@@ -42,7 +56,6 @@ setMethod("eval_code", signature = c("qenv", "character"), function(object, code
4256
for (i in seq_along(code_split)) {
4357
current_code <- code_split[[i]]
4458
current_call <- parse(text = current_code, keep.source = TRUE)
45-
4659
# Using withCallingHandlers to capture warnings and messages.
4760
# Using tryCatch to capture the error and abort further evaluation.
4861
x <- withCallingHandlers(
@@ -60,7 +73,7 @@ setMethod("eval_code", signature = c("qenv", "character"), function(object, code
6073
errorCondition(
6174
message = sprintf(
6275
"%s \n when evaluating qenv code:\n%s",
63-
.ansi_strip(conditionMessage(e)),
76+
cli::ansi_strip(conditionMessage(e)),
6477
current_code
6578
),
6679
class = c("qenv.error", "try-error", "simpleError"),
@@ -69,11 +82,11 @@ setMethod("eval_code", signature = c("qenv", "character"), function(object, code
6982
}
7083
),
7184
warning = function(w) {
72-
attr(current_code, "warning") <<- .ansi_strip(sprintf("> %s\n", conditionMessage(w)))
85+
attr(current_code, "warning") <<- cli::ansi_strip(sprintf("> %s\n", conditionMessage(w)))
7386
invokeRestart("muffleWarning")
7487
},
7588
message = function(m) {
76-
attr(current_code, "message") <<- .ansi_strip(sprintf("> %s", conditionMessage(m)))
89+
attr(current_code, "message") <<- cli::ansi_strip(sprintf("> %s", conditionMessage(m)))
7790
invokeRestart("muffleMessage")
7891
}
7992
)
@@ -87,42 +100,17 @@ setMethod("eval_code", signature = c("qenv", "character"), function(object, code
87100

88101
lockEnvironment(object@.xData, bindings = TRUE)
89102
object
90-
})
91-
92-
setMethod("eval_code", signature = c("qenv", "language"), function(object, code) {
93-
eval_code(object, code = paste(vapply(lang2calls(code), deparse1, collapse = "\n", character(1L)), collapse = "\n"))
94-
})
103+
}
95104

96-
setMethod("eval_code", signature = c("qenv", "expression"), function(object, code) {
97-
srcref <- attr(code, "wholeSrcref")
98-
if (length(srcref)) {
99-
eval_code(object, code = paste(attr(code, "wholeSrcref"), collapse = "\n"))
105+
setGeneric(".preprocess_code", function(code) standardGeneric(".preprocess_code"))
106+
setMethod(".preprocess_code", signature = c("character"), function(code) paste(code, collapse = "\n"))
107+
setMethod(".preprocess_code", signature = c("ANY"), function(code) {
108+
if (is.expression(code) && length(attr(code, "wholeSrcref"))) {
109+
paste(attr(code, "wholeSrcref"), collapse = "\n")
100110
} else {
101-
Reduce(function(u, v) {
102-
if (inherits(v, "=") && identical(typeof(v), "language")) {
103-
# typeof(`=`) is language, but it doesn't dispatch on it, so we need to
104-
# explicitly pass it as first class of the object
105-
class(v) <- unique(c("language", class(v)))
106-
}
107-
eval_code(u, v)
108-
}, init = object, x = code)
111+
paste(
112+
vapply(lang2calls(code), deparse1, collapse = "\n", character(1L)),
113+
collapse = "\n"
114+
)
109115
}
110116
})
111-
112-
setMethod("eval_code", signature = c("qenv.error", "ANY"), function(object, code) {
113-
object
114-
})
115-
116-
# if cli is installed rlang adds terminal printing characters
117-
# which need to be removed
118-
.ansi_strip <- function(chr) {
119-
if (requireNamespace("cli", quietly = TRUE)) {
120-
cli::ansi_strip(chr)
121-
} else {
122-
chr
123-
}
124-
}
125-
126-
get_code_attr <- function(qenv, attr) {
127-
unlist(lapply(qenv@code, function(x) attr(x, attr)))
128-
}

R/qenv-within.R

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#' Evaluate code in `qenv`
12
#' @details
23
#' `within()` is a convenience method that wraps `eval_code` to provide a simplified way of passing expression.
34
#' `within` accepts only inline expressions (both simple and compound) and allows to substitute `expr`
@@ -43,25 +44,18 @@
4344
#' within(q, exprlist) # fails
4445
#' do.call(within, list(q, do.call(c, exprlist)))
4546
#'
46-
#' @rdname eval_code
47-
#'
4847
#' @export
4948
#'
5049
within.qenv <- function(data, expr, ...) {
51-
expr <- substitute(expr)
50+
expr <- as.expression(substitute(expr))
5251
extras <- list(...)
5352

54-
# Add braces for consistency.
55-
if (!identical(as.list(expr)[[1L]], as.symbol("{"))) {
56-
expr <- call("{", expr)
57-
}
58-
59-
calls <- as.list(expr)[-1]
60-
6153
# Inject extra values into expressions.
62-
calls <- lapply(calls, function(x) do.call(substitute, list(x, env = extras)))
63-
64-
eval_code(object = data, code = as.expression(calls))
54+
calls <- lapply(expr, function(x) do.call(substitute, list(x, env = extras)))
55+
do.call(
56+
eval_code,
57+
utils::modifyList(extras, list(object = data, code = as.expression(calls)))
58+
)
6559
}
6660

6761

man/eval_code.Rd

Lines changed: 8 additions & 55 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/within.qenv.Rd

Lines changed: 62 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/test-qenv_eval_code.R

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ testthat::test_that("eval_code works with expression", {
4545
testthat::expect_equal(q1, list2env(list(a = 1, b = 2)))
4646
})
4747

48+
testthat::test_that("eval_code ignores empty code", {
49+
q <- qenv()
50+
testthat::expect_identical(q, eval_code(q, ""))
51+
})
52+
4853
testthat::test_that("eval_code preserves original formatting when `srcref` is present in the expression", {
4954
code <- "# comment
5055
a <- 1L"
@@ -77,12 +82,11 @@ testthat::test_that("eval_code works with quoted code block", {
7782
testthat::expect_equal(q1, list2env(list(a = 1, b = 2)))
7883
})
7984

80-
testthat::test_that("eval_code fails with unquoted expression", {
81-
b <- 3
82-
testthat::expect_error(
83-
eval_code(qenv(), a <- b),
84-
"unable to find an inherited method for function .eval_code. for signature"
85-
)
85+
testthat::test_that("eval_code fails with code not being language nor character", {
86+
msg <- "eval_code accepts code being language or character"
87+
testthat::expect_error(eval_code(qenv(), NULL), msg)
88+
testthat::expect_error(eval_code(qenv(), 1), msg)
89+
testthat::expect_error(eval_code(qenv(), list()), msg)
8690
})
8791

8892
testthat::test_that("an error when calling eval_code returns a qenv.error object which has message and trace", {
@@ -182,8 +186,3 @@ testthat::test_that("comments passed alone to eval_code that contain @linksto ta
182186
"x"
183187
)
184188
})
185-
186-
testthat::test_that("Code executed with integer shorthand (1L) is the same as original", {
187-
q <- within(qenv(), a <- 1L)
188-
testthat::expect_identical(get_code(q), "a <- 1L")
189-
})

tests/testthat/test-qenv_join.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ testthat::test_that("Joining two independent qenvs with warnings results in obje
131131
q <- c(q1, q2)
132132

133133
testthat::expect_equal(
134-
unname(get_code_attr(q, "warning")),
134+
vapply(q@code, attr, which = "warning", character(1L), USE.NAMES = FALSE),
135135
c(
136136
"> This is warning 1\n",
137137
"> This is warning 2\n"
@@ -146,7 +146,7 @@ testthat::test_that("Joining two independent qenvs with messages results in obje
146146
q <- c(q1, q2)
147147

148148
testthat::expect_equal(
149-
unname(get_code_attr(q, "message")),
149+
vapply(q@code, attr, which = "message", character(1L), USE.NAMES = FALSE),
150150
c(
151151
"> This is message 1\n",
152152
"> This is message 2\n"

tests/testthat/test-qenv_within.R

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,3 +149,8 @@ testthat::describe("within run with `=`", {
149149
testthat::expect_equal(q$i, 1)
150150
})
151151
})
152+
153+
testthat::test_that("Code executed with integer shorthand (1L) is the same as original", {
154+
q <- within(qenv(), a <- 1L)
155+
testthat::expect_identical(get_code(q), "a <- 1L")
156+
})

0 commit comments

Comments
 (0)