From 5a77add70add7727fadf3030829446a607a106ab Mon Sep 17 00:00:00 2001 From: Lionel Henry Date: Thu, 2 Dec 2021 14:10:34 +0100 Subject: [PATCH 1/2] Add `knit_cnd_format()` generic --- DESCRIPTION | 3 +- NAMESPACE | 4 +++ NEWS.md | 2 ++ R/output.R | 49 +++++++++++++++++++++---- man/knit_cnd_format.Rd | 15 ++++++++ tests/testit/test-output.R | 73 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 139 insertions(+), 7 deletions(-) create mode 100644 man/knit_cnd_format.Rd diff --git a/DESCRIPTION b/DESCRIPTION index 4d9e8c2cbd..0880311fc0 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -143,7 +143,8 @@ Suggests: bslib, ragg, styler (>= 1.2.0), - targets (>= 0.6.0) + targets (>= 0.6.0), + rlang License: GPL URL: https://yihui.org/knitr/ BugReports: https://github.com/yihui/knitr/issues diff --git a/NAMESPACE b/NAMESPACE index 5e13c798d9..655ec9ba1a 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -2,6 +2,9 @@ S3method("$",knitr_strict_list) S3method(is_low_change,default) +S3method(knit_cnd_format,error) +S3method(knit_cnd_format,message) +S3method(knit_cnd_format,warning) S3method(knit_print,default) S3method(knit_print,knit_asis) S3method(knit_print,knit_asis_url) @@ -81,6 +84,7 @@ export(knit2pandoc) export(knit2pdf) export(knit2wp) export(knit_child) +export(knit_cnd_format) export(knit_code) export(knit_engines) export(knit_exit) diff --git a/NEWS.md b/NEWS.md index ea9c2951bb..f0d7d8573c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -74,6 +74,8 @@ This engine also works for other types of documents (e.g., `Rnw`) but it will not allow for nested code chunks within the `verbatim` engine. +* New `knit_cnd_format()` generic. It is called by the condition methods for `sew()`. Methods should return a full condition message, including the prefix (e.g. `Error:` or `Warning:`, including a call if the condition includes one). + ## BUG FIXES - The chunk option `child` also respects the package option `root.dir` now (thanks, @salim-b, https://community.rstudio.com/t/117563). diff --git a/R/output.R b/R/output.R index b928bc6a9b..3ddbfa30ae 100644 --- a/R/output.R +++ b/R/output.R @@ -529,23 +529,60 @@ msg_sanitize = function(message, type) { #' @export sew.warning = function(x, options, ...) { - call = if (is.null(x$call)) '' else { - call = deparse(x$call)[1] - if (call == 'eval(expr, envir, enclos)') '' else paste(' in', call) + if (is_eval_call(x$call)) { + x$call = NULL } - msg_wrap(sprintf('Warning%s: %s', call, conditionMessage(x)), 'warning', options) + msg_wrap(knit_cnd_format(x), 'warning', options) +} +is_eval_call = function(x) { + is.call(x) && identical(x[1], quote(eval())) } #' @export sew.message = function(x, options, ...) { - msg_wrap(paste(conditionMessage(x), collapse = ''), 'message', options) + msg_wrap(knit_cnd_format(x), 'message', options) } #' @export sew.error = function(x, options, ...) { - msg_wrap(as.character(x), 'error', options) + msg_wrap(knit_cnd_format(x), 'error', options) +} + +#' Format a condition prior to sewing +#' +#' This generic is called by condition methods for +#' \code{\link{sew}()}. Methods should return a full condition +#' message, including the prefix (e.g. `Error:` or `Warning:`, +#' including a call if the condition includes one). +#' +#' @keywords internal +#' @export +knit_cnd_format = function(cnd) { + UseMethod('knit_cnd_format') +} + +#' @export +knit_cnd_format.message = function(cnd) { + conditionMessage(cnd) } +#' @export +knit_cnd_format.warning = function(cnd) { + call = conditionCall(cnd) + if (is.null(call)) { + call = '' + } else { + call = paste(' in', deparse(call))[1] + } + sprintf('Warning%s: %s', call, conditionMessage(cnd)) +} + +#' @export +knit_cnd_format.error = function(cnd) { + as.character(cnd) +} + + #' @export sew.recordedplot = function(x, options, ...) { # figure number sequence for multiple plots diff --git a/man/knit_cnd_format.Rd b/man/knit_cnd_format.Rd new file mode 100644 index 0000000000..b7928582e4 --- /dev/null +++ b/man/knit_cnd_format.Rd @@ -0,0 +1,15 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/output.R +\name{knit_cnd_format} +\alias{knit_cnd_format} +\title{Format a condition prior to sewing} +\usage{ +knit_cnd_format(cnd) +} +\description{ +This generic is called by condition methods for +\code{\link{sew}()}. Methods should return a full condition +message, including the prefix (e.g. `Error:` or `Warning:`, +including a call if the condition includes one). +} +\keyword{internal} diff --git a/tests/testit/test-output.R b/tests/testit/test-output.R index ea5e0eaaf1..0de1122c3b 100644 --- a/tests/testit/test-output.R +++ b/tests/testit/test-output.R @@ -119,3 +119,76 @@ assert('knit_meta_add() adds meta objects with the correct number of labels', { knit_meta(clean = TRUE) (m %==% c('', '', 'a', 'b')) }) + +assert('sew() handles conditions', + identical( + sew(simpleError('msg', call = call('foo')), list()), + 'Error in foo(): msg\n' + ), + identical( + sew(simpleWarning('msg', call = call('foo')), list()), + 'Warning in foo(): msg\n' + ), + identical( + sew(simpleMessage('msg', call = call('foo')), list()), + 'msg\n' + ) +) + +local({ + assert('sew() strips `eval()` calls stored in warnings', { + cnd = tryCatch( + warning = identity, + eval(quote(warning("foo"))) + ) + identical( + sew(cnd, list()), + 'Warning: foo\n' + ) + }) +}) + +local({ + assert('knit_cnd_format() formats conditions', { + cnd = simpleMessage('msg', call = quote(foo())) + identical( + knit_cnd_format(cnd), + 'msg' + ) + }, { + cnd = simpleWarning('msg', call = quote(foo())) + identical( + knit_cnd_format(cnd), + 'Warning in foo(): msg' + ) + }, { + cnd = simpleWarning('msg', call = quote({ foo; bar; baz })) + identical( + knit_cnd_format(cnd), + 'Warning in {: msg' + ) + }, { + cnd = simpleMessage('msg', call = quote(foo())) + identical( + knit_cnd_format(cnd), + 'msg' + ) + }) +}) + +local({ + rlang::local_bindings( + .env = globalenv(), + knit_cnd_format.knitr_foobar = function(cnd) 'dispatched!' + ) + assert('sew() dispatches on knit_cnd_format() with condition objects', { + cnd = structure( + list(message = 'foo'), + class = c('knitr_foobar', 'error', 'condition') + ) + identical( + sew(cnd, list()), + 'dispatched!\n' + ) + }) +}) From 2e37579dbecad434b9918ef31aae00b295992a4f Mon Sep 17 00:00:00 2001 From: Lionel Henry Date: Thu, 2 Dec 2021 18:53:56 +0100 Subject: [PATCH 2/2] Collapse messages collected by `merge_class()` --- R/output.R | 4 +++- tests/testit/test-output.R | 9 +++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/R/output.R b/R/output.R index 3ddbfa30ae..f65231ffd1 100644 --- a/R/output.R +++ b/R/output.R @@ -563,7 +563,9 @@ knit_cnd_format = function(cnd) { #' @export knit_cnd_format.message = function(cnd) { - conditionMessage(cnd) + # Character vectors are created by `merge_class()` to support + # https://github.com/yihui/knitr-examples/blob/master/117-messages.Rmd + paste(conditionMessage(cnd), collapse = '') } #' @export diff --git a/tests/testit/test-output.R b/tests/testit/test-output.R index 0de1122c3b..9f5f8e492f 100644 --- a/tests/testit/test-output.R +++ b/tests/testit/test-output.R @@ -192,3 +192,12 @@ local({ ) }) }) + +# Character vectors are created by `merge_class()` to support +# https://github.com/yihui/knitr-examples/blob/master/117-messages.Rmd +assert('knit_cnd_format.message() supports character vectors', + identical( + knit_cnd_format(simpleMessage(c("foo", "bar"))), + "foobar" + ) +)