1 |
#' Evaluate code in `qenv` |
|
2 |
#' |
|
3 |
#' @details |
|
4 |
#' `eval_code` evaluates given code in the `qenv` environment and appends it to the `code` slot. |
|
5 |
#' Thus, if the `qenv` had been instantiated empty, contents of the environment are always a result of the stored code. |
|
6 |
#' |
|
7 |
#' @param object (`qenv`) |
|
8 |
#' @param code (`character` or `language`) code to evaluate. If `character`, comments are retained. |
|
9 |
#' |
|
10 |
#' @return |
|
11 |
#' `eval_code` returns a `qenv` object with `expr` evaluated or `qenv.error` if evaluation fails. |
|
12 |
#' |
|
13 |
#' @examples |
|
14 |
#' # evaluate code in qenv |
|
15 |
#' q <- qenv() |
|
16 |
#' q <- eval_code(q, "a <- 1") |
|
17 |
#' q <- eval_code(q, quote(library(checkmate))) |
|
18 |
#' q <- eval_code(q, expression(assert_number(a))) |
|
19 |
#' |
|
20 |
#' @name eval_code |
|
21 |
#' @rdname qenv |
|
22 |
#' @aliases eval_code,qenv,character-method |
|
23 |
#' @aliases eval_code,qenv,language-method |
|
24 |
#' @aliases eval_code,qenv,expression-method |
|
25 |
#' @aliases eval_code,qenv.error,ANY-method |
|
26 |
#' |
|
27 |
#' @export |
|
28 | 196x |
setGeneric("eval_code", function(object, code) standardGeneric("eval_code")) |
29 | ||
30 |
setMethod("eval_code", signature = c("qenv", "character"), function(object, code) { |
|
31 | 106x |
id <- sample.int(.Machine$integer.max, size = 1) |
32 | ||
33 | 106x |
object@id <- c(object@id, id) |
34 | 106x |
object@env <- rlang::env_clone(object@env, parent = parent.env(.GlobalEnv)) |
35 | 106x |
code <- paste(code, collapse = "\n") |
36 | 106x |
object@code <- c(object@code, code) |
37 | ||
38 | 106x |
current_warnings <- "" |
39 | 106x |
current_messages <- "" |
40 | ||
41 | 106x |
parsed_code <- parse(text = code, keep.source = TRUE) |
42 | 106x |
for (single_call in parsed_code) { |
43 |
# Using withCallingHandlers to capture warnings and messages. |
|
44 |
# Using tryCatch to capture the error and abort further evaluation. |
|
45 | 120x |
x <- withCallingHandlers( |
46 | 120x |
tryCatch( |
47 |
{ |
|
48 | 120x |
eval(single_call, envir = object@env) |
49 | 108x |
if (!identical(parent.env(object@env), parent.env(.GlobalEnv))) { |
50 |
# needed to make sure that @env is always a sibling of .GlobalEnv |
|
51 |
# could be changed when any new package is added to search path (through library or require call) |
|
52 | 1x |
parent.env(object@env) <- parent.env(.GlobalEnv) |
53 |
} |
|
54 | 108x |
NULL |
55 |
}, |
|
56 | 120x |
error = function(e) { |
57 | 12x |
errorCondition( |
58 | 12x |
message = sprintf( |
59 | 12x |
"%s \n when evaluating qenv code:\n%s", |
60 | 12x |
.ansi_strip(conditionMessage(e)), |
61 | 12x |
deparse1(single_call) |
62 |
), |
|
63 | 12x |
class = c("qenv.error", "try-error", "simpleError"), |
64 | 12x |
trace = object@code |
65 |
) |
|
66 |
} |
|
67 |
), |
|
68 | 120x |
warning = function(w) { |
69 | 15x |
current_warnings <<- paste0(current_warnings, .ansi_strip(sprintf("> %s\n", conditionMessage(w)))) |
70 | 15x |
invokeRestart("muffleWarning") |
71 |
}, |
|
72 | 120x |
message = function(m) { |
73 | 5x |
current_messages <<- paste0(current_messages, .ansi_strip(sprintf("> %s", conditionMessage(m)))) |
74 | 5x |
invokeRestart("muffleMessage") |
75 |
} |
|
76 |
) |
|
77 | ||
78 | 120x |
if (!is.null(x)) { |
79 | 12x |
return(x) |
80 |
} |
|
81 |
} |
|
82 | ||
83 | ||
84 | 94x |
object@warnings <- c(object@warnings, current_warnings) |
85 | 94x |
object@messages <- c(object@messages, current_messages) |
86 | ||
87 | 94x |
lockEnvironment(object@env, bindings = TRUE) |
88 | 94x |
object |
89 |
}) |
|
90 | ||
91 |
setMethod("eval_code", signature = c("qenv", "language"), function(object, code) { |
|
92 | 63x |
eval_code(object, code = paste(lang2calls(code), collapse = "\n")) |
93 |
}) |
|
94 | ||
95 |
setMethod("eval_code", signature = c("qenv", "expression"), function(object, code) { |
|
96 | 26x |
eval_code(object, code = paste(lang2calls(code), collapse = "\n")) |
97 |
}) |
|
98 | ||
99 |
setMethod("eval_code", signature = c("qenv.error", "ANY"), function(object, code) { |
|
100 | ! |
object |
101 |
}) |
|
102 | ||
103 |
# if cli is installed rlang adds terminal printing characters |
|
104 |
# which need to be removed |
|
105 |
.ansi_strip <- function(chr) { |
|
106 | 32x |
if (requireNamespace("cli", quietly = TRUE)) { |
107 | 32x |
cli::ansi_strip(chr) |
108 |
} else { |
|
109 | ! |
chr |
110 |
} |
|
111 |
} |
1 |
#' Join `qenv` objects |
|
2 |
#' |
|
3 |
#' Checks and merges two `qenv` objects into one `qenv` object. |
|
4 |
#' |
|
5 |
#' Any common code at the start of the `qenvs` is only placed once at the start of the joined `qenv`. |
|
6 |
#' This allows consistent behavior when joining `qenvs` which share a common ancestor. |
|
7 |
#' See below for an example. |
|
8 |
#' |
|
9 |
#' There are some situations where `join()` cannot be properly performed, such as these three scenarios: |
|
10 |
#' 1. Both `qenv` objects contain an object of the same name but are not identical. |
|
11 |
#' |
|
12 |
#' Example: |
|
13 |
#' |
|
14 |
#' ```r |
|
15 |
#' x <- eval_code(qenv(), expression(mtcars1 <- mtcars)) |
|
16 |
#' y <- eval_code(qenv(), expression(mtcars1 <- mtcars['wt'])) |
|
17 |
#' |
|
18 |
#' z <- join(x, y) |
|
19 |
#' # Error message will occur |
|
20 |
#' ``` |
|
21 |
#' In this example, `mtcars1` object exists in both `x` and `y` objects but the content are not identical. |
|
22 |
#' `mtcars1` in the `x qenv` object has more columns than `mtcars1` in the `y qenv` object (only has one column). |
|
23 |
#' |
|
24 |
#' 2. `join()` will look for identical `@id` values in both `qenv` objects. |
|
25 |
#' The index position of these `@id`s must be the same to determine the evaluation order. |
|
26 |
#' Otherwise, `join()` will throw an error message. |
|
27 |
#' |
|
28 |
#' Example: |
|
29 |
#' ```r |
|
30 |
#' common_q <- eval_code(qenv(), expression(v <- 1)) |
|
31 |
#' x <- eval_code( |
|
32 |
#' common_q, |
|
33 |
#' "x <- v" |
|
34 |
#' ) |
|
35 |
#' y <- eval_code( |
|
36 |
#' common_q, |
|
37 |
#' "y <- v" |
|
38 |
#' ) |
|
39 |
#' z <- eval_code( |
|
40 |
#' y, |
|
41 |
#' "z <- v" |
|
42 |
#' ) |
|
43 |
#' q <- join(x, y) |
|
44 |
#' join_q <- join(q, z) |
|
45 |
#' # Error message will occur |
|
46 |
#' |
|
47 |
#' # Check the order of evaluation based on the id slot |
|
48 |
#' shared_ids <- intersect(q@id, z@id) |
|
49 |
#' match(shared_ids, q@id) # Output: 1 3 |
|
50 |
#' match(shared_ids, z@id) # Output: 1 2 |
|
51 |
#' ``` |
|
52 |
#' The error occurs because the index position of identical `@id` between the two objects is not the same. |
|
53 |
#' |
|
54 |
#' 3. The usage of temporary variable in the code expression could cause `join()` to fail. |
|
55 |
#' |
|
56 |
#' Example: |
|
57 |
#' ```r |
|
58 |
#' common_q <- qenv() |
|
59 |
#' x <- eval_code( |
|
60 |
#' common_q, |
|
61 |
#' "x <- numeric(0) |
|
62 |
#' for (i in 1:2) { |
|
63 |
#' x <- c(x, i) |
|
64 |
#' }" |
|
65 |
#' ) |
|
66 |
#' y <- eval_code( |
|
67 |
#' common_q, |
|
68 |
#' "y <- numeric(0) |
|
69 |
#' for (i in 1:3) { |
|
70 |
#' y <- c(y, i) |
|
71 |
#' }" |
|
72 |
#' ) |
|
73 |
#' q <- join(x,y) |
|
74 |
#' # Error message will occur |
|
75 |
#' |
|
76 |
#' # Check the value of temporary variable i in both objects |
|
77 |
#' x@env$i # Output: 2 |
|
78 |
#' y@env$i # Output: 3 |
|
79 |
#' ``` |
|
80 |
#' `join()` fails to provide a proper result because of the temporary variable `i` exists |
|
81 |
#' in both objects but has different value. |
|
82 |
#' To fix this, we can set `i <- NULL` in the code expression for both objects. |
|
83 |
#' ```r |
|
84 |
#' common_q <- qenv() |
|
85 |
#' x <- eval_code( |
|
86 |
#' common_q, |
|
87 |
#' "x <- numeric(0) |
|
88 |
#' for (i in 1:2) { |
|
89 |
#' x <- c(x, i) |
|
90 |
#' } |
|
91 |
#' # dummy i variable to fix it |
|
92 |
#' i <- NULL" |
|
93 |
#' ) |
|
94 |
#' y <- eval_code( |
|
95 |
#' common_q, |
|
96 |
#' "y <- numeric(0) |
|
97 |
#' for (i in 1:3) { |
|
98 |
#' y <- c(y, i) |
|
99 |
#' } |
|
100 |
#' # dummy i variable to fix it |
|
101 |
#' i <- NULL" |
|
102 |
#' ) |
|
103 |
#' q <- join(x,y) |
|
104 |
#' ``` |
|
105 |
#' |
|
106 |
#' @param x (`qenv`) |
|
107 |
#' @param y (`qenv`) |
|
108 |
#' |
|
109 |
#' @return `qenv` object. |
|
110 |
#' |
|
111 |
#' @examples |
|
112 |
#' q <- qenv() |
|
113 |
#' q1 <- eval_code(q, expression(iris1 <- iris, mtcars1 <- mtcars)) |
|
114 |
#' q2 <- q1 |
|
115 |
#' q1 <- eval_code(q1, "iris2 <- iris") |
|
116 |
#' q2 <- eval_code(q2, "mtcars2 <- mtcars") |
|
117 |
#' qq <- join(q1, q2) |
|
118 |
#' get_code(qq) |
|
119 |
#' |
|
120 |
#' common_q <- eval_code(q, quote(x <- 1)) |
|
121 |
#' y_q <- eval_code(common_q, quote(y <- x * 2)) |
|
122 |
#' z_q <- eval_code(common_q, quote(z <- x * 3)) |
|
123 |
#' join_q <- join(y_q, z_q) |
|
124 |
#' # get_code only has "x <- 1" occurring once |
|
125 |
#' get_code(join_q) |
|
126 |
#' |
|
127 |
#' @include qenv-errors.R |
|
128 |
#' |
|
129 |
#' @name join |
|
130 |
#' @rdname join |
|
131 |
#' @aliases join,qenv,qenv-method |
|
132 |
#' @aliases join,qenv,qenv.error-method |
|
133 |
#' @aliases join,qenv.error,ANY-method |
|
134 |
#' |
|
135 |
#' @export |
|
136 | 20x |
setGeneric("join", function(x, y) standardGeneric("join")) |
137 | ||
138 |
setMethod("join", signature = c("qenv", "qenv"), function(x, y) { |
|
139 | 16x |
join_validation <- .check_joinable(x, y) |
140 | ||
141 |
# join expressions |
|
142 | 16x |
if (!isTRUE(join_validation)) { |
143 | 4x |
stop(join_validation) |
144 |
} |
|
145 | ||
146 | 12x |
id_unique <- !y@id %in% x@id |
147 | 12x |
x@id <- c(x@id, y@id[id_unique]) |
148 | 12x |
x@code <- c(x@code, y@code[id_unique]) |
149 | 12x |
x@warnings <- c(x@warnings, y@warnings[id_unique]) |
150 | 12x |
x@messages <- c(x@messages, y@messages[id_unique]) |
151 | ||
152 |
# insert (and overwrite) objects from y to x |
|
153 | 12x |
x@env <- rlang::env_clone(x@env, parent = parent.env(.GlobalEnv)) |
154 | 12x |
rlang::env_coalesce(env = x@env, from = y@env) |
155 | 12x |
x |
156 |
}) |
|
157 | ||
158 |
setMethod("join", signature = c("qenv", "qenv.error"), function(x, y) { |
|
159 | 1x |
y |
160 |
}) |
|
161 | ||
162 |
setMethod("join", signature = c("qenv.error", "ANY"), function(x, y) { |
|
163 | 3x |
x |
164 |
}) |
|
165 | ||
166 |
#' If two `qenv` can be joined |
|
167 |
#' |
|
168 |
#' Checks if two `qenv` objects can be combined. |
|
169 |
#' For more information, please see [`join`] |
|
170 |
#' @param x (`qenv`) |
|
171 |
#' @param y (`qenv`) |
|
172 |
#' @return `TRUE` if able to join or `character` used to print error message. |
|
173 |
#' @keywords internal |
|
174 |
.check_joinable <- function(x, y) { |
|
175 | 30x |
checkmate::assert_class(x, "qenv") |
176 | 30x |
checkmate::assert_class(y, "qenv") |
177 | ||
178 | 30x |
common_names <- intersect(rlang::env_names(x@env), rlang::env_names(y@env)) |
179 | 30x |
is_overwritten <- vapply(common_names, function(el) { |
180 | 28x |
!identical(get(el, x@env), get(el, y@env)) |
181 | 30x |
}, logical(1)) |
182 | 30x |
if (any(is_overwritten)) { |
183 | 4x |
return( |
184 | 4x |
paste( |
185 | 4x |
"Not possible to join qenv objects if anything in their environment has been modified.\n", |
186 | 4x |
"Following object(s) have been modified:\n - ", |
187 | 4x |
paste(common_names[is_overwritten], collapse = "\n - ") |
188 |
) |
|
189 |
) |
|
190 |
} |
|
191 | ||
192 | 26x |
shared_ids <- intersect(x@id, y@id) |
193 | 26x |
if (length(shared_ids) == 0) { |
194 | 12x |
return(TRUE) |
195 |
} |
|
196 | ||
197 | 14x |
shared_in_x <- match(shared_ids, x@id) |
198 | 14x |
shared_in_y <- match(shared_ids, y@id) |
199 | ||
200 |
# indices of shared ids should be 1:n in both slots |
|
201 | 14x |
if (identical(shared_in_x, shared_in_y) && identical(shared_in_x, seq_along(shared_ids))) { |
202 | 8x |
TRUE |
203 | 6x |
} else if (!identical(shared_in_x, shared_in_y)) { |
204 | 3x |
paste( |
205 | 3x |
"The common shared code of the qenvs does not occur in the same position in both qenv objects", |
206 | 3x |
"so they cannot be joined together as it's impossible to determine the evaluation's order.", |
207 | 3x |
collapse = "" |
208 |
) |
|
209 |
} else { |
|
210 | 3x |
paste( |
211 | 3x |
"There is code in the qenv objects before their common shared code", |
212 | 3x |
"which means these objects cannot be joined.", |
213 | 3x |
collapse = "" |
214 |
) |
|
215 |
} |
|
216 |
} |
1 |
#' Code tracking with `qenv` object |
|
2 |
#' |
|
3 |
#' @description |
|
4 |
#' `r badge("stable")` |
|
5 |
#' |
|
6 |
#' Create a `qenv` object and evaluate code in it to track code history. |
|
7 |
#' |
|
8 |
#' @details |
|
9 |
#' |
|
10 |
#' `qenv()` instantiates a `qenv` with an empty environment. |
|
11 |
#' Any changes must be made by evaluating code in it with `eval_code` or `within`, thereby ensuring reproducibility. |
|
12 |
#' |
|
13 |
#' `new_qenv()` (`r badge("deprecated")` and not recommended) |
|
14 |
#' can instantiate a `qenv` object with data in the environment and code registered. |
|
15 |
#' |
|
16 |
#' @name qenv |
|
17 |
#' |
|
18 |
#' @return `qenv` and `new_qenv` return a `qenv` object. |
|
19 |
#' |
|
20 |
#' @examples |
|
21 |
#' # create empty qenv |
|
22 |
#' qenv() |
|
23 |
#' |
|
24 |
#' @export |
|
25 |
qenv <- function() { |
|
26 | 76x |
q_env <- new.env(parent = parent.env(.GlobalEnv)) |
27 | 76x |
lockEnvironment(q_env, bindings = TRUE) |
28 | 76x |
methods::new("qenv", env = q_env) |
29 |
} |
|
30 | ||
31 | ||
32 |
#' @param code `r badge("deprecated")` |
|
33 |
#' (`character(1)` or `language`) code to evaluate. Accepts and stores comments also. |
|
34 |
#' @param env `r badge("deprecated")` (`environment`) |
|
35 |
#' Environment being a result of the `code` evaluation. |
|
36 |
#' |
|
37 |
#' @examples |
|
38 |
#' # create qenv with data and code (deprecated) |
|
39 |
#' new_qenv(env = list2env(list(a = 1)), code = quote(a <- 1)) |
|
40 |
#' new_qenv(env = list2env(list(a = 1)), code = parse(text = "a <- 1", keep.source = TRUE)) |
|
41 |
#' new_qenv(env = list2env(list(a = 1)), code = "a <- 1") |
|
42 |
#' |
|
43 |
#' @rdname qenv |
|
44 |
#' @aliases new_qenv,environment,expression-method |
|
45 |
#' @aliases new_qenv,environment,character-method |
|
46 |
#' @aliases new_qenv,environment,language-method |
|
47 |
#' @aliases new_qenv,environment,missing-method |
|
48 |
#' @aliases new_qenv,missing,missing-method |
|
49 |
#' |
|
50 |
#' @seealso [`base::within()`], [`get_var()`], [`get_env()`], [`get_warnings()`], [`join()`], [`concat()`] |
|
51 |
#' |
|
52 |
#' @export |
|
53 |
setGeneric("new_qenv", function(env = new.env(parent = parent.env(.GlobalEnv)), code = character()) { |
|
54 | ! |
lifecycle::deprecate_warn(when = " 0.5.0", what = "new_qenv()", with = "qenv()", always = TRUE) |
55 | ! |
standardGeneric("new_qenv") |
56 |
}) |
|
57 | ||
58 |
setMethod( |
|
59 |
"new_qenv", |
|
60 |
signature = c(env = "environment", code = "expression"), |
|
61 |
function(env, code) { |
|
62 | ! |
new_qenv(env, paste(lang2calls(code), collapse = "\n")) |
63 |
} |
|
64 |
) |
|
65 | ||
66 |
setMethod( |
|
67 |
"new_qenv", |
|
68 |
signature = c(env = "environment", code = "character"), |
|
69 |
function(env, code) { |
|
70 | ! |
new_env <- rlang::env_clone(env, parent = parent.env(.GlobalEnv)) |
71 | ! |
lockEnvironment(new_env, bindings = TRUE) |
72 | ! |
if (length(code) > 0) code <- paste(code, collapse = "\n") |
73 | ! |
id <- sample.int(.Machine$integer.max, size = length(code)) |
74 | ! |
methods::new( |
75 | ! |
"qenv", |
76 | ! |
env = new_env, code = code, warnings = rep("", length(code)), messages = rep("", length(code)), id = id |
77 |
) |
|
78 |
} |
|
79 |
) |
|
80 | ||
81 |
setMethod( |
|
82 |
"new_qenv", |
|
83 |
signature = c(env = "environment", code = "language"), |
|
84 |
function(env, code) { |
|
85 | ! |
new_qenv(env = env, code = paste(lang2calls(code), collapse = "\n")) |
86 |
} |
|
87 |
) |
|
88 | ||
89 |
setMethod( |
|
90 |
"new_qenv", |
|
91 |
signature = c(code = "missing", env = "missing"), |
|
92 |
function(env, code) { |
|
93 | ! |
new_qenv(env = env, code = code) |
94 |
} |
|
95 |
) |
1 |
#' Concatenate two `qenv` objects |
|
2 |
#' |
|
3 |
#' Combine two `qenv` objects by simple concatenate their environments and the code. |
|
4 |
#' |
|
5 |
#' We recommend to use the `join` method to have a stricter control |
|
6 |
#' in case `x` and `y` contain duplicated bindings and code. |
|
7 |
#' RHS argument content has priority over the LHS one. |
|
8 |
#' |
|
9 |
#' @param x (`qenv`) |
|
10 |
#' @param y (`qenv`) |
|
11 |
#' |
|
12 |
#' @return `qenv` object. |
|
13 |
#' |
|
14 |
#' @examples |
|
15 |
#' q <- qenv() |
|
16 |
#' q1 <- eval_code(q, expression(iris1 <- iris, mtcars1 <- mtcars)) |
|
17 |
#' q2 <- q1 |
|
18 |
#' q1 <- eval_code(q1, "iris2 <- iris") |
|
19 |
#' q2 <- eval_code(q2, "mtcars2 <- mtcars") |
|
20 |
#' qq <- concat(q1, q2) |
|
21 |
#' get_code(qq) |
|
22 |
#' |
|
23 |
#' @include qenv-errors.R |
|
24 |
#' |
|
25 |
#' @name concat |
|
26 |
#' @rdname concat |
|
27 |
#' @aliases concat,qenv,qenv-method |
|
28 |
#' @aliases concat,qenv.error,ANY-method |
|
29 |
#' @aliases concat,qenv,qenv.error-method |
|
30 |
#' |
|
31 |
#' @export |
|
32 | 9x |
setGeneric("concat", function(x, y) standardGeneric("concat")) |
33 | ||
34 |
setMethod("concat", signature = c("qenv", "qenv"), function(x, y) { |
|
35 | 5x |
y@id <- c(x@id, y@id) |
36 | 5x |
y@code <- c(x@code, y@code) |
37 | 5x |
y@warnings <- c(x@warnings, y@warnings) |
38 | 5x |
y@messages <- c(x@messages, y@messages) |
39 | ||
40 |
# insert (and overwrite) objects from y to x |
|
41 | 5x |
y@env <- rlang::env_clone(y@env, parent = parent.env(.GlobalEnv)) |
42 | 5x |
rlang::env_coalesce(env = y@env, from = x@env) |
43 | 5x |
y |
44 |
}) |
|
45 | ||
46 |
setMethod("concat", signature = c("qenv.error", "ANY"), function(x, y) { |
|
47 | 3x |
x |
48 |
}) |
|
49 | ||
50 |
setMethod("concat", signature = c("qenv", "qenv.error"), function(x, y) { |
|
51 | 1x |
y |
52 |
}) |
1 |
#' Evaluate Expression in `qenv` |
|
2 |
#' |
|
3 |
#' @details |
|
4 |
#' `within` is a convenience function for evaluating inline code inside the environment of a `qenv`. |
|
5 |
#' It is a method for the `base` generic that wraps `eval_code` to provide a simplified way of passing code. |
|
6 |
#' `within` accepts only inline expressions (both simple and compound) and allows for injecting values into `expr` |
|
7 |
#' through the `...` argument: |
|
8 |
#' as `name:value` pairs are passed to `...`, `name` in `expr` will be replaced with `value`. |
|
9 |
#' |
|
10 |
#' @section Using language objects with `within`: |
|
11 |
#' Passing language objects to `expr` is generally not intended but can be achieved with `do.call`. |
|
12 |
#' Only single `expression`s will work and substitution is not available. See examples. |
|
13 |
#' |
|
14 |
#' @param data (`qenv`) |
|
15 |
#' @param expr (`expression`) to evaluate. Must be inline code, see `Using language objects...` |
|
16 |
#' @param ... see `Details` |
|
17 |
#' |
|
18 |
#' @return |
|
19 |
#' `within` returns a `qenv` object with `expr` evaluated or `qenv.error` if evaluation fails. |
|
20 |
#' |
|
21 |
#' @examples |
|
22 |
#' # evaluate code using within |
|
23 |
#' q <- qenv() |
|
24 |
#' q <- within(q, { |
|
25 |
#' i <- iris |
|
26 |
#' }) |
|
27 |
#' q <- within(q, { |
|
28 |
#' m <- mtcars |
|
29 |
#' f <- faithful |
|
30 |
#' }) |
|
31 |
#' q |
|
32 |
#' get_code(q) |
|
33 |
#' |
|
34 |
#' # inject values into code |
|
35 |
#' q <- qenv() |
|
36 |
#' q <- within(q, i <- iris) |
|
37 |
#' within(q, print(dim(subset(i, Species == "virginica")))) |
|
38 |
#' within(q, print(dim(subset(i, Species == species)))) # fails |
|
39 |
#' within(q, print(dim(subset(i, Species == species))), species = "versicolor") |
|
40 |
#' species_external <- "versicolor" |
|
41 |
#' within(q, print(dim(subset(i, Species == species))), species = species_external) |
|
42 |
#' |
|
43 |
#' # pass language objects |
|
44 |
#' expr <- expression(i <- iris, m <- mtcars) |
|
45 |
#' within(q, expr) # fails |
|
46 |
#' do.call(within, list(q, expr)) |
|
47 |
#' |
|
48 |
#' exprlist <- list(expression(i <- iris), expression(m <- mtcars)) |
|
49 |
#' within(q, exprlist) # fails |
|
50 |
#' do.call(within, list(q, do.call(c, exprlist))) |
|
51 |
#' |
|
52 |
#' @rdname qenv |
|
53 |
#' |
|
54 |
#' @export |
|
55 |
#' |
|
56 |
within.qenv <- function(data, expr, ...) { |
|
57 | 19x |
expr <- substitute(expr) |
58 | 19x |
extras <- list(...) |
59 | ||
60 |
# Add braces for consistency. |
|
61 | 19x |
if (!identical(as.list(expr)[[1L]], as.symbol("{"))) { |
62 | 7x |
expr <- call("{", expr) |
63 |
} |
|
64 | ||
65 | 19x |
calls <- as.list(expr)[-1] |
66 | ||
67 |
# Inject extra values into expressions. |
|
68 | 19x |
calls <- lapply(calls, function(x) do.call(substitute, list(x, env = extras))) |
69 | ||
70 | 19x |
eval_code(object = data, code = as.expression(calls)) |
71 |
} |
|
72 | ||
73 | ||
74 |
#' @keywords internal |
|
75 |
#' |
|
76 |
#' @export |
|
77 |
within.qenv.error <- function(data, expr, ...) { |
|
78 | 1x |
data |
79 |
} |
1 |
#' Display `qenv` object |
|
2 |
#' |
|
3 |
#' Prints the `qenv` object. |
|
4 |
#' |
|
5 |
#' @param object (`qenv`) |
|
6 |
#' |
|
7 |
#' @return `object`, invisibly. |
|
8 |
#' |
|
9 |
#' @examples |
|
10 |
#' q <- qenv() |
|
11 |
#' q1 <- eval_code(q, expression(a <- 5, b <- data.frame(x = 1:10))) |
|
12 |
#' q1 |
|
13 |
#' |
|
14 |
#' @aliases show-qenv |
|
15 |
#' |
|
16 |
#' @importFrom methods show |
|
17 |
#' @export |
|
18 |
setMethod("show", "qenv", function(object) { |
|
19 | ! |
rlang::env_print(object@env) |
20 |
}) |
1 |
#' Suppresses plot display in the IDE by opening a PDF graphics device |
|
2 |
#' |
|
3 |
#' This function opens a PDF graphics device using [`grDevices::pdf`] to suppress |
|
4 |
#' the plot display in the IDE. The purpose of this function is to avoid opening graphic devices |
|
5 |
#' directly in the IDE. |
|
6 |
#' |
|
7 |
#' @param x lazy binding which generates the plot(s) |
|
8 |
#' |
|
9 |
#' @details The function uses [`base::on.exit`] to ensure that the PDF graphics |
|
10 |
#' device is closed (using [`grDevices::dev.off`]) when the function exits, |
|
11 |
#' regardless of whether it exits normally or due to an error. This is necessary to |
|
12 |
#' clean up the graphics device properly and avoid any potential issues. |
|
13 |
#' |
|
14 |
#' @return No return value, called for side effects. |
|
15 |
#' |
|
16 |
#' @examples |
|
17 |
#' dev_suppress(plot(1:10)) |
|
18 |
#' @export |
|
19 |
dev_suppress <- function(x) { |
|
20 | 2x |
grDevices::pdf(nullfile()) |
21 | 2x |
on.exit(grDevices::dev.off()) |
22 | 2x |
force(x) |
23 |
} |
|
24 | ||
25 |
#' Separate calls |
|
26 |
#' |
|
27 |
#' Converts language object or lists of language objects to list of simple calls. |
|
28 |
#' |
|
29 |
#' @param x `language` object or a list of thereof |
|
30 |
#' @return |
|
31 |
#' Given a `call`, an `expression`, a list of `call`s or a list of `expression`s, returns a list of `calls`. |
|
32 |
#' Symbols and atomic vectors (which may get mixed up in a list) are returned wrapped in list. |
|
33 |
#' @examples |
|
34 |
#' # use non-exported function from teal.code |
|
35 |
#' lang2calls <- getFromNamespace("lang2calls", "teal.code") |
|
36 |
#' expr <- expression( |
|
37 |
#' i <- iris, |
|
38 |
#' m <- mtcars |
|
39 |
#' ) |
|
40 |
#' lang2calls(expr) |
|
41 |
#' @keywords internal |
|
42 |
lang2calls <- function(x) { |
|
43 | 167x |
if (is.atomic(x) || is.symbol(x)) { |
44 | 9x |
return(list(x)) |
45 |
} |
|
46 | 158x |
if (is.call(x)) { |
47 | 118x |
if (identical(as.list(x)[[1L]], as.symbol("{"))) { |
48 | 7x |
as.list(x)[-1L] |
49 |
} else { |
|
50 | 111x |
list(x) |
51 |
} |
|
52 |
} else { |
|
53 | 40x |
unlist(lapply(x, lang2calls), recursive = FALSE) |
54 |
} |
|
55 |
} |
1 |
#' Get warnings from `qenv` object |
|
2 |
#' |
|
3 |
#' Retrieve all warnings raised during code evaluation in a `qenv`. |
|
4 |
#' |
|
5 |
#' @param object (`qenv`) |
|
6 |
#' |
|
7 |
#' @return `character` containing warning information or `NULL` if no warnings. |
|
8 |
#' |
|
9 |
#' @examples |
|
10 |
#' data_q <- qenv() |
|
11 |
#' data_q <- eval_code(data_q, "iris_data <- iris") |
|
12 |
#' warning_qenv <- eval_code( |
|
13 |
#' data_q, |
|
14 |
#' bquote(p <- hist(iris_data[, .("Sepal.Length")], ff = "")) |
|
15 |
#' ) |
|
16 |
#' cat(get_warnings(warning_qenv)) |
|
17 |
#' |
|
18 |
#' @name get_warnings |
|
19 |
#' @rdname get_warnings |
|
20 |
#' @aliases get_warnings,qenv-method |
|
21 |
#' @aliases get_warnings,qenv.error-method |
|
22 |
#' @aliases get_warnings,NULL-method |
|
23 |
#' |
|
24 |
#' @export |
|
25 |
setGeneric("get_warnings", function(object) { |
|
26 |
# this line forces evaluation of object before passing to the generic |
|
27 |
# needed for error handling to work properly |
|
28 | 7x |
grDevices::pdf(nullfile()) |
29 | 7x |
on.exit(grDevices::dev.off()) |
30 | 7x |
object |
31 | ||
32 | 7x |
standardGeneric("get_warnings") |
33 |
}) |
|
34 | ||
35 |
setMethod("get_warnings", signature = c("qenv"), function(object) { |
|
36 | 5x |
if (all(object@warnings == "")) { |
37 | 1x |
return(NULL) |
38 |
} |
|
39 | ||
40 | 4x |
lines <- mapply( |
41 | 4x |
function(warn, expr) { |
42 | 6x |
if (warn == "") { |
43 | 1x |
return(NULL) |
44 |
} |
|
45 | 5x |
sprintf("%swhen running code:\n%s", warn, paste(lang2calls(expr), collapse = "\n")) |
46 |
}, |
|
47 | 4x |
warn = as.list(object@warnings), |
48 | 4x |
expr = as.list(as.character(object@code)) |
49 |
) |
|
50 | 4x |
lines <- Filter(Negate(is.null), lines) |
51 | ||
52 | 4x |
sprintf( |
53 | 4x |
"~~~ Warnings ~~~\n\n%s\n\n~~~ Trace ~~~\n\n%s", |
54 | 4x |
paste(lines, collapse = "\n\n"), |
55 | 4x |
get_code(object) |
56 |
) |
|
57 |
}) |
|
58 | ||
59 |
setMethod("get_warnings", signature = c("qenv.error"), function(object) { |
|
60 | 1x |
NULL |
61 |
}) |
|
62 | ||
63 |
setMethod("get_warnings", "NULL", function(object) { |
|
64 | 1x |
NULL |
65 |
}) |
1 |
#' Access environment included in `qenv` |
|
2 |
#' |
|
3 |
#' The access of environment included in `qenv@env` allows to e.g. list object names included in `qenv@env` slot. |
|
4 |
#' |
|
5 |
#' @param object (`qenv`) |
|
6 |
#' |
|
7 |
#' @return An `environment` stored in `qenv@env` slot. |
|
8 |
#' |
|
9 |
#' @examples |
|
10 |
#' q <- qenv() |
|
11 |
#' q1 <- within(q, { |
|
12 |
#' a <- 5 |
|
13 |
#' b <- data.frame(x = 1:10) |
|
14 |
#' }) |
|
15 |
#' get_env(q1) |
|
16 |
#' ls(get_env(q1)) |
|
17 |
#' |
|
18 |
#' @aliases get_env,qenv-method |
|
19 |
#' @aliases get_env,qenv.error-method |
|
20 |
#' |
|
21 |
#' @export |
|
22 |
setGeneric("get_env", function(object) { |
|
23 | ! |
standardGeneric("get_env") |
24 |
}) |
|
25 | ||
26 |
setMethod("get_env", "qenv", function(object) { |
|
27 | ! |
object@env |
28 |
}) |
|
29 | ||
30 |
setMethod("get_env", "qenv.error", function(object) { |
|
31 | ! |
object |
32 |
}) |
1 |
#' Get code from `qenv` |
|
2 |
#' |
|
3 |
#' @details |
|
4 |
#' `get_code` retrieves the code stored in the `qenv`. `...` passes arguments to methods. |
|
5 |
#' |
|
6 |
#' @param object (`qenv`) |
|
7 |
#' @param deparse (`logical(1)`) flag specifying whether to return code as `character` or `expression`. |
|
8 |
#' @param ... see `Details` |
|
9 |
#' |
|
10 |
#' @return |
|
11 |
#' `get_code` returns the traced code (from `@code` slot) in the form specified by `deparse`. |
|
12 |
#' |
|
13 |
#' @examples |
|
14 |
#' # retrieve code |
|
15 |
#' get_code(q) |
|
16 |
#' get_code(q, deparse = FALSE) |
|
17 |
#' |
|
18 |
#' @name get_code |
|
19 |
#' @rdname qenv |
|
20 |
#' @aliases get_code,qenv-method |
|
21 |
#' @aliases get_code,qenv.error-method |
|
22 |
#' |
|
23 |
#' @export |
|
24 |
setGeneric("get_code", function(object, deparse = TRUE, ...) { |
|
25 |
# this line forces evaluation of object before passing to the generic |
|
26 |
# needed for error handling to work properly |
|
27 | 13x |
grDevices::pdf(nullfile()) |
28 | 13x |
on.exit(grDevices::dev.off()) |
29 | 13x |
object |
30 | ||
31 | 13x |
standardGeneric("get_code") |
32 |
}) |
|
33 | ||
34 |
setMethod("get_code", signature = "qenv", function(object, deparse = TRUE) { |
|
35 | 11x |
checkmate::assert_flag(deparse) |
36 | 11x |
if (deparse) { |
37 | 10x |
if (length(object@code) == 0) { |
38 | ! |
object@code |
39 |
} else { |
|
40 | 10x |
paste(object@code, collapse = "\n") |
41 |
} |
|
42 |
} else { |
|
43 | 1x |
parse(text = paste(c("{", object@code, "}"), collapse = "\n"), keep.source = TRUE) |
44 |
} |
|
45 |
}) |
|
46 | ||
47 |
setMethod("get_code", signature = "qenv.error", function(object, ...) { |
|
48 | 2x |
stop( |
49 | 2x |
errorCondition( |
50 | 2x |
sprintf( |
51 | 2x |
"%s\n\ntrace: \n %s\n", |
52 | 2x |
conditionMessage(object), |
53 | 2x |
paste(object$trace, collapse = "\n ") |
54 |
), |
|
55 | 2x |
class = c("validation", "try-error", "simpleError") |
56 |
) |
|
57 |
) |
|
58 |
}) |
1 |
#' Get object from `qenv` |
|
2 |
#' |
|
3 |
#' Retrieve variables from the `qenv` environment. |
|
4 |
#' |
|
5 |
#' @param object,x (`qenv`) |
|
6 |
#' @param var,i (`character(1)`) variable name. |
|
7 |
#' |
|
8 |
#' @return The value of required variable (`var`) within `qenv` object. |
|
9 |
#' |
|
10 |
#' @examples |
|
11 |
#' q <- qenv() |
|
12 |
#' q1 <- eval_code(q, code = quote(a <- 1)) |
|
13 |
#' q2 <- eval_code(q1, code = "b <- a") |
|
14 |
#' get_var(q2, "b") |
|
15 |
#' q2[["b"]] |
|
16 |
#' |
|
17 |
#' @name get_var |
|
18 |
#' @rdname get_var |
|
19 |
#' @aliases get_var,qenv,character-method |
|
20 |
#' @aliases get_var,qenv.error,ANY-method |
|
21 |
#' |
|
22 |
#' @export |
|
23 |
setGeneric("get_var", function(object, var) { |
|
24 | 11x |
grDevices::pdf(nullfile()) |
25 | 11x |
on.exit(grDevices::dev.off()) |
26 | 11x |
standardGeneric("get_var") |
27 |
}) |
|
28 | ||
29 |
setMethod("get_var", signature = c("qenv", "character"), function(object, var) { |
|
30 | 10x |
tryCatch( |
31 | 10x |
get(var, envir = object@env, inherits = FALSE), |
32 | 10x |
error = function(e) { |
33 | 6x |
message(conditionMessage(e)) |
34 | 6x |
NULL |
35 |
} |
|
36 |
) |
|
37 |
}) |
|
38 | ||
39 |
setMethod("get_var", signature = c("qenv.error", "ANY"), function(object, var) { |
|
40 | 1x |
stop(errorCondition( |
41 | 1x |
list(message = conditionMessage(object)), |
42 | 1x |
class = c("validation", "try-error", "simpleError") |
43 |
)) |
|
44 |
}) |
|
45 | ||
46 |
#' @rdname get_var |
|
47 |
setMethod("[[", signature = c("qenv", "ANY"), function(x, i) { |
|
48 | 5x |
get_var(x, i) |
49 |
}) |
|
50 | ||
51 |
#' @export |
|
52 |
`[[.qenv.error` <- function(x, i) { |
|
53 | 1x |
stop(errorCondition( |
54 | 1x |
list(message = conditionMessage(x)), |
55 | 1x |
class = c("validation", "try-error", "simpleError") |
56 |
)) |
|
57 |
} |