Skip to content

Commit b53f9e1

Browse files
authored
Merge d17b8df into 5a26afd
2 parents 5a26afd + d17b8df commit b53f9e1

18 files changed

+341
-112
lines changed

.lintr

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
linters: linters_with_defaults(
22
line_length_linter = line_length_linter(120),
3-
cyclocomp_linter = NULL,
43
object_usage_linter = NULL
54
)

DESCRIPTION

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Depends:
2929
Imports:
3030
checkmate (>= 2.1.0),
3131
cli (>= 3.4.0),
32+
evaluate (>= 1.0.0),
3233
grDevices,
3334
lifecycle (>= 0.2.0),
3435
rlang (>= 1.1.0),
@@ -64,6 +65,7 @@ Collate:
6465
'qenv-get_code.R'
6566
'qenv-get_env.R'
6667
'qenv-get_messages.r'
68+
'qenv-get_outputs.R'
6769
'qenv-get_var.R'
6870
'qenv-get_warnings.R'
6971
'qenv-join.R'

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export(eval_code)
1717
export(get_code)
1818
export(get_env)
1919
export(get_messages)
20+
export(get_outputs)
2021
export(get_var)
2122
export(get_warnings)
2223
export(join)

NEWS.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
# teal.code 0.6.1.9003
22

3+
### Enhancements
4+
5+
* Introduced `get_outputs` function to fetch objects which have been printed or plotted in the `qenv` code.
6+
37
### Bug fixes
48

59
* Fix a problem detecting co-occurrences when expression has multiple lines.
610

711
### Miscellaneous
812

13+
* `eval_code` uses `evaluate::evaluate` and stores returned outputs in the code's attribute.
914
* Refactor `eval_code` method signature to allow for more flexibility when extending the `eval_code`/`within` functions.
1015

1116
# teal.code 0.6.1

R/qenv-class.R

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,12 @@ setMethod(
3131
"initialize",
3232
"qenv",
3333
function(.Object, .xData, code = list(), ...) { # nolint: object_name.
34+
parent <- parent.env(.GlobalEnv)
3435
new_xdata <- if (rlang::is_missing(.xData)) {
35-
new.env(parent = parent.env(.GlobalEnv))
36+
new.env(parent = parent)
3637
} else {
3738
checkmate::assert_environment(.xData)
38-
rlang::env_clone(.xData, parent = parent.env(.GlobalEnv))
39+
rlang::env_clone(.xData, parent = parent)
3940
}
4041
lockEnvironment(new_xdata, bindings = TRUE)
4142

R/qenv-eval_code.R

Lines changed: 40 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -44,60 +44,57 @@ setMethod("eval_code", signature = c(object = "qenv.error"), function(object, co
4444
if (identical(trimws(code), "") || length(code) == 0) {
4545
return(object)
4646
}
47+
code <- paste(split_code(code), collapse = "\n")
48+
49+
object@.xData <- rlang::env_clone(object@.xData, parent = parent.env(object@.xData))
4750
parsed_code <- parse(text = code, keep.source = TRUE)
48-
object@.xData <- rlang::env_clone(object@.xData, parent = parent.env(.GlobalEnv))
49-
if (length(parsed_code) == 0) {
50-
# empty code, or just comments
51-
attr(code, "dependency") <- extract_dependency(parsed_code) # in case comment contains @linksto tag
52-
object@code <- c(object@code, stats::setNames(list(code), sample.int(.Machine$integer.max, size = 1)))
53-
return(object)
54-
}
55-
code_split <- split_code(paste(code, collapse = "\n"))
56-
for (i in seq_along(code_split)) {
57-
current_code <- code_split[[i]]
58-
current_call <- parse(text = current_code, keep.source = TRUE)
59-
# Using withCallingHandlers to capture warnings and messages.
60-
# Using tryCatch to capture the error and abort further evaluation.
61-
x <- withCallingHandlers(
62-
tryCatch(
63-
{
64-
eval(current_call, envir = object@.xData)
65-
if (!identical(parent.env(object@.xData), parent.env(.GlobalEnv))) {
66-
# needed to make sure that @.xData is always a sibling of .GlobalEnv
67-
# could be changed when any new package is added to search path (through library or require call)
68-
parent.env(object@.xData) <- parent.env(.GlobalEnv)
69-
}
70-
NULL
71-
},
72-
error = function(e) {
51+
52+
old <- evaluate::inject_funs(
53+
library = function(...) {
54+
x <- library(...)
55+
if (!identical(parent.env(object@.xData), parent.env(.GlobalEnv))) {
56+
parent.env(object@.xData) <- parent.env(.GlobalEnv)
57+
}
58+
invisible(x)
59+
}
60+
)
61+
out <- evaluate::evaluate(
62+
code,
63+
envir = object@.xData,
64+
stop_on_error = 1,
65+
output_handler = evaluate::new_output_handler(value = identity)
66+
)
67+
out <- evaluate::trim_intermediate_plots(out)
68+
69+
evaluate::inject_funs(old) # remove library() override
70+
71+
new_code <- list()
72+
for (this in out) {
73+
if (inherits(this, "source")) {
74+
this_code <- gsub("\n$", "", this$src)
75+
attr(this_code, "dependency") <- extract_dependency(parse(text = this_code, keep.source = TRUE))
76+
new_code <- c(new_code, stats::setNames(list(this_code), sample.int(.Machine$integer.max, size = 1)))
77+
} else {
78+
last_code <- new_code[[length(new_code)]]
79+
if (inherits(this, "error")) {
80+
return(
7381
errorCondition(
7482
message = sprintf(
7583
"%s \n when evaluating qenv code:\n%s",
76-
cli::ansi_strip(conditionMessage(e)),
77-
current_code
84+
cli::ansi_strip(conditionMessage(this)),
85+
last_code
7886
),
7987
class = c("qenv.error", "try-error", "simpleError"),
80-
trace = unlist(c(object@code, list(current_code)))
88+
trace = unlist(c(object@code, list(new_code)))
8189
)
82-
}
83-
),
84-
warning = function(w) {
85-
attr(current_code, "warning") <<- cli::ansi_strip(sprintf("> %s\n", conditionMessage(w)))
86-
invokeRestart("muffleWarning")
87-
},
88-
message = function(m) {
89-
attr(current_code, "message") <<- cli::ansi_strip(sprintf("> %s", conditionMessage(m)))
90-
invokeRestart("muffleMessage")
90+
)
9191
}
92-
)
93-
94-
if (!is.null(x)) {
95-
return(x)
92+
attr(last_code, "outputs") <- c(attr(last_code, "outputs"), list(this))
93+
new_code[[length(new_code)]] <- last_code
9694
}
97-
attr(current_code, "dependency") <- extract_dependency(current_call)
98-
object@code <- c(object@code, stats::setNames(list(current_code), sample.int(.Machine$integer.max, size = 1)))
9995
}
10096

97+
object@code <- c(object@code, new_code)
10198
lockEnvironment(object@.xData, bindings = TRUE)
10299
object
103100
}

R/qenv-get_outputs.R

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#' Get outputs
2+
#'
3+
#' @description
4+
#' `eval_code` evaluates code silently so plots and prints don't show up in the console or graphic devices.
5+
#' If one wants to use an output outside of the `qenv` (e.g. use a graph in `renderPlot`) then use `get_outputs`.
6+
#' @param object (`qenv`)
7+
#' @return list of outputs generated in a `qenv``
8+
#' @examples
9+
#' q <- eval_code(
10+
#' qenv(),
11+
#' quote({
12+
#' a <- 1
13+
#' print("I'm an output")
14+
#' plot(1)
15+
#' })
16+
#' )
17+
#' get_outputs(q)
18+
#'
19+
#' @aliases get_outputs,qenv-method
20+
#'
21+
#' @export
22+
setGeneric("get_outputs", function(object) standardGeneric("get_outputs"))
23+
24+
setMethod("get_outputs", signature = "qenv", function(object) {
25+
Reduce(
26+
function(x, y) c(x, attr(y, "outputs")),
27+
init = list(),
28+
x = object@code
29+
)
30+
})

R/utils-get_code_dependency.R

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,9 @@ get_call_breaks <- function(code) {
521521
}
522522
))
523523
call_breaks <- call_breaks[-nrow(call_breaks), , drop = FALSE] # breaks in between needed only
524+
if (nrow(call_breaks) == 0L) {
525+
call_breaks <- matrix(numeric(0), ncol = 2)
526+
}
524527
colnames(call_breaks) <- c("line", "col")
525528
call_breaks
526529
}

R/utils.R

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,25 @@ lang2calls <- function(x) {
5757
#' Obtain warnings or messages from code slot
5858
#'
5959
#' @param object (`qenv`)
60-
#' @param what (`"warning"` or `"message"`)
60+
#' @param what (`warning` or `message`)
6161
#' @return `character(1)` containing combined message or `NULL` when no warnings/messages
6262
#' @keywords internal
6363
get_warn_message_util <- function(object, what) {
6464
checkmate::matchArg(what, choices = c("warning", "message"))
65-
messages <- lapply(object@code, "attr", what)
65+
messages <- lapply(
66+
object@code,
67+
function(x) {
68+
unlist(lapply(
69+
attr(x, "outputs"),
70+
function(el) {
71+
if (inherits(el, what)) {
72+
sprintf("> %s", conditionMessage(el))
73+
}
74+
}
75+
))
76+
}
77+
)
78+
6679
idx_warn <- which(sapply(messages, function(x) !is.null(x) && !identical(x, "")))
6780
if (!any(idx_warn)) {
6881
return(NULL)
@@ -74,7 +87,7 @@ get_warn_message_util <- function(object, what) {
7487
warn = messages,
7588
expr = code,
7689
function(warn, expr) {
77-
sprintf("%swhen running code:\n%s", warn, expr)
90+
sprintf("%s\nwhen running code:\n%s", trimws(warn), trimws(expr))
7891
}
7992
)
8093

_pkgdown.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ reference:
3131
- eval_code
3232
- get_code
3333
- get_env
34+
- get_outputs
3435
- get_var
3536
- get_messages
3637
- get_warnings

0 commit comments

Comments
 (0)