diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index 4df14ebc92..c3dce49234 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -57,7 +57,7 @@ jobs: actions: read steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: r-lib/actions/setup-r@v2 with: diff --git a/.github/workflows/check-pandoc-daily.yaml b/.github/workflows/check-pandoc-daily.yaml index ede043bf14..553769c92a 100644 --- a/.github/workflows/check-pandoc-daily.yaml +++ b/.github/workflows/check-pandoc-daily.yaml @@ -26,7 +26,7 @@ jobs: R_KEEP_PKG_SOURCE: yes steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: r-lib/actions/setup-r@v2 with: diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml index 21e25e364b..eb8984c113 100644 --- a/.github/workflows/pkgdown.yaml +++ b/.github/workflows/pkgdown.yaml @@ -20,7 +20,7 @@ jobs: env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: r-lib/actions/setup-pandoc@v2 diff --git a/.github/workflows/update-citation-cff.yaml b/.github/workflows/update-citation-cff.yaml index 15ca84acfa..9606bdf108 100644 --- a/.github/workflows/update-citation-cff.yaml +++ b/.github/workflows/update-citation-cff.yaml @@ -17,7 +17,7 @@ jobs: env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: r-lib/actions/setup-r@v2 with: diff --git a/DESCRIPTION b/DESCRIPTION index c9fe009720..43c9ff3538 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Type: Package Package: rmarkdown Title: Dynamic Documents for R -Version: 2.27.1.9000 +Version: 2.28.2.9000 Authors@R: c( person("JJ", "Allaire", , "jj@posit.co", role = "aut"), person("Yihui", "Xie", , "xie@yihui.name", role = c("aut", "cre"), comment = c(ORCID = "0000-0003-0645-5666")), diff --git a/NEWS.md b/NEWS.md index 589321552e..3239cebfa0 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,4 +1,4 @@ -rmarkdown 2.28 +rmarkdown 2.29 ================================================================================ - `html_document` output allows `lib_dir` to point to a parent of the output @@ -9,8 +9,37 @@ rmarkdown 2.28 child directories with RMarkdown files. #146 and #1859. (thanks, @jonathan-g, #2199) +- `find_external_resources()` now correctly detects knitr child document provided with option like `child = c("child.Rmd")` (thanks, @rempsyc, #2574). + +- `knit_params_ask()` uses a `select` input for parameters which allow multiple selected values. Previously, a `radio` input was incorrectly used when the parameter had a small number of choices. + + ```yaml + params: + primaries: + choices: ["red", "yellow", "blue"] + multiple: true + ``` + + When `multiple` is not enabled, parameter configuration still uses `radio` when there are fewer than five choices. + + The `input` parameter field can still be used to force the configuration control. + + ```yaml + params: + grade: + input: radio + choices: ["A", "B", "C", "D", "F"] + ``` + + +rmarkdown 2.28 +================================================================================ + +- Add classes `odd`, `even`, and `header` back to table rows for Pandoc >= 3.2.1, so tables can be styled properly (thanks, @therealgenna, #2567). + - `beamer_presentation` support handling latex dependencies via the new `extra_dependencies` argument and declarations within chunks (e.g., `knitr::asis_output("", meta = list(rmarkdown::latex_dependency("longtable")))`) (thanks, @cderv, @atusy, #2478). + rmarkdown 2.27 ================================================================================ diff --git a/R/github_document.R b/R/github_document.R index 994496a115..8784c55641 100644 --- a/R/github_document.R +++ b/R/github_document.R @@ -102,8 +102,8 @@ github_document <- function(toc = FALSE, # don't activate math in Pandoc and pass it as is # https://github.blog/changelog/2022-05-19-render-mathematical-expressions-in-markdown/ math <- NULL - # TODO: Check for version - should be the default in Pandoc 2.19+ - variant <- paste0(variant, "+tex_math_dollars") + # Pandoc 3.4 uses +tex_math_gfm by default to write special gfm syntax. We choose to keep $$ and $ which are still supported by Github + variant <- if (pandoc_available("3.4")) paste0(variant, "-tex_math_gfm") else paste0(variant, "+tex_math_dollars") preview_math <- check_math_argument("mathjax") } else { # fallback to webtex diff --git a/R/html_document_base.R b/R/html_document_base.R index f22421d9ec..d8e03c32a0 100644 --- a/R/html_document_base.R +++ b/R/html_document_base.R @@ -249,7 +249,10 @@ html_document_base <- function(theme = NULL, knitr = NULL, pandoc = pandoc_options( to = "html", from = NULL, args = args, - lua_filters = pkg_file_lua(c("pagebreak.lua", "latex-div.lua")) + lua_filters = pkg_file_lua(c( + "pagebreak.lua", "latex-div.lua", + if (pandoc_available("3.2.1")) "table-classes.lua" + )) ), keep_md = FALSE, clean_supporting = FALSE, diff --git a/R/html_resources.R b/R/html_resources.R index c4aa4f68d0..e7005c56e4 100644 --- a/R/html_resources.R +++ b/R/html_resources.R @@ -316,7 +316,7 @@ discover_rmd_resources <- function(rmd_file, discover_single_resource) { rmd_content[idx], chunk_start, chunk_start + attr(chunk_line, "capture.length", exact = TRUE) - 2 ) - for (child_expr in c("\\bchild\\s*=\\s*'([^']+)'", "\\bchild\\s*=\\s*\"([^\"]+)\"")) { + for (child_expr in c("\\bchild\\s*=\\s*(?:c\\()?'([^']+)'\\)?", "\\bchild\\s*=\\s*(?:c\\()?\"([^\"]+)\"\\)?")) { child_match <- gregexpr(child_expr, chunk_text, perl = TRUE)[[1]] if (child_match > 0) { child_start <- attr(child_match, "capture.start", exact = TRUE) diff --git a/R/params.R b/R/params.R index 17b5ddccb1..74ca9d1eca 100644 --- a/R/params.R +++ b/R/params.R @@ -161,11 +161,13 @@ params_get_input <- function(param) { input <- param$input if (is.null(input)) { if (!is.null(param$choices)) { - ## radio buttons for a small number of choices, select otherwise. - if (length(param$choices) <= 4) { - input <- "radio" - } else { + ## select for a large number of choices and multiple choices. + if (isTRUE(param$multiple)) { + input <- "select" + } else if (length(param$choices) > 4) { input <- "select" + } else { + input <- "radio" } } else { ## Not choices. Look at the value type to find what input control we @@ -219,8 +221,7 @@ params_configurable <- function(param) { } # Some inputs (like selectInput) support the selection of # multiple entries through a "multiple" argument. - multiple_ok <- (!is.null(param$multiple) && param$multiple) - if (multiple_ok) { + if (isTRUE(param$multiple)) { return(TRUE) } # sliderInput supports either one or two-value inputs. diff --git a/README.md b/README.md index 2ba4dbcb43..f2f2015e69 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ If you want to use the rmarkdown package outside of RStudio, you can install the install.packages("rmarkdown") ``` -If you want to use the development version of the rmarkdown package (either with or without RStudio), you can install the package from GitHub via the [**remotes** package](https://remotes.r-lib.org): +If you want to use the development version of the rmarkdown package (either with or without RStudio), you can install the package from GitHub via the [**pak** package](https://pak.r-lib.org): ```r # install.packages("pak") diff --git a/inst/rmarkdown/lua/table-classes.lua b/inst/rmarkdown/lua/table-classes.lua new file mode 100644 index 0000000000..b4fb23ad36 --- /dev/null +++ b/inst/rmarkdown/lua/table-classes.lua @@ -0,0 +1,20 @@ +--[[ +Add classes 'odd' (or 'header' in table header) / 'even' to table rows +]] + +local function add_odd_even (rows, odd) + odd = odd or 'odd' + for rownum, row in ipairs(rows) do + row.classes:insert((rownum % 2) == 0 and 'even' or odd) + end + return rows +end + +function Table (tbl) + add_odd_even(tbl.head.rows, 'header') + for _, tblbody in ipairs(tbl.bodies) do + add_odd_even(tblbody.body) + end + add_odd_even(tbl.foot.rows) + return tbl +end diff --git a/tests/testthat/test-github_document.R b/tests/testthat/test-github_document.R index 3e622087ee..cc996513d9 100644 --- a/tests/testthat/test-github_document.R +++ b/tests/testthat/test-github_document.R @@ -13,7 +13,7 @@ test_that("toc has correct identifier", { } else { "before-pandoc-2.18" } - res <- render(tmp_file, github_document(toc = TRUE, html_preview = FALSE)) + res <- render(tmp_file, github_document(toc = TRUE, html_preview = FALSE), quiet = TRUE) expect_snapshot_file(res, "github-toc.md", compare = compare_file_text, variant = pandoc_version) }) @@ -32,7 +32,7 @@ test_that("toc has correct identifier also when sections are numbered ", { } else { "before-pandoc-2.18" } - res <- render(tmp_file, github_document(toc = TRUE, number_sections = TRUE, html_preview = FALSE)) + res <- render(tmp_file, github_document(toc = TRUE, number_sections = TRUE, html_preview = FALSE), quiet = TRUE) expect_snapshot_file(res, "github-toc-numbered.md", compare = compare_file_text, variant = pandoc_version) }) @@ -41,7 +41,7 @@ test_that("github_document produces atx-header", { skip_on_cran() # avoid pandoc issue on CRAN h <- paste0(Reduce(paste0, rep("#", 5), accumulate = TRUE), " title ", 1:5, "\n\n") tmp_file <- local_rmd_file(h) - res <- render(tmp_file, github_document(html_preview = FALSE)) + res <- render(tmp_file, github_document(html_preview = FALSE), quiet = TRUE) expect_snapshot_file(res, "github-atx.md", compare = compare_file_text) }) diff --git a/tests/testthat/test-lua-filters.R b/tests/testthat/test-lua-filters.R index 4f273dc0fe..9a907392cc 100644 --- a/tests/testthat/test-lua-filters.R +++ b/tests/testthat/test-lua-filters.R @@ -108,14 +108,15 @@ test_that("formats have the expected Lua filter", { } # different lua filter pgb <- "pagebreak"; lxd <- "latex-div" + tbl <- if (pandoc_available("3.2.1")) "table-classes" nbs <- "number-sections"; acs <- "anchor-sections" expect_filters(beamer_presentation(), c(pgb, lxd)) expect_filters(github_document(number_sections = TRUE), md_document(number_sections = TRUE)) - expect_filters(html_document(), c(pgb, lxd)) - expect_filters(html_document(anchor_sections = TRUE), c(pgb, lxd, acs)) - expect_filters(html_document(anchor_sections = list(depth = 3)), c(pgb, lxd, acs)) - expect_filters(html_document_base(), c(pgb, lxd)) + expect_filters(html_document(), c(pgb, lxd, tbl)) + expect_filters(html_document(anchor_sections = TRUE), c(pgb, lxd, tbl, acs)) + expect_filters(html_document(anchor_sections = list(depth = 3)), c(pgb, lxd, tbl, acs)) + expect_filters(html_document_base(), c(pgb, lxd, tbl)) expect_filters(latex_document(), c(pgb, lxd)) expect_filters(context_document(ext = ".tex"), c(pgb)) expect_filters(md_document(number_sections = TRUE), c(nbs)) @@ -123,7 +124,7 @@ test_that("formats have the expected Lua filter", { expect_filters(powerpoint_presentation(number_sections = TRUE), c(nbs)) expect_filters(odt_document(number_sections = TRUE), c(pgb, nbs)) expect_filters(rtf_document(number_sections = TRUE), c(nbs)) - expect_filters(slidy_presentation(number_sections = TRUE), c(pgb, lxd, nbs)) + expect_filters(slidy_presentation(number_sections = TRUE), c(pgb, lxd, tbl, nbs)) expect_filters( word_document(number_sections = TRUE), c(pgb, if (!pandoc_available("2.10.1")) nbs) diff --git a/tests/testthat/test-params.R b/tests/testthat/test-params.R index 04a8678bb4..2d45378704 100644 --- a/tests/testthat/test-params.R +++ b/tests/testthat/test-params.R @@ -105,6 +105,86 @@ test_that("parameters are configurable", { value = c(10, 20, 30)))) }) +test_that("params_get_input", { + # input derived from value type. + expect_equal(params_get_input(list( + value = TRUE + )), "checkbox") + expect_equal(params_get_input(list( + value = as.integer(42) + )), "numeric") + expect_equal(params_get_input(list( + value = 7.5 + )), "numeric") + expect_equal(params_get_input(list( + value = "character" + )), "text") + expect_equal(params_get_input(list( + value = Sys.Date() + )), "date") + expect_equal(params_get_input(list( + value = Sys.time() + )), "datetime") + + # numeric becomes a slider with both min and max + expect_equal(params_get_input(list( + value = as.integer(42), + min = 0 + )), "numeric") + expect_equal(params_get_input(list( + value = as.integer(42), + max = 100 + )), "numeric") + expect_equal(params_get_input(list( + value = as.integer(42), + min = 0, + max = 100 + )), "slider") + expect_equal(params_get_input(list( + value = 7.5, + min = 0 + )), "numeric") + expect_equal(params_get_input(list( + value = 7.5, + max = 10 + )), "numeric") + expect_equal(params_get_input(list( + value = 7.5, + min = 0, + max = 10 + )), "slider") + + # choices implies radio or select + expect_equal(params_get_input(list( + value = "red", + choices = c("red", "green", "blue") + )), "radio") + expect_equal(params_get_input(list( + value = "red", + multiple = TRUE, + choices = c("red", "green", "blue") + )), "select") + expect_equal(params_get_input(list( + value = "red", + choices = c("red", "green", "blue", "yellow", "black", "white") + )), "select") + expect_equal(params_get_input(list( + value = "red", + multiple = TRUE, + choices = c("red", "green", "blue", "yellow", "black", "white") + )), "select") + + # explicit input overrides inference + expect_equal(params_get_input(list( + input = "slider", + value = 42 + )), "slider") + expect_equal(params_get_input(list( + input = "file", + value = "data.csv" + )), "file") +}) + test_that("params hidden w/o show_default", { # file input is always NULL diff --git a/tests/testthat/test-resources.R b/tests/testthat/test-resources.R index 65c7891434..50d5beaf44 100644 --- a/tests/testthat/test-resources.R +++ b/tests/testthat/test-resources.R @@ -263,3 +263,24 @@ test_that("multiple resources in the includes option can be discovered", { expect_equal(resources, expected) }) + +test_that("knitr child are correctly discovered as resources from chunk options", { + expect_child_resource_found <- function(child_opts, child) { + dir <- withr::local_tempdir("find-child") + withr::local_dir(dir) + rmd <- "test.Rmd" + xfun::write_utf8( + text = knitr::knit_expand(text = c("```{r,<>}", "```"), delim = c("<<", ">>")), + rmd + ) + xfun::write_utf8(c("Content"), child) + expect_contains(find_external_resources(!!rmd)$path, !!child) + } + child <- "child.Rmd" + expect_child_resource_found('child="child.Rmd"', child) + expect_child_resource_found(' child = "child.Rmd"', child) + expect_child_resource_found('child=c("child.Rmd")', child) + expect_child_resource_found("child='child.Rmd'", child) + expect_child_resource_found(" child = 'child.Rmd'", child) + expect_child_resource_found("child=c('child.Rmd')", child) +})