Skip to content

Commit

Permalink
Support level 1 headings in markdown
Browse files Browse the repository at this point in the history
They are supported in (well, after, really) selected
roxygen tags currently (see `tag_markdown_with_sections`
entries in rd.R).
  • Loading branch information
gaborcsardi committed Sep 13, 2019
1 parent bd7276d commit 635c605
Show file tree
Hide file tree
Showing 9 changed files with 105 additions and 28 deletions.
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
9 changes: 5 additions & 4 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
13 changes: 7 additions & 6 deletions R/markdown.R
Original file line number Diff line number Diff line change
@@ -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) {
Expand All @@ -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) {
Expand Down Expand Up @@ -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(
Expand Down
1 change: 1 addition & 0 deletions R/rd-include-rmd.R
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
25 changes: 14 additions & 11 deletions R/rd.R
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,16 @@ 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,
example = tag_value,
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,
Expand All @@ -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,
Expand Down Expand Up @@ -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)
}
}
}

Expand Down Expand Up @@ -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]]
Expand All @@ -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)
})
}
Expand Down
18 changes: 18 additions & 0 deletions R/tag.R
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
3 changes: 3 additions & 0 deletions man/roxy_tag.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

52 changes: 51 additions & 1 deletion tests/testthat/test-markdown.R
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
})
11 changes: 5 additions & 6 deletions tests/testthat/test-rd-includermd.R
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})

Expand Down

0 comments on commit 635c605

Please sign in to comment.