Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make watcher more simpler and more self-contained #149

Merged
merged 5 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 12 additions & 15 deletions R/eval.R
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,15 @@
if (tolower(Sys.getenv('R_EVALUATE_BYPASS_MESSAGES')) == 'true')
keep_message = keep_warning = NA

# Capture output
watcher <- watchout(output_handler, debug = debug)

out <- vector("list", nrow(parsed))
for (i in seq_along(out)) {
if (debug) {
message(parsed$src[[i]])

Check warning on line 98 in R/eval.R

View check run for this annotation

Codecov / codecov/patch

R/eval.R#L98

Added line #L98 was not covered by tests
}

# if dev.off() was called, make sure to restore device to the one opened by
# evaluate() or existed before evaluate()
if (length(dev.list()) < devn) dev.set(dev)
Expand All @@ -99,8 +106,8 @@
out[[i]] <- evaluate_top_level_expression(
exprs = parsed$expr[[i]],
src = parsed$src[[i]],
watcher = watcher,
envir = envir,
debug = debug,
last = i == length(out),
use_try = stop_on_error != 2L,
keep_warning = keep_warning,
Expand All @@ -127,9 +134,9 @@
}

evaluate_top_level_expression <- function(exprs,
src = NULL,
src,
watcher,
envir = parent.frame(),
debug = FALSE,
last = FALSE,
use_try = FALSE,
keep_warning = TRUE,
Expand All @@ -139,15 +146,6 @@
output_handler = new_output_handler(),
include_timing = FALSE) {
stopifnot(is.expression(exprs))
if (debug) message(src)

# Capture output
w <- watchout(debug)
on.exit(w$close())

# Capture error output from try() (#88)
old_try_outfile <- options(try.outFile = w$get_con())
on.exit(options(old_try_outfile), add = TRUE)

if (log_echo && !is.null(src)) {
cat(src, "\n", sep = "", file = stderr())
Expand All @@ -160,8 +158,7 @@
handle_output <- function(plot = TRUE, incomplete_plots = FALSE) {
# if dev.cur() has changed, we should not record plots any more
plot <- plot && identical(dev, dev.cur())
out <- w$get_new(plot, incomplete_plots,
output_handler$text, output_handler$graphics)
out <- watcher(plot, incomplete_plots)
output <<- c(output, out)
}

Expand Down Expand Up @@ -221,7 +218,7 @@
if (include_timing) {
timing_fn <- function(x) system.time(x)[1:3]
} else {
timing_fn <- function(x) {x; NULL};
timing_fn <- function(x) {x; NULL}
}

user_handlers <- output_handler$calling_handlers
Expand Down
4 changes: 4 additions & 0 deletions R/utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,7 @@ defer <- function(expr, frame = parent.frame(), after = FALSE) {
}

`%||%` <- function(a, b) if (is.null(a)) b else a

compact <- function(x) {
x[!vapply(x, is.null, logical(1))]
}
85 changes: 44 additions & 41 deletions R/watcher.R
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,56 @@
#'
#' @param debug activate debug mode where output will be both printed to
#' screen and captured.
#' @param handler An ouptut handler object.
#' @param frame When this frame terminates, the watcher will automatically close.`
#' @return list containing four functions: `get_new`, `pause`,
#' `unpause`, `close`.
#' @keywords internal
watchout <- function(debug = FALSE) {
output <- character()
prev <- character()

con <- textConnection("output", "wr", local = TRUE)
watchout <- function(handler = new_output_handler(),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is technically a breaking change, but (unsurprisingly) I don't see any evidence that anyone has ever used it. It might be worth un-exporting in the next version to make it very clear that it's for internal use only.

debug = FALSE,
frame = parent.frame()) {
con <- file("", "w+b")
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A file() connection takes a bit longer to set up than a text connection, but it is faster to append too (especially as the output grows long since appending to a character vector is O(n^2)). It's also simpler and faster to read from, and any set up time is now amortised over all top level expressions.

defer(frame = frame, {
if (!test_con(con, isOpen)) {
con_error('The connection has been closed')

Check warning on line 16 in R/watcher.R

View check run for this annotation

Codecov / codecov/patch

R/watcher.R#L16

Added line #L16 was not covered by tests
}
sink()
close(con)
})
sink(con, split = debug)

list(
get_new = function(plot = FALSE, incomplete_plots = FALSE,
text_callback = identity, graphics_callback = identity) {
incomplete <- test_con(con, isIncomplete)
if (incomplete) cat("\n")

out <- list()

if (plot) {
out$graphics <- plot_snapshot(incomplete_plots)
if (!is.null(out$graphics)) graphics_callback(out$graphics)
}

n0 <- length(prev)
n1 <- length(output)
if (n1 > n0) {
new <- output[n0 + seq_len(n1 - n0)]
prev <<- output

out$text <- paste0(new, collapse = "\n")
if (!incomplete) out$text <- paste0(out$text, "\n")

text_callback(out$text)
}
# try() defaults to using stderr() so we need to explicitly override(#88)
old <- options(try.outFile = con)
defer(options(old), frame = frame)

function(plot = TRUE, incomplete_plots = FALSE) {
out <- list(
if (plot) plot_snapshot(incomplete_plots),
read_con(con)
)
if (!is.null(out[[1]])) {
handler$graphics(out[[1]])
}
if (!is.null(out[[2]])) {
handler$text(out[[2]])
}

compact(out)
}
}

unname(out)
},
pause = function() sink(),
unpause = function() sink(con, split = debug),
close = function() {
if (!test_con(con, isOpen)) con_error('The connection has been closed')
sink()
close(con)
output
},
get_con = function() con
)
read_con <- function(con, buffer = 32 * 1024) {
bytes <- raw()
repeat {
new <- readBin(con, "raw", n = buffer)
if (length(new) == 0) break
bytes <- c(bytes, new)
}
if (length(bytes) == 0) {
NULL
} else {
rawToChar(bytes)
}
}

test_con = function(con, test) {
Expand Down
6 changes: 5 additions & 1 deletion man/watchout.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading