Skip to content

Commit ccc2063

Browse files
committed
Merged origin/main into 224_magrittr@main
2 parents f24353b + 34f0772 commit ccc2063

33 files changed

+504
-204
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ default_language_version:
66
python: python3
77
repos:
88
- repo: https://github.com/lorenzwalthert/precommit
9-
rev: v0.4.3.9001
9+
rev: v0.4.3.9003
1010
hooks:
1111
- id: style-files
1212
name: Style code with `styler`

DESCRIPTION

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
Type: Package
22
Package: teal.code
33
Title: Code Storage and Execution Class for 'teal' Applications
4-
Version: 0.5.0.9012
5-
Date: 2024-10-29
4+
Version: 0.5.0.9013
5+
Date: 2024-11-08
66
Authors@R: c(
77
person("Dawid", "Kaledkowski", , "[email protected]", role = c("aut", "cre")),
88
person("Aleksander", "Chlebowski", , "[email protected]", role = "aut"),
@@ -52,6 +52,7 @@ Language: en-US
5252
Roxygen: list(markdown = TRUE)
5353
RoxygenNote: 7.3.2
5454
Collate:
55+
'qenv-c.R'
5556
'qenv-class.R'
5657
'qenv-errors.R'
5758
'qenv-concat.R'
@@ -62,6 +63,7 @@ Collate:
6263
'qenv-get_var.R'
6364
'qenv-get_warnings.R'
6465
'qenv-join.R'
66+
'qenv-length.R'
6567
'qenv-show.R'
6668
'qenv-within.R'
6769
'teal.code-package.R'

NAMESPACE

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
# Generated by roxygen2: do not edit by hand
22

3+
S3method("$",qenv.error)
34
S3method("[[",qenv.error)
5+
S3method(as.list,qenv.error)
6+
S3method(c,qenv)
7+
S3method(c,qenv.error)
8+
S3method(length,qenv)
9+
S3method(length,qenv.error)
10+
S3method(names,qenv.error)
411
S3method(within,qenv)
512
S3method(within,qenv.error)
613
export(concat)

NEWS.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
# teal.code 0.5.0.9012
1+
# teal.code 0.5.0.9013
22

33
### Enhancements
44

55
* `get_code()` was extended with `names` parameter and allows the code extraction to be limited to objects stored in
66
`qenv` but limited to `names`.
7+
* `qenv` inherits from the `environment` class, allowing to use `ls()`, `names()`, `as.environment()` and other functions on `qenv` objects.
8+
* `join()` method is deprecated, please use `c()` instead
9+
* `get_var()` method is deprecated, please use `get`, `[[` or `$` instead.
710

811
# teal.code 0.5.0
912

R/qenv-c.R

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
#' If two `qenv` can be joined
2+
#'
3+
#' Checks if two `qenv` objects can be combined.
4+
#' For more information, please see [`join`]
5+
#' @param x (`qenv`)
6+
#' @param y (`qenv`)
7+
#' @return `TRUE` if able to join or `character` used to print error message.
8+
#' @keywords internal
9+
.check_joinable <- function(x, y) {
10+
checkmate::assert_class(x, "qenv")
11+
checkmate::assert_class(y, "qenv")
12+
13+
common_names <- intersect(rlang::env_names(x@.xData), rlang::env_names(y@.xData))
14+
is_overwritten <- vapply(common_names, function(el) {
15+
!identical(get(el, x@.xData), get(el, y@.xData))
16+
}, logical(1))
17+
if (any(is_overwritten)) {
18+
return(
19+
paste(
20+
"Not possible to join qenv objects if anything in their environment has been modified.\n",
21+
"Following object(s) have been modified:\n - ",
22+
paste(common_names[is_overwritten], collapse = "\n - ")
23+
)
24+
)
25+
}
26+
27+
shared_ids <- intersect(x@id, y@id)
28+
if (length(shared_ids) == 0) {
29+
return(TRUE)
30+
}
31+
32+
shared_in_x <- match(shared_ids, x@id)
33+
shared_in_y <- match(shared_ids, y@id)
34+
35+
# indices of shared ids should be 1:n in both slots
36+
if (identical(shared_in_x, shared_in_y) && identical(shared_in_x, seq_along(shared_ids))) {
37+
TRUE
38+
} else if (!identical(shared_in_x, shared_in_y)) {
39+
paste(
40+
"The common shared code of the qenvs does not occur in the same position in both qenv objects",
41+
"so they cannot be joined together as it's impossible to determine the evaluation's order.",
42+
collapse = ""
43+
)
44+
} else {
45+
paste(
46+
"There is code in the qenv objects before their common shared code",
47+
"which means these objects cannot be joined.",
48+
collapse = ""
49+
)
50+
}
51+
}
52+
53+
#' @rdname join
54+
#' @param ... (`qenv` or `qenv.error`).
55+
#' @examples
56+
#' q <- qenv()
57+
#' q1 <- within(q, {
58+
#' iris1 <- iris
59+
#' mtcars1 <- mtcars
60+
#' })
61+
#' q1 <- within(q1, iris2 <- iris)
62+
#' q2 <- within(q1, mtcars2 <- mtcars)
63+
#' qq <- c(q1, q2)
64+
#' cat(get_code(qq))
65+
#'
66+
#' @export
67+
c.qenv <- function(...) {
68+
dots <- rlang::list2(...)
69+
if (!checkmate::test_list(dots[-1], types = c("qenv", "qenv.error"))) {
70+
return(NextMethod(c, dots[[1]]))
71+
}
72+
73+
first_non_qenv_ix <- which.min(vapply(dots, inherits, what = "qenv", logical(1)))
74+
if (first_non_qenv_ix > 1) {
75+
return(dots[[first_non_qenv_ix]])
76+
}
77+
78+
Reduce(
79+
x = dots[-1],
80+
init = dots[[1]],
81+
f = function(x, y) {
82+
join_validation <- .check_joinable(x, y)
83+
84+
# join expressions
85+
if (!isTRUE(join_validation)) {
86+
stop(join_validation)
87+
}
88+
89+
id_unique <- !y@id %in% x@id
90+
x@id <- c(x@id, y@id[id_unique])
91+
x@code <- c(x@code, y@code[id_unique])
92+
x@warnings <- c(x@warnings, y@warnings[id_unique])
93+
x@messages <- c(x@messages, y@messages[id_unique])
94+
95+
# insert (and overwrite) objects from y to x
96+
x@.xData <- rlang::env_clone(x@.xData, parent = parent.env(.GlobalEnv))
97+
rlang::env_coalesce(env = x@.xData, from = y@.xData)
98+
x
99+
}
100+
)
101+
}
102+
103+
#' @rdname join
104+
#' @export
105+
c.qenv.error <- function(...) {
106+
rlang::list2(...)[[1]]
107+
}

R/qenv-class.R

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#' @name qenv-class
55
#' @rdname qenv-class
66
#' @slot code (`character`) representing code necessary to reproduce the environment
7-
#' @slot env (`environment`) environment which content was generated by the evaluation
7+
#' @slot .xData (`environment`) environment with content was generated by the evaluation
88
#' of the `code` slot.
99
#' @slot id (`integer`) random identifier of the code element to make sure uniqueness
1010
#' when joining.
@@ -14,11 +14,60 @@
1414
#' @exportClass qenv
1515
setClass(
1616
"qenv",
17-
slots = c(env = "environment", code = "character", id = "integer", warnings = "character", messages = "character"),
18-
prototype = list(
19-
env = new.env(parent = parent.env(.GlobalEnv)), code = character(0), id = integer(0),
20-
warnings = character(0), messages = character(0)
21-
)
17+
slots = c(
18+
code = "character",
19+
id = "integer",
20+
warnings = "character",
21+
messages = "character"
22+
),
23+
contains = "environment"
24+
)
25+
26+
#' It initializes the `qenv` class
27+
#' @noRd
28+
setMethod(
29+
"initialize",
30+
"qenv",
31+
function(.Object, # nolint: object_name.
32+
.xData, # nolint: object_name.
33+
code = character(0L),
34+
warnings = rep("", length(code)),
35+
messages = rep("", length(code)),
36+
id = integer(0L),
37+
...) {
38+
# # Pre-process parameters to ensure they are ready to be used by parent constructors
39+
stopifnot("`code` must be a character or language object." = any(is.language(code), is.character(code)))
40+
41+
if (is.language(code)) {
42+
code <- paste(lang2calls(code), collapse = "\n")
43+
}
44+
if (length(code)) {
45+
code <- paste(code, collapse = "\n")
46+
}
47+
48+
if (length(id) == 0L) {
49+
id <- sample.int(.Machine$integer.max, size = length(code))
50+
}
51+
52+
new_xdata <- if (rlang::is_missing(.xData)) {
53+
new.env(parent = parent.env(.GlobalEnv))
54+
} else {
55+
checkmate::assert_environment(.xData)
56+
rlang::env_clone(.xData, parent = parent.env(.GlobalEnv))
57+
}
58+
lockEnvironment(new_xdata, bindings = TRUE)
59+
60+
# .xData needs to be unnamed as the `.environment` constructor allows at
61+
# most 1 unnamed formal argument of class `environment`.
62+
# See methods::findMethods("initialize")$.environment
63+
.Object <- methods::callNextMethod( # nolint: object_name.
64+
# Mandatory use of `xData` to build a correct [email protected]
65+
.Object, new_xdata,
66+
code = code, messages = messages, warnings = warnings, id = id, ...
67+
)
68+
69+
.Object
70+
}
2271
)
2372

2473
#' It takes a `qenv` class and returns `TRUE` if the input is valid
@@ -33,6 +82,8 @@ setValidity("qenv", function(object) {
3382
"@code and @messages slots must have the same length"
3483
} else if (any(duplicated(object@id))) {
3584
"@id contains duplicated values."
85+
} else if (!environmentIsLocked(object@.xData)) {
86+
"@.xData must be locked."
3687
} else {
3788
TRUE
3889
}

R/qenv-concat.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ setMethod("concat", signature = c("qenv", "qenv"), function(x, y) {
3838
y@messages <- c(x@messages, y@messages)
3939

4040
# insert (and overwrite) objects from y to x
41-
y@env <- rlang::env_clone(y@env, parent = parent.env(.GlobalEnv))
42-
rlang::env_coalesce(env = y@env, from = x@env)
41+
y@.xData <- rlang::env_clone(y@.xData, parent = parent.env(.GlobalEnv))
42+
rlang::env_coalesce(env = y@.xData, from = x@.xData)
4343
y
4444
})
4545

R/qenv-constructor.R

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
#'
1313
#' @name qenv
1414
#'
15-
#' @return Returns a `qenv` object.
15+
#' @return `qenv` returns a `qenv` object.
1616
#'
1717
#' @seealso [`base::within()`], [`get_var()`], [`get_env()`], [`get_warnings()`], [`join()`], [`concat()`]
1818
#' @examples
@@ -21,7 +21,5 @@
2121
#'
2222
#' @export
2323
qenv <- function() {
24-
q_env <- new.env(parent = parent.env(.GlobalEnv))
25-
lockEnvironment(q_env, bindings = TRUE)
26-
methods::new("qenv", env = q_env)
24+
methods::new("qenv")
2725
}

R/qenv-errors.R

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,10 @@
11
# needed to handle try-error
22
setOldClass("qenv.error")
3+
4+
#' @export
5+
as.list.qenv.error <- function(x, ...) {
6+
stop(errorCondition(
7+
list(message = conditionMessage(x)),
8+
class = c("validation", "try-error", "simpleError")
9+
))
10+
}

R/qenv-eval_code.R

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#' Evaluate code in `qenv`
22
#'
33
#' @details
4-
#' `eval_code` evaluates given code in the `qenv` environment and appends it to the `code` slot.
4+
#' `eval_code()` evaluates given code in the `qenv` environment and appends it to the `code` slot.
55
#' Thus, if the `qenv` had been instantiated empty, contents of the environment are always a result of the stored code.
66
#'
77
#' @param object (`qenv`)
@@ -31,7 +31,7 @@ setMethod("eval_code", signature = c("qenv", "character"), function(object, code
3131
id <- sample.int(.Machine$integer.max, size = 1)
3232

3333
object@id <- c(object@id, id)
34-
object@env <- rlang::env_clone(object@env, parent = parent.env(.GlobalEnv))
34+
object@.xData <- rlang::env_clone(object@.xData, parent = parent.env(.GlobalEnv))
3535
code <- paste(code, collapse = "\n")
3636
object@code <- c(object@code, code)
3737

@@ -45,11 +45,11 @@ setMethod("eval_code", signature = c("qenv", "character"), function(object, code
4545
x <- withCallingHandlers(
4646
tryCatch(
4747
{
48-
eval(single_call, envir = object@env)
49-
if (!identical(parent.env(object@env), parent.env(.GlobalEnv))) {
50-
# needed to make sure that @env is always a sibling of .GlobalEnv
48+
eval(single_call, envir = object@.xData)
49+
if (!identical(parent.env(object@.xData), parent.env(.GlobalEnv))) {
50+
# needed to make sure that @.xData is always a sibling of .GlobalEnv
5151
# could be changed when any new package is added to search path (through library or require call)
52-
parent.env(object@env) <- parent.env(.GlobalEnv)
52+
parent.env(object@.xData) <- parent.env(.GlobalEnv)
5353
}
5454
NULL
5555
},
@@ -80,20 +80,19 @@ setMethod("eval_code", signature = c("qenv", "character"), function(object, code
8080
}
8181
}
8282

83-
8483
object@warnings <- c(object@warnings, current_warnings)
8584
object@messages <- c(object@messages, current_messages)
8685

87-
lockEnvironment(object@env, bindings = TRUE)
86+
lockEnvironment(object@.xData, bindings = TRUE)
8887
object
8988
})
9089

9190
setMethod("eval_code", signature = c("qenv", "language"), function(object, code) {
92-
eval_code(object, code = paste(lang2calls(code), collapse = "\n"))
91+
eval_code(object, code = paste(vapply(lang2calls(code), deparse1, collapse = "\n", character(1L)), collapse = "\n"))
9392
})
9493

9594
setMethod("eval_code", signature = c("qenv", "expression"), function(object, code) {
96-
eval_code(object, code = paste(lang2calls(code), collapse = "\n"))
95+
eval_code(object, code = paste(vapply(lang2calls(code), deparse1, collapse = "\n", character(1L)), collapse = "\n"))
9796
})
9897

9998
setMethod("eval_code", signature = c("qenv.error", "ANY"), function(object, code) {

0 commit comments

Comments
 (0)