Skip to content

Commit e57417c

Browse files
committed
import get_code_dependency from teal.data
1 parent 9415556 commit e57417c

File tree

7 files changed

+1346
-3
lines changed

7 files changed

+1346
-3
lines changed

DESCRIPTION

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,5 @@ Collate:
6666
'qenv-show.R'
6767
'qenv-within.R'
6868
'teal.code-package.R'
69+
'utils-get_code_dependency.R'
6970
'utils.R'

NEWS.md

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

3+
### Enhancements
4+
5+
* `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+
38
# teal.code 0.5.0
49

510
### Breaking Change

R/qenv-get_code.R

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,97 @@
55
#'
66
#' @param object (`qenv`)
77
#' @param deparse (`logical(1)`) flag specifying whether to return code as `character` or `expression`.
8+
#' @param names `r lifecycle::badge("experimental")` (`character`) vector of object names to return the code for.
9+
#' For more details see the "Extracting dataset-specific code" section.
810
#' @param ... see `Details`
911
#'
12+
#'
13+
#' @section Extracting dataset-specific code:
14+
#' When `names` is specified, the code returned will be limited to the lines needed to _create_
15+
#' the requested objects. The code stored in the `@code` slot is analyzed statically to determine
16+
#' which lines the objects of interest depend upon. The analysis works well when objects are created
17+
#' with standard infix assignment operators (see `?assignOps`) but it can fail in some situations.
18+
#'
19+
#' Consider the following examples:
20+
#'
21+
#' _Case 1: Usual assignments._
22+
#' ```r
23+
#' q1 <- qenv() |>
24+
#' within({
25+
#' foo <- function(x) {
26+
#' x + 1
27+
#' }
28+
#' x <- 0
29+
#' y <- foo(x)
30+
#' })
31+
#' get_code(q1, names = "y")
32+
#' ```
33+
#' `x` has no dependencies, so `get_code(data, names = "x")` will return only the second call.\cr
34+
#' `y` depends on `x` and `foo`, so `get_code(data, names = "y")` will contain all three calls.
35+
#'
36+
#' _Case 2: Some objects are created by a function's side effects._
37+
#' ```r
38+
#' q2 <- qenv() |>
39+
#' within({
40+
#' foo <- function() {
41+
#' x <<- x + 1
42+
#' }
43+
#' x <- 0
44+
#' foo()
45+
#' y <- x
46+
#' })
47+
#' get_code(q2, names = "y")
48+
#' ```
49+
#' Here, `y` depends on `x` but `x` is modified by `foo` as a side effect (not by reassignment)
50+
#' and so `get_code(data, names = "y")` will not return the `foo()` call.\cr
51+
#' To overcome this limitation, code dependencies can be specified manually.
52+
#' Lines where side effects occur can be flagged by adding "`# @linksto <object name>`" at the end.\cr
53+
#' Note that `within` evaluates code passed to `expr` as is and comments are ignored.
54+
#' In order to include comments in code one must use the `eval_code` function instead.
55+
#'
56+
#' ```r
57+
#' q3 <- qenv() |>
58+
#' eval_code("
59+
#' foo <- function() {
60+
#' x <<- x + 1
61+
#' }
62+
#' x <- 0
63+
#' foo() # @linksto x
64+
#' y <- x
65+
#' ")
66+
#' get_code(q3, names = "y")
67+
#' ```
68+
#' Now the `foo()` call will be properly included in the code required to recreate `y`.
69+
#'
70+
#' Note that two functions that create objects as side effects, `assign` and `data`, are handled automatically.
71+
#'
72+
#' Here are known cases where manual tagging is necessary:
73+
#' - non-standard assignment operators, _e.g._ `%<>%`
74+
#' - objects used as conditions in `if` statements: `if (<condition>)`
75+
#' - objects used to iterate over in `for` loops: `for(i in <sequence>)`
76+
#' - creating and evaluating language objects, _e.g._ `eval(<call>)`
77+
#'
1078
#' @return
1179
#' `get_code` returns the traced code (from `@code` slot) in the form specified by `deparse`.
1280
#'
1381
#' @examples
1482
#' # retrieve code
83+
#' q <- within(qenv(), {a <- 1; b <- 2})
1584
#' get_code(q)
1685
#' get_code(q, deparse = FALSE)
86+
#' get_code(q, names = "a")
87+
#'
88+
#' q <- qenv()
89+
#' q <- eval_code(q, code = c("a <- 1", "b <- 2"))
90+
#' get_code(q, names = "a")
1791
#'
1892
#' @name get_code
1993
#' @rdname qenv
2094
#' @aliases get_code,qenv-method
2195
#' @aliases get_code,qenv.error-method
2296
#'
2397
#' @export
24-
setGeneric("get_code", function(object, deparse = TRUE, ...) {
98+
setGeneric("get_code", function(object, deparse = TRUE, names = NULL, ...) {
2599
# this line forces evaluation of object before passing to the generic
26100
# needed for error handling to work properly
27101
grDevices::pdf(nullfile())
@@ -31,8 +105,16 @@ setGeneric("get_code", function(object, deparse = TRUE, ...) {
31105
standardGeneric("get_code")
32106
})
33107

34-
setMethod("get_code", signature = "qenv", function(object, deparse = TRUE) {
108+
setMethod("get_code", signature = "qenv", function(object, deparse = TRUE, names = NULL, ...) {
35109
checkmate::assert_flag(deparse)
110+
checkmate::assert_character(names, min.len = 1L, null.ok = TRUE)
111+
112+
code <- if (!is.null(names)) {
113+
get_code_dependency(object@code, names, ...)
114+
} else {
115+
object@code
116+
}
117+
36118
if (deparse) {
37119
if (length(object@code) == 0) {
38120
object@code

0 commit comments

Comments
 (0)