From 11d37b96ab72b1389573d79b4955b48417cca944 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Cs=C3=A1rdi?= Date: Fri, 13 Sep 2019 11:21:37 +0100 Subject: [PATCH] Support level 1 headings in markdown They are supported in (well, after, really) selected roxygen tags currently (see `tag_markdown_with_sections` entries in rd.R). --- NAMESPACE | 1 + NEWS.md | 9 ++--- R/markdown.R | 13 ++++---- R/rd-include-rmd.R | 1 + R/rd.R | 25 ++++++++------ R/tag.R | 18 ++++++++++ man/roxy_tag.Rd | 3 ++ tests/testthat/test-markdown.R | 52 ++++++++++++++++++++++++++++- tests/testthat/test-rd-includermd.R | 11 +++--- 9 files changed, 105 insertions(+), 28 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index e488a32ac..2c45e2f51 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -102,6 +102,7 @@ export(tag_code) export(tag_examples) export(tag_inherit) export(tag_markdown) +export(tag_markdown_with_sections) export(tag_name) export(tag_name_description) export(tag_toggle) diff --git a/NEWS.md b/NEWS.md index 62f1ab6df..415f84150 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,9 +1,10 @@ # roxygen2 (development version) -* Subheadings, i.e. headings of level 2 and above, are now allowed in - markdown text. They generate subsections within the roxygen tag - (within `@description`, `@details`, `@return`, etc.) `##` creates a - subsection, `###` a sub-subsection, etc. Headings can be nested (#907). +* Headings are now allowed in markdown text. Subheadings, i.e. headings + with level 2 and above, generate subsections within roxygen tags + (e.g. within `@description`, `@details`, `@return`, etc.) `##` creates a + subsection, `###` a sub-subsection, etc. Level 1 headings generate + separate sections in the manual page (#907, #908). * `\link{foo}` links in external inherited documentation are automatically transformed to `\link{package}{foo}` so that they work in the generated diff --git a/R/markdown.R b/R/markdown.R index 83ec24192..5484de292 100644 --- a/R/markdown.R +++ b/R/markdown.R @@ -1,21 +1,22 @@ -markdown_if_active <- function(text, tag) { +markdown_if_active <- function(text, tag, sections = FALSE) { if (markdown_on()) { - markdown(text, tag) + markdown(text, tag, sections) } else { text } } -markdown <- function(text, tag = NULL) { +markdown <- function(text, tag = NULL, sections = FALSE) { esc_text <- escape_rd_for_md(text) esc_text_linkrefs <- add_linkrefs_to_md(esc_text) mdxml <- md_to_mdxml(esc_text_linkrefs) state <- new.env(parent = emptyenv()) state$tag <- tag + state$has_sections <- sections rd <- mdxml_children_to_rd_top(mdxml, state) - unescape_rd_for_md(str_trim(rd$main), esc_text) + map_chr(rd, unescape_rd_for_md, esc_text) } md_to_mdxml <- function(x) { @@ -29,7 +30,7 @@ mdxml_children_to_rd_top <- function(xml, state) { out <- c(out, mdxml_close_sections(state)) rd <- paste0(out, collapse = "") secs <- strsplit(rd, state$section_tag, fixed = TRUE)[[1]] - list(main = secs[[1]], sections = secs[-1]) + str_trim(secs) } mdxml_children_to_rd <- function(xml, state) { @@ -175,7 +176,7 @@ escape_comment <- function(x) { mdxml_heading <- function(xml, state) { level <- xml_attr(xml, "level") - if (state$tag$tag != "@includeRmd" && level == 1) { + if (! state$has_sections && level == 1) { return(mdxml_unsupported(xml, state$tag, "level 1 markdown headings")) } head <- paste0( diff --git a/R/rd-include-rmd.R b/R/rd-include-rmd.R index 00db169bf..1b7911a9a 100644 --- a/R/rd-include-rmd.R +++ b/R/rd-include-rmd.R @@ -54,6 +54,7 @@ rmd_eval_rd <- function(path, tag) { mdxml <- xml2::read_xml(mdx) state <- new.env(parent = emptyenv()) state$tag <- tag + state$has_sections <- TRUE rd <- mdxml_children_to_rd_top(mdxml, state = state) rd } diff --git a/R/rd.R b/R/rd.R index 43389f876..b64fa7d2a 100644 --- a/R/rd.R +++ b/R/rd.R @@ -41,8 +41,8 @@ roclet_tags.roclet_rd <- function(x) { backref = tag_value, concept = tag_markdown, describeIn = tag_name_description, - description = tag_markdown, - details = tag_markdown, + description = tag_markdown_with_sections, + details = tag_markdown_with_sections, docType = tag_name, encoding = tag_value, evalRd = tag_code, @@ -50,7 +50,7 @@ roclet_tags.roclet_rd <- function(x) { examples = tag_examples, family = tag_value, field = tag_name_description, - format = tag_markdown, + format = tag_markdown_with_sections, includeRmd = tag_value, inherit = tag_inherit, inheritParams = tag_value, @@ -62,16 +62,16 @@ roclet_tags.roclet_rd <- function(x) { md = tag_toggle, noMd = tag_toggle, noRd = tag_toggle, - note = tag_markdown, + note = tag_markdown_with_sections, param = tag_name_description, rdname = tag_value, rawRd = tag_value, references = tag_markdown, - return = tag_markdown, + return = tag_markdown_with_sections, section = tag_markdown, seealso = tag_markdown, slot = tag_name_description, - source = tag_markdown, + source = tag_markdown_with_sections, template = tag_value, templateVar = tag_name_description, title = tag_markdown, @@ -243,7 +243,10 @@ topic_add_simple_tags <- function(topic, block) { tag_names <- names(block)[is_simple] for (i in seq_along(tag_values)) { - topic$add_simple_field(tag_names[[i]], tag_values[[i]]) + topic$add_simple_field(tag_names[[i]], tag_values[[i]][1]) + for (sec in tag_values[[i]][-1]) { + topic$add_simple_field("rawRd", sec) + } } } @@ -411,7 +414,7 @@ topic_add_include_rmd <- function(topic, block, base_path) { for (rmd in rmds) { tag <- roxy_tag( - "@includeRmd", + "includeRmd", rmd, attr(block, "filename"), attr(block, "location")[[1]] @@ -420,10 +423,10 @@ topic_add_include_rmd <- function(topic, block, base_path) { roxy_tag_warning(tag, "Needs the rmarkdown package") } out <- block_include_rmd(tag, block, base_path) - if (!is.null(out$main)) { - topic$add_simple_field("details", out$main) + if (!is.null(out[[1]])) { + topic$add_simple_field("details", out[[1]]) } - lapply(out$sections, function(s) { + lapply(out[-1], function(s) { topic$add_simple_field("rawRd", s) }) } diff --git a/R/tag.R b/R/tag.R index 470a2b01b..2f5833dda 100644 --- a/R/tag.R +++ b/R/tag.R @@ -241,3 +241,21 @@ tag_markdown <- function(x) { x$val <- markdown_if_active(x$val, x) tag_value(x) } + +#' @export +#' @rdname roxy_tag +tag_markdown_with_sections <- function(x) { + x$val <- markdown_if_active(x$val, x, sections = TRUE) + if (length(x$val) == 1 && x$val[[1]] == "") { + roxy_tag_warning(x, "requires a value") + } + for (i in seq_along(x$val)) { + if (!rdComplete(x$val[i])) { + roxy_tag_warning(x, "mismatched braces or quotes") + } else { + x$val[i] <- str_trim(x$val[i]) + } + } + + x +} diff --git a/man/roxy_tag.Rd b/man/roxy_tag.Rd index ffee9ba8a..6f6eea24e 100644 --- a/man/roxy_tag.Rd +++ b/man/roxy_tag.Rd @@ -14,6 +14,7 @@ \alias{tag_code} \alias{tag_examples} \alias{tag_markdown} +\alias{tag_markdown_with_sections} \title{Parsing tags.} \usage{ roxy_tag(tag, val, file = "", line = 0) @@ -41,6 +42,8 @@ tag_code(x) tag_examples(x) tag_markdown(x) + +tag_markdown_with_sections(x) } \arguments{ \item{tag}{Tag name} diff --git a/tests/testthat/test-markdown.R b/tests/testthat/test-markdown.R index 330a2af77..2372914b0 100644 --- a/tests/testthat/test-markdown.R +++ b/tests/testthat/test-markdown.R @@ -404,10 +404,11 @@ test_that("unhandled markdown generates warning", { expect_warning(roc_proc_text(rd_roclet(), text), "block quotes") }) -test_that("level 1 heading in markdown generates warning", { +test_that("level 1 heading in markdown generates warning in some tags", { text <- " #' Title #' + #' @seealso this and that #' # This is not good #' #' Blabla @@ -482,3 +483,52 @@ test_that("level >2 markdown headings work in @return", { "Even this\n\\subsection{Can have a subsection.}{\n\nYes.\n}" ) }) + +test_that("level 1 heading in @details", { + text1 <- " + #' Title + #' + #' Description. + #' + #' @details + #' Leading text goes into details. + #' # This is its own section + #' ## Can have a subsection + #' Yes. + #' # Another section + #' With text. + #' @md + #' @name x + NULL + " + out1 <- roc_proc_text(rd_roclet(), text1)[[1]] + text2 <- " + #' Title + #' + #' Description. + #' @details + #' Leading text goes into details. + #' @rawRd + #' \\section{This is its own section}{ + #' \\subsection{Can have a subsection}{ + #' + #' Yes. + #' } + #' + #' } + #' @rawRd + #' \\section{Another section}{ + #' + #' With text. + #' } + #' @name x + NULL + " + out2 <- roc_proc_text(rd_roclet(), text2)[[1]] + + # make sure fields are in the same order + expect_equal(sort(names(out1$fields)), sort(names(out2$fields))) + out2$fields <- out2$fields[names(out1$fields)] + + expect_equivalent_rd(out1, out2) +}) diff --git a/tests/testthat/test-rd-includermd.R b/tests/testthat/test-rd-includermd.R index 54da4831a..4ebf44bd6 100644 --- a/tests/testthat/test-rd-includermd.R +++ b/tests/testthat/test-rd-includermd.R @@ -11,21 +11,20 @@ test_that("markdown file can be included", { #' @name foobar NULL", tmp) out1 <- roc_proc_text(rd_roclet(), rox)[[1]] - out1$fields$details$values <- str_trim(out1$fields$details$values) out2 <- roc_proc_text(rd_roclet(), " - #' @details - #' - #' - #' List: + #' @title Title + #' @details List: #' \\itemize{ #' \\item item1 #' \\item item2 #' } #' #' Inline \\code{code} and \\emph{emphasis}. - #' @title Title #' @name foobar NULL")[[1]] + # make sure fields are in the same order + expect_equal(sort(names(out1$fields)), sort(names(out2$fields))) + out2$fields <- out2$fields[names(out1$fields)] expect_equivalent_rd(out1, out2) })