From 2fc8018a280b531fe92a03dcd36ef6b69f67477f Mon Sep 17 00:00:00 2001 From: Pawel Rucki Date: Fri, 4 Nov 2022 16:39:43 +0100 Subject: [PATCH] Teal refactor (#66) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Part of https://github.com/insightsengineering/teal/issues/731 Signed-off-by: Nikolas Burkoff Signed-off-by: Nikolas Burkoff Co-authored-by: Dawid Kałędkowski <6959016+gogonzo@users.noreply.github.com> Co-authored-by: Dawid Kałędkowski Co-authored-by: Nikolas Burkoff Co-authored-by: Nikolas Burkoff Co-authored-by: Mahmoud Hallal Co-authored-by: Mahmoud Hallal <86970066+mhallal1@users.noreply.github.com> Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- DESCRIPTION | 5 +- NAMESPACE | 16 + NEWS.md | 3 + R/chunk.R | 8 +- R/chunks.R | 96 ++++-- R/get_eval_details.R | 18 +- R/qenv-class.R | 38 +++ R/qenv-constructor.R | 66 ++++ R/qenv-errors.R | 2 + R/qenv-eval_code.R | 100 ++++++ R/qenv-get_code.R | 46 +++ R/qenv-get_var.R | 58 ++++ R/qenv-get_warnings.R | 54 ++++ R/qenv-join.R | 116 +++++++ R/qenv-show.R | 18 ++ README.md | 8 +- _pkgdown.yml | 14 +- man/chunk_call.Rd | 4 +- man/chunk_comment.Rd | 3 +- man/chunks.Rd | 27 +- man/chunks_deep_clone.Rd | 3 +- man/chunks_eval.Rd | 3 +- man/chunks_get_eval_msg.Rd | 3 +- man/chunks_get_rcode.Rd | 3 +- man/chunks_get_var.Rd | 3 +- man/chunks_is_ok.Rd | 3 +- man/chunks_messages.Rd | 3 +- man/chunks_new.Rd | 3 +- man/chunks_push.Rd | 3 +- man/chunks_push_chunks.Rd | 3 +- man/chunks_push_comment.Rd | 3 +- man/chunks_push_data_merge.Rd | 3 +- man/chunks_push_new_line.Rd | 3 +- man/chunks_reset.Rd | 3 +- man/chunks_safe_eval.Rd | 3 +- man/chunks_uneval.Rd | 3 +- man/chunks_validate_all.Rd | 3 +- man/chunks_validate_custom.Rd | 3 +- man/chunks_validate_is.Rd | 3 +- man/chunks_validate_is_ok.Rd | 3 +- man/chunks_warnings.Rd | 4 +- man/dot-check_joinable.Rd | 25 ++ man/eval_code.Rd | 36 +++ man/get_chunks_object.Rd | 3 +- man/get_code.Rd | 32 ++ man/get_eval_details_srv.Rd | 3 +- man/get_eval_details_ui.Rd | 3 +- man/get_var.Rd | 43 +++ man/get_warnings.Rd | 35 +++ man/init_chunks.Rd | 3 +- man/join.Rd | 47 +++ man/new_qenv.Rd | 36 +++ man/overwrite_chunks.Rd | 3 +- man/qenv-class.Rd | 26 ++ man/show-qenv-method.Rd | 21 ++ man/show_eval_details_modal.Rd | 4 +- tests/testthat/test-chunks.R | 11 +- tests/testthat/test-qenv_constructor.R | 82 +++++ tests/testthat/test-qenv_eval_code.R | 154 +++++++++ tests/testthat/test-qenv_get_code.R | 39 +++ tests/testthat/test-qenv_get_var.R | 26 ++ tests/testthat/test-qenv_get_warnings.R | 66 ++++ tests/testthat/test-qenv_join.R | 203 ++++++++++++ tests/testthat/test-qenv_show.R | 11 + vignettes/advanced_chunks.Rmd | 394 +----------------------- vignettes/basic_chunks.Rmd | 244 +-------------- vignettes/images/container.png | Bin 10140 -> 0 bytes vignettes/images/eval.png | Bin 25095 -> 0 bytes vignettes/images/get_rcode.png | Bin 19371 -> 0 bytes vignettes/images/initialize_env.png | Bin 36339 -> 0 bytes vignettes/images/is_ok.png | Bin 27718 -> 0 bytes vignettes/images/push.png | Bin 16410 -> 0 bytes vignettes/images/reset.png | Bin 46822 -> 0 bytes vignettes/images/show_r_code.gif | Bin 2710502 -> 0 bytes vignettes/qenv.Rmd | 144 +++++++++ vignettes/teal-code.Rmd | 42 +-- 76 files changed, 1747 insertions(+), 753 deletions(-) create mode 100644 R/qenv-class.R create mode 100644 R/qenv-constructor.R create mode 100644 R/qenv-errors.R create mode 100644 R/qenv-eval_code.R create mode 100644 R/qenv-get_code.R create mode 100644 R/qenv-get_var.R create mode 100644 R/qenv-get_warnings.R create mode 100644 R/qenv-join.R create mode 100644 R/qenv-show.R create mode 100644 man/dot-check_joinable.Rd create mode 100644 man/eval_code.Rd create mode 100644 man/get_code.Rd create mode 100644 man/get_var.Rd create mode 100644 man/get_warnings.Rd create mode 100644 man/join.Rd create mode 100644 man/new_qenv.Rd create mode 100644 man/qenv-class.Rd create mode 100644 man/show-qenv-method.Rd create mode 100644 tests/testthat/test-qenv_constructor.R create mode 100644 tests/testthat/test-qenv_eval_code.R create mode 100644 tests/testthat/test-qenv_get_code.R create mode 100644 tests/testthat/test-qenv_get_var.R create mode 100644 tests/testthat/test-qenv_get_warnings.R create mode 100644 tests/testthat/test-qenv_join.R create mode 100644 tests/testthat/test-qenv_show.R delete mode 100644 vignettes/images/container.png delete mode 100644 vignettes/images/eval.png delete mode 100644 vignettes/images/get_rcode.png delete mode 100644 vignettes/images/initialize_env.png delete mode 100644 vignettes/images/is_ok.png delete mode 100644 vignettes/images/push.png delete mode 100644 vignettes/images/reset.png delete mode 100644 vignettes/images/show_r_code.gif create mode 100644 vignettes/qenv.Rmd diff --git a/DESCRIPTION b/DESCRIPTION index 5eff9c24..6cca0964 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -21,13 +21,16 @@ Imports: checkmate, crayon, lifecycle, + methods, R6, + rlang, shiny, styler, teal.widgets (>= 0.2.0) Suggests: + cli, knitr, - rlang, + magrittr, rmarkdown, testthat (>= 2.0) VignetteBuilder: diff --git a/NAMESPACE b/NAMESPACE index 56112038..506f74f4 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,5 +1,6 @@ # Generated by roxygen2: do not edit by hand +S3method("[[",qenv.error) export(chunk) export(chunk_call) export(chunk_comment) @@ -25,13 +26,28 @@ export(chunks_validate_custom) export(chunks_validate_is) export(chunks_validate_is_ok) export(chunks_warnings) +export(eval_code) export(get_chunks_object) +export(get_code) export(get_eval_details_srv) export(get_eval_details_ui) +export(get_var) +export(get_warnings) export(init_chunks) +export(join) +export(new_qenv) export(overwrite_chunks) export(show_eval_details_modal) +exportMethods("[[") +exportMethods(eval_code) +exportMethods(get_code) +exportMethods(get_var) +exportMethods(get_warnings) +exportMethods(join) +exportMethods(new_qenv) +exportMethods(show) import(shiny) importFrom(R6,R6Class) importFrom(lifecycle,badge) +importFrom(methods,show) importFrom(styler,style_text) diff --git a/NEWS.md b/NEWS.md index cad60127..16dbafdd 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,8 @@ # teal.code 0.2.0.9000 +### Major breaking change +* `chunks` have now been deprecated and will be removed from the package in a future release. The new `qenv` object should be used instead. See the new `qenv` vignette in the package for further details. + # teal.code 0.2.0 ### Miscellaneous diff --git a/R/chunk.R b/R/chunk.R index ff37968e..14b0a4ef 100644 --- a/R/chunk.R +++ b/R/chunk.R @@ -3,9 +3,8 @@ #' Code chunk - including expression and variables #' #' @name chunk_call -#' @description `r lifecycle::badge("stable")` -#' -#' @docType class +#' @description `r lifecycle::badge("deprecated")` +#' Chunks are being deprecated `qenv` objects should be used instead #' #' @keywords data #' @@ -276,7 +275,8 @@ chunk <- chunk_call #' Code Chunk comment #' -#' @description `r lifecycle::badge("stable")` +#' @description `r lifecycle::badge("deprecated")` +#' Chunks are being deprecated `qenv` objects should be used instead #' @name chunk_comment #' @docType class #' @keywords data diff --git a/R/chunks.R b/R/chunks.R index 73836a95..88a8a414 100644 --- a/R/chunks.R +++ b/R/chunks.R @@ -2,12 +2,12 @@ #' Multiple Code chunk handler #' -#' @description `r lifecycle::badge("stable")` +#' @description `r lifecycle::badge("deprecated")` +#' Chunks are being deprecated `qenv` objects should be used instead #' @name chunks #' @docType class #' @keywords data #' -#' @description #' \code{chunks} is a specialized stack for call objects and comments. It is intended to capture and evaluate R code for #' a sequence of analysis steps. #' @@ -681,7 +681,8 @@ get_session_object <- function() { #' Gets chunks object #' -#' @description `r lifecycle::badge("stable")` +#' @description `r lifecycle::badge("deprecated")` +#' Chunks are being deprecated `qenv` objects should be used instead #' #' @param session optional, (\code{ShinySession})\cr #' \code{shiny} session object, if missing then @@ -721,8 +722,8 @@ clone_env <- function(envir_from, envir_to) { #' Pushes a code chunk for global chunks #' -#' @description `r lifecycle::badge("stable")` -#' +#' @description `r lifecycle::badge("deprecated")` +#' Chunks are being deprecated `qenv` objects should be used instead #' @param expression (\code{call}) Expression that contains a function call. #' @param id optional, (\code{character}) ID given for the code chunk #' @param chunks optional, (\code{chunks}) object. @@ -751,8 +752,8 @@ chunks_push <- function(expression, #' Pushes a chunks stack to global chunks #' -#' @description `r lifecycle::badge("stable")` -#' +#' @description `r lifecycle::badge("deprecated")` +#' Chunks are being deprecated `qenv` objects should be used instead #' @inheritParams chunks_push #' @param x (\code{chunks}) stack object #' @param overwrite optional, (\code{logical}) Whether to ignore conflicts @@ -784,8 +785,8 @@ chunks_push_chunks <- function(x, #' Pushes a merged_dataset to chunks #' -#' @description `r lifecycle::badge("stable")` -#' +#' @description `r lifecycle::badge("deprecated")` +#' Chunks are being deprecated `qenv` objects should be used instead #' @inheritParams chunks_push #' @param x (\code{list}) outcome of \code{data_merge_srv} #' @@ -801,8 +802,8 @@ chunks_push_data_merge <- function(x, chunks = get_chunks_object()) { #' Pushes a code comment chunk for global chunks #' -#' @description `r lifecycle::badge("stable")` -#' +#' @description `r lifecycle::badge("deprecated")` +#' Chunks are being deprecated `qenv` objects should be used instead #' @inheritParams chunks_push #' @param comment (code{character}) Comment to be inserted into the Show-R code. #' @@ -827,8 +828,8 @@ chunks_push_comment <- function(comment, #' Adds an empty line to the code chunks #' -#' @description `r lifecycle::badge("stable")` -#' +#' @description `r lifecycle::badge("deprecated")` +#' Chunks are being deprecated `qenv` objects should be used instead #' @inheritParams chunks_push #' #' @return NULL @@ -850,7 +851,8 @@ chunks_push_new_line <- function(chunks = get_chunks_object()) { #' Evaluates all remaining chunks #' -#' @description `r lifecycle::badge("stable")` +#' @description `r lifecycle::badge("deprecated")` +#' Chunks are being deprecated `qenv` objects should be used instead #' #' You can evaluate all remaining chunks of chunks being setup in the shiny session (see [get_chunks_object()]). #' The value of the last chunk being evaluated will be returned. @@ -877,8 +879,8 @@ chunks_eval <- function(chunks = get_chunks_object()) { #' #' Keep code pieces. #' -#' @description `r lifecycle::badge("stable")` -#' +#' @description `r lifecycle::badge("deprecated")` +#' Chunks are being deprecated `qenv` objects should be used instead #' @inheritParams chunks_push #' @param overwrite (\code{logical}) Whether to use values from the current environment #' or just use a new one @@ -895,7 +897,8 @@ chunks_uneval <- function(chunks = get_chunks_object(), overwrite = FALSE, envir #' Evaluates all remaining chunks and validate if error #' -#' @description `r lifecycle::badge("stable")` +#' @description `r lifecycle::badge("deprecated")` +#' Chunks are being deprecated `qenv` objects should be used instead #' #' You can evaluate all remaining chunks of chunks being setup in the shiny session (see [get_chunks_object()]). #' The value of the last chunk being evaluated will be returned. @@ -912,7 +915,8 @@ chunks_safe_eval <- function(chunks = get_chunks_object()) { #' Returns the R-Code from a `chunks` object #' -#' @description `r lifecycle::badge("stable")` +#' @description `r lifecycle::badge("deprecated")` +#' Chunks are being deprecated `qenv` objects should be used instead #' #' This function returns a list of the R-Code that reproduces all currently registered chunks #' inside the chunk stack. @@ -932,7 +936,9 @@ chunks_get_rcode <- function(chunks = get_chunks_object()) { #' Gets warnings from the \code{chunks} object #' -#' @description `r lifecycle::badge("stable")` +#' @description `r lifecycle::badge("deprecated")` +#' Chunks are being deprecated `qenv` objects should be used instead +#' #' This function returns a list of all the warnings encountered during evaluation of the code #' in the \code{chunks} object. #' @@ -948,7 +954,8 @@ chunks_warnings <- function(chunks = get_chunks_object()) { #' Gets messages from the \code{chunks} object #' -#' @description `r lifecycle::badge("stable")` +#' @description `r lifecycle::badge("deprecated")` +#' Chunks are being deprecated `qenv` objects should be used instead #' #' This function returns a list of all the messages encountered during evaluation of the code #' in the \code{chunks} object. @@ -965,7 +972,8 @@ chunks_messages <- function(chunks = get_chunks_object()) { #' Allows using chunks in the global environment of a teal app #' -#' @description `r lifecycle::badge("stable")` +#' @description `r lifecycle::badge("deprecated")` +#' Chunks are being deprecated `qenv` objects should be used instead #' #' @param new_chunks optional, (\code{chunks_stack}) new object to initialize with #' @param session optional, (\code{ShinySession}) \code{shiny} session object. @@ -977,6 +985,12 @@ chunks_messages <- function(chunks = get_chunks_object()) { #' #' @references chunks init_chunks <- function(new_chunks = chunks_new(), session = get_session_object()) { + lifecycle::deprecate_warn( + when = "0.2.1", + what = "init_chunks()", + details = "Chunks are being deprecated qenv objects should be used instead" + ) + session$userData[[session$ns(character(0))]]$chunks <- "A" suppressWarnings(rm(envir = session$userData, list = session$ns(character(0)))) session$userData[[session$ns(character(0))]]$chunks <- new_chunks # nolint @@ -985,7 +999,8 @@ init_chunks <- function(new_chunks = chunks_new(), session = get_session_object( #' Creates and returns a R6 chunks object to be used in a shiny/teal app #' -#' @description `r lifecycle::badge("experimental")` +#' @description `r lifecycle::badge("deprecated")` +#' Chunks are being deprecated `qenv` objects should be used instead #' #' @param envir (`environment` or `NULL`) optional, environment to get objects from to chunks environment #' @@ -997,13 +1012,20 @@ init_chunks <- function(new_chunks = chunks_new(), session = get_session_object( #' @examples #' new_chunks <- chunks_new() chunks_new <- function(envir = new.env()) { + lifecycle::deprecate_warn( + when = "0.2.1", + what = "chunks_new()", + details = "Chunks are being deprecated qenv objects should be used instead" + ) + checkmate::assert_environment(envir) return(chunks$new(envir = envir)) } #' Overwrites chunks object in session #' -#' @description `r lifecycle::badge("stable")` +#' @description `r lifecycle::badge("deprecated")` +#' Chunks are being deprecated `qenv` objects should be used instead #' #' @param x (\code{chunks}) \link{chunks}-object to be used inside the current #' session. @@ -1031,7 +1053,8 @@ overwrite_chunks <- function(x = chunks_new(envir = parent.frame()), session = g #' Resets the current chunk list #' -#' @description `r lifecycle::badge("stable")` +#' @description `r lifecycle::badge("deprecated")` +#' Chunks are being deprecated `qenv` objects should be used instead #' #' Empty the chunk list and the remaining and evaluated list. #' @@ -1053,7 +1076,8 @@ chunks_reset <- function(envir = parent.frame(), #' Get variable from chunk environment #' -#' @description `r lifecycle::badge("stable")` +#' @description `r lifecycle::badge("deprecated")` +#' Chunks are being deprecated `qenv` objects should be used instead #' #' @inheritParams chunks_push #' @param var (\code{character}) variable name @@ -1073,7 +1097,8 @@ chunks_get_var <- function(var, #' Check chunks status (i.e. evaluated and no errors) #' -#' @description `r lifecycle::badge("stable")` +#' @description `r lifecycle::badge("deprecated")` +#' Chunks are being deprecated `qenv` objects should be used instead #' #' @inheritParams chunks_push #' @@ -1089,7 +1114,8 @@ chunks_is_ok <- function(chunks = get_chunks_object()) { #' Get chunks evaluation message #' -#' @description `r lifecycle::badge("stable")` +#' @description `r lifecycle::badge("deprecated")` +#' Chunks are being deprecated `qenv` objects should be used instead #' #' @inheritParams chunks_push #' @@ -1105,7 +1131,8 @@ chunks_get_eval_msg <- function(chunks = get_chunks_object()) { #' Raise shiny validate error if chunks status is not ok #' -#' @description `r lifecycle::badge("stable")` +#' @description `r lifecycle::badge("deprecated")` +#' Chunks are being deprecated `qenv` objects should be used instead #' #' @inheritParams chunks_push #' @param msg optional, (\code{character}) custom error message, if \code{NULL} then default error message is used @@ -1129,7 +1156,8 @@ chunks_validate_is_ok <- function(msg = NULL, #' Raise shiny validate error if variable is not of a certain class #' -#' @description `r lifecycle::badge("stable")` +#' @description `r lifecycle::badge("deprecated")` +#' Chunks are being deprecated `qenv` objects should be used instead #' #' @inheritParams chunks_validate_is_ok #' @param var (\code{character}) variable name @@ -1153,7 +1181,8 @@ chunks_validate_is <- function(var, #' Raise shiny validate error if chunks status is not ok or variable is not of a certain class #' -#' @description `r lifecycle::badge("stable")` +#' @description `r lifecycle::badge("deprecated")` +#' Chunks are being deprecated `qenv` objects should be used instead #' #' @inheritParams chunks_validate_is #' @@ -1181,7 +1210,8 @@ chunks_validate_all <- function(var, #' Executes validate statements on custom expressions that are evaluated inside a chunks object's environment #' -#' @description `r lifecycle::badge("stable")` +#' @description `r lifecycle::badge("deprecated")` +#' Chunks are being deprecated `qenv` objects should be used instead #' #' @inheritParams chunks_validate_is #' @param x (\code{language}) an expression that evaluates to \code{TRUE} or \code{FALSE} inside \code{chunks} @@ -1211,7 +1241,9 @@ chunks_validate_custom <- function(x, #' Deep clones a chunks object -#' @description `r lifecycle::badge("stable")` +#' @description `r lifecycle::badge("deprecated")` +#' Chunks are being deprecated `qenv` objects should be used instead +#' #' @inheritParams chunks_push #' #' @details use this function if you need to copy a `chunks` object as this diff --git a/R/get_eval_details.R b/R/get_eval_details.R index 2024f26c..5e8e2d45 100644 --- a/R/get_eval_details.R +++ b/R/get_eval_details.R @@ -1,7 +1,9 @@ ## Module ---- #' Shows Evaluation Details Modal #' -#' @description `r lifecycle::badge("stable")` +#' @description `r lifecycle::badge("deprecated")` +#' Chunks are being deprecated `qenv` objects should be used instead +#' #' Use the [shiny::showModal()] function to show the errors generated from chunks. #' #' @param chunks (`chunks`)\cr @@ -59,8 +61,8 @@ show_eval_details_modal <- function(chunks) { #' Server part of \code{get_chunks_info} #' -#' @description `r lifecycle::badge("stable")` -#' +#' @description `r lifecycle::badge("deprecated")` +#' Chunks are being deprecated `qenv` objects should be used instead #' @inheritParams shiny::moduleServer #' @inheritParams show_eval_details_modal #' @@ -69,6 +71,12 @@ show_eval_details_modal <- function(chunks) { #' @export #' get_eval_details_srv <- function(id, chunks) { + lifecycle::deprecate_warn( + when = "0.2.1", + what = "get_eval_details_srv()", + details = "Chunks are being deprecated qenv objects should be used instead" + ) + if (!inherits(chunks, "chunks")) { stop("Provided chunks are not of class chunks. Make sure to use init_chunks() in the module's server function.") } @@ -117,7 +125,9 @@ get_eval_details_srv <- function(id, chunks) { #' The `UI` part of get chunks info module #' -#' @description `r lifecycle::badge("stable")` +#' @description `r lifecycle::badge("deprecated")` +#' Chunks are being deprecated `qenv` objects should be used instead +#' #' @param id (`character`)\cr #' id of a shiny module #' diff --git a/R/qenv-class.R b/R/qenv-class.R new file mode 100644 index 00000000..a2e603ab --- /dev/null +++ b/R/qenv-class.R @@ -0,0 +1,38 @@ +#' Reproducible class with environment and code. +#' +#' Reproducible class with environment and code. +#' @name qenv-class +#' @rdname qenv-class +#' @slot code (`expression`) to reproduce the environment +#' @slot env (`environment`) environment which content was generated by the evaluation +#' of the `code` slot. +#' @slot id (`integer`) random identifier of the code element to make sure uniqueness +#' when joining. +#' @slot warnings (`character`) the warnings output when evaluating the code +#' @slot messages (`character`) the messages output when evaluating the code +#' @keywords internal +setClass( + "qenv", + slots = c(env = "environment", code = "expression", id = "integer", warnings = "character", messages = "character"), + prototype = list( + env = new.env(parent = parent.env(.GlobalEnv)), code = expression(), id = integer(0), + warnings = character(0), messages = character(0) + ) +) + +#' It takes a `qenv` class and returns `TRUE` if the input is valid +#' @name qenv-class +#' @keywords internal +setValidity("qenv", function(object) { + if (length(object@code) != length(object@id)) { + "@code and @id slots must have the same length." + } else if (length(object@code) != length(object@warnings)) { + "@code and @warnings slots must have the same length" + } else if (length(object@code) != length(object@messages)) { + "@code and @messages slots must have the same length" + } else if (any(duplicated(object@id))) { + "@id contains duplicated values." + } else { + TRUE + } +}) diff --git a/R/qenv-constructor.R b/R/qenv-constructor.R new file mode 100644 index 00000000..f41fdf76 --- /dev/null +++ b/R/qenv-constructor.R @@ -0,0 +1,66 @@ +#' Initialize `qenv` object +#' +#' Initialize `qenv` object with `code` and `env`. In order to have `qenv` reproducible +#' one needs to initialize with `env` which can be reproduced by the `code`. Alternatively, one +#' can create an empty `qenv` and evaluate the expressions in this object using `eval_code`. +#' @name new_qenv +#' +#' @param code (`character(1)` or `language`) code to evaluate. Accepts and stores comments also. +#' @param env (`environment`) Environment being a result of the `code` evaluation. +#' +#' @examples +#' new_qenv(env = list2env(list(a = 1)), code = quote(a <- 1)) +#' new_qenv(env = list2env(list(a = 1)), code = parse(text = "a <- 1")) +#' new_qenv(env = list2env(list(a = 1)), code = "a <- 1") +#' +#' @export +setGeneric("new_qenv", function(env = new.env(parent = parent.env(.GlobalEnv)), code = expression()) { + standardGeneric("new_qenv") +}) + +#' @rdname new_qenv +#' @export +setMethod( + "new_qenv", + signature = c(env = "environment", code = "expression"), + function(env, code) { + new_env <- rlang::env_clone(env, parent = parent.env(.GlobalEnv)) + lockEnvironment(new_env, bindings = TRUE) + id <- sample.int(.Machine$integer.max, size = length(code)) + methods::new( + "qenv", + env = new_env, code = code, warnings = rep("", length(code)), messages = rep("", length(code)), id = id + ) + } +) + +#' @rdname new_qenv +#' @export +setMethod( + "new_qenv", + signature = c(env = "environment", code = "character"), + function(env, code) { + new_qenv(env, code = parse(text = code, keep.source = FALSE)) + } +) + +#' @rdname new_qenv +#' @export +setMethod( + "new_qenv", + signature = c(env = "environment", code = "language"), + function(env, code) { + code_expr <- as.expression(code) + new_qenv(env = env, code = code_expr) + } +) + +#' @rdname new_qenv +#' @export +setMethod( + "new_qenv", + signature = c(code = "missing", env = "missing"), + function(env, code) { + new_qenv(env = env, code = code) + } +) diff --git a/R/qenv-errors.R b/R/qenv-errors.R new file mode 100644 index 00000000..4af63a56 --- /dev/null +++ b/R/qenv-errors.R @@ -0,0 +1,2 @@ +# needed to handle try-error +setOldClass("qenv.error") diff --git a/R/qenv-eval_code.R b/R/qenv-eval_code.R new file mode 100644 index 00000000..153faf5b --- /dev/null +++ b/R/qenv-eval_code.R @@ -0,0 +1,100 @@ +#' Evaluate the code in the `qenv` environment +#' +#' Given code is evaluated in the `qenv` environment and appended to the `code` slot. This means +#' that state of the environment is always a result of the stored code (if `qenv` was initialized) +#' with reproducible code. +#' +#' @name eval_code +#' +#' @param object (`qenv`) +#' @param code (`character` or `language`) code to evaluate. Also accepts and stores comments +#' +#' @examples +#' q1 <- new_qenv(env = list2env(list(a = 1)), code = quote(a <- 1)) +#' q2 <- eval_code(q1, quote(library(checkmate))) +#' q3 <- eval_code(q2, quote(assert_number(a))) +#' +#' @export +setGeneric("eval_code", function(object, code) { + standardGeneric("eval_code") +}) + +#' @rdname eval_code +#' @export +setMethod("eval_code", signature = c("qenv", "expression"), function(object, code) { + id <- sample.int(.Machine$integer.max, size = length(code)) + + object@id <- c(object@id, id) + object@env <- rlang::env_clone(object@env, parent = parent.env(.GlobalEnv)) + object@code <- c(object@code, code) + + current_warnings <- "" + current_messages <- "" + + for (code_line in code) { + x <- withCallingHandlers( + tryCatch( + { + eval(code_line, envir = object@env) + NULL + }, + error = function(e) { + errorCondition( + message = sprintf( + "%s \n when evaluating qenv code:\n %s", + .ansi_strip(conditionMessage(e)), + paste(code, collapse = "\n ") + ), + class = c("qenv.error", "try-error", "simpleError"), + trace = object@code + ) + } + ), + warning = function(w) { + current_warnings <<- paste0(current_warnings, .ansi_strip(conditionMessage(w))) + invokeRestart("muffleWarning") + }, + message = function(m) { + current_messages <<- paste0(current_messages, .ansi_strip(conditionMessage(m))) + invokeRestart("muffleMessage") + } + ) + if (!is.null(x)) { + return(x) + } + + object@warnings <- c(object@warnings, current_warnings) + object@messages <- c(object@messages, current_messages) + } + lockEnvironment(object@env, bindings = TRUE) + object +}) + +#' @rdname eval_code +#' @export +setMethod("eval_code", signature = c("qenv", "language"), function(object, code) { + code_char <- as.expression(code) + eval_code(object, code_char) +}) + +#' @rdname eval_code +#' @export +setMethod("eval_code", signature = c("qenv", "character"), function(object, code) { + eval_code(object, code = parse(text = code, keep.source = FALSE)) +}) + +#' @rdname eval_code +#' @export +setMethod("eval_code", signature = "qenv.error", function(object, code) { + object +}) + +# if cli is installed rlang adds terminal printing characters +# which need to be removed +.ansi_strip <- function(chr) { + if (requireNamespace("cli", quietly = TRUE)) { + cli::ansi_strip(chr) + } else { + chr + } +} diff --git a/R/qenv-get_code.R b/R/qenv-get_code.R new file mode 100644 index 00000000..af342eb9 --- /dev/null +++ b/R/qenv-get_code.R @@ -0,0 +1,46 @@ +#' Get code from `qenv` +#' +#' @name get_code +#' @param object (`qenv`) +#' @param deparse (`logical(1)`) if the returned code should be converted to character. +#' @return named `character` with the reproducible code. +#' @examples +#' q1 <- new_qenv(env = list2env(list(a = 1)), code = quote(a <- 1)) +#' q2 <- eval_code(q1, code = quote(b <- a)) +#' q3 <- eval_code(q2, code = quote(d <- 2)) +#' get_code(q3) +#' get_code(q3, deparse = FALSE) +#' @export +setGeneric("get_code", function(object, deparse = TRUE) { + # this line forces evaluation of object before passing to the generic + # needed for error handling to work properly + object + + standardGeneric("get_code") +}) + +#' @rdname get_code +#' @export +setMethod("get_code", signature = "qenv", function(object, deparse = TRUE) { + checkmate::assert_flag(deparse) + if (deparse) { + as.character(object@code) + } else { + object@code + } +}) + +#' @rdname get_code +#' @export +setMethod("get_code", signature = "qenv.error", function(object) { + stop( + errorCondition( + sprintf( + "%s\n\ntrace: \n %s\n", + conditionMessage(object), + paste(object$trace, collapse = "\n ") + ), + class = c("validation", "try-error", "simpleError") + ) + ) +}) diff --git a/R/qenv-get_var.R b/R/qenv-get_var.R new file mode 100644 index 00000000..df212bd2 --- /dev/null +++ b/R/qenv-get_var.R @@ -0,0 +1,58 @@ +#' Get object from the `qenv` environment +#' +#' Get object from the `qenv` environment. +#' @param object (`qenv`) +#' @param var (`character(1)`) name of the variable to pull from the environment. +#' @name get_var +#' @examples +#' q1 <- new_qenv(env = list2env(list(a = 1)), code = quote(a <- 1)) +#' q2 <- eval_code(q1, code = "b <- a") +#' get_var(q2, "b") +#' q2[["b"]] +#' +#' @export +setGeneric("get_var", function(object, var) { + standardGeneric("get_var") +}) + + +#' @rdname get_var +#' @export +setMethod("get_var", signature = c("qenv", "character"), function(object, var) { + tryCatch( + get(var, envir = object@env), + error = function(e) { + message(conditionMessage(e)) + NULL + } + ) +}) + +#' @rdname get_var +#' @export +setMethod("get_var", signature = "qenv.error", function(object, var) { + stop(errorCondition( + list(message = conditionMessage(object)), + class = c("validation", "try-error", "simpleError") + )) +}) + + +#' @param x (`qenv`) +#' @param i (`character`) name of the binding in environment (name of the variable) +#' @param j not used +#' @param ... not used +#' @rdname get_var +#' @export +setMethod("[[", signature = c("qenv", "ANY", "missing"), function(x, i, j, ...) { + get_var(x, i) +}) + +#' @rdname get_var +#' @export +`[[.qenv.error` <- function(x, i, j, ...) { + stop(errorCondition( + list(message = conditionMessage(x)), + class = c("validation", "try-error", "simpleError") + )) +} diff --git a/R/qenv-get_warnings.R b/R/qenv-get_warnings.R new file mode 100644 index 00000000..492ccb6e --- /dev/null +++ b/R/qenv-get_warnings.R @@ -0,0 +1,54 @@ +#' Get the warnings of `qenv` object +#' +#' @param object (`qenv`) +#' +#' @return `character` containing warning information or `NULL` if no warnings +#' @export +#' +#' @examples +#' data_q <- new_qenv() +#' data_q <- eval_code(new_qenv(), "iris_data <- iris") +#' warning_qenv <- eval_code( +#' data_q, +#' bquote(p <- hist(iris_data[, .("Sepal.Length")], ff = "")) +#' ) +#' cat(get_warnings(warning_qenv)) +#' @export +setGeneric("get_warnings", function(object) { + # this line forces evaluation of object before passing to the generic + # needed for error handling to work properly + object + + standardGeneric("get_warnings") +}) + +#' @rdname get_warnings +#' @export +setMethod("get_warnings", signature = c("qenv.error"), function(object) { + NULL +}) + +#' @rdname get_warnings +#' @export +setMethod("get_warnings", signature = c("qenv"), function(object) { + if (all(object@warnings == "")) { + return(NULL) + } + + warning_output <- "Warnings:" + for (warn_idx in seq_along(object@warnings)) { + warn <- object@warnings[warn_idx] + if (warn != "") { + warning_output <- paste( + warning_output, "\n>", warn, "\nWhen running code:\n", paste(object@code[warn_idx], collapse = "\n") + ) + } + } + paste0(warning_output, "\n\nTrace:\n", paste(get_code(object), collapse = "\n")) +}) + +#' @rdname get_warnings +#' @export +setMethod("get_warnings", "NULL", function(object) { + NULL +}) diff --git a/R/qenv-join.R b/R/qenv-join.R new file mode 100644 index 00000000..6c513aa4 --- /dev/null +++ b/R/qenv-join.R @@ -0,0 +1,116 @@ +#' Join two `qenv` objects +#' +#' Combine two `qenv` objects by merging their environments and the code. +#' Not all `qenv` objects can be combined: +#' - if their environments contains objects of the same name but not identical +#' - if `y` has unique code element placed before common element. This means that `y` +#' in the environment of the `y` was evaluated some extra code before which can influence +#' reproducibility +#' - more cases to be done +#' @param x (`qenv`) +#' @param y (`qenv`) +#' @examples +#' q1 <- new_qenv( +#' code = c(iris1 = "iris1 <- iris", mtcars1 = "mtcars1 <- mtcars"), +#' env = list2env(list( +#' iris1 = iris, +#' mtcars1 = mtcars +#' )) +#' ) +#' q2 <- q1 +#' q1 <- eval_code(q1, "iris2 <- iris") +#' q2 <- eval_code(q2, "mtcars2 <- mtcars") +#' qq <- join(q1, q2) +#' get_code(qq) +#' @export +setGeneric("join", function(x, y) { + standardGeneric("join") +}) + +#' @rdname join +#' @export +setMethod("join", signature = c("qenv", "qenv"), function(x, y) { + join_validation <- .check_joinable(x, y) + + # join expressions + if (!isTRUE(join_validation)) { + stop(join_validation) + } + + id_unique <- !y@id %in% x@id + x@id <- c(x@id, y@id[id_unique]) + x@code <- c(x@code, y@code[id_unique]) + x@warnings <- c(x@warnings, y@warnings[id_unique]) + x@messages <- c(x@messages, y@messages[id_unique]) + + # insert (and overwrite) objects from y to x + x@env <- rlang::env_clone(x@env, parent = parent.env(.GlobalEnv)) + rlang::env_coalesce(env = x@env, from = y@env) + x +}) + +#' @rdname join +#' @export +setMethod("join", signature = "qenv.error", function(x, y) { + x +}) + +#' @rdname join +#' @export +setMethod("join", signature = c("qenv", "qenv.error"), function(x, y) { + y +}) + +#' If two `qenv` can be joined +#' +#' Checks if two `qenv` objects can be combined. +#' They can't be combined if (and): +#' - both share the same code (identified by `id`) +#' - indices of the shared code are not consecutive or don't start from 1 +#' @param x (`qenv`) +#' @param y (`qenv`) +#' @return `TRUE` if able to join or `character` used to print error message. +#' @keywords internal +.check_joinable <- function(x, y) { + checkmate::assert_class(x, "qenv") + checkmate::assert_class(y, "qenv") + + common_names <- intersect(rlang::env_names(x@env), rlang::env_names(y@env)) + is_overwritten <- vapply(common_names, function(el) { + !identical(get(el, x@env), get(el, y@env)) + }, logical(1)) + if (any(is_overwritten)) { + return( + paste( + "Not possible to join qenv objects if anything in their environment has been modified.\n", + "Following object(s) have been modified:\n - ", + paste(common_names[is_overwritten], collapse = "\n - ") + ) + ) + } + + shared_ids <- intersect(x@id, y@id) + if (length(shared_ids) == 0) { + return(TRUE) + } + + shared_in_x <- match(shared_ids, x@id) + shared_in_y <- match(shared_ids, y@id) + + # indices of shared ids should be 1:n in both slots + if (identical(shared_in_x, shared_in_y) && identical(shared_in_x, seq_along(shared_ids))) { + TRUE + } else if (!identical(shared_in_x, shared_in_y)) { + paste( + "The common shared code of the qenvs does not occur in the same position in both qenv objects", + "so they cannot be joined together as it's impossible to determine the evaluation's order.", + collapse = "" + ) + } else { + paste( + "There is code in the qenv objects before their common shared code", + "which means these objects cannot be joined.", + collapse = "" + ) + } +} diff --git a/R/qenv-show.R b/R/qenv-show.R new file mode 100644 index 00000000..60707699 --- /dev/null +++ b/R/qenv-show.R @@ -0,0 +1,18 @@ +#' Show the `qenv` object +#' +#' Prints the `qenv` object +#' @param object (`qenv`) +#' @return nothing +#' @importFrom methods show +#' @examples +#' q1 <- new_qenv(code = "print('a')", env = new.env()) +#' q1 +#' @export +setMethod("show", "qenv", function(object) { + obs <- names(as.list(object@env)) + if (length(obs) > 0) { + cat(sprintf("A qenv object containing: %s\n", paste(obs, collapse = ", "))) + } else { + cat("A qenv object containing no objects.\n") + } +}) diff --git a/README.md b/README.md index 04070354..542ae6de 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,9 @@ `teal.code` is an R library providing tools to store code and an execution environment associated with it. The features include: -* storing character literals as code, -* storing an execution environment, -* swapping the execution environment of the stored code, -* evaluating only parts of the stored code, -* means to execute code with a no-throw guarantee (errors demoted to warnings and messages stored for retrieval). +* an object `qenv` for storing code and an execution environment which integrates well with `shiny reactives` for use in `shiny` applications whose outputs require reproducibility (i.e. the code used to generate them) +* ability to chain and join `qenv` objects together to provide fine-grained control over executed code +* automatic error and warning handling for executed code `teal.code` also ships a [`shiny`](https://shiny.rstudio.com/) module that helps inspect the stored code as well as messages, warnings and error messages resulting from evaluation via `shiny` web application. diff --git a/_pkgdown.yml b/_pkgdown.yml index 7bb09253..0d6336c6 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -10,11 +10,21 @@ navbar: href: https://github.com/insightsengineering/teal.code reference: - - title: "Reproducible Modules" + - title: "Reproducible qenv objects" + desc: "methods to get and modify values of qenv objects" + contents: + - eval_code + - get_code + - get_var + - get_warnings + - join + - new_qenv + - show,qenv-method + - title: "Reproducible Modules (deprecated)" desc: Functions to create a code chunk stack contents: - contains("chunk") - - title: "Debugging chunks evaluation" + - title: "Debugging chunks evaluation (deprecated)" desc: "shiny modules to examine chunks evaluation results" contents: - get_eval_details_srv diff --git a/man/chunk_call.Rd b/man/chunk_call.Rd index cf5aed55..41e908a6 100644 --- a/man/chunk_call.Rd +++ b/man/chunk_call.Rd @@ -1,6 +1,5 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/chunk.R -\docType{class} \name{chunk_call} \alias{chunk_call} \alias{chunk_call$new} @@ -10,7 +9,8 @@ An \code{\link{R6Class}} generator object } \description{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} +Chunks are being deprecated \code{qenv} objects should be used instead } \section{Methods}{ diff --git a/man/chunk_comment.Rd b/man/chunk_comment.Rd index 54bc6f12..beb85df8 100644 --- a/man/chunk_comment.Rd +++ b/man/chunk_comment.Rd @@ -9,7 +9,8 @@ An \code{\link{R6Class}} generator object } \description{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} +Chunks are being deprecated \code{qenv} objects should be used instead } \section{Fields}{ diff --git a/man/chunks.Rd b/man/chunks.Rd index 1681a7d0..5e7cc2b4 100644 --- a/man/chunks.Rd +++ b/man/chunks.Rd @@ -8,10 +8,8 @@ An \code{\link{R6Class}} generator object } \description{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} - -\code{chunks} is a specialized stack for call objects and comments. It is intended to capture and evaluate R code for -a sequence of analysis steps. +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} +Chunks are being deprecated \code{qenv} objects should be used instead } \section{Methods}{ @@ -165,4 +163,25 @@ x$is_ok() # now all chunks were evaluated and no errors occured cat(paste(x$get_rcode(), collapse = "\n")) } +\keyword{It} +\keyword{R} +\keyword{\code{chunks}} +\keyword{a} +\keyword{analysis} +\keyword{and} +\keyword{call} +\keyword{capture} +\keyword{code} +\keyword{comments.} \keyword{data} +\keyword{evaluate} +\keyword{for} +\keyword{intended} +\keyword{is} +\keyword{objects} +\keyword{of} +\keyword{sequence} +\keyword{specialized} +\keyword{stack} +\keyword{steps.} +\keyword{to} diff --git a/man/chunks_deep_clone.Rd b/man/chunks_deep_clone.Rd index d842e13d..9ec04a4e 100644 --- a/man/chunks_deep_clone.Rd +++ b/man/chunks_deep_clone.Rd @@ -14,7 +14,8 @@ If not provided then automatic \code{chunks} object detection is run via \link{g a deep copy of \code{chunks} } \description{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} +Chunks are being deprecated \code{qenv} objects should be used instead } \details{ use this function if you need to copy a \code{chunks} object as this diff --git a/man/chunks_eval.Rd b/man/chunks_eval.Rd index 05859375..2df5c5ed 100644 --- a/man/chunks_eval.Rd +++ b/man/chunks_eval.Rd @@ -11,7 +11,8 @@ chunks_eval(chunks = get_chunks_object()) If not provided then automatic \code{chunks} object detection is run via \link{get_chunks_object}} } \description{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} +Chunks are being deprecated \code{qenv} objects should be used instead You can evaluate all remaining chunks of chunks being setup in the shiny session (see \code{\link[=get_chunks_object]{get_chunks_object()}}). The value of the last chunk being evaluated will be returned. diff --git a/man/chunks_get_eval_msg.Rd b/man/chunks_get_eval_msg.Rd index 3dc7e271..53a41de2 100644 --- a/man/chunks_get_eval_msg.Rd +++ b/man/chunks_get_eval_msg.Rd @@ -14,5 +14,6 @@ If not provided then automatic \code{chunks} object detection is run via \link{g (\code{character}) chunks evaluation message } \description{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} +Chunks are being deprecated \code{qenv} objects should be used instead } diff --git a/man/chunks_get_rcode.Rd b/man/chunks_get_rcode.Rd index 9004f5b8..ef87ca14 100644 --- a/man/chunks_get_rcode.Rd +++ b/man/chunks_get_rcode.Rd @@ -15,7 +15,8 @@ The R code stored inside the chunks that can be used to reproduce the value of t of all code chunks. } \description{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} +Chunks are being deprecated \code{qenv} objects should be used instead This function returns a list of the R-Code that reproduces all currently registered chunks inside the chunk stack. diff --git a/man/chunks_get_var.Rd b/man/chunks_get_var.Rd index 755d3d68..a79b8ce0 100644 --- a/man/chunks_get_var.Rd +++ b/man/chunks_get_var.Rd @@ -16,5 +16,6 @@ If not provided then automatic \code{chunks} object detection is run via \link{g variable from chunks environment } \description{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} +Chunks are being deprecated \code{qenv} objects should be used instead } diff --git a/man/chunks_is_ok.Rd b/man/chunks_is_ok.Rd index d0d90058..a3494530 100644 --- a/man/chunks_is_ok.Rd +++ b/man/chunks_is_ok.Rd @@ -14,5 +14,6 @@ If not provided then automatic \code{chunks} object detection is run via \link{g (\code{logical}) flag whether everything is good } \description{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} +Chunks are being deprecated \code{qenv} objects should be used instead } diff --git a/man/chunks_messages.Rd b/man/chunks_messages.Rd index e7f68213..7230d479 100644 --- a/man/chunks_messages.Rd +++ b/man/chunks_messages.Rd @@ -11,7 +11,8 @@ chunks_messages(chunks = get_chunks_object()) If not provided then automatic \code{chunks} object detection is run via \link{get_chunks_object}} } \description{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} +Chunks are being deprecated \code{qenv} objects should be used instead This function returns a list of all the messages encountered during evaluation of the code in the \code{chunks} object. diff --git a/man/chunks_new.Rd b/man/chunks_new.Rd index 48b84831..dcbe453d 100644 --- a/man/chunks_new.Rd +++ b/man/chunks_new.Rd @@ -13,7 +13,8 @@ chunks_new(envir = new.env()) R6 chunks object } \description{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} +Chunks are being deprecated \code{qenv} objects should be used instead } \examples{ new_chunks <- chunks_new() diff --git a/man/chunks_push.Rd b/man/chunks_push.Rd index 63a103ac..59e9333e 100644 --- a/man/chunks_push.Rd +++ b/man/chunks_push.Rd @@ -18,7 +18,8 @@ If not provided then automatic \code{chunks} object detection is run via \link{g Nothing, just add the chunk to the \code{chunks} argument } \description{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} +Chunks are being deprecated \code{qenv} objects should be used instead } \examples{ all_chunks <- chunks_new() diff --git a/man/chunks_push_chunks.Rd b/man/chunks_push_chunks.Rd index 113943fb..4c4b86b2 100644 --- a/man/chunks_push_chunks.Rd +++ b/man/chunks_push_chunks.Rd @@ -19,7 +19,8 @@ If not provided then automatic \code{chunks} object detection is run via \link{g Nothing, just add the chunk to the \code{chunks} argument } \description{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} +Chunks are being deprecated \code{qenv} objects should be used instead } \examples{ chunks_object <- chunks_new() diff --git a/man/chunks_push_comment.Rd b/man/chunks_push_comment.Rd index f4f4b627..3ee3a21c 100644 --- a/man/chunks_push_comment.Rd +++ b/man/chunks_push_comment.Rd @@ -13,7 +13,8 @@ chunks_push_comment(comment, chunks = get_chunks_object()) If not provided then automatic \code{chunks} object detection is run via \link{get_chunks_object}} } \description{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} +Chunks are being deprecated \code{qenv} objects should be used instead } \examples{ all_chunks <- chunks_new() diff --git a/man/chunks_push_data_merge.Rd b/man/chunks_push_data_merge.Rd index 4570ce23..e4465e5f 100644 --- a/man/chunks_push_data_merge.Rd +++ b/man/chunks_push_data_merge.Rd @@ -13,5 +13,6 @@ chunks_push_data_merge(x, chunks = get_chunks_object()) If not provided then automatic \code{chunks} object detection is run via \link{get_chunks_object}} } \description{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} +Chunks are being deprecated \code{qenv} objects should be used instead } diff --git a/man/chunks_push_new_line.Rd b/man/chunks_push_new_line.Rd index bccfa11f..46aa1d38 100644 --- a/man/chunks_push_new_line.Rd +++ b/man/chunks_push_new_line.Rd @@ -11,7 +11,8 @@ chunks_push_new_line(chunks = get_chunks_object()) If not provided then automatic \code{chunks} object detection is run via \link{get_chunks_object}} } \description{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} +Chunks are being deprecated \code{qenv} objects should be used instead } \examples{ all_chunks <- chunks_new() diff --git a/man/chunks_reset.Rd b/man/chunks_reset.Rd index 6c03bbeb..dec9d084 100644 --- a/man/chunks_reset.Rd +++ b/man/chunks_reset.Rd @@ -16,7 +16,8 @@ If not provided then automatic \code{chunks} object detection is run via \link{g nothing, it modifies shiny session object } \description{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} +Chunks are being deprecated \code{qenv} objects should be used instead Empty the chunk list and the remaining and evaluated list. } diff --git a/man/chunks_safe_eval.Rd b/man/chunks_safe_eval.Rd index f5fb3951..a839ccf3 100644 --- a/man/chunks_safe_eval.Rd +++ b/man/chunks_safe_eval.Rd @@ -11,7 +11,8 @@ chunks_safe_eval(chunks = get_chunks_object()) If not provided then automatic \code{chunks} object detection is run via \link{get_chunks_object}} } \description{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} +Chunks are being deprecated \code{qenv} objects should be used instead You can evaluate all remaining chunks of chunks being setup in the shiny session (see \code{\link[=get_chunks_object]{get_chunks_object()}}). The value of the last chunk being evaluated will be returned. diff --git a/man/chunks_uneval.Rd b/man/chunks_uneval.Rd index ea71df4f..80b92ed5 100644 --- a/man/chunks_uneval.Rd +++ b/man/chunks_uneval.Rd @@ -21,7 +21,8 @@ or just use a new one} \code{overwrite} is \code{TRUE}} } \description{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} +Chunks are being deprecated \code{qenv} objects should be used instead } \details{ Keep code pieces. diff --git a/man/chunks_validate_all.Rd b/man/chunks_validate_all.Rd index f11dd7d4..74cbd7d8 100644 --- a/man/chunks_validate_all.Rd +++ b/man/chunks_validate_all.Rd @@ -20,7 +20,8 @@ If not provided then automatic \code{chunks} object detection is run via \link{g shiny validation error if conditions are met } \description{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} +Chunks are being deprecated \code{qenv} objects should be used instead } \references{ chunks_validate_is_ok chunks_validate_is diff --git a/man/chunks_validate_custom.Rd b/man/chunks_validate_custom.Rd index 9c0cc952..32071fcb 100644 --- a/man/chunks_validate_custom.Rd +++ b/man/chunks_validate_custom.Rd @@ -19,5 +19,6 @@ If not provided then automatic \code{chunks} object detection is run via \link{g shiny validation error if x evaluates to FALSE in chunks environment } \description{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} +Chunks are being deprecated \code{qenv} objects should be used instead } diff --git a/man/chunks_validate_is.Rd b/man/chunks_validate_is.Rd index bf1a83e7..1a4047ab 100644 --- a/man/chunks_validate_is.Rd +++ b/man/chunks_validate_is.Rd @@ -20,5 +20,6 @@ If not provided then automatic \code{chunks} object detection is run via \link{g shiny validation error if variable is not of certain class } \description{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} +Chunks are being deprecated \code{qenv} objects should be used instead } diff --git a/man/chunks_validate_is_ok.Rd b/man/chunks_validate_is_ok.Rd index c391f997..d5d2de52 100644 --- a/man/chunks_validate_is_ok.Rd +++ b/man/chunks_validate_is_ok.Rd @@ -16,7 +16,8 @@ If not provided then automatic \code{chunks} object detection is run via \link{g shiny validation error if chunks are not ok } \description{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} +Chunks are being deprecated \code{qenv} objects should be used instead } \references{ chunks_is_ok diff --git a/man/chunks_warnings.Rd b/man/chunks_warnings.Rd index 7a8c1c12..b468db8e 100644 --- a/man/chunks_warnings.Rd +++ b/man/chunks_warnings.Rd @@ -11,7 +11,9 @@ chunks_warnings(chunks = get_chunks_object()) If not provided then automatic \code{chunks} object detection is run via \link{get_chunks_object}} } \description{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} +Chunks are being deprecated \code{qenv} objects should be used instead + This function returns a list of all the warnings encountered during evaluation of the code in the \code{chunks} object. } diff --git a/man/dot-check_joinable.Rd b/man/dot-check_joinable.Rd new file mode 100644 index 00000000..2861aca8 --- /dev/null +++ b/man/dot-check_joinable.Rd @@ -0,0 +1,25 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/qenv-join.R +\name{.check_joinable} +\alias{.check_joinable} +\title{If two \code{qenv} can be joined} +\usage{ +.check_joinable(x, y) +} +\arguments{ +\item{x}{(\code{qenv})} + +\item{y}{(\code{qenv})} +} +\value{ +\code{TRUE} if able to join or \code{character} used to print error message. +} +\description{ +Checks if two \code{qenv} objects can be combined. +They can't be combined if (and): +\itemize{ +\item both share the same code (identified by \code{id}) +\item indices of the shared code are not consecutive or don't start from 1 +} +} +\keyword{internal} diff --git a/man/eval_code.Rd b/man/eval_code.Rd new file mode 100644 index 00000000..eacb3bb9 --- /dev/null +++ b/man/eval_code.Rd @@ -0,0 +1,36 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/qenv-eval_code.R +\name{eval_code} +\alias{eval_code} +\alias{eval_code,qenv,expression-method} +\alias{eval_code,qenv,language-method} +\alias{eval_code,qenv,character-method} +\alias{eval_code,qenv.error,ANY-method} +\title{Evaluate the code in the \code{qenv} environment} +\usage{ +eval_code(object, code) + +\S4method{eval_code}{qenv,expression}(object, code) + +\S4method{eval_code}{qenv,language}(object, code) + +\S4method{eval_code}{qenv,character}(object, code) + +\S4method{eval_code}{qenv.error,ANY}(object, code) +} +\arguments{ +\item{object}{(\code{qenv})} + +\item{code}{(\code{character} or \code{language}) code to evaluate. Also accepts and stores comments} +} +\description{ +Given code is evaluated in the \code{qenv} environment and appended to the \code{code} slot. This means +that state of the environment is always a result of the stored code (if \code{qenv} was initialized) +with reproducible code. +} +\examples{ +q1 <- new_qenv(env = list2env(list(a = 1)), code = quote(a <- 1)) +q2 <- eval_code(q1, quote(library(checkmate))) +q3 <- eval_code(q2, quote(assert_number(a))) + +} diff --git a/man/get_chunks_object.Rd b/man/get_chunks_object.Rd index b35cb7d4..36dd1abb 100644 --- a/man/get_chunks_object.Rd +++ b/man/get_chunks_object.Rd @@ -18,5 +18,6 @@ from which shiny parent environment chunks object should be taken.} \code{chunks} object } \description{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} +Chunks are being deprecated \code{qenv} objects should be used instead } diff --git a/man/get_code.Rd b/man/get_code.Rd new file mode 100644 index 00000000..ed600837 --- /dev/null +++ b/man/get_code.Rd @@ -0,0 +1,32 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/qenv-get_code.R +\name{get_code} +\alias{get_code} +\alias{get_code,qenv-method} +\alias{get_code,qenv.error-method} +\title{Get code from \code{qenv}} +\usage{ +get_code(object, deparse = TRUE) + +\S4method{get_code}{qenv}(object, deparse = TRUE) + +\S4method{get_code}{qenv.error}(object) +} +\arguments{ +\item{object}{(\code{qenv})} + +\item{deparse}{(\code{logical(1)}) if the returned code should be converted to character.} +} +\value{ +named \code{character} with the reproducible code. +} +\description{ +Get code from \code{qenv} +} +\examples{ +q1 <- new_qenv(env = list2env(list(a = 1)), code = quote(a <- 1)) +q2 <- eval_code(q1, code = quote(b <- a)) +q3 <- eval_code(q2, code = quote(d <- 2)) +get_code(q3) +get_code(q3, deparse = FALSE) +} diff --git a/man/get_eval_details_srv.Rd b/man/get_eval_details_srv.Rd index 4d910d45..e3fbfa19 100644 --- a/man/get_eval_details_srv.Rd +++ b/man/get_eval_details_srv.Rd @@ -17,5 +17,6 @@ object, from which the messages, warnings and errors are gathered} an observer for \code{evaluation_details actionButton} } \description{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} +Chunks are being deprecated \code{qenv} objects should be used instead } diff --git a/man/get_eval_details_ui.Rd b/man/get_eval_details_ui.Rd index 1c37e2bd..a2f53436 100644 --- a/man/get_eval_details_ui.Rd +++ b/man/get_eval_details_ui.Rd @@ -14,5 +14,6 @@ id of a shiny module} (\code{shiny.tag}) } \description{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} +Chunks are being deprecated \code{qenv} objects should be used instead } diff --git a/man/get_var.Rd b/man/get_var.Rd new file mode 100644 index 00000000..3bc518c7 --- /dev/null +++ b/man/get_var.Rd @@ -0,0 +1,43 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/qenv-get_var.R +\name{get_var} +\alias{get_var} +\alias{get_var,qenv,character-method} +\alias{get_var,qenv.error,ANY-method} +\alias{[[,qenv,ANY,missing-method} +\alias{[[.qenv.error} +\title{Get object from the \code{qenv} environment} +\usage{ +get_var(object, var) + +\S4method{get_var}{qenv,character}(object, var) + +\S4method{get_var}{qenv.error,ANY}(object, var) + +\S4method{[[}{qenv,ANY,missing}(x, i, j, ...) + +\method{[[}{qenv.error}(x, i, j, ...) +} +\arguments{ +\item{object}{(\code{qenv})} + +\item{var}{(\code{character(1)}) name of the variable to pull from the environment.} + +\item{x}{(\code{qenv})} + +\item{i}{(\code{character}) name of the binding in environment (name of the variable)} + +\item{j}{not used} + +\item{...}{not used} +} +\description{ +Get object from the \code{qenv} environment. +} +\examples{ +q1 <- new_qenv(env = list2env(list(a = 1)), code = quote(a <- 1)) +q2 <- eval_code(q1, code = "b <- a") +get_var(q2, "b") +q2[["b"]] + +} diff --git a/man/get_warnings.Rd b/man/get_warnings.Rd new file mode 100644 index 00000000..b2b6f99a --- /dev/null +++ b/man/get_warnings.Rd @@ -0,0 +1,35 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/qenv-get_warnings.R +\name{get_warnings} +\alias{get_warnings} +\alias{get_warnings,qenv.error-method} +\alias{get_warnings,qenv-method} +\alias{get_warnings,NULL-method} +\title{Get the warnings of \code{qenv} object} +\usage{ +get_warnings(object) + +\S4method{get_warnings}{qenv.error}(object) + +\S4method{get_warnings}{qenv}(object) + +\S4method{get_warnings}{`NULL`}(object) +} +\arguments{ +\item{object}{(\code{qenv})} +} +\value{ +\code{character} containing warning information or \code{NULL} if no warnings +} +\description{ +Get the warnings of \code{qenv} object +} +\examples{ +data_q <- new_qenv() +data_q <- eval_code(new_qenv(), "iris_data <- iris") +warning_qenv <- eval_code( + data_q, + bquote(p <- hist(iris_data[, .("Sepal.Length")], ff = "")) +) +cat(get_warnings(warning_qenv)) +} diff --git a/man/init_chunks.Rd b/man/init_chunks.Rd index 49671d03..b3ecf393 100644 --- a/man/init_chunks.Rd +++ b/man/init_chunks.Rd @@ -15,7 +15,8 @@ init_chunks(new_chunks = chunks_new(), session = get_session_object()) nothing, it modifies shiny session object } \description{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} +Chunks are being deprecated \code{qenv} objects should be used instead } \references{ chunks diff --git a/man/join.Rd b/man/join.Rd new file mode 100644 index 00000000..822cf819 --- /dev/null +++ b/man/join.Rd @@ -0,0 +1,47 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/qenv-join.R +\name{join} +\alias{join} +\alias{join,qenv,qenv-method} +\alias{join,qenv.error,ANY-method} +\alias{join,qenv,qenv.error-method} +\title{Join two \code{qenv} objects} +\usage{ +join(x, y) + +\S4method{join}{qenv,qenv}(x, y) + +\S4method{join}{qenv.error,ANY}(x, y) + +\S4method{join}{qenv,qenv.error}(x, y) +} +\arguments{ +\item{x}{(\code{qenv})} + +\item{y}{(\code{qenv})} +} +\description{ +Combine two \code{qenv} objects by merging their environments and the code. +Not all \code{qenv} objects can be combined: +\itemize{ +\item if their environments contains objects of the same name but not identical +\item if \code{y} has unique code element placed before common element. This means that \code{y} +in the environment of the \code{y} was evaluated some extra code before which can influence +reproducibility +\item more cases to be done +} +} +\examples{ +q1 <- new_qenv( + code = c(iris1 = "iris1 <- iris", mtcars1 = "mtcars1 <- mtcars"), + env = list2env(list( + iris1 = iris, + mtcars1 = mtcars + )) +) +q2 <- q1 +q1 <- eval_code(q1, "iris2 <- iris") +q2 <- eval_code(q2, "mtcars2 <- mtcars") +qq <- join(q1, q2) +get_code(qq) +} diff --git a/man/new_qenv.Rd b/man/new_qenv.Rd new file mode 100644 index 00000000..ef0ace7d --- /dev/null +++ b/man/new_qenv.Rd @@ -0,0 +1,36 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/qenv-constructor.R +\name{new_qenv} +\alias{new_qenv} +\alias{new_qenv,environment,expression-method} +\alias{new_qenv,environment,character-method} +\alias{new_qenv,environment,language-method} +\alias{new_qenv,missing,missing-method} +\title{Initialize \code{qenv} object} +\usage{ +new_qenv(env = new.env(parent = parent.env(.GlobalEnv)), code = expression()) + +\S4method{new_qenv}{environment,expression}(env = new.env(parent = parent.env(.GlobalEnv)), code = expression()) + +\S4method{new_qenv}{environment,character}(env = new.env(parent = parent.env(.GlobalEnv)), code = expression()) + +\S4method{new_qenv}{environment,language}(env = new.env(parent = parent.env(.GlobalEnv)), code = expression()) + +\S4method{new_qenv}{missing,missing}(env = new.env(parent = parent.env(.GlobalEnv)), code = expression()) +} +\arguments{ +\item{env}{(\code{environment}) Environment being a result of the \code{code} evaluation.} + +\item{code}{(\code{character(1)} or \code{language}) code to evaluate. Accepts and stores comments also.} +} +\description{ +Initialize \code{qenv} object with \code{code} and \code{env}. In order to have \code{qenv} reproducible +one needs to initialize with \code{env} which can be reproduced by the \code{code}. Alternatively, one +can create an empty \code{qenv} and evaluate the expressions in this object using \code{eval_code}. +} +\examples{ +new_qenv(env = list2env(list(a = 1)), code = quote(a <- 1)) +new_qenv(env = list2env(list(a = 1)), code = parse(text = "a <- 1")) +new_qenv(env = list2env(list(a = 1)), code = "a <- 1") + +} diff --git a/man/overwrite_chunks.Rd b/man/overwrite_chunks.Rd index 44b82ee3..042a3be6 100644 --- a/man/overwrite_chunks.Rd +++ b/man/overwrite_chunks.Rd @@ -20,5 +20,6 @@ if missing then \link[shiny]{getDefaultReactiveDomain} is used.} nothing, it modifies shiny session object } \description{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} +Chunks are being deprecated \code{qenv} objects should be used instead } diff --git a/man/qenv-class.Rd b/man/qenv-class.Rd new file mode 100644 index 00000000..476fb83d --- /dev/null +++ b/man/qenv-class.Rd @@ -0,0 +1,26 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/qenv-class.R +\docType{class} +\name{qenv-class} +\alias{qenv-class} +\title{Reproducible class with environment and code.} +\description{ +Reproducible class with environment and code. +} +\section{Slots}{ + +\describe{ +\item{\code{code}}{(\code{expression}) to reproduce the environment} + +\item{\code{env}}{(\code{environment}) environment which content was generated by the evaluation +of the \code{code} slot.} + +\item{\code{id}}{(\code{integer}) random identifier of the code element to make sure uniqueness +when joining.} + +\item{\code{warnings}}{(\code{character}) the warnings output when evaluating the code} + +\item{\code{messages}}{(\code{character}) the messages output when evaluating the code} +}} + +\keyword{internal} diff --git a/man/show-qenv-method.Rd b/man/show-qenv-method.Rd new file mode 100644 index 00000000..91a2a81c --- /dev/null +++ b/man/show-qenv-method.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/qenv-show.R +\name{show,qenv-method} +\alias{show,qenv-method} +\title{Show the \code{qenv} object} +\usage{ +\S4method{show}{qenv}(object) +} +\arguments{ +\item{object}{(\code{qenv})} +} +\value{ +nothing +} +\description{ +Prints the \code{qenv} object +} +\examples{ +q1 <- new_qenv(code = "print('a')", env = new.env()) +q1 +} diff --git a/man/show_eval_details_modal.Rd b/man/show_eval_details_modal.Rd index 58bae793..1d022a19 100644 --- a/man/show_eval_details_modal.Rd +++ b/man/show_eval_details_modal.Rd @@ -14,7 +14,9 @@ object, from which the messages, warnings and errors are gathered} \code{shiny.tag} } \description{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} +Chunks are being deprecated \code{qenv} objects should be used instead + Use the \code{\link[shiny:showModal]{shiny::showModal()}} function to show the errors generated from chunks. } \references{ diff --git a/tests/testthat/test-chunks.R b/tests/testthat/test-chunks.R index 0b0fa268..3ed1b4ac 100644 --- a/tests/testthat/test-chunks.R +++ b/tests/testthat/test-chunks.R @@ -582,6 +582,7 @@ testthat::test_that("chunks get_rcode", { testthat::test_that("chunks_eval", { + rlang::local_options(lifecycle_verbosity = "quiet") testthat::expect_silent({ rm(list = ls()) dataset <- data.frame(x = c(1, 2, 3, 4, 5, 6), y = c(1, 1, 1, 1, 1, 1)) @@ -604,6 +605,7 @@ testthat::test_that("chunks_eval", { ) }) testthat::test_that("get_code_chunk", { + rlang::local_options(lifecycle_verbosity = "quiet") testthat::expect_silent({ rm(list = ls()) dataset <- data.frame(x = c(1, 2, 3, 4, 5, 6), y = c(1, 1, 1, 1, 1, 1)) @@ -627,6 +629,7 @@ testthat::test_that("get_code_chunk", { }) testthat::test_that("get_code_chunk curly braces", { + rlang::local_options(lifecycle_verbosity = "quiet") testthat::expect_silent({ rm(list = ls()) dataset <- data.frame(x = c(1, 2, 3, 4, 5, 6), y = c(1, 1, 1, 1, 1, 1)) @@ -650,6 +653,7 @@ testthat::test_that("get_code_chunk curly braces", { }) testthat::test_that("init_chunks", { + rlang::local_options(lifecycle_verbosity = "quiet") session <- new.env() session$userData <- new.env() # nolint session$ns <- function(x) { @@ -666,21 +670,25 @@ testthat::test_that("init_chunks", { }) testthat::test_that("chunks_new returns R6 chunks object", { + rlang::local_options(lifecycle_verbosity = "quiet") chunks_1 <- chunks_new() testthat::expect_identical(class(chunks_1), c("chunks", "R6")) }) testthat::test_that("chunks_new accepts environment object or empty as input", { + rlang::local_options(lifecycle_verbosity = "quiet") testthat::expect_error(chunks_new(envir = new.env()), NA) testthat::expect_error(chunks_new(), NA) }) testthat::test_that("chunks_new throws error when an input is not environment", { + rlang::local_options(lifecycle_verbosity = "quiet") testthat::expect_error(chunks_new(envir = "AA"), "Must be an environment") testthat::expect_error(chunks_new(envir = 123), "Must be an environment") }) testthat::test_that("set chunk environment", { + rlang::local_options(lifecycle_verbosity = "quiet") rm(list = ls()) my_chunks <- chunks_new(envir = environment()) testthat::expect_equal( @@ -794,6 +802,7 @@ testthat::test_that("deep clone method", { testthat::test_that("lhs and rhs", { + rlang::local_options(lifecycle_verbosity = "quiet") testthat::expect_silent({ rm(list = ls()) dataset <- data.frame(x = c(1, 2, 3, 4, 5, 6), y = c(1, 1, 1, 1, 1, 1)) @@ -818,6 +827,7 @@ testthat::test_that("lhs and rhs", { # * push_chunks ==== testthat::test_that("chunks push_chunks", { + rlang::local_options(lifecycle_verbosity = "quiet") testthat::expect_silent({ chunks_object <- chunks$new() chunks_object$push(bquote(x <- 1)) @@ -1653,7 +1663,6 @@ testthat::test_that("chunks_validate_all", { testthat::test_that("chunks_deep_clone", { - # check validation testthat::expect_error(chunks_deep_clone(list()), "Assertion on 'chunks' failed") # note chunk not chunks here diff --git a/tests/testthat/test-qenv_constructor.R b/tests/testthat/test-qenv_constructor.R new file mode 100644 index 00000000..6dd0ffe1 --- /dev/null +++ b/tests/testthat/test-qenv_constructor.R @@ -0,0 +1,82 @@ +testthat::test_that("constructor returns qenv if nothing is specified", { + q <- new_qenv() + testthat::expect_s4_class(q, "qenv") + testthat::expect_identical(ls(q@env), character(0)) + testthat::expect_identical(q@code, expression()) + testthat::expect_identical(q@id, integer(0)) + testthat::expect_identical(q@warnings, character(0)) + testthat::expect_identical(q@messages, character(0)) +}) + +testthat::test_that("parent of qenv environment is the parent of .GlobalEnv", { + a <- 1L + q1 <- new_qenv(quote(a <- 1), env = environment()) + q2 <- new_qenv() + q3 <- new_qenv(code = quote(a <- 1), env = list2env(list(a = 1))) + testthat::expect_identical(parent.env(q1@env), parent.env(.GlobalEnv)) + testthat::expect_identical(parent.env(q1@env), parent.env(q2@env)) + testthat::expect_identical(parent.env(q1@env), parent.env(q3@env)) +}) + +testthat::test_that("provided environment is copied so the change of the environment won't affect qenv", { + a <- 1L + q1 <- new_qenv(quote(a <- 1), env = environment()) + b <- 2L + testthat::expect_identical(ls(q1@env), "a") +}) + +testthat::test_that("new_qenv fails if code is provided but not environment", { + testthat::expect_error(new_qenv(quote(iris1 <- iris))) +}) + +testthat::test_that("new_qenv works with code being character", { + env <- new.env() + env$iris1 <- iris + q <- new_qenv("iris1 <- iris", env = env) + testthat::expect_equal(q@env, env) + testthat::expect_identical(q@code, as.expression(quote(iris1 <- iris))) + testthat::expect_true(checkmate::test_int(q@id)) +}) + +testthat::test_that("new_qenv works with code being expression", { + env <- new.env() + env$iris1 <- iris + q <- new_qenv(as.expression(quote(iris1 <- iris)), env = env) + testthat::expect_equal(q@env, env) + testthat::expect_identical(q@code, as.expression(quote(iris1 <- iris))) + testthat::expect_true(checkmate::test_int(q@id)) +}) + +testthat::test_that("new_qenv works with code being quoted expression", { + env <- new.env() + env$iris1 <- iris + q <- new_qenv(quote(iris1 <- iris), env = env) + testthat::expect_equal(q@env, env) + testthat::expect_identical(q@code, as.expression(quote(iris1 <- iris))) + testthat::expect_true(checkmate::test_int(q@id)) +}) + +testthat::test_that("new_qenv works with code being length > 1", { + env <- new.env() + env$iris1 <- iris + env$iris1$new <- 1L + q <- new_qenv( + as.expression(c(quote(iris1 <- iris), quote(iris1$new <- 1L))), + env = env + ) + testthat::expect_identical( + q@code, + as.expression(c(quote(iris1 <- iris), quote(iris1$new <- 1L))) + ) + testthat::expect_equal(q@env, env) +}) + +testthat::test_that("new_qenv allows to pass irreproducible env and code", { + testthat::expect_error(new_qenv(quote(a <- 1), env = list2env(b = 0))) +}) + +testthat::test_that("Initializing qenv with code only creates corresponding warnings and messages slots", { + q1 <- new_qenv(bquote(a <- 1), env = list2env(list(a = 1))) + testthat::expect_identical(q1@warnings, "") + testthat::expect_identical(q1@messages, "") +}) diff --git a/tests/testthat/test-qenv_eval_code.R b/tests/testthat/test-qenv_eval_code.R new file mode 100644 index 00000000..434f5dc1 --- /dev/null +++ b/tests/testthat/test-qenv_eval_code.R @@ -0,0 +1,154 @@ +testthat::test_that("eval_code evaluates the code in the qenvs environment", { + q1 <- new_qenv(quote(iris1 <- iris), env = list2env(list(iris1 = iris))) + q2 <- eval_code(q1, quote(b <- nrow(iris1))) + testthat::expect_identical(get_var(q2, "b"), 150L) +}) + +testthat::test_that("eval_code doesn't have access to environment where it's called", { + a <- 1L + q1 <- new_qenv(quote(a <- 1), env = environment()) + b <- 2L + testthat::expect_s3_class( + eval_code(q1, quote(d <- b)), + c("qenv.error", "try-error", "error", "condition") + ) +}) + +testthat::test_that("@env in qenv is always a sibling of .GlobalEnv", { + q1 <- new_qenv() + testthat::expect_identical(parent.env(q1@env), parent.env(.GlobalEnv)) + + q2 <- eval_code(q1, quote(a <- 1L)) + testthat::expect_identical(parent.env(q2@env), parent.env(.GlobalEnv)) + q3 <- eval_code(q2, quote(b <- 2L)) + testthat::expect_identical(parent.env(q3@env), parent.env(.GlobalEnv)) +}) + +testthat::test_that("library have to be called separately before using function from package", { + q1 <- eval_code(new_qenv(), quote(library(checkmate))) + # library call adds package env in the search path just over .GlobalEnv + # it means that q3@env before the call was a parent of .GlobalEnv but not after the call + + q2 <- eval_code(q1, quote(assert_number(1))) + testthat::expect_identical(parent.env(q2@env), parent.env(.GlobalEnv)) + + detach("package:checkmate", unload = TRUE) + testthat::expect_s3_class( + eval_code( + new_qenv(), + as.expression(c( + quote(library(checkmate)), + quote(assert_number(1)) + )) + ), + "qenv.error" + ) +}) + +testthat::test_that("eval_code works with character", { + q1 <- eval_code(new_qenv(), "a <- 1") + + testthat::expect_identical(q1@code, as.expression(quote(a <- 1))) + testthat::expect_equal(q1@env, list2env(list(a = 1))) +}) + +testthat::test_that("eval_code works with expression", { + q1 <- eval_code(new_qenv(), as.expression(quote(a <- 1))) + + testthat::expect_identical(q1@code, as.expression(quote(a <- 1))) + testthat::expect_equal(q1@env, list2env(list(a = 1))) +}) + +testthat::test_that("eval_code works with quoted", { + q1 <- eval_code(new_qenv(), quote(a <- 1)) + + testthat::expect_identical(q1@code, as.expression(quote(a <- 1))) + testthat::expect_equal(q1@env, list2env(list(a = 1))) +}) + +testthat::test_that("eval_code works with quoted code block", { + q1 <- eval_code( + new_qenv(), + quote({ + a <- 1 + b <- 2 + }) + ) + + testthat::expect_equal( + q1@code, + as.expression( + quote({ + a <- 1 + b <- 2 + }) + ) + ) + testthat::expect_equal(q1@env, list2env(list(a = 1, b = 2))) +}) + +testthat::test_that("eval_code fails with unquoted expression", { + testthat::expect_error(eval_code(new_qenv(), a <- b), "object 'b' not found") +}) + +testthat::test_that("an error when calling eval_code returns a qenv.error object which has message and trace", { + q <- eval_code(new_qenv(), quote(x <- 1)) + q <- eval_code(q, quote(y <- 2)) + q <- eval_code(q, quote(z <- w * x)) + testthat::expect_s3_class(q, "qenv.error") + testthat::expect_equal( + unname(q$trace), + as.expression( + c( + quote(x <- 1), + quote(y <- 2), + quote(z <- w * x) + ) + ) + ) + testthat::expect_equal(q$message, "object 'w' not found \n when evaluating qenv code:\n z <- w * x") +}) + +testthat::test_that("a warning when calling eval_code returns a qenv object which has warnings", { + q <- eval_code(new_qenv(), quote("iris_data <- iris")) + q <- eval_code(q, quote("p <- hist(iris_data[, 'Sepal.Length'], ff = '')")) + testthat::expect_s4_class(q, "qenv") + testthat::expect_equal( + q@warnings, + c( + "", + paste0( + "\"ff\" is not a graphical parameter", + "\"ff\" is not a graphical parameter", + "\"ff\" is not a graphical parameter", + "\"ff\" is not a graphical parameter" + ) + ) + ) +}) + +testthat::test_that("eval_code with a vector of code produces one warning element per code element", { + q <- eval_code(new_qenv(), c("x <- 1", "y <- 1", "warning('warn1')")) + testthat::expect_equal(c("", "", "warn1"), q@warnings) +}) + + +testthat::test_that("a message when calling eval_code returns a qenv object which has messages", { + q <- eval_code(new_qenv(), quote("iris_data <- head(iris)")) + q <- eval_code(q, quote("message('This is a message')")) + testthat::expect_s4_class(q, "qenv") + testthat::expect_equal( + q@messages, + c( + "", + "This is a message\n" + ) + ) +}) + +testthat::test_that("eval_code returns a qenv object with empty messages and warnings when none are returned", { + q <- eval_code(new_qenv(), quote("iris_data <- head(iris)")) + testthat::expect_s4_class(q, "qenv") + testthat::expect_equal(q@messages, "") + testthat::expect_equal(q@warnings, "") +}) diff --git a/tests/testthat/test-qenv_get_code.R b/tests/testthat/test-qenv_get_code.R new file mode 100644 index 00000000..365cefda --- /dev/null +++ b/tests/testthat/test-qenv_get_code.R @@ -0,0 +1,39 @@ +testthat::test_that("get_code returns code (character by default) of qenv object", { + q <- new_qenv(list2env(list(x = 1)), code = quote(x <- 1)) + q <- eval_code(q, quote(y <- x)) + testthat::expect_equal(get_code(q), c("x <- 1", "y <- x")) +}) + +testthat::test_that("get_code returns code elements being code-blocks as character(1)", { + q <- new_qenv(list2env(list(x = 1)), code = quote(x <- 1)) + q <- eval_code( + q, + quote({ + y <- x + z <- 5 + }) + ) + testthat::expect_equal(get_code(q), c("x <- 1", "{\n y <- x\n z <- 5\n}")) +}) + +testthat::test_that("get_code returns code (unparsed) of qenv object if deparse = FALSE", { + q <- new_qenv(list2env(list(x = 1)), code = quote(x <- 1)) + q <- eval_code(q, quote(y <- x)) + testthat::expect_equal(get_code(q, deparse = FALSE), q@code) +}) + +testthat::test_that("get_code called with qenv.error returns error with trace in error message", { + q1 <- new_qenv(list2env(list(x = 1)), code = quote(x <- 1)) + q2 <- eval_code(q1, quote(y <- x)) + q3 <- eval_code(q2, quote(w <- v)) + + code <- tryCatch( + get_code(q3), + error = function(e) e + ) + testthat::expect_equal(class(code), c("validation", "try-error", "simpleError", "error", "condition")) + testthat::expect_equal( + code$message, + "object 'v' not found \n when evaluating qenv code:\n w <- v\n\ntrace: \n x <- 1\n y <- x\n w <- v\n" + ) +}) diff --git a/tests/testthat/test-qenv_get_var.R b/tests/testthat/test-qenv_get_var.R new file mode 100644 index 00000000..ecb5c329 --- /dev/null +++ b/tests/testthat/test-qenv_get_var.R @@ -0,0 +1,26 @@ +testthat::test_that("get_var and `[[`return error if object is qenv.error", { + q <- eval_code(new_qenv(), quote(x <- 1)) + q <- eval_code(q, quote(y <- w * x)) + + testthat::expect_error(get_var(q, "x"), "when evaluating qenv code") + testthat::expect_error(q[["x"]], "when evaluating qenv code") +}) + +testthat::test_that("get_var and `[[` return object from qenv environment", { + q <- eval_code(new_qenv(), quote(x <- 1)) + q <- eval_code(q, quote(y <- 5 * x)) + + testthat::expect_equal(get_var(q, "y"), 5) + testthat::expect_equal(q[["x"]], 1) +}) + +testthat::test_that("get_var and `[[` return NULL if object not in qenv environment", { + q <- eval_code(new_qenv(), quote(x <- 1)) + q <- eval_code(q, quote(y <- 5 * x)) + + testthat::expect_null(get_var(q, "z")) + testthat::expect_message(get_var(q, "z"), "object 'z' not found") + + testthat::expect_null(q[["w"]]) + testthat::expect_message(q[["w"]], "object 'w' not found") +}) diff --git a/tests/testthat/test-qenv_get_warnings.R b/tests/testthat/test-qenv_get_warnings.R new file mode 100644 index 00000000..f887d8eb --- /dev/null +++ b/tests/testthat/test-qenv_get_warnings.R @@ -0,0 +1,66 @@ +testthat::test_that("get_warnings accepts a qenv object and returns character", { + q <- new_qenv() %>% eval_code(bquote(warning("This is a warning!"))) + testthat::expect_identical( + get_warnings(q), + paste0( + "Warnings: \n> This is a warning! \nWhen running code:\n warning(\"This is a warning!\")\n\n", + "Trace:\nwarning(\"This is a warning!\")" + ) + ) +}) + +testthat::test_that("get_warnings accepts a qenv.error object and returns NULL", { + q <- new_qenv() %>% eval_code(bquote(error("This is a error!"))) + testthat::expect_null(get_warnings(q)) +}) + +testthat::test_that("get_warnings accepts a NULL object and returns NULL", { + testthat::expect_null(get_warnings(NULL)) +}) + +testthat::test_that("get_warnings accepts a qenv object with no warning and returns NULL", { + q <- new_qenv() %>% eval_code(bquote("x <- 1")) + testthat::expect_null(get_warnings(q)) +}) + +testthat::test_that("get_warnings accepts a qenv object with 2 warnings", { + q <- new_qenv() %>% + eval_code(bquote(warning("This is a warning 1!"))) %>% + eval_code(bquote(warning("This is a warning 2!"))) + testthat::expect_identical( + get_warnings(q), + paste0( + "Warnings: \n> This is a warning 1! \nWhen running code:\n warning(\"This is a warning 1!\")", + " \n> This is a warning 2! \nWhen running code:\n warning(\"This is a warning 2!\")\n\n", + "Trace:\nwarning(\"This is a warning 1!\")\nwarning(\"This is a warning 2!\")" + ) + ) +}) + +testthat::test_that("get_warnings accepts a qenv object with a single eval_code returning 2 warnings", { + q <- new_qenv() %>% eval_code(bquote({ + warning("This is a warning 1!") + warning("This is a warning 2!") + })) + testthat::expect_identical( + get_warnings(q), + paste0( + "Warnings: \n> This is a warning 1!This is a warning 2! \nWhen running code:\n {\n ", + "warning(\"This is a warning 1!\")\n warning(\"This is a warning 2!\")\n}\n\nTrace:\n{\n", + " warning(\"This is a warning 1!\")\n warning(\"This is a warning 2!\")\n}" + ) + ) +}) + +testthat::test_that("get_warnings accepts a qenv object with 1 warning eval_code and 1 no warning eval_code", { + q <- new_qenv() %>% + eval_code(bquote("x <- 1")) %>% + eval_code(bquote(warning("This is a warning 2!"))) + testthat::expect_identical( + get_warnings(q), + paste0( + "Warnings: \n> This is a warning 2! \nWhen running code:\n warning(\"This is a warning 2!\")\n\n", + "Trace:\nx <- 1\nwarning(\"This is a warning 2!\")" + ) + ) +}) diff --git a/tests/testthat/test-qenv_join.R b/tests/testthat/test-qenv_join.R new file mode 100644 index 00000000..a7134fa7 --- /dev/null +++ b/tests/testthat/test-qenv_join.R @@ -0,0 +1,203 @@ +testthat::test_that("Joining two identical qenvs outputs the same object", { + env <- new.env() + env$iris1 <- iris + q1 <- new_qenv(quote(iris1 <- iris), env = env) + q2 <- q1 + + testthat::expect_true(.check_joinable(q1, q2)) + q <- join(q1, q2) + + testthat::expect_equal(q@env, env) + testthat::expect_identical(q@code, as.expression(quote(iris1 <- iris))) + testthat::expect_identical(q@id, q1@id) +}) + +testthat::test_that("Joining two independent qenvs results in object having combined code and environments", { + q1 <- new_qenv(quote(iris1 <- iris), env = list2env(list(iris1 = iris))) + q2 <- new_qenv(quote(mtcars1 <- mtcars), env = list2env(list(mtcars1 = mtcars))) + + testthat::expect_true(.check_joinable(q1, q2)) + q <- join(q1, q2) + + testthat::expect_equal(q@env, list2env(list(iris1 = iris, mtcars1 = mtcars))) + testthat::expect_identical( + q@code, + as.expression(c(quote(iris1 <- iris), quote(mtcars1 <- mtcars))) + ) + testthat::expect_identical(q@id, c(q1@id, q2@id)) +}) + +testthat::test_that("Joined qenv does not duplicate common code", { + env <- list2env(list( + iris1 = iris, + mtcars1 = mtcars + )) + + q1 <- new_qenv(code = as.expression(c(quote(iris1 <- iris), quote(mtcars1 <- mtcars))), env = env) + q2 <- q1 + q2 <- eval_code(q2, quote(mtcars2 <- mtcars)) + + testthat::expect_true(.check_joinable(q1, q2)) + q <- join(q1, q2) + + testthat::expect_identical( + q@code, + as.expression(c(quote(iris1 <- iris), quote(mtcars1 <- mtcars), quote(mtcars2 <- mtcars))) + ) + testthat::expect_identical(q@id, c(q1@id, q2@id[3])) +}) + +testthat::test_that("Not able to join two qenvs if any of the shared objects changed", { + env1 <- list2env(list( + iris1 = iris, + iris2 = iris + )) + env2 <- list2env(list(iris1 = head(iris))) + + q1 <- new_qenv(quote(iris1 <- iris), env = env1) + q2 <- new_qenv(quote(iris1 <- head(iris)), env = env2) + + testthat::expect_match(.check_joinable(q1, q2), "Not possible to join qenv objects") + testthat::expect_error(join(q1, q2), "Not possible to join qenv objects") +}) + +testthat::test_that("join does not duplicate code but adds only extra code", { + env <- list2env(list( + iris1 = iris, + mtcars1 = mtcars + )) + + q1 <- new_qenv(code = as.expression(c(quote(iris1 <- iris), quote(mtcars1 <- mtcars))), env = env) + q2 <- q1 + q1 <- eval_code(q1, quote(iris2 <- iris)) + q2 <- eval_code(q2, quote(mtcars2 <- mtcars)) + + testthat::expect_true(.check_joinable(q1, q2)) + q <- join(q1, q2) + + testthat::expect_identical( + q@code, + as.expression( + c(quote(iris1 <- iris), quote(mtcars1 <- mtcars), quote(iris2 <- iris), quote(mtcars2 <- mtcars)) + ) + ) + + testthat::expect_equal( + as.list(q@env), + list(iris1 = iris, iris2 = iris, mtcars1 = mtcars, mtcars2 = mtcars) + ) + + testthat::expect_identical(q@id, c(q1@id, q2@id[3])) +}) + +testthat::test_that("Not possible to join qenvs which share some code when one of the shared object was modified", { + env <- new.env() + env$iris1 <- iris + env$mtcars1 <- mtcars + + q1 <- new_qenv(code = as.expression(c(quote(iris1 <- iris), quote(mtcars1 <- mtcars))), env = env) + q2 <- q1 + q1 <- eval_code(q1, quote(iris2 <- iris)) + q2 <- eval_code(q2, quote(mtcars1 <- head(mtcars))) + + testthat::expect_error(join(q1, q2)) +}) + +testthat::test_that("qenv objects are mergeable if they don't share any code (identified by id)", { + q1 <- new_qenv(code = quote(a1 <- 1), env = list2env(list(a1 = 1))) + q2 <- new_qenv(code = quote(a1 <- 1), env = list2env(list(a1 = 1))) + testthat::expect_true(.check_joinable(q1, q2)) + + cq <- join(q1, q2) + testthat::expect_s4_class(cq, "qenv") + testthat::expect_equal(cq@env, list2env(list(a1 = 1))) + testthat::expect_identical(cq@code, as.expression(c(quote(a1 <- 1), quote(a1 <- 1)))) + testthat::expect_identical(cq@id, c(q1@id, q2@id)) +}) + +testthat::test_that("qenv objects are mergeable if they share common initial qenv elements", { + q1 <- new_qenv(code = quote(a1 <- 1), env = list2env(list(a1 = 1))) + q2 <- eval_code(q1, quote(b1 <- 2)) + q1 <- eval_code(q1, quote(a2 <- 3)) + testthat::expect_true(.check_joinable(q1, q2)) + + cq <- join(q1, q2) + testthat::expect_s4_class(cq, "qenv") + testthat::expect_equal(cq@env, list2env(list(a1 = 1, b1 = 2, a2 = 3))) + testthat::expect_identical( + cq@code, + as.expression(c(quote(a1 <- 1), quote(a2 <- 3), quote(b1 <- 2))) + ) + testthat::expect_identical(cq@id, union(q1@id, q2@id)) +}) + +testthat::test_that( + "qenv objects aren't mergeable if they share common qenv elements proceeded with some other code", + { + q1 <- new_qenv(code = quote(a1 <- 1), env = list2env(list(a1 = 1))) + q2 <- new_qenv(code = quote(b1 <- 2), env = list2env(list(b1 = 2))) + q_common <- new_qenv(quote(c1 <- 3), env = list2env(list(c1 = 3))) + q1 <- join(q1, q_common) + q2 <- join(q2, q_common) + testthat::expect_match(.check_joinable(q1, q2), "these objects cannot be joined") + testthat::expect_error(join(q1, q2), "these objects cannot be joined") + } +) + +testthat::test_that("qenv objects are not mergable if they have multiple common streaks", { + q_common1 <- new_qenv(code = quote(c1 <- 1), env = list2env(list(c1 = 1))) + q_common2 <- new_qenv(code = quote(c2 <- 2), env = list2env(list(c2 = 2))) + + q1 <- eval_code(q_common1, quote(a1 <- 3)) + q1 <- join(q1, q_common2) # c1, a1, c2 + q2 <- join(q_common1, q_common2) # c1, c2 + + testthat::expect_match(.check_joinable(q1, q2), "it's impossible to determine the evaluation's order") + testthat::expect_error(join(q1, q2), "it's impossible to determine the evaluation's order") +}) + + +testthat::test_that("joining with a qenv.error object returns the qenv.error object", { + q1 <- eval_code(new_qenv(), quote(x <- 1)) + error_q <- eval_code(new_qenv(), quote(y <- w)) + error_q2 <- eval_code(new_qenv(), quote(z <- w)) + + testthat::expect_s3_class(join(q1, error_q), "qenv.error") + testthat::expect_s3_class(join(error_q, error_q2), "qenv.error") + testthat::expect_s3_class(join(error_q, q1), "qenv.error") + + # if joining two qenv.error objects keep the first + testthat::expect_equal(join(error_q, error_q2), error_q) +}) + +testthat::test_that("Joining two independent qenvs with warnings results in object having combined warnings", { + q1 <- new_qenv() %>% eval_code("warning('This is warning 1')") + q2 <- new_qenv() %>% eval_code("warning('This is warning 2')") + + testthat::expect_true(.check_joinable(q1, q2)) + q <- join(q1, q2) + + testthat::expect_equal( + q@warnings, + c( + "This is warning 1", + "This is warning 2" + ) + ) +}) + +testthat::test_that("Joining two independent qenvs with messages results in object having combined messages", { + q1 <- new_qenv() %>% eval_code("message('This is message 1')") + q2 <- new_qenv() %>% eval_code("message('This is message 2')") + + testthat::expect_true(.check_joinable(q1, q2)) + q <- join(q1, q2) + + testthat::expect_equal( + q@messages, + c( + "This is message 1\n", + "This is message 2\n" + ) + ) +}) diff --git a/tests/testthat/test-qenv_show.R b/tests/testthat/test-qenv_show.R new file mode 100644 index 00000000..120e7723 --- /dev/null +++ b/tests/testthat/test-qenv_show.R @@ -0,0 +1,11 @@ +testthat::test_that("showing an empty qenv states qenv is empty", { + q <- new_qenv() + testthat::expect_output(show(q), "A qenv object containing no objects") +}) + +testthat::test_that("showing a non-empty qenv lists its contents", { + q <- new_qenv() + q <- eval_code(q, quote(x <- 1)) + q <- eval_code(q, quote(y <- 2)) + testthat::expect_output(show(q), "A qenv object containing: x, y") +}) diff --git a/vignettes/advanced_chunks.Rmd b/vignettes/advanced_chunks.Rmd index 025c2bd2..b4beecac 100644 --- a/vignettes/advanced_chunks.Rmd +++ b/vignettes/advanced_chunks.Rmd @@ -1,7 +1,7 @@ --- title: "Advanced chunks" author: "NEST coreDev" -date: "2022-05-09" +date: "2022-11-03" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Advanced chunks} @@ -9,394 +9,6 @@ vignette: > %\VignetteEncoding{UTF-8} --- -## Decoupling chunks containers +Chunks have been deprecated and will be removed from `teal.code` in a later release. +Please use [`qenv`](https://insightsengineering.github.io/teal.code/articles/qenv.html) instead. -In a shiny app there is often a need to have multiple chunks containers that may be used independently by certain parts of the application and may be used together by other parts of the application. The ability to have multiple chunks containers combined into one allows for greater flexibility of the code, which allows the developer to apply the `DRY` principle (stands for `Do not repeat` which means code that are used in multiple places should only be written and defined once) and optimize performance. Much of the functionality to use a single chunks container object independently has already been covered in the [Basic chunks](./basic_chunks.Rmd) vignette, which you should read first before going any further. - -## Creating a chunks container and assigning it to a variable - -In order to have multiple chunks containers, each must be accessible and distinguishable via a variable. - -```{r} -ch_0 <- teal.code::chunks_new() -ch_1 <- teal.code::chunks_new() -ch_2 <- teal.code::chunks_new() - -# if data used is static, then it should be computed once -# isolating it into its own chunks container accomplishes this goal -ch_0$push(quote(df <- data.frame(a = runif(5, -10, 10), b = runif(5, -10, 10)))) -ch_1$push(quote({ - Sys.sleep(1) # some really long running process - foo_plot <- function(df_arg) plot(df_arg) -})) -ch_2$push(quote({ - Sys.sleep(1) # some really long running process - foo_print <- function(df_arg) df_arg -})) - -# chunks containers should be evaluated before being merged -# the reason for this will be explain later -teal.code::chunks_safe_eval(chunks = ch_0) -teal.code::chunks_safe_eval(chunks = ch_1) -teal.code::chunks_safe_eval(chunks = ch_2) -``` - -The `teal.code::chunks_push_chunks` function is called to combine chunks containers together. Note that the base chunks container to start combining from in the following examples are all empty. However, that does not have to be the case. Starting with a clean slate is easier to comprehend. - -Perhaps a part of your shiny application needs to output a plot - -```{r} -ch_plot <- teal.code::chunks_new() -teal.code::chunks_push_chunks(chunks = ch_plot, x = ch_0) -teal.code::chunks_push_chunks(chunks = ch_plot, x = ch_1) -teal.code::chunks_push(chunks = ch_plot, expression = quote(foo_plot(df))) -teal.code::chunks_safe_eval(chunks = ch_plot) -``` - -Perhaps a part of your shiny application needs to output a table - -```{r} -ch_table <- teal.code::chunks_new() -teal.code::chunks_push_chunks(chunks = ch_table, x = ch_0) -teal.code::chunks_push_chunks(chunks = ch_table, x = ch_2) -teal.code::chunks_push(chunks = ch_table, expression = quote(foo_print(df))) -teal.code::chunks_safe_eval(chunks = ch_table) -``` - -Perhaps the main part of your shiny application needs to output a plot, a table, and the code to generate both - -```{r} -ch_ouput <- teal.code::chunks_new() -teal.code::chunks_push_chunks(chunks = ch_ouput, x = ch_0) -teal.code::chunks_push_chunks(chunks = ch_ouput, x = ch_1) -teal.code::chunks_push_chunks(chunks = ch_ouput, x = ch_2) -teal.code::chunks_push(chunks = ch_ouput, expression = quote(foo_plot(df))) -teal.code::chunks_push(chunks = ch_ouput, expression = quote(foo_print(df))) -teal.code::chunks_safe_eval(chunks = ch_ouput) - -code <- teal.code::chunks_get_rcode(chunks = ch_ouput) -cat( - paste(code, collapse = "\n") -) -``` - -When chunks containers are merged together, their code expressions are combined in the order that the containers were merged and their environments are combined into a single environment. - -Even in the event that the expressions that are combined will lead to errors being thrown when evaluated, there is no way for the chunks object to know that before evaluation. Hence code expressions will not interrupt the merging of chunks containers together. - -However, the potential conflict of when two chunks containers have the same variables which holds different values in their respective environments will throw an error when merging chunks containers. - -```{r} -ch_0 <- teal.code::chunks_new() -ch_1 <- teal.code::chunks_new() - -ch_0$push(quote(a_var <- 0)) -ch_1$push(quote(a_var <- 0)) - -teal.code::chunks_safe_eval(chunks = ch_0) -teal.code::chunks_safe_eval(chunks = ch_1) - -# OK - operation is successful as the variable a_var holds the same value in both chunks -teal.code::chunks_push_chunks(chunks = ch_0, x = ch_1) - -ch_2 <- teal.code::chunks_new() -ch_2$push(quote(a_var <- 1)) -teal.code::chunks_safe_eval(chunks = ch_2) - -# ERROR - operation is rejected as the variable a_var holds different values -tryCatch( - teal.code::chunks_push_chunks(chunks = ch_0, x = ch_2), - error = function(e) e -) -``` - -### Why should you evaluate before merging? - -With the exception of code that is meant to return something at the end of a function, code usually modifies the environment and / or the variables inside the environment. - -When chunks containers have code that have not been evaluated, these code will not block any merging operations as explained above. But if they are evaluated, then their environment will be updated and will reflect the intended state of the chunks container as described by the code expressions. - -And as described above, it is the environment that will block the operation to merge chunks containers together when there are conflicts. So evaluating before merging will prevent some bugs in your app. - -```{r} -ch_0 <- teal.code::chunks_new() -ch_0$push(quote(a_var <- 0)) -teal.code::chunks_safe_eval(chunks = ch_0) - -ch_2 <- teal.code::chunks_new() -ch_2$push(quote(a_var <- 1)) - -# note that ch_2 is not evaluated - -# OK - operation is successful -teal.code::chunks_push_chunks(chunks = ch_0, x = ch_2) -``` - - -## Cloning a chunks container - -A chunks container is an R6 reference class, so by default contains a `clone` method. A deep copy is needed if some part of your app needs to start from the state of an already existing chunks container and modify or add on to it, but some other part of your app still needs the original chunks container unaltered. - -The example code below is another way to print the plot above, which starts combining chunks from a non-empty chunks container derived from cloning another chunks container - -```{r} -ch_0 <- teal.code::chunks_new() -ch_0$push(quote({ - df <- data.frame(a = runif(5, -10, 10), b = runif(5, -10, 10)) - foo_plot <- function(df_arg) plot(df_arg) -})) -teal.code::chunks_safe_eval(chunks = ch_0) - -ch_plot_clone <- teal.code::chunks_deep_clone(ch_0) -teal.code::chunks_push(chunks = ch_plot_clone, expression = quote(foo_plot(df))) -teal.code::chunks_safe_eval(chunks = ch_plot_clone) -``` - -## Extracting values of variables in a chunks container - -In order to extract values of variables inside a chunks container, the code that generates that variable must have been evaluated - -```{r} -ch_0 <- teal.code::chunks_new() -ch_0$push(quote(df <- data.frame(a = runif(5, -10, 10), b = runif(5, -10, 10)))) -teal.code::chunks_safe_eval(chunks = ch_0) - -env <- new.env() -env$df <- teal.code::chunks_get_var(var = "df", chunks = ch_0) - -# Once extracted, the value can be passed into other chunks containers if needed - -ch_print_df <- teal.code::chunks_new() -teal.code::chunks_reset(envir = env, chunks = ch_print_df) -teal.code::chunks_push(chunks = ch_print_df, expression = quote(df)) -teal.code::chunks_safe_eval(chunks = ch_print_df) -``` - - -# Example - -The following is a shiny app that uses the chunks container object in ways that mirror how a sophistical `teal` app would use it. This app constructs several chunks containers, allowing each to be used and combined by different parts of the app, which enables the app to apply the `DRY` principle and optimize performance. - -```{r, eval = FALSE} -library(shiny) -library(teal.code) -library(dplyr) -library(rlang) - -ui <- fluidPage( - sidebarLayout( - sidebarPanel( - # Input of Response can be chosen from Event Table - selectInput( - inputId = "filter_paramcd", - label = "Filter Paramcd", - choices = c("OS", "EFS", "PFS"), - selected = "OS", - multiple = TRUE - ), - selectInput( - inputId = "filter_sex", - label = "Filter sex", - choices = c("F", "M", "U"), - selected = c("F", "M", "U"), - multiple = TRUE - ), - selectInput( - inputId = "anl_columns", - label = "ANL columns", - choices = NULL, - multiple = TRUE - ) - ), - mainPanel( - verbatimTextOutput("code"), - plotOutput("plot"), - DT::DTOutput("anl_table") - ) - ) -) - -server <- function(input, output, session) { - # chunks container to hold the datasets used in this app. - # The purpose of storing the code to generate the datasets is reproducibility. - data_def_chunks <- chunks_new() - chunks_push( - chunks = data_def_chunks, - expression = quote({ - library(shiny) - library(teal.code) - library(dplyr) - library(rlang) - }) - ) - chunks_push( - chunks = data_def_chunks, - expression = quote( - adsl <- list( - SUBJID = 1:100, - STUDYID = c(rep(1, 20), rep(2, 50), rep(3, 30)), - AGE = sample(20:88, 100, replace = T) %>% as.numeric(), - SEX = sample(c("M", "F", "U"), 100, replace = T) %>% as.factor() - ) %>% - as_tibble() - ) - ) - chunks_push( - chunks = data_def_chunks, - expression = quote( - events <- list( - SUBJID = rep(1:100, 3), - STUDYID = rep(c(rep(1, 20), rep(2, 50), rep(3, 30)), 3), - PARAMCD = c(rep("OS", 100), rep("EFS", 100), rep("PFS", 100)), - AVAL = c(rexp(100, 1 / 100), rexp(100, 1 / 80), rexp(100, 1 / 60)) %>% as.numeric(), - AVALU = rep("DAYS", 300) %>% as.factor() - ) %>% - as_tibble() - ) - ) - # evaluating the code so that the datasets become available - chunks_safe_eval(data_def_chunks) - - # reactive that needs to extract a value from another chunks container - adsl_filtered_reactive <- reactive({ - env <- new.env() - env$adsl <- chunks_get_var(var = "adsl", chunks = data_def_chunks) - ch <- chunks_new() - chunks_reset(chunks = ch, env = env) - chunks_push( - chunks = ch, - expression = expr(adsl_filtered <- adsl %>% dplyr::filter(SEX %in% !!input$filter_sex)) - ) - chunks_safe_eval(ch) - ch - }) - - # reactive that needs to extract a value from another chunks container - events_filtered_reactive <- reactive({ - env <- new.env() - env$events <- chunks_get_var(var = "events", chunks = data_def_chunks) - ch <- chunks_new() - chunks_reset(chunks = ch, env = env) - chunks_push( - chunks = ch, - expression = expr(events_filtered <- events %>% dplyr::filter(PARAMCD %in% !!input$filter_paramcd)) - ) - chunks_safe_eval(ch) - ch - }) - - # part of the app that merges two other chunks containers - merge_data <- reactive({ - ch <- chunks_new() - - chunks_push_chunks(chunks = ch, x = adsl_filtered_reactive()) - chunks_push_chunks(chunks = ch, x = events_filtered_reactive()) - - chunks_push( - chunks = ch, - expression = expr(anl <- left_join(adsl_filtered, events_filtered, by = c("STUDYID", "SUBJID"))) - ) - chunks_safe_eval(ch) - ch - }) - - observeEvent(merge_data(), { - anl <- chunks_get_var("anl", merge_data()) - updateSelectInput( - session, - "anl_columns", - choices = colnames(anl), - selected = colnames(anl) - ) - }) - - # need to clone as chunks are R6 object - this would change ch in merge_data() - subset_anl <- reactive({ - ch <- chunks_deep_clone(merge_data()) - chunks_push( - chunks = ch, - expression = rlang::expr(anl_subset <- anl[!!input$anl_columns]) - ) - chunks_safe_eval(ch) - ch - }) - - # a chunks container that needs to extract a value from another chunks container - table_call <- reactive({ - env <- new.env() - env$anl_subset <- chunks_get_var(chunks = subset_anl(), var = "anl_subset") - ch <- chunks_new() - chunks_reset(chunks = ch, env = env) - chunks_push( - chunks = ch, - expression = rlang::expr({ - table <- DT::datatable(anl_subset) - table - }) - ) - chunks_safe_eval(ch) - ch - }) - - # a chunks container that needs to extract a value from another chunks container - plot_call <- reactive({ - env <- new.env() - env$anl_subset <- chunks_get_var(chunks = subset_anl(), var = "anl_subset") - ch <- chunks_new() - chunks_reset(chunks = ch, env = env) - chunks_push( - chunks = ch, - expression = rlang::expr({ - plot_output <- plot(anl_subset$AGE, anl_subset$AVAL) - plot_output - }) - ) - chunks_safe_eval(ch) - ch - }) - - # Really long running process that should not be recomputed if can be avoided. - # - # The ability to put this process in its own chunks container, independent of the rest, - # allows the app to avoid re-computation and yet be able to capture its code to ensure reproducibility. - sleep_5_seconds <- reactive({ - ch <- chunks_new() - chunks_push( - chunks = ch, - expression = quote(Sys.sleep(5)) - ) - chunks_safe_eval(ch) - ch - }) - - # part of the app that needs to combine all chunks containers - outputs_call <- reactive({ - ch <- chunks_new() - chunks_push_chunks(chunks = ch, x = data_def_chunks) - chunks_push_chunks(chunks = ch, x = sleep_5_seconds()) - chunks_push_chunks(chunks = ch, x = subset_anl()) - chunks_push_chunks(chunks = ch, x = plot_call()) - chunks_push_chunks(chunks = ch, x = table_call()) - ch - }) - - # part of the app that only needs a single chunks container - output$anl_table <- DT::renderDT(chunks_get_var("table", table_call())) - output$plot <- renderPlot(chunks_get_var("plot_output", plot_call())) - - output$code <- renderPrint({ - cat( - paste( - chunks_get_rcode(chunks = outputs_call()), - collapse = "\n" - ) - ) - }) -} - -shinyApp(ui, server) -``` - -## Conclusion - -The ability to have multiple chunks containers combined into one allows for greater flexibility of the code, which allows the developer to apply the `DRY` principle and optimize performance. diff --git a/vignettes/basic_chunks.Rmd b/vignettes/basic_chunks.Rmd index fce1e3c6..e243794d 100644 --- a/vignettes/basic_chunks.Rmd +++ b/vignettes/basic_chunks.Rmd @@ -1,7 +1,7 @@ --- title: "Basic chunks" author: "NEST coreDev" -date: "2022-04-22" +date: "2022-11-03" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Basic chunks} @@ -9,243 +9,5 @@ vignette: > %\VignetteEncoding{UTF-8} --- -# The chunks container - -The main concept behind code reproducibility in teal is the chunks container object. This object consists of two elements: - -1. A stack of quoted R expressions, each called a "chunk" -2. An environment carrying variables and their values - -container - -The chunks container object allows the evaluation of all expressions it contains in its own isolated environment, thus having no side effects on the surrounding environment. - -The next sections will explain what a chunk is and how it is evaluated. - -# What is a chunk? - -A quoted R expression is a necessary step to create a **chunk** object, which is an R6 object of class `chunk_call`. Quoted R expressions can be created in many different ways, four of which will be described: -```{r} -a <- 3 - -# Creating a chunk by quote ------------------------------ -expr_a <- quote(sum(a, a)) -print(expr_a) - -# Creating a chunk by substitute ------------------------------ -expr_b <- substitute(b <- sum(a, a)) -print(expr_b) - -# Creating a chunk by call ------------------------------- -expr_c <- call("sum", a, a) -print(expr_c) - -# Creating a chunk by rlang::expr ------------------------------- -expr_d <- rlang::expr(sum(a, a)) -print(expr_d) -``` - -To evaluate the expressions of class `call` or an assignment given by class `<-` above, R uses the `eval` function. This function evaluates each single `call` inside the current environment by default, but it does contain a parameter to input a specific environment argument to execute the expression in. - -```{r} -a <- 3 -expr_a <- quote(sum(a, a)) -expr_b <- substitute(b <- a + a) - -eval(expr_a) -eval(expr_b) -print(b) -``` - -`chunk` objects can be created and evaluated using the expressions above as follows: - -```{r} -chunk_1 <- teal.code::chunk$new(expression = expr_a) -chunk_1$eval() - -chunk_2 <- teal.code::chunk$new(expression = expr_b) -chunk_2$eval() -print(b) -``` - -Note that `teal.code::chunk` is merely an alias for `teal.code::chunk_call`. And so the following code is the same as the above code: - -```{r} -chunk_1 <- teal.code::chunk_call$new(expression = expr_a) -chunk_1$eval() - -chunk_2 <- teal.code::chunk_call$new(expression = expr_b) -chunk_2$eval() -print(b) -``` - -## Motivation for the chunk (chunk_call) object - -A quoted R expression can simply be evaluated with the base function, `eval`, as demonstrated above. But additional functionalities are needed for the `chunks` container to work. Here are some additional methods, besides `eval`, that the `chunk` object contains: - -```{r} -# answers the question of whether the code executed without error -chunk_1$is_ok() - -# answers the question of whether the code has been executed -chunk_1$is_evaluated() - -# returns error messages, if any, in the form of a string -chunk_err <- teal.code::chunk_call$new(expression = quote(stop("error in chunk"))) -chunk_err$get_errors() # no error before evaluation -chunk_err$eval() -chunk_err$get_errors() -``` - -Internally, the `chunks` container will convert quoted base R expressions into `chunk` objects as they are pushed in. - -## Creation of a chunks container object - -A chunks container may be initialized as an empty container. - -```{r} -# initializing code chunks ------------------------------------------- -chunks_container_empty <- teal.code::chunks_new() -``` - -However, it can also be initialized with a specific environment. - -```{r} -# initializing code chunks ------------------------------------------- -env <- new.env() -env$var_to_be_erased <- "some_value" -env$x <- 0 -chunks_container <- teal.code::chunks_new(envir = env) - -# method to list all variables in the chunks environment -chunks_container$ls() - -# function to add a chunk to a chunks container object -teal.code::chunks_push(chunks = chunks_container, expression = quote(print(x))) - -# function to get all expressions from a chunks container code stack -teal.code::chunks_get_rcode(chunks_container) -``` - -reset - -At any point, a chunks container can be reset. Resetting means that all expressions in its code stack will be emptied and its environment will be overridden by the inputted environment, which defaults to the parent environment. - -```{r} -env <- new.env() -env$anl <- data.frame(left = c(1, 2, 3), right = c(4, 5, 6)) -env$x <- "abc" -env$y <- 5 - -teal.code::chunks_reset(envir = env, chunks = chunks_container) - -# note that the variable var_to_be_erased is removed -chunks_container$ls() - -# this function is used to extract values of variables in a chunks container environment -# note that the variable x is overriden -teal.code::chunks_get_var("x", chunks = chunks_container) - -# note that the code stack has been emptied -teal.code::chunks_get_rcode(chunks_container) -``` - -reset - -As mentioned above, the `teal.code::chunks_push` function is used to push expressions into the chunks container. - -```{r} -teal.code::chunks_push(chunks = chunks_container, expression = substitute(y <- y + 1)) -teal.code::chunks_push(chunks = chunks_container, expression = substitute(x <- paste0(x, y))) -``` - -push - -As mentioned above, the `teal.code::chunks_get_rcode` function is used to get all expressions pushed into the chunks container. - -```{r} -teal.code::chunks_get_rcode(chunks_container) -``` -get_rcode - -## Executing the code stored in the stack - -The chunks container also has an `eval` method which runs all code inside the chunks container. This method is wrapped inside the function `teal.code::chunks_safe_eval`. It evaluates all chunks inside the container in the order they were pushed and returns the value of the last evaluated expression. It is not possible to change the order or run just some of the expressions. - -```{r} -teal.code::chunks_safe_eval(chunks_container) -teal.code::chunks_get_var("x", chunks = chunks_container) -teal.code::chunks_get_var("y", chunks = chunks_container) -``` - -eval - -It is still possible to push more code expressions into a chunks container that has already been evaluated. These newly added expressions may also modify the environment. - -```{r} -teal.code::chunks_push(chunks = chunks_container, expression = quote(z <- 10)) -teal.code::chunks_get_rcode(chunks_container) - -teal.code::chunks_safe_eval(chunks_container) -chunks_container$ls() -teal.code::chunks_get_var("z", chunks = chunks_container) -``` - -Note that code that have already been evaluated will not be re-evaluated when newly added code is evaluated. - -```{r} -teal.code::chunks_push( - chunks = chunks_container, - expression = quote(print("I will only be evaluated once")) -) -teal.code::chunks_safe_eval(chunks_container) - -teal.code::chunks_push(chunks = chunks_container, expression = quote(rm(z))) - -# note that the string "I will only be evaluated once" is not printed again -teal.code::chunks_safe_eval(chunks_container) - -# z is removed -chunks_container$ls() -``` - -## Handling errors (and warnings) - -The function `teal.code::chunks_safe_eval` is named `"safe_eval"` because it performs an additional step to handle errors by calling another method of the chunks container object, `validate_is_ok`. If any error occurs during the evaluation of expressions pushed into the chunks container, the error is handled and stored in the `chunk` object that contains the expression, and thus no error is thrown to the calling environment. - -The function `teal.code::chunks_is_ok` will return `TRUE` if all evaluated expressions in the chunks container evaluated without throwing an error. - -`teal.code::chunks_validate_is_ok` returns a useful `validate(need(...))` which a shiny app will use to display an error message in the UI instead of crashing the app. - -```{r, error=TRUE} -teal.code::chunks_is_ok(chunks_container) -teal.code::chunks_validate_is_ok(chunks = chunks_container) - -# Trying an error inside a chunk ------------------------ -teal.code::chunks_push(chunks = chunks_container, expression = quote(stop("ERROR"))) -teal.code::chunks_safe_eval(chunks_container) - -teal.code::chunks_is_ok(chunks_container) - -# internally, teal.code::chunks_safe_eval calls teal.code::chunks_validate_is_ok before returning -teal.code::chunks_validate_is_ok(chunks = chunks_container) -``` - -is_ok - -The use of the `teal.code::chunks_safe_eval` is good practice in a reactive context. Since errors are not thrown to the environment calling the chunks container, it will not crash the shiny app, which is a good thing. However, the shiny app will also not know that an error has occurred. Calling `teal.code::chunks_safe_eval` instead of the `eval` method of the chunks container ensures that a validation step occurs. - -## Tutorial Summary - -In summary: - -1. chunks containers host code snippets -2. chunks containers host their own environment -3. chunks container can be accessed to retrieve variables from the environment using `teal.code::chunks_get_var` -4. expressions can be added to the chunks container by `teal.code::chunks_push` -5. code inside a container is executed by its `eval` method. -6. `teal.code::chunks_is_ok` allows checking for execution errors -7. `teal.code::chunks_validate_is_ok` allows a shiny app to display a validate message of the errors in the UI -8. `teal.code::chunks_safe_eval` will evaluate all snippets of the chunks container and then call `validate(need(...))` to let a shiny app silently handle any errors that occurred so that error messages can be outputted in the UI. - -For more information about the implementation of chunks inside of shiny/teal module, please go on to the Advanced chunks article. +Chunks have been deprecated and will be removed from `teal.code` in a later release. +Please use [`qenv`](https://insightsengineering.github.io/teal.code/articles/qenv.html) instead. diff --git a/vignettes/images/container.png b/vignettes/images/container.png deleted file mode 100644 index 9eb7ed9272ed00f669d27e3f0b2d3e16b53e3639..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10140 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 K(UM=!|MnlUK;ZuX diff --git a/vignettes/images/eval.png b/vignettes/images/eval.png deleted file mode 100644 index b98f78aeadb7dd934898667b70a2a3ac6fe00cfb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25095 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 CIv`{K diff --git a/vignettes/images/initialize_env.png b/vignettes/images/initialize_env.png deleted file mode 100644 index f2ae8bb38ee354949597205ad352a871f2d73d68..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36339 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