diff --git a/DESCRIPTION b/DESCRIPTION index 5b9076c..73c4df2 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: tinyrox Title: Minimal R Documentation Generator -Version: 0.3.3.2 +Version: 0.3.3.3 Authors@R: person("Troy", "Hernandez", email = "troy@cornball.ai", role = c("aut", "cre"), comment = c(ORCID = "0009-0005-4248-604X")) diff --git a/NEWS.md b/NEWS.md index 8d017e6..4b627f8 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,7 @@ +# tinyrox 0.3.3.3 + +* Render `@section` blocks in the Rd for ordinary functions and `@rdname` groups. They were parsed but only emitted for package-level docs, so a function's `@section` was silently dropped (#10). + # tinyrox 0.3.3.2 * Fix false "undocumented parameters" warning for functions documented via a sibling block in an `@rdname` group; the check is now group-wide. Also gate the warning on `cran_check` rather than `silent` (#12). diff --git a/R/rd.R b/R/rd.R index 8329784..5100705 100644 --- a/R/rd.R +++ b/R/rd.R @@ -1,3 +1,22 @@ +#' Render User-Defined @section Blocks to Rd +#' +#' Emits one `\\section{title}{content}` per parsed `@section`. Content is +#' passed through verbatim as Rd (tinyrox does no markdown parsing), matching +#' how the title-only macros elsewhere treat hand-written Rd markup. +#' +#' @param sections List of `list(title=, content=)` from parse_tags(). +#' @return Character vector of Rd lines (empty when there are no sections). +#' @keywords internal +render_sections <- function(sections) { + lines <- character() + for (sec in sections) { + lines <- c(lines, paste0("\\section{", escape_rd(sec$title), "}{")) + lines <- c(lines, sec$content) + lines <- c(lines, "}") + } + lines +} + #' Generate Rd File Content #' #' @param tags Parsed tags from parse_tags(). @@ -97,6 +116,9 @@ generate_rd <- function(tags, formals = NULL, source_file = NULL, lines <- c(lines, "}") } + # User-defined @section blocks (after details, like roxygen2) + lines <- c(lines, render_sections(tags$sections)) + # References if (!is.null(tags$references)) { lines <- c(lines, "\\references{") @@ -625,6 +647,13 @@ generate_rd_grouped <- function(topic, entries, all_tags, lines <- c(lines, "}") } + # User-defined @section blocks - concatenated from all blocks + all_sections <- list() + for (entry in entries) { + all_sections <- c(all_sections, entry$tags$sections) + } + lines <- c(lines, render_sections(all_sections)) + # \references{} - from primary if (!is.null(primary$tags$references)) { lines <- c(lines, "\\references{") @@ -881,13 +910,7 @@ generate_package_rd <- function(tags, pkg_name, source_file) { } # User-defined sections (@section Title: ...) - if (!is.null(tags$sections)) { - for (sec in tags$sections) { - lines <- c(lines, paste0("\\section{", escape_rd(sec$title), "}{")) - lines <- c(lines, sec$content) # Content may have Rd markup, don't escape - lines <- c(lines, "}") - } - } + lines <- c(lines, render_sections(tags$sections)) # Auto-generated function index lines <- c(lines, paste0("\\section{Package Content}{\\packageIndices{", diff --git a/inst/tinytest/test_rd.R b/inst/tinytest/test_rd.R index 435a7a3..9659565 100644 --- a/inst/tinytest/test_rd.R +++ b/inst/tinytest/test_rd.R @@ -52,6 +52,50 @@ rd_alias <- tinyrox:::generate_rd(tags_alias, list(names = c("x", "y"), usage = expect_true(grepl("\\\\alias\\{plus\\}", rd_alias)) expect_true(grepl("\\\\alias\\{sum2\\}", rd_alias)) +# Test @section blocks are rendered (#10) - parse -> generate, the real path +sec_block <- c( + "Example function", + "", + "Main description.", + "", + "@section Permissions:", + "This text should appear in generated Rd.", + "@export" +) +sec_tags <- tinyrox:::parse_tags(sec_block, "f") +expect_equal(length(sec_tags$sections), 1L) +rd_sec <- tinyrox:::generate_rd(sec_tags, list(names = character(), usage = "")) +expect_true(grepl("\\\\section\\{Permissions\\}\\{", rd_sec)) +expect_true(grepl("This text should appear in generated Rd", rd_sec)) + +# Multi-word section title +sec_block2 <- c( + "Title", "", "Desc.", + "@section Special Permissions:", + "Body text.", + "@export" +) +sec_tags2 <- tinyrox:::parse_tags(sec_block2, "g") +rd_sec2 <- tinyrox:::generate_rd(sec_tags2, list(names = character(), usage = "")) +expect_true(grepl("\\\\section\\{Special Permissions\\}\\{", rd_sec2)) + +# Multiple @section blocks on one function, in order +sec_block3 <- c( + "Title", "", "Desc.", + "@section First:", "One.", + "@section Second:", "Two.", + "@export" +) +sec_tags3 <- tinyrox:::parse_tags(sec_block3, "h") +expect_equal(length(sec_tags3$sections), 2L) +rd_sec3 <- tinyrox:::generate_rd(sec_tags3, list(names = character(), usage = "")) +expect_true(grepl("\\\\section\\{First\\}\\{", rd_sec3)) +expect_true(grepl("\\\\section\\{Second\\}\\{", rd_sec3)) +expect_true(regexpr("First", rd_sec3) < regexpr("Second", rd_sec3)) + +# No @section -> no \section{} block leaks in +expect_false(grepl("\\\\section\\{", rd)) + # Test with keywords tags_kw <- tags tags_kw$keywords <- c("internal", "math") diff --git a/inst/tinytest/test_rdname.R b/inst/tinytest/test_rdname.R index 86e54c2..10aea27 100644 --- a/inst/tinytest/test_rdname.R +++ b/inst/tinytest/test_rdname.R @@ -236,6 +236,38 @@ expect_true(grepl("func_a\\(1\\)", rd_c)) expect_true(grepl("func_b\\(2\\)", rd_c)) +# --- Test: @section blocks from all members of an @rdname group (#10) --- + +blocks_sec <- list( + list( + lines = c("Topic", "", "Desc.", "@section Alpha:", "Alpha body.", + "@rdname secgroup", "@export"), + object = "sec_a", + type = "function", + formals = list(names = "x", usage = "x"), + file = "test.R", + line = 1 + ), + list( + lines = c("@section Beta:", "Beta body.", "@rdname secgroup", "@export"), + object = "sec_b", + type = "function", + formals = list(names = "y", usage = "y"), + file = "test.R", + line = 10 + ) +) +tags_sa <- tinyrox:::parse_tags(blocks_sec[[1]]$lines, "sec_a") +tags_sb <- tinyrox:::parse_tags(blocks_sec[[2]]$lines, "sec_b") +entries_s <- list( + list(tags = tags_sa, block = blocks_sec[[1]]), + list(tags = tags_sb, block = blocks_sec[[2]]) +) +rd_s <- tinyrox:::generate_rd_grouped("secgroup", entries_s, list()) +expect_true(grepl("\\\\section\\{Alpha\\}\\{", rd_s)) +expect_true(grepl("\\\\section\\{Beta\\}\\{", rd_s)) + + # --- Test 7: @rdname params documented on a sibling block (issue #12) --- # A function whose formals are documented on the primary block (not its own) # must NOT be flagged as having undocumented parameters: blocks sharing an diff --git a/man/render_sections.Rd b/man/render_sections.Rd new file mode 100644 index 0000000..4ec1b33 --- /dev/null +++ b/man/render_sections.Rd @@ -0,0 +1,19 @@ +% tinyrox says don't edit this manually, but it can't stop you! +\name{render_sections} +\alias{render_sections} +\title{Render User-Defined @section Blocks to Rd} +\usage{ +render_sections(sections) +} +\arguments{ +\item{sections}{List of `list(title=, content=)` from parse_tags().} +} +\value{ +Character vector of Rd lines (empty when there are no sections). +} +\description{ +Emits one `\\section{title}{content}` per parsed `@section`. Content is +passed through verbatim as Rd (tinyrox does no markdown parsing), matching +how the title-only macros elsewhere treat hand-written Rd markup. +} +\keyword{internal}