Skip to content

Commit 7a81203

Browse files
authored
Merge branch 'main' into 224_magrittr@main
2 parents 7eeb999 + a235176 commit 7a81203

38 files changed

+1203
-801
lines changed

DESCRIPTION

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Type: Package
22
Package: teal.code
33
Title: Code Storage and Execution Class for 'teal' Applications
4-
Version: 0.5.0.9013
4+
Version: 0.5.0.9014
55
Date: 2024-11-08
66
Authors@R: c(
77
person("Dawid", "Kaledkowski", , "[email protected]", role = c("aut", "cre")),
@@ -58,8 +58,10 @@ Collate:
5858
'qenv-concat.R'
5959
'qenv-constructor.R'
6060
'qenv-eval_code.R'
61+
'qenv-extract.R'
6162
'qenv-get_code.R'
6263
'qenv-get_env.R'
64+
'qenv-get_messages.r'
6365
'qenv-get_var.R'
6466
'qenv-get_warnings.R'
6567
'qenv-join.R'

NAMESPACE

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

33
S3method("$",qenv.error)
4+
S3method("[",qenv)
45
S3method("[[",qenv.error)
56
S3method(as.list,qenv.error)
67
S3method(c,qenv)
@@ -15,6 +16,7 @@ export(dev_suppress)
1516
export(eval_code)
1617
export(get_code)
1718
export(get_env)
19+
export(get_messages)
1820
export(get_var)
1921
export(get_warnings)
2022
export(join)

NEWS.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
# teal.code 0.5.0.9013
1+
# teal.code 0.5.0.9014
22

33
### Enhancements
44

5+
* Introduced `[.qenv` function to subset `qenv` object (code and environment) to specified object names. #211
56
* `get_code()` was extended with `names` parameter and allows the code extraction to be limited to objects stored in
6-
`qenv` but limited to `names`.
7+
`qenv` but limited to `names`. #210
8+
* Introduced `get_messages()` to get messages produced during code evaluation. #217
9+
* `get_code()` returns original code formatting (white spaces and comments) passed to `eval_code()`. #212
710
* `qenv` inherits from the `environment` class, allowing to use `ls()`, `names()`, `as.environment()` and other functions on `qenv` objects.
811
* `join()` method is deprecated, please use `c()` instead
912
* `get_var()` method is deprecated, please use `get`, `[[` or `$` instead.

R/qenv-c.R

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,16 @@
2424
)
2525
}
2626

27-
shared_ids <- intersect(x@id, y@id)
27+
x_id <- get_code_attr(x, "id")
28+
y_id <- get_code_attr(y, "id")
29+
30+
shared_ids <- intersect(x_id, y_id)
2831
if (length(shared_ids) == 0) {
2932
return(TRUE)
3033
}
3134

32-
shared_in_x <- match(shared_ids, x@id)
33-
shared_in_y <- match(shared_ids, y@id)
35+
shared_in_x <- match(shared_ids, x_id)
36+
shared_in_y <- match(shared_ids, y_id)
3437

3538
# indices of shared ids should be 1:n in both slots
3639
if (identical(shared_in_x, shared_in_y) && identical(shared_in_x, seq_along(shared_ids))) {
@@ -86,11 +89,7 @@ c.qenv <- function(...) {
8689
stop(join_validation)
8790
}
8891

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])
92+
x@code <- union(x@code, y@code)
9493

9594
# insert (and overwrite) objects from y to x
9695
x@.xData <- rlang::env_clone(x@.xData, parent = parent.env(.GlobalEnv))

R/qenv-class.R

Lines changed: 23 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,25 @@
33
#' Reproducible class with environment and code.
44
#' @name qenv-class
55
#' @rdname qenv-class
6-
#' @slot code (`character`) representing code necessary to reproduce the environment
76
#' @slot .xData (`environment`) environment with content was generated by the evaluation
7+
#' @slot code (`list` of `character`) representing code necessary to reproduce the environment.
8+
#' Read more in Code section.
89
#' of the `code` slot.
9-
#' @slot id (`integer`) random identifier of the code element to make sure uniqueness
10-
#' when joining.
11-
#' @slot warnings (`character`) the warnings output when evaluating the code
12-
#' @slot messages (`character`) the messages output when evaluating the code
10+
#'
11+
#' @section Code:
12+
#'
13+
#' Each code element is a character representing one call. Each element has possible attributes:
14+
#' - `warnings` (`character`) the warnings output when evaluating the code element
15+
#' - `messages` (`character`) the messages output when evaluating the code element
16+
#' - `id (`integer`) random identifier of the code element to make sure uniqueness when joining
17+
#' - `dependency` (`character`) names of objects that appear in this call and gets affected by this call,
18+
#' separated by `<-` (objects on LHS of `<-` are affected by this line, and objects on RHS are affecting this line)
19+
#'
1320
#' @keywords internal
1421
#' @exportClass qenv
1522
setClass(
1623
"qenv",
17-
slots = c(
18-
code = "character",
19-
id = "integer",
20-
warnings = "character",
21-
messages = "character"
22-
),
24+
slots = c(code = "list"),
2325
contains = "environment"
2426
)
2527

@@ -28,27 +30,7 @@ setClass(
2830
setMethod(
2931
"initialize",
3032
"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-
33+
function(.Object, .xData, code = list(), ...) { # nolint: object_name.
5234
new_xdata <- if (rlang::is_missing(.xData)) {
5335
new.env(parent = parent.env(.GlobalEnv))
5436
} else {
@@ -60,28 +42,23 @@ setMethod(
6042
# .xData needs to be unnamed as the `.environment` constructor allows at
6143
# most 1 unnamed formal argument of class `environment`.
6244
# See methods::findMethods("initialize")$.environment
63-
.Object <- methods::callNextMethod( # nolint: object_name.
64-
# Mandatory use of `xData` to build a correct .Object@.xData
65-
.Object, new_xdata,
66-
code = code, messages = messages, warnings = warnings, id = id, ...
45+
methods::callNextMethod(
46+
.Object,
47+
new_xdata, # Mandatory use of unnamed environment arg
48+
code = code, ...
6749
)
68-
69-
.Object
7050
}
7151
)
7252

7353
#' It takes a `qenv` class and returns `TRUE` if the input is valid
7454
#' @name qenv-class
7555
#' @keywords internal
7656
setValidity("qenv", function(object) {
77-
if (length(object@code) != length(object@id)) {
78-
"@code and @id slots must have the same length."
79-
} else if (length(object@code) != length(object@warnings)) {
80-
"@code and @warnings slots must have the same length"
81-
} else if (length(object@code) != length(object@messages)) {
82-
"@code and @messages slots must have the same length"
83-
} else if (any(duplicated(object@id))) {
84-
"@id contains duplicated values."
57+
ids <- lapply(object@code, "attr", "id")
58+
if (any(sapply(ids, is.null))) {
59+
"All @code slots must have an 'id' attribute"
60+
} else if (any(duplicated(unlist(ids)))) {
61+
"@code contains duplicated 'id' attributes."
8562
} else if (!environmentIsLocked(object@.xData)) {
8663
"@.xData must be locked."
8764
} else {

R/qenv-concat.R

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,7 @@
3232
setGeneric("concat", function(x, y) standardGeneric("concat"))
3333

3434
setMethod("concat", signature = c("qenv", "qenv"), function(x, y) {
35-
y@id <- c(x@id, y@id)
3635
y@code <- c(x@code, y@code)
37-
y@warnings <- c(x@warnings, y@warnings)
38-
y@messages <- c(x@messages, y@messages)
3936

4037
# insert (and overwrite) objects from y to x
4138
y@.xData <- rlang::env_clone(y@.xData, parent = parent.env(.GlobalEnv))

R/qenv-constructor.R

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
#'
66
#' Create a `qenv` object and evaluate code in it to track code history.
77
#'
8+
#' @param names (`character`) for `x[names]`, names of objects included in `qenv` to subset. Names not present in `qenv`
9+
#' are skipped. For `get_code` `r lifecycle::badge("experimental")` vector of object names to return the code for.
10+
#' For more details see the "Extracting dataset-specific code" section.
11+
#'
812
#' @details
913
#'
1014
#' `qenv()` instantiates a `qenv` with an empty environment.

R/qenv-eval_code.R

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -28,24 +28,27 @@
2828
setGeneric("eval_code", function(object, code) standardGeneric("eval_code"))
2929

3030
setMethod("eval_code", signature = c("qenv", "character"), function(object, code) {
31-
id <- sample.int(.Machine$integer.max, size = 1)
32-
33-
object@id <- c(object@id, id)
31+
parsed_code <- parse(text = code, keep.source = TRUE)
3432
object@.xData <- rlang::env_clone(object@.xData, parent = parent.env(.GlobalEnv))
35-
code <- paste(code, collapse = "\n")
36-
object@code <- c(object@code, code)
33+
if (length(parsed_code) == 0) {
34+
# empty code, or just comments
35+
attr(code, "id") <- sample.int(.Machine$integer.max, size = 1)
36+
attr(code, "dependency") <- extract_dependency(parsed_code) # in case comment contains @linksto tag
37+
object@code <- c(object@code, list(code))
38+
return(object)
39+
}
40+
code_split <- split_code(paste(code, collapse = "\n"))
3741

38-
current_warnings <- ""
39-
current_messages <- ""
42+
for (i in seq_along(code_split)) {
43+
current_code <- code_split[[i]]
44+
current_call <- parse(text = current_code, keep.source = TRUE)
4045

41-
parsed_code <- parse(text = code, keep.source = TRUE)
42-
for (single_call in parsed_code) {
4346
# Using withCallingHandlers to capture warnings and messages.
4447
# Using tryCatch to capture the error and abort further evaluation.
4548
x <- withCallingHandlers(
4649
tryCatch(
4750
{
48-
eval(single_call, envir = object@.xData)
51+
eval(current_call, envir = object@.xData)
4952
if (!identical(parent.env(object@.xData), parent.env(.GlobalEnv))) {
5053
# needed to make sure that @.xData is always a sibling of .GlobalEnv
5154
# could be changed when any new package is added to search path (through library or require call)
@@ -58,30 +61,31 @@ setMethod("eval_code", signature = c("qenv", "character"), function(object, code
5861
message = sprintf(
5962
"%s \n when evaluating qenv code:\n%s",
6063
.ansi_strip(conditionMessage(e)),
61-
deparse1(single_call)
64+
current_code
6265
),
6366
class = c("qenv.error", "try-error", "simpleError"),
64-
trace = object@code
67+
trace = unlist(c(object@code, list(current_code)))
6568
)
6669
}
6770
),
6871
warning = function(w) {
69-
current_warnings <<- paste0(current_warnings, .ansi_strip(sprintf("> %s\n", conditionMessage(w))))
72+
attr(current_code, "warning") <<- .ansi_strip(sprintf("> %s\n", conditionMessage(w)))
7073
invokeRestart("muffleWarning")
7174
},
7275
message = function(m) {
73-
current_messages <<- paste0(current_messages, .ansi_strip(sprintf("> %s", conditionMessage(m))))
76+
attr(current_code, "message") <<- .ansi_strip(sprintf("> %s", conditionMessage(m)))
7477
invokeRestart("muffleMessage")
7578
}
7679
)
7780

7881
if (!is.null(x)) {
7982
return(x)
8083
}
81-
}
8284

83-
object@warnings <- c(object@warnings, current_warnings)
84-
object@messages <- c(object@messages, current_messages)
85+
attr(current_code, "id") <- sample.int(.Machine$integer.max, size = 1)
86+
attr(current_code, "dependency") <- extract_dependency(current_call)
87+
object@code <- c(object@code, list(current_code))
88+
}
8589

8690
lockEnvironment(object@.xData, bindings = TRUE)
8791
object
@@ -92,7 +96,12 @@ setMethod("eval_code", signature = c("qenv", "language"), function(object, code)
9296
})
9397

9498
setMethod("eval_code", signature = c("qenv", "expression"), function(object, code) {
95-
eval_code(object, code = paste(vapply(lang2calls(code), deparse1, collapse = "\n", character(1L)), collapse = "\n"))
99+
srcref <- attr(code, "wholeSrcref")
100+
if (length(srcref)) {
101+
eval_code(object, code = paste(attr(code, "wholeSrcref"), collapse = "\n"))
102+
} else {
103+
eval_code(object, code = paste(lang2calls(code), collapse = "\n"))
104+
}
96105
})
97106

98107
setMethod("eval_code", signature = c("qenv.error", "ANY"), function(object, code) {
@@ -108,3 +117,7 @@ setMethod("eval_code", signature = c("qenv.error", "ANY"), function(object, code
108117
chr
109118
}
110119
}
120+
121+
get_code_attr <- function(qenv, attr) {
122+
unlist(lapply(qenv@code, function(x) attr(x, attr)))
123+
}

R/qenv-extract.R

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#'
2+
#' @section Subsetting:
3+
#' `x[names]` subsets objects in `qenv` environment and limit the code to the necessary needed to build limited objects.
4+
#' `...` passes parameters to further methods.
5+
#'
6+
#' @param x (`qenv`)
7+
#'
8+
#' @examples
9+
#'
10+
#' # Subsetting
11+
#' q <- qenv()
12+
#' q <- eval_code(q, "a <- 1;b<-2")
13+
#' q["a"]
14+
#' q[c("a", "b")]
15+
#'
16+
#' @rdname qenv
17+
#'
18+
#' @export
19+
`[.qenv` <- function(x, names, ...) {
20+
checkmate::assert_character(names, any.missing = FALSE)
21+
possible_names <- ls(get_env(x), all.names = TRUE)
22+
names_corrected <- intersect(names, possible_names)
23+
env <- if (length(names_corrected)) {
24+
names_missing <- setdiff(names, possible_names)
25+
if (length(names_missing)) {
26+
warning(
27+
sprintf(
28+
"Some 'names' do not exist in the environment of the '%s'. Skipping those: %s.",
29+
class(x)[1],
30+
paste(names_missing, collapse = ", ")
31+
)
32+
)
33+
}
34+
list2env(as.list(x, all.names = TRUE)[names_corrected], parent = parent.env(.GlobalEnv))
35+
} else {
36+
warning(
37+
sprintf(
38+
"None of 'names' exist in the environment of the '%1$s'. Returning empty '%1$s'.",
39+
class(x)[1]
40+
),
41+
call. = FALSE
42+
)
43+
new.env(parent = parent.env(.GlobalEnv))
44+
}
45+
lockEnvironment(env)
46+
x@.xData <- env
47+
48+
normalized_names <- gsub("^`(.*)`$", "\\1", names)
49+
x@code <- get_code_dependency(x@code, names = normalized_names, ...)
50+
51+
x
52+
}

0 commit comments

Comments
 (0)