Skip to content

Commit b807058

Browse files
committed
Merge branch 'main' into 211_subset@main
2 parents 7cbc93d + 34f0772 commit b807058

32 files changed

+518
-136
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"),
@@ -53,6 +53,7 @@ Language: en-US
5353
Roxygen: list(markdown = TRUE)
5454
RoxygenNote: 7.3.2
5555
Collate:
56+
'qenv-c.R'
5657
'qenv-class.R'
5758
'qenv-errors.R'
5859
'qenv-concat.R'
@@ -65,6 +66,7 @@ Collate:
6566
'qenv-get_var.R'
6667
'qenv-get_warnings.R'
6768
'qenv-join.R'
69+
'qenv-length.R'
6870
'qenv-show.R'
6971
'qenv-within.R'
7072
'teal.code-package.R'

NAMESPACE

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

33
S3method("[",qenv)
4+
S3method("$",qenv.error)
45
S3method("[[",qenv.error)
6+
S3method(as.list,qenv.error)
7+
S3method(c,qenv)
8+
S3method(c,qenv.error)
9+
S3method(length,qenv)
10+
S3method(length,qenv.error)
11+
S3method(names,qenv.error)
512
S3method(within,qenv)
613
S3method(within,qenv.error)
714
export(concat)

NEWS.md

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

33
### Enhancements
44

@@ -7,6 +7,9 @@
77
`qenv` but limited to `names`. #210
88
* Introduced `get_messages()` to get messages produced during code evaluation. #217
99
* `get_code()` returns original code formatting (white spaces and comments) passed to `eval_code()`. #212
10+
* `qenv` inherits from the `environment` class, allowing to use `ls()`, `names()`, `as.environment()` and other functions on `qenv` objects.
11+
* `join()` method is deprecated, please use `c()` instead
12+
* `get_var()` method is deprecated, please use `get`, `[[` or `$` instead.
1013

1114
# teal.code 0.5.0
1215

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: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
#' Reproducible class with environment and code.
44
#' @name qenv-class
55
#' @rdname qenv-class
6+
#' @slot .xData (`environment`) environment with content was generated by the evaluation
67
#' @slot code (`list` of `character`) representing code necessary to reproduce the environment.
78
#' Read more in Code section.
8-
#' @slot env (`environment`) environment which content was generated by the evaluation
99
#' of the `code` slot.
1010
#'
1111
#' @section Code:
@@ -21,14 +21,77 @@
2121
#' @exportClass qenv
2222
setClass(
2323
"qenv",
24-
slots = c(env = "environment", code = "list"),
25-
prototype = list(
26-
env = new.env(parent = parent.env(.GlobalEnv)), code = list()
27-
)
24+
slots = c(
25+
code = "character",
26+
id = "integer",
27+
warnings = "character",
28+
messages = "character"
29+
),
30+
contains = "environment"
31+
)
32+
33+
#' It initializes the `qenv` class
34+
#' @noRd
35+
setMethod(
36+
"initialize",
37+
"qenv",
38+
function(.Object, # nolint: object_name.
39+
.xData, # nolint: object_name.
40+
code = character(0L),
41+
warnings = rep("", length(code)),
42+
messages = rep("", length(code)),
43+
id = integer(0L),
44+
...) {
45+
# # Pre-process parameters to ensure they are ready to be used by parent constructors
46+
stopifnot("`code` must be a character or language object." = any(is.language(code), is.character(code)))
47+
48+
if (is.language(code)) {
49+
code <- paste(lang2calls(code), collapse = "\n")
50+
}
51+
if (length(code)) {
52+
code <- paste(code, collapse = "\n")
53+
}
54+
55+
if (length(id) == 0L) {
56+
id <- sample.int(.Machine$integer.max, size = length(code))
57+
}
58+
59+
new_xdata <- if (rlang::is_missing(.xData)) {
60+
new.env(parent = parent.env(.GlobalEnv))
61+
} else {
62+
checkmate::assert_environment(.xData)
63+
rlang::env_clone(.xData, parent = parent.env(.GlobalEnv))
64+
}
65+
lockEnvironment(new_xdata, bindings = TRUE)
66+
67+
# .xData needs to be unnamed as the `.environment` constructor allows at
68+
# most 1 unnamed formal argument of class `environment`.
69+
# See methods::findMethods("initialize")$.environment
70+
.Object <- methods::callNextMethod( # nolint: object_name.
71+
# Mandatory use of `xData` to build a correct [email protected]
72+
.Object, new_xdata,
73+
code = code, messages = messages, warnings = warnings, id = id, ...
74+
)
75+
76+
.Object
77+
}
2878
)
2979

3080
#' It takes a `qenv` class and returns `TRUE` if the input is valid
3181
#' @name qenv-class
3282
#' @keywords internal
3383
setValidity("qenv", function(object) {
84+
if (length(object@code) != length(object@id)) {
85+
"@code and @id slots must have the same length."
86+
} else if (length(object@code) != length(object@warnings)) {
87+
"@code and @warnings slots must have the same length"
88+
} else if (length(object@code) != length(object@messages)) {
89+
"@code and @messages slots must have the same length"
90+
} else if (any(duplicated(object@id))) {
91+
"@id contains duplicated values."
92+
} else if (!environmentIsLocked(object@.xData)) {
93+
"@.xData must be locked."
94+
} else {
95+
TRUE
96+
}
3497
})

R/qenv-concat.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ setMethod("concat", signature = c("qenv", "qenv"), function(x, y) {
3535
y@code <- c(x@code, y@code)
3636

3737
# insert (and overwrite) objects from y to x
38-
y@env <- rlang::env_clone(y@env, parent = parent.env(.GlobalEnv))
39-
rlang::env_coalesce(env = y@env, from = x@env)
38+
y@.xData <- rlang::env_clone(y@.xData, parent = parent.env(.GlobalEnv))
39+
rlang::env_coalesce(env = y@.xData, from = x@.xData)
4040
y
4141
})
4242

R/qenv-constructor.R

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
#'
1717
#' @name qenv
1818
#'
19-
#' @return Returns a `qenv` object.
19+
#' @return `qenv` returns a `qenv` object.
2020
#'
2121
#' @seealso [`base::within()`], [`get_var()`], [`get_env()`], [`get_warnings()`], [`join()`], [`concat()`]
2222
#' @examples
@@ -25,7 +25,5 @@
2525
#'
2626
#' @export
2727
qenv <- function() {
28-
q_env <- new.env(parent = parent.env(.GlobalEnv))
29-
lockEnvironment(q_env, bindings = TRUE)
30-
methods::new("qenv", env = q_env)
28+
methods::new("qenv")
3129
}

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: 3 additions & 3 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`)
@@ -52,7 +52,7 @@ setMethod("eval_code", signature = c("qenv", "character"), function(object, code
5252
if (!identical(parent.env(object@env), parent.env(.GlobalEnv))) {
5353
# needed to make sure that @env is always a sibling of .GlobalEnv
5454
# could be changed when any new package is added to search path (through library or require call)
55-
parent.env(object@env) <- parent.env(.GlobalEnv)
55+
parent.env(object@.xData) <- parent.env(.GlobalEnv)
5656
}
5757
NULL
5858
},
@@ -92,7 +92,7 @@ setMethod("eval_code", signature = c("qenv", "character"), function(object, code
9292
})
9393

9494
setMethod("eval_code", signature = c("qenv", "language"), function(object, code) {
95-
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"))
9696
})
9797

9898
setMethod("eval_code", signature = c("qenv", "expression"), function(object, code) {

0 commit comments

Comments
 (0)