From cfe7834fe79b4021353a1612954059a641b7a81b Mon Sep 17 00:00:00 2001 From: Maxime Pettinger Date: Wed, 24 May 2023 07:05:21 +0200 Subject: [PATCH 1/7] add support for Emacs Org-mode tables --- R/table.R | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/R/table.R b/R/table.R index 076362b029..7fa3c49ca2 100644 --- a/R/table.R +++ b/R/table.R @@ -181,7 +181,7 @@ kable_format = function(format = NULL) { if (is.null(format)) format = if (is.null(pandoc_to())) switch( out_format() %n% 'markdown', latex = 'latex', listings = 'latex', sweave = 'latex', - html = 'html', markdown = 'pipe', rst = 'rst', + html = 'html', markdown = 'pipe', rst = 'rst', org = 'org', stop('table format not implemented yet!') ) else if (isTRUE(opts_knit$get('kable.force.latex')) && is_latex_output()) { # force LaTeX table because Pandoc's longtable may not work well with floats @@ -468,11 +468,29 @@ kable_jira = function(x, caption = NULL, padding = 1, ...) { kable_pandoc_caption(tab, caption) } +# Emacs Org-mode table +kable_org = function(x, caption = NULL, padding = 1, ...) { + if (is.null(colnames(x))) colnames(x) = rep('', ncol(x)) + res = kable_mark(x, c(NA, '-', NA), '|', padding, align.fun = function(s, a) { + if (is.null(a)) return(s) + r = c(l = '^.', c = '^.|.$', r = '.$') + s + }, ...) + res = sprintf('|%s|', res) + res = gsub("\\-\\|\\-", "-+-", x = res) + kable_org_caption(res, caption) +} + kable_pandoc_caption = function(x, caption) { if (identical(caption, NA)) caption = NULL if (length(caption)) c(paste('Table:', caption), "", x) else x } +kable_org_caption = function(x, caption) { + if (identical(caption, NA)) caption = NULL + if (length(caption)) c(paste('#+CAPTION:', caption), x) else x +} + # pad a matrix mat_pad = function(m, width, align = NULL) { n = nrow(m); p = ncol(m) From eb6f6353e102f9f3c729956950a8f2842f6f833c Mon Sep 17 00:00:00 2001 From: Yihui Xie Date: Fri, 26 May 2023 23:25:49 -0500 Subject: [PATCH 2/7] out_format() can't be `org` --- R/table.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/table.R b/R/table.R index 7fa3c49ca2..04ee5dc04e 100644 --- a/R/table.R +++ b/R/table.R @@ -181,7 +181,7 @@ kable_format = function(format = NULL) { if (is.null(format)) format = if (is.null(pandoc_to())) switch( out_format() %n% 'markdown', latex = 'latex', listings = 'latex', sweave = 'latex', - html = 'html', markdown = 'pipe', rst = 'rst', org = 'org', + html = 'html', markdown = 'pipe', rst = 'rst', stop('table format not implemented yet!') ) else if (isTRUE(opts_knit$get('kable.force.latex')) && is_latex_output()) { # force LaTeX table because Pandoc's longtable may not work well with floats From 1b383a1d6aa6a095bfc21f356453b98cf664cc65 Mon Sep 17 00:00:00 2001 From: Yihui Xie Date: Fri, 26 May 2023 23:41:24 -0500 Subject: [PATCH 3/7] mention the `org` format on help page --- R/table.R | 10 +++++----- man/kable.Rd | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/R/table.R b/R/table.R index 04ee5dc04e..947eff9fd0 100644 --- a/R/table.R +++ b/R/table.R @@ -17,11 +17,11 @@ #' returned value from \code{kable()}. #' @param format A character string. Possible values are \code{latex}, #' \code{html}, \code{pipe} (Pandoc's pipe tables), \code{simple} (Pandoc's -#' simple tables), \code{rst}, and \code{jira}. The value of this argument -#' will be automatically determined if the function is called within a -#' \pkg{knitr} document. The \code{format} value can also be set in the global -#' option \code{knitr.table.format}. If \code{format} is a function, it must -#' return a character string. +#' simple tables), \code{rst}, \code{jira}, and \code{org} (Emacs Org-mode). +#' The value of this argument will be automatically determined if the function +#' is called within a \pkg{knitr} document. The \code{format} value can also +#' be set in the global option \code{knitr.table.format}. If \code{format} is +#' a function, it must return a character string. #' @param digits Maximum number of digits for numeric columns, passed to #' \code{round()}. This can also be a vector of length \code{ncol(x)}, to set #' the number of digits for individual columns. diff --git a/man/kable.Rd b/man/kable.Rd index 109491c445..e43b362675 100644 --- a/man/kable.Rd +++ b/man/kable.Rd @@ -28,11 +28,11 @@ returned value from \code{kable()}.} \item{format}{A character string. Possible values are \code{latex}, \code{html}, \code{pipe} (Pandoc's pipe tables), \code{simple} (Pandoc's -simple tables), \code{rst}, and \code{jira}. The value of this argument -will be automatically determined if the function is called within a -\pkg{knitr} document. The \code{format} value can also be set in the global -option \code{knitr.table.format}. If \code{format} is a function, it must -return a character string.} +simple tables), \code{rst}, \code{jira}, and \code{org} (Emacs Org-mode). +The value of this argument will be automatically determined if the function +is called within a \pkg{knitr} document. The \code{format} value can also +be set in the global option \code{knitr.table.format}. If \code{format} is +a function, it must return a character string.} \item{digits}{Maximum number of digits for numeric columns, passed to \code{round()}. This can also be a vector of length \code{ncol(x)}, to set From 129c292e6ebb54ce24e6c897298ca92040218973 Mon Sep 17 00:00:00 2001 From: Yihui Xie Date: Thu, 31 Aug 2023 21:49:37 -0500 Subject: [PATCH 4/7] simply the code with strrep() --- R/table.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/table.R b/R/table.R index 40024983da..0725c4ef1a 100644 --- a/R/table.R +++ b/R/table.R @@ -408,7 +408,7 @@ kable_mark = function(x, sep.row = c('=', '=', '='), sep.col = ' ', padding = 0 ifelse(align == 'c', 2, 1) } l = pmax(l + padding, 3) # at least of width 3 for Github Markdown - s = unlist(lapply(l, function(i) paste(rep(sep.row[2], i), collapse = ''))) + s = strrep(sep.row[2], l) res = rbind(if (!is.na(sep.row[1])) s, cn, align.fun(s, align), x, if (!is.na(sep.row[3])) s) res = mat_pad(res, l, align) From a29fd348917dc715bd0daa4e164e66f57615f922 Mon Sep 17 00:00:00 2001 From: Yihui Xie Date: Thu, 31 Aug 2023 22:35:25 -0500 Subject: [PATCH 5/7] let kable_org() post-process results from kable_pipe() since the two table formats are very similar the two differences are: 1. org uses + as separator for the line under header; 2. org uses #+CAPTION as the caption label --- R/table.R | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/R/table.R b/R/table.R index 0725c4ef1a..c2be226363 100644 --- a/R/table.R +++ b/R/table.R @@ -431,7 +431,7 @@ kable_rst = function(x, rownames.name = '\\', ...) { } # Pandoc's pipe table -kable_pipe = function(x, caption = NULL, padding = 1, ...) { +kable_pipe = function(x, caption = NULL, padding = 1, caption.label = 'Table:', ...) { if (is.null(colnames(x))) colnames(x) = rep('', ncol(x)) res = kable_mark(x, c(NA, '-', NA), '|', padding, align.fun = function(s, a) { if (is.null(a)) return(s) @@ -442,7 +442,7 @@ kable_pipe = function(x, caption = NULL, padding = 1, ...) { s }, ...) res = sprintf('|%s|', res) - kable_pandoc_caption(res, caption) + kable_pandoc_caption(res, caption, caption.label) } # Pandoc's simple table @@ -469,26 +469,19 @@ kable_jira = function(x, caption = NULL, padding = 1, ...) { } # Emacs Org-mode table -kable_org = function(x, caption = NULL, padding = 1, ...) { - if (is.null(colnames(x))) colnames(x) = rep('', ncol(x)) - res = kable_mark(x, c(NA, '-', NA), '|', padding, align.fun = function(s, a) { - if (is.null(a)) return(s) - r = c(l = '^.', c = '^.|.$', r = '.$') - s - }, ...) - res = sprintf('|%s|', res) - res = gsub("\\-\\|\\-", "-+-", x = res) - kable_org_caption(res, caption) -} - -kable_pandoc_caption = function(x, caption) { - if (identical(caption, NA)) caption = NULL - if (length(caption)) c(paste('Table:', caption), "", x) else x +kable_org = function(...) { + res = kable_pipe(..., caption.label = '#+CAPTION:') + i = grep('^[-:|]+$', res) # find the line like |--:|---| under header + if (length(i)) { + i = i[1] + res[i] = gsub('(-|:)[|](-|:)', '\\1+\\2', res[i]) # use + as separator + } + res } -kable_org_caption = function(x, caption) { +kable_pandoc_caption = function(x, caption, label = 'Table:') { if (identical(caption, NA)) caption = NULL - if (length(caption)) c(paste('#+CAPTION:', caption), x) else x + if (length(caption)) c(paste(label, caption), '', x) else x } # pad a matrix From 1c038c09bc1d8a87eadb3217f9873d46cc18a666 Mon Sep 17 00:00:00 2001 From: Yihui Xie Date: Thu, 31 Aug 2023 22:38:53 -0500 Subject: [PATCH 6/7] add tests --- tests/testit/test-table.R | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/testit/test-table.R b/tests/testit/test-table.R index b5cb419f1f..dc4dba44d6 100644 --- a/tests/testit/test-table.R +++ b/tests/testit/test-table.R @@ -39,6 +39,10 @@ assert('kable() works with the jira format', { (kable2(m, 'jira') %==% c('|| || x|| y||', '|a | 1| 2|')) }) +assert('kable() works with the org format', { + (kable2(m, 'org') %==% c('| | x| y|', '|:--+--:+--:|', '|a | 1| 2|')) +}) + assert('kable() does not add extra spaces to character columns', { (kable2(data.frame(x = c(1.2, 4.87), y = c('fooooo', 'bar')), 'latex') %==% ' \\begin{tabular}{r|l} @@ -136,7 +140,7 @@ assert('kable() works on matrices with NA colname', { x1 = matrix(NA, 0, 0) x2 = matrix(NA, 0, 1) x3 = matrix(NA, 1, 0) -for (f in c('simple', 'html', 'latex', 'rst', 'jira')) { +for (f in c('simple', 'html', 'latex', 'rst', 'jira', 'org')) { kable(x1, f) kable(x2, f) kable(x3, f) From 0ce9ff71b31629f739b059969f054177951e85ec Mon Sep 17 00:00:00 2001 From: Yihui Xie Date: Thu, 31 Aug 2023 22:44:52 -0500 Subject: [PATCH 7/7] add news --- DESCRIPTION | 2 +- NEWS.md | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 3d99d0bca4..e751e3c154 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: knitr Type: Package Title: A General-Purpose Package for Dynamic Report Generation in R -Version: 1.43.14 +Version: 1.43.15 Authors@R: c( person("Yihui", "Xie", role = c("aut", "cre"), email = "xie@yihui.name", comment = c(ORCID = "0000-0003-0645-5666")), person("Abhraneel", "Sarma", role = "ctb"), diff --git a/NEWS.md b/NEWS.md index 01b8a74dbc..28abd52646 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,8 @@ ## NEW FEATURES +- `kable()` can generate Emacs org-mode tables now via `kable(..., format = 'org')` (thanks, @xvrdm #1372, @maxecharel #2258). + - Added support for the `qmd` (Quarto) output format to `spin()`, e.g., `spin('script.R', format = 'qmd') (thanks, @cderv, #2284). - `write_bib()` has a new argument `packageURL` to control whether to use a URL from the `DESCRIPTION` file or the one generated by `utils::citation()` (thanks, @dmurdoch, #2264).