From 73c00f604c4cb03da934d5f939f559b58d2fd7a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kirill=20M=C3=BCller?= Date: Sat, 14 Dec 2024 16:11:15 +0100 Subject: [PATCH] feat: `tbl_format_setup()` gains a `setup` argument that supports printing the header before the data for the body is available, e.g., for remote backends such as databases (#686) * Live printing * Two-step computation of setup * Results * Provide rows_total as missing value in first pass * No failures * Address feedback --- R/tbl-format-setup.R | 59 +++++++++++++++++++++++++--------- R/tbl-format.R | 62 +++++++++++++++++++++++++++++------- man/new_tbl_format_setup.Rd | 26 +++++++-------- man/tbl_format_setup.Rd | 28 +++++++++++++++-- revdep/README.md | 6 ---- revdep/cran.md | 9 ++---- revdep/failures.md | 63 +------------------------------------ 7 files changed, 136 insertions(+), 117 deletions(-) diff --git a/R/tbl-format-setup.R b/R/tbl-format-setup.R index 45ab1be9f..3b017a279 100644 --- a/R/tbl-format-setup.R +++ b/R/tbl-format-setup.R @@ -10,13 +10,18 @@ #' and (implicitly) in the footer of a tibble; #' - the columns shown in the body decide which columns are shown in the footer. #' -#' This information is computed once in `tbl_format_setup()`. +#' This information is computed in `tbl_format_setup()`. #' The result is passed on to the #' [tbl_format_header()], [tbl_format_body()], and [tbl_format_footer()] #' methods. #' If you need to customize parts of the printed output independently, #' override these methods instead. #' +#' By checking the `setup` argument, you can return an object that is +#' suitable for a call to [tbl_format_header()] if `setup` is `NULL`. +#' In this case, the method is called a second time with the return value +#' of the first call as `setup`. +#' #' @details #' Extend this method to prepare information that is used #' in several parts of the printed output of a tibble-like object, @@ -41,6 +46,15 @@ #' This argument is mandatory for all implementations of this method. #' @param ... #' Extra arguments to [print.tbl()] or [format.tbl()]. +#' @param setup +#' This generic is first called with `setup = NULL` . +#' If the method _evaluates_ this argument, the return value +#' will only be used in a call to [tbl_format_header()], +#' and after that, a second call to this generic will be made +#' with the return value of the first call as `setup` +#' which then will be used in calls to [tbl_format_body()] and [tbl_format_footer()]. +#' This allows displaying the header before starting the computation +#' required for the body and footer. #' @param n #' Actual number of rows to print. #' No [options][pillar_options] should be considered @@ -67,6 +81,7 @@ tbl_format_setup <- function( x, width = NULL, ..., + setup = list(tbl_sum = tbl_sum(x)), n = NULL, max_extra_cols = NULL, max_footer_lines = NULL, @@ -87,6 +102,7 @@ tbl_format_setup <- function( x, width, ..., + setup = setup, n = n, max_extra_cols = max_extra_cols, max_footer_lines = max_footer_lines, @@ -107,10 +123,26 @@ tbl_format_setup_dispatch <- function(x, width, ..., n, max_extra_cols, max_foot #' #' @rdname tbl_format_setup #' @export -tbl_format_setup.tbl <- function(x, width, ..., - n, max_extra_cols, max_footer_lines, focus) { +tbl_format_setup.tbl <- function( + x, + width, + ..., + setup, + n, + max_extra_cols, + max_footer_lines, + focus +) { "!!!!DEBUG tbl_format_setup.tbl()" + if (is.null(setup)) { + # Header with early exit + tbl_sum <- tbl_sum(x) + return(new_tbl_format_setup(width, tbl_sum, rows_total = NA_integer_)) + } else { + tbl_sum <- setup$tbl_sum + } + # Number of rows rows <- tbl_nrow(x) @@ -140,9 +172,6 @@ tbl_format_setup.tbl <- function(x, width, ..., rows_missing <- 0L } - # Header - tbl_sum <- tbl_sum(x) - # Body rownames(df) <- NULL @@ -233,17 +262,17 @@ tbl_nrow.tbl <- function(x, ...) { #' #' @keywords internal new_tbl_format_setup <- function( - x, - df, width, tbl_sum, - body, - rows_missing, - rows_total, - extra_cols, - extra_cols_total, - max_footer_lines, - abbrev_cols + x = NULL, + df = NULL, + body = NULL, + rows_missing = NULL, + rows_total = NULL, + extra_cols = NULL, + extra_cols_total = NULL, + max_footer_lines = NULL, + abbrev_cols = NULL ) { trunc_info <- list( x = x, diff --git a/R/tbl-format.R b/R/tbl-format.R index 2d0c1a831..08bfe8d31 100644 --- a/R/tbl-format.R +++ b/R/tbl-format.R @@ -18,16 +18,17 @@ print_tbl <- function(x, width = NULL, ..., n = NULL, max_extra_cols = NULL, max_footer_lines = NULL) { if (!is.null(n_extra)) { deprecate_stop("1.6.2", "pillar::print(n_extra = )", "pillar::print(max_extra_cols = )") - if (is.null(max_extra_cols)) { - max_extra_cols <- n_extra - } } - writeLines(format( + # Printing happens as a side effect thanks to `transform = writeLines` . + # For formatting, the default `transform = identity` returns the data instead. + format_tbl( x, width = width, ..., - n = n, max_extra_cols = max_extra_cols, max_footer_lines = max_footer_lines - )) + n = n, max_extra_cols = max_extra_cols, max_footer_lines = max_footer_lines, + transform = writeLines + ) + invisible(x) } @@ -48,7 +49,8 @@ format_tbl <- function( n_extra = NULL, n = NULL, max_extra_cols = NULL, - max_footer_lines = NULL + max_footer_lines = NULL, + transform = identity ) { check_dots_empty(action = signal) @@ -60,17 +62,53 @@ format_tbl <- function( force(x) num_colors(forget = TRUE) - setup <- tbl_format_setup(x, - width = width, ..., + # This is a bit of a hack to allow the setup function to be called twice + # if the implementer is prepared to handle that. + # We detect that by checking if the `setup` argument has been evaluated. + setup_used <- FALSE + + # In either case, we expect a `setup` object that can be passed to `tbl_format_header()` + # as a return from this call. + setup <- tbl_format_setup( + x, + width = width, + ..., + setup = { + # This construct updates the `setup_used` variable in the parent scope + # when the `setup` argument is evaluated. + setup_used <- TRUE + NULL + }, n = n, max_extra_cols = max_extra_cols, max_footer_lines = max_footer_lines, focus = attr(x, "pillar_focus") ) - header <- tbl_format_header(x, setup) - body <- tbl_format_body(x, setup) - footer <- tbl_format_footer(x, setup) + header <- transform(tbl_format_header(x, setup)) + + # If the implementation did not request the `setup` argument in the first call, + # the default behavior before 1.9.1 is used: the first call already + # has returned the full setup object. + # Otherwise, we assume that a second call is required, and we pass it the + # setup object returned from the first call. + if (setup_used) { + setup <- tbl_format_setup( + x, + width = width, + ..., + setup = setup, + n = n, + max_extra_cols = max_extra_cols, + max_footer_lines = max_footer_lines, + focus = attr(x, "pillar_focus") + ) + } + + # In either case, the `setup` object is now complete and can be used to format the body + # and the footer. + body <- transform(tbl_format_body(x, setup)) + footer <- transform(tbl_format_footer(x, setup)) c(header, body, footer) } diff --git a/man/new_tbl_format_setup.Rd b/man/new_tbl_format_setup.Rd index d8a422684..d7b59df55 100644 --- a/man/new_tbl_format_setup.Rd +++ b/man/new_tbl_format_setup.Rd @@ -5,29 +5,29 @@ \title{Construct a setup object for formatting} \usage{ new_tbl_format_setup( - x, - df, width, tbl_sum, - body, - rows_missing, - rows_total, - extra_cols, - extra_cols_total, - max_footer_lines, - abbrev_cols + x = NULL, + df = NULL, + body = NULL, + rows_missing = NULL, + rows_total = NULL, + extra_cols = NULL, + extra_cols_total = NULL, + max_footer_lines = NULL, + abbrev_cols = NULL ) } \arguments{ +\item{width}{The \code{width} argument unchanged.} + +\item{tbl_sum}{A named character vector, as returned from \code{\link[=tbl_sum]{tbl_sum()}}.} + \item{x}{The input object unchanged.} \item{df}{A data frame representation of the intended output, trimmed to the desired number of rows.} -\item{width}{The \code{width} argument unchanged.} - -\item{tbl_sum}{A named character vector, as returned from \code{\link[=tbl_sum]{tbl_sum()}}.} - \item{body}{A character vector with the formatted body, one element per line,} diff --git a/man/tbl_format_setup.Rd b/man/tbl_format_setup.Rd index f71551442..b19d99c99 100644 --- a/man/tbl_format_setup.Rd +++ b/man/tbl_format_setup.Rd @@ -9,13 +9,23 @@ tbl_format_setup( x, width = NULL, ..., + setup = list(tbl_sum = tbl_sum(x)), n = NULL, max_extra_cols = NULL, max_footer_lines = NULL, focus = NULL ) -\method{tbl_format_setup}{tbl}(x, width, ..., n, max_extra_cols, max_footer_lines, focus) +\method{tbl_format_setup}{tbl}( + x, + width, + ..., + setup, + n, + max_extra_cols, + max_footer_lines, + focus +) } \arguments{ \item{x}{An object.} @@ -25,6 +35,15 @@ This argument is mandatory for all implementations of this method.} \item{...}{Extra arguments to \code{\link[=print.tbl]{print.tbl()}} or \code{\link[=format.tbl]{format.tbl()}}.} +\item{setup}{This generic is first called with \code{setup = NULL} . +If the method \emph{evaluates} this argument, the return value +will only be used in a call to \code{\link[=tbl_format_header]{tbl_format_header()}}, +and after that, a second call to this generic will be made +with the return value of the first call as \code{setup} +which then will be used in calls to \code{\link[=tbl_format_body]{tbl_format_body()}} and \code{\link[=tbl_format_footer]{tbl_format_footer()}}. +This allows displaying the header before starting the computation +required for the body and footer.} + \item{n}{Actual number of rows to print. No \link[=pillar_options]{options} should be considered by implementations of this method.} @@ -57,12 +76,17 @@ and (implicitly) in the footer of a tibble; \item the columns shown in the body decide which columns are shown in the footer. } -This information is computed once in \code{tbl_format_setup()}. +This information is computed in \code{tbl_format_setup()}. The result is passed on to the \code{\link[=tbl_format_header]{tbl_format_header()}}, \code{\link[=tbl_format_body]{tbl_format_body()}}, and \code{\link[=tbl_format_footer]{tbl_format_footer()}} methods. If you need to customize parts of the printed output independently, override these methods instead. + +By checking the \code{setup} argument, you can return an object that is +suitable for a call to \code{\link[=tbl_format_header]{tbl_format_header()}} if \code{setup} is \code{NULL}. +In this case, the method is called a second time with the return value +of the first call as \code{setup}. } \details{ Extend this method to prepare information that is used diff --git a/revdep/README.md b/revdep/README.md index 5377f304a..52e12c714 100644 --- a/revdep/README.md +++ b/revdep/README.md @@ -1,8 +1,2 @@ # Revdeps -## Failed to check (1) - -|package |version |error |warning |note | -|:----------|:-------|:-----|:-------|:----| -|tidyseurat |0.8.0 |1 | | | - diff --git a/revdep/cran.md b/revdep/cran.md index 95a78e658..29c196116 100644 --- a/revdep/cran.md +++ b/revdep/cran.md @@ -1,12 +1,7 @@ ## revdepcheck results -We checked 122 reverse dependencies, comparing R CMD check results across CRAN and dev versions of this package. +We checked 3 reverse dependencies, comparing R CMD check results across CRAN and dev versions of this package. * We saw 0 new problems - * We failed to check 1 packages + * We failed to check 0 packages -Issues with CRAN packages are summarised below. - -### Failed to check - -* tidyseurat (NA) diff --git a/revdep/failures.md b/revdep/failures.md index a49456a4a..9a2073633 100644 --- a/revdep/failures.md +++ b/revdep/failures.md @@ -1,62 +1 @@ -# tidyseurat - -
- -* Version: 0.8.0 -* GitHub: https://github.com/stemangiola/tidyseurat -* Source code: https://github.com/cran/tidyseurat -* Date/Publication: 2024-01-10 04:50:02 UTC -* Number of recursive dependencies: 195 - -Run `revdepcheck::cloud_details(, "tidyseurat")` for more info - -
- -## In both - -* checking whether package ‘tidyseurat’ can be installed ... ERROR - ``` - Installation failed. - See ‘/tmp/workdir/tidyseurat/new/tidyseurat.Rcheck/00install.out’ for details. - ``` - -## Installation - -### Devel - -``` -* installing *source* package ‘tidyseurat’ ... -** package ‘tidyseurat’ successfully unpacked and MD5 sums checked -** using staged installation -** R -** data -*** moving datasets to lazyload DB -** inst -** byte-compile and prepare package for lazy loading -Error: package or namespace load failed for ‘SeuratObject’ in loadNamespace(j <- i[[1L]], c(lib.loc, .libPaths()), versionCheck = vI[[j]]): - namespace ‘Matrix’ 1.5-4.1 is already loaded, but >= 1.6.4 is required -Execution halted -ERROR: lazy loading failed for package ‘tidyseurat’ -* removing ‘/tmp/workdir/tidyseurat/new/tidyseurat.Rcheck/tidyseurat’ - - -``` -### CRAN - -``` -* installing *source* package ‘tidyseurat’ ... -** package ‘tidyseurat’ successfully unpacked and MD5 sums checked -** using staged installation -** R -** data -*** moving datasets to lazyload DB -** inst -** byte-compile and prepare package for lazy loading -Error: package or namespace load failed for ‘SeuratObject’ in loadNamespace(j <- i[[1L]], c(lib.loc, .libPaths()), versionCheck = vI[[j]]): - namespace ‘Matrix’ 1.5-4.1 is already loaded, but >= 1.6.4 is required -Execution halted -ERROR: lazy loading failed for package ‘tidyseurat’ -* removing ‘/tmp/workdir/tidyseurat/old/tidyseurat.Rcheck/tidyseurat’ - - -``` +*Wow, no problems at all. :)* \ No newline at end of file