From 29db38fb823fcfe9cf2ad05f885bf20dfa1c964b Mon Sep 17 00:00:00 2001 From: Hadley Wickham <h.wickham@gmail.com> Date: Fri, 20 Sep 2019 14:37:23 -0500 Subject: [PATCH] Revamp operation of rd and namespace roclets. At the heart of both roclets is now a loop that processes each tag in turn, calling `roxy_tag_rd()` or `roxy_tag_ns()`. This makes it easier to closely connected the parsing and processing tag, making the pieces easier to understand. Other changes: * `@docType package` no longer adds `-package` alias as this broke encapsulation and was only mildly useful. * .rexports, .formals, and .methods all now work by defining internal tags in object_defaults. Fixes #915 --- NAMESPACE | 46 +++ NEWS.md | 4 + R/namespace.R | 243 +++++++++------ R/object-defaults.R | 21 +- R/rd-examples.R | 40 +-- R/rd-include-rmd.R | 3 +- R/rd-inherit.R | 36 +++ R/rd.R | 462 +++++++++++++--------------- R/tag.R | 9 +- R/topic.R | 10 + man/roclet.Rd | 4 +- man/roxy_tag.Rd | 3 +- tests/testthat/test-block-print.txt | 1 + tests/testthat/test-rd-doctype.R | 22 -- 14 files changed, 510 insertions(+), 394 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 3c0b6b719..de5f300a1 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -51,6 +51,7 @@ S3method(merge,roxy_field_minidesc) S3method(merge,roxy_field_param) S3method(merge,roxy_field_reexport) S3method(merge,roxy_field_section) +S3method(object_defaults,"function") S3method(object_defaults,data) S3method(object_defaults,default) S3method(object_defaults,import) @@ -74,6 +75,19 @@ S3method(roclet_preprocess,roclet_namespace) S3method(roclet_process,roclet_namespace) S3method(roclet_process,roclet_rd) S3method(roclet_process,roclet_vignette) +S3method(roxy_tag_ns,default) +S3method(roxy_tag_ns,roxy_tag_evalNamespace) +S3method(roxy_tag_ns,roxy_tag_export) +S3method(roxy_tag_ns,roxy_tag_exportClass) +S3method(roxy_tag_ns,roxy_tag_exportMethod) +S3method(roxy_tag_ns,roxy_tag_exportPattern) +S3method(roxy_tag_ns,roxy_tag_exportS3Method) +S3method(roxy_tag_ns,roxy_tag_import) +S3method(roxy_tag_ns,roxy_tag_importClassesFrom) +S3method(roxy_tag_ns,roxy_tag_importFrom) +S3method(roxy_tag_ns,roxy_tag_importMethodsFrom) +S3method(roxy_tag_ns,roxy_tag_rawNamespace) +S3method(roxy_tag_ns,roxy_tag_useDynLib) S3method(roxy_tag_parse,default) S3method(roxy_tag_parse,roxy_tag_aliases) S3method(roxy_tag_parse,roxy_tag_author) @@ -130,6 +144,38 @@ S3method(roxy_tag_parse,roxy_tag_templateVar) S3method(roxy_tag_parse,roxy_tag_title) S3method(roxy_tag_parse,roxy_tag_usage) S3method(roxy_tag_parse,roxy_tag_useDynLib) +S3method(roxy_tag_rd,roxy_tag_.formals) +S3method(roxy_tag_rd,roxy_tag_.methods) +S3method(roxy_tag_rd,roxy_tag_.reexport) +S3method(roxy_tag_rd,roxy_tag_author) +S3method(roxy_tag_rd,roxy_tag_backref) +S3method(roxy_tag_rd,roxy_tag_concept) +S3method(roxy_tag_rd,roxy_tag_description) +S3method(roxy_tag_rd,roxy_tag_details) +S3method(roxy_tag_rd,roxy_tag_docType) +S3method(roxy_tag_rd,roxy_tag_encoding) +S3method(roxy_tag_rd,roxy_tag_evalRd) +S3method(roxy_tag_rd,roxy_tag_example) +S3method(roxy_tag_rd,roxy_tag_examples) +S3method(roxy_tag_rd,roxy_tag_family) +S3method(roxy_tag_rd,roxy_tag_field) +S3method(roxy_tag_rd,roxy_tag_format) +S3method(roxy_tag_rd,roxy_tag_includeRmd) +S3method(roxy_tag_rd,roxy_tag_inherit) +S3method(roxy_tag_rd,roxy_tag_inheritDotParams) +S3method(roxy_tag_rd,roxy_tag_inheritParams) +S3method(roxy_tag_rd,roxy_tag_inheritSection) +S3method(roxy_tag_rd,roxy_tag_keywords) +S3method(roxy_tag_rd,roxy_tag_note) +S3method(roxy_tag_rd,roxy_tag_param) +S3method(roxy_tag_rd,roxy_tag_rawRd) +S3method(roxy_tag_rd,roxy_tag_references) +S3method(roxy_tag_rd,roxy_tag_return) +S3method(roxy_tag_rd,roxy_tag_section) +S3method(roxy_tag_rd,roxy_tag_seealso) +S3method(roxy_tag_rd,roxy_tag_slot) +S3method(roxy_tag_rd,roxy_tag_source) +S3method(roxy_tag_rd,roxy_tag_title) export(block_get_tag) export(block_get_tag_value) export(block_get_tags) diff --git a/NEWS.md b/NEWS.md index fd5238df4..b9b1ba40e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,9 @@ # roxygen2 (development version) +* Using `@docType package` no longer automatically adds `-name`. Instead + document `_PACKAGE` to get all the defaults for package documentation. + (Removing this feature allowed for simplification of the code.) + * `roclet_tags()` is no longer used; instead define a `roxy_tag_parse()` method. If you tag is `@mytag`, then the class name associated with it is `roxy_tag_mytag` so the method should be call `roxy_tag_parse.roxy_tag_mytag()`. diff --git a/R/namespace.R b/R/namespace.R index a3570afed..11b71446f 100644 --- a/R/namespace.R +++ b/R/namespace.R @@ -1,22 +1,3 @@ -# Processed first -ns_tags_import <- c( - "import", - "importFrom", - "importClassesFrom", - "importMethodsFrom", - "useDynLib", - "rawNamespace" -) -ns_tags <- c( - ns_tags_import, - "evalNamespace", - "export", - "exportClass", - "exportMethod", - "exportS3Method", - "exportPattern" -) - #' Roclet: make `NAMESPACE` #' #' @description @@ -55,7 +36,7 @@ roclet_preprocess.roclet_namespace <- function(x, base_path, global_options = list()) { - lines <- blocks_to_ns(blocks, env, tag_set = ns_tags_import) + lines <- blocks_to_ns(blocks, env, import_only = TRUE) NAMESPACE <- file.path(base_path, "NAMESPACE") if (length(lines) == 0 && !made_by_roxygen(NAMESPACE)) { @@ -63,7 +44,7 @@ roclet_preprocess.roclet_namespace <- function(x, } results <- c(made_by("#"), lines) - write_if_different(NAMESPACE, results, check = FALSE) + write_if_different(NAMESPACE, results, check = TRUE) invisible(x) } @@ -77,30 +58,6 @@ roclet_process.roclet_namespace <- function(x, blocks_to_ns(blocks, env) } -#' @export -roxy_tag_parse.roxy_tag_evalNamespace <- function(x) tag_code(x) -#' @export -roxy_tag_parse.roxy_tag_export <- function(x) tag_words_line(x) -#' @export -roxy_tag_parse.roxy_tag_exportClass <- function(x) tag_words(x, 1) -#' @export -roxy_tag_parse.roxy_tag_exportS3Method <- function(x) tag_words(x, min = 0, max = 2) -#' @export -roxy_tag_parse.roxy_tag_exportMethod <- function(x) tag_words(x, min = 1) -#' @export -roxy_tag_parse.roxy_tag_exportPattern <- function(x) tag_words(x, min = 1) -#' @export -roxy_tag_parse.roxy_tag_import <- function(x) tag_words(x, min = 1) -#' @export -roxy_tag_parse.roxy_tag_importClassesFrom <- function(x) tag_words(x, min = 2) -#' @export -roxy_tag_parse.roxy_tag_importFrom <- function(x) tag_words(x, min = 2) -#' @export -roxy_tag_parse.roxy_tag_importMethodsFrom <- function(x) tag_words(x, min = 2) -#' @export -roxy_tag_parse.roxy_tag_rawNamespace <- function(x) tag_code(x) -#' @export -roxy_tag_parse.roxy_tag_useDynLib <- function(x) tag_words(x, min = 1) #' @export roclet_output.roclet_namespace <- function(x, results, base_path, ...) { @@ -124,86 +81,185 @@ roclet_clean.roclet_namespace <- function(x, base_path) { # NAMESPACE generation ---------------------------------------------------- -blocks_to_ns <- function(blocks, env, tag_set = ns_tags) { - lines <- map(blocks, block_to_ns, env = env, tag_set = tag_set) +blocks_to_ns <- function(blocks, env, import_only = FALSE) { + lines <- map(blocks, block_to_ns, env = env, import_only = import_only) lines <- unlist(lines) %||% character() sort_c(unique(lines)) } -block_to_ns <- function(block, env, tag_set = ns_tags) { - tags <- block_get_tags(block, tag_set) +block_to_ns <- function(block, env, import_only = FALSE) { + map(block$tags, roxy_tag_ns, block = block, env = env, import_only = import_only) +} + +# Namespace tag methods --------------------------------------------------- - map(tags, function(tag) { - exec(paste0("ns_", tag$tag), tag, block, env) - }) +roxy_tag_ns <- function(x, block, env, import_only = FALSE) { + UseMethod("roxy_tag_ns") } -ns_export <- function(tag, block, env) { - if (identical(tag$val, "")) { +#' @export +roxy_tag_ns.default <- function(x, block, env, import_only = FALSE) { + +} + +#' @export +roxy_tag_parse.roxy_tag_evalNamespace <- function(x) { + tag_code(x) +} +#' @export +roxy_tag_ns.roxy_tag_evalNamespace <- function(x, block, env, import_only = FALSE) { + roxy_tag_eval(x, env) +} + +#' @export +roxy_tag_parse.roxy_tag_export <- function(x) { + tag_words_line(x) +} +#' @export +roxy_tag_ns.roxy_tag_export <- function(x, block, env, import_only = FALSE) { + if (import_only) { + return() + } + + if (identical(x$val, "")) { # FIXME: check for empty exports (i.e. no name) default_export(block$object, block) } else { - export(tag$val) + export(x$val) } } -ns_exportClass <- function(tag, block, env) export_class(tag$val) -ns_exportMethod <- function(tag, block, env) export_s4_method(tag$val) -ns_exportPattern <- function(tag, block, env) one_per_line("exportPattern", tag$val) -ns_import <- function(tag, block, env) one_per_line("import", tag$val) -ns_importFrom <- function(tag, block, env) repeat_first("importFrom", tag$val) -ns_importClassesFrom <- function(tag, block, env) repeat_first("importClassesFrom", tag$val) -ns_importMethodsFrom <- function(tag, block, env) repeat_first("importMethodsFrom", tag$val) +#' @export +roxy_tag_parse.roxy_tag_exportClass <- function(x) { + tag_words(x, 1) +} +#' @export +roxy_tag_ns.roxy_tag_exportClass <- function(x, block, env, import_only = FALSE) { + if (import_only) { + return() + } -ns_exportS3Method <- function(tag, block, env) { - obj <- block$object + export_class(x$val) +} + +#' @export +roxy_tag_parse.roxy_tag_exportMethod <- function(x) { + tag_words(x, min = 1) +} +#' @export +roxy_tag_ns.roxy_tag_exportMethod <- function(x, block, env, import_only = FALSE) { + if (import_only) { + return() + } + export_s4_method(x$val) +} - if (length(tag$val) < 2 && !inherits(obj, "s3method")) { - roxy_tag_warning(tag, +#' @export +roxy_tag_parse.roxy_tag_exportPattern <- function(x) { + tag_words(x, min = 1) +} +#' @export +roxy_tag_ns.roxy_tag_exportPattern <- function(x, block, env, import_only = FALSE) { + if (import_only) { + return() + } + one_per_line("exportPattern", x$val) +} + +#' @export +roxy_tag_parse.roxy_tag_exportS3Method <- function(x) { + tag_words(x, min = 0, max = 2) +} +#' @export +roxy_tag_ns.roxy_tag_exportS3Method <- function(x, block, env, import_only = FALSE) { + if (import_only) { + return() + } + + obj <- block$object + if (length(x$val) < 2 && !inherits(obj, "s3method")) { + roxy_tag_warning(x, "`@exportS3Method` and `@exportS3Method generic` must be used with an S3 method" ) return() } - if (identical(tag$val, "")) { + if (identical(x$val, "")) { method <- attr(obj$value, "s3method") - } else if (length(tag$val) == 1) { - method <- c(tag$val, attr(obj$value, "s3method")[[2]]) + } else if (length(x$val) == 1) { + method <- c(x$val, attr(obj$value, "s3method")[[2]]) } else { - method <- tag$val + method <- x$val } export_s3_method(method) } -ns_useDynLib <- function(tag, block, env) { - if (length(tag$val) == 1) { - return(paste0("useDynLib(", auto_quote(tag$val), ")")) +#' @export +roxy_tag_parse.roxy_tag_import <- function(x) { + tag_words(x, min = 1) +} +#' @export +roxy_tag_ns.roxy_tag_import <- function(x, block, env, import_only = FALSE) { + one_per_line("import", x$val) +} + +#' @export +roxy_tag_parse.roxy_tag_importClassesFrom <- function(x) { + tag_words(x, min = 2) +} +#' @export +roxy_tag_ns.roxy_tag_importClassesFrom <- function(x, block, env, import_only = FALSE) { + repeat_first("importClassesFrom", x$val) +} + +#' @export +roxy_tag_parse.roxy_tag_importFrom <- function(x) { + tag_words(x, min = 2) +} +#' @export +roxy_tag_ns.roxy_tag_importFrom <- function(x, block, env, import_only = FALSE) { + repeat_first("importFrom", x$val) +} + +#' @export +roxy_tag_parse.roxy_tag_importMethodsFrom <- function(x) { + tag_words(x, min = 2) +} +#' @export +roxy_tag_ns.roxy_tag_importMethodsFrom <- function(x, block, env, import_only = FALSE) { + repeat_first("importMethodsFrom", x$val) +} + +#' @export +roxy_tag_parse.roxy_tag_rawNamespace <- function(x) { + tag_code(x) +} +#' @export +roxy_tag_ns.roxy_tag_rawNamespace <- function(x, block, env, import_only = FALSE) { + x$val +} + +#' @export +roxy_tag_parse.roxy_tag_useDynLib <- function(x) { + tag_words(x, min = 1) +} +#' @export +roxy_tag_ns.roxy_tag_useDynLib <- function(x, block, env, import_only = FALSE) { + if (length(x$val) == 1) { + return(paste0("useDynLib(", auto_quote(x$val), ")")) } - if (any(grepl(",", tag$val))) { + if (any(grepl(",", x$val))) { # If there's a comma in list, don't quote output. This makes it possible # for roxygen2 to support other NAMESPACE forms not otherwise mapped - args <- paste0(tag$val, collapse = " ") + args <- paste0(x$val, collapse = " ") paste0("useDynLib(", args, ")") } else { - repeat_first("useDynLib", tag$val) + repeat_first("useDynLib", x$val) } } -ns_rawNamespace <- function(tag, block, env) tag$val -ns_evalNamespace <- function(tag, block, env) { - roxy_tag_eval(tag, env) -} - -# Functions used by both default_export and ns_* functions -export <- function(x) one_per_line("export", x) -export_class <- function(x) one_per_line("exportClasses", x) -export_s4_method <- function(x) one_per_line("exportMethods", x) -export_s3_method <- function(x) { - args <- paste0(auto_backtick(x), collapse = ",") - paste0("S3method(", args, ")") -} # Default export methods -------------------------------------------------- @@ -223,9 +279,16 @@ default_export.default <- function(x, block) export(x$alias) #' @export default_export.NULL <- function(x, block) export(block_get_tag_value(block, "name")) - # Helpers ----------------------------------------------------------------- +export <- function(x) one_per_line("export", x) +export_class <- function(x) one_per_line("exportClasses", x) +export_s4_method <- function(x) one_per_line("exportMethods", x) +export_s3_method <- function(x) { + args <- paste0(auto_backtick(x), collapse = ",") + paste0("S3method(", args, ")") +} + one_per_line <- function(name, x) { paste0(name, "(", auto_backtick(x), ")") } diff --git a/R/object-defaults.R b/R/object-defaults.R index 5df30bd6c..3518e3f58 100644 --- a/R/object-defaults.R +++ b/R/object-defaults.R @@ -3,6 +3,14 @@ object_defaults <- function(x) UseMethod("object_defaults") #' @export object_defaults.default <- function(x) list() +#' @exportS3Method object_defaults "function" +object_defaults.function <- function(x) { + # Used in process_inherit_params() + list( + roxy_tag(".formals", NULL, names(formals(x$value))) + ) +} + #' @export object_defaults.data <- function(x) { str_out <- rd(object_format(x$value)) @@ -44,7 +52,7 @@ object_defaults.import <- function(x) { roxy_tag("name", NULL, "reexports"), roxy_tag("keywords", NULL, "internal"), roxy_tag("title", NULL, "Objects exported from other packages"), - roxy_tag(".reexport", NULL, roxy_field_reexport(x$value$pkg, x$value$fun)) + roxy_tag(".reexport", NULL, list(pkg = x$value$pkg, fun = x$value$fun)) ) } @@ -58,7 +66,9 @@ object_defaults.s4class <- function(x) { #' @export object_defaults.rcclass <- function(x) { list( - roxy_tag("docType", NULL, "class") + roxy_tag("docType", NULL, "class"), + if (!is.null(x$methods)) + roxy_tag(".methods", NULL, x$methods) ) } @@ -68,3 +78,10 @@ object_defaults.s4method <- function(x) { roxy_tag("docType", NULL, "class") ) } + +# Helpers ----------------------------------------------------------------- + +package_suffix <- function(name) { + paste0(name, "-package") +} + diff --git a/R/rd-examples.R b/R/rd-examples.R index c25fe83b0..264d0d388 100644 --- a/R/rd-examples.R +++ b/R/rd-examples.R @@ -1,32 +1,34 @@ -topic_add_examples <- function(topic, block, base_path) { - tags <- block_get_tags(block, c("examples", "example")) +#' @export +roxy_tag_parse.roxy_tag_example <- function(x) { + x <- tag_value(x) - for (tag in tags) { - if (tag$tag == "examples") { - example <- tag$val - } else { - example <- read_example_from_path(tag, base_path) - } - topic$add_simple_field("examples", example) - } -} - -read_example_from_path <- function(tag, base_path) { - path <- str_trim(tag$val) - nl <- str_count(path, "\n") + nl <- str_count(x$val, "\n") if (any(nl) > 0) { - roxy_tag_warning(tag, "spans multiple lines. Do you want @examples?") + roxy_tag_warning(x, "spans multiple lines. Do you want @examples?") return() } - path <- file.path(base_path, path) + x +} +#' @export +roxy_tag_rd.roxy_tag_example <- function(x, topic, base_path, env) { + path <- file.path(base_path, x$val) if (!file.exists(path)) { - roxy_tag_warning(tag, "'", path, "' doesn't exist") + roxy_tag_warning(x, "'", path, "' doesn't exist") return() } code <- read_lines(path) - escape_examples(code) + topic$add_simple_field("examples", escape_examples(code)) +} + +#' @export +roxy_tag_parse.roxy_tag_examples <- function(x) { + tag_examples(x) +} +#' @export +roxy_tag_rd.roxy_tag_examples <- function(x, topic, base_path, env) { + topic$add_simple_field("examples", x$val) } # Works like escape, but unescapes special rd example commands. diff --git a/R/rd-include-rmd.R b/R/rd-include-rmd.R index 1b7911a9a..55a3ee089 100644 --- a/R/rd-include-rmd.R +++ b/R/rd-include-rmd.R @@ -1,5 +1,4 @@ - -block_include_rmd <- function(tag, block, base_path) { +roxy_tag_include_rmd <- function(tag, base_path) { rmd <- tag$val stopifnot(is.character(rmd), length(rmd) == 1, !is.na(rmd)) diff --git a/R/rd-inherit.R b/R/rd-inherit.R index 9fb11fef7..969c122d0 100644 --- a/R/rd-inherit.R +++ b/R/rd-inherit.R @@ -1,3 +1,39 @@ +#' @export +roxy_tag_parse.roxy_tag_inherit <- function(x) tag_inherit(x) +#' @export +roxy_tag_rd.roxy_tag_inherit <- function(x, topic, base_path, env) { + field <- roxy_field_inherit(x$val$source, list(x$val$fields)) + topic$add_field(field) +} + +#' @export +roxy_tag_parse.roxy_tag_inheritParams <- function(x) tag_value(x) +#' @export +roxy_tag_rd.roxy_tag_inheritParams <- function(x, topic, base_path, env) { + field <- roxy_field_inherit(x$val, list("params")) + topic$add_field(field) +} + +#' @export +roxy_tag_parse.roxy_tag_inheritDotParams <- function(x) { + tag_two_part(x, "source", "args", required = FALSE, markdown = FALSE) +} +#' @export +roxy_tag_rd.roxy_tag_inheritDotParams <- function(x, topic, base_path, env) { + field <- roxy_field_inherit_dot_params(x$val$source, x$val$args) + topic$add_field(field) +} + +#' @export +roxy_tag_parse.roxy_tag_inheritSection <- function(x) tag_name_description(x) +#' @export +roxy_tag_rd.roxy_tag_inheritSection <- function(x, topic, base_path, env) { + field <- roxy_field_inherit_section(x$val$name, x$val$description) + topic$add_field(field) +} + +# Process inheritance ----------------------------------------------------- + topics_process_inherit <- function(topics, env) { inherits <- function(type) { function(x) x$inherits_from(type) diff --git a/R/rd.R b/R/rd.R index 4e6010bda..c8c04cbd6 100644 --- a/R/rd.R +++ b/R/rd.R @@ -118,32 +118,13 @@ block_to_rd <- function(block, base_path, env, global_options = list()) { return() } - # Note that order of operations here doesn't matter: fields are - # ordered by RoxyFile$format() rd <- RoxyTopic$new() topic_add_name_aliases(rd, block, name) - - # Some fields added directly by roxygen internals - tags <- Filter(roxy_tag_is_field, block$tags) - for (tag in tags) { - rd$add(tag$val) + for (tag in block$tags) { + roxy_tag_rd(tag, rd, env = env, base_path = base_path) } - topic_add_backref(rd, block) - topic_add_doc_type(rd, block) - topic_add_eval_rd(rd, block, env) - topic_add_include_rmd(rd, block, base_path) - topic_add_examples(rd, block, base_path) - topic_add_fields(rd, block) - topic_add_inherit(rd, block) - topic_add_keyword(rd, block) - topic_add_methods(rd, block) - topic_add_params(rd, block) - topic_add_simple_tags(rd, block) - topic_add_sections(rd, block) - topic_add_slots(rd, block) topic_add_usage(rd, block, old_usage = global_options$old_usage) - topic_add_value(rd, block) if (rd$has_field("description") && rd$has_field("reexport")) { roxy_tag_warning(block$tags[[1]], "Can't use description when re-exporting") @@ -157,87 +138,7 @@ block_to_rd <- function(block, base_path, env, global_options = list()) { rd } -#' @export -roxy_tag_parse.roxy_tag_aliases <- function(x) tag_value(x) -#' @export -roxy_tag_parse.roxy_tag_author <- function(x) tag_markdown(x) -#' @export -roxy_tag_parse.roxy_tag_backref <- function(x) tag_value(x) -#' @export -roxy_tag_parse.roxy_tag_concept <- function(x) tag_markdown(x) -#' @export -roxy_tag_parse.roxy_tag_describeIn <- function(x) tag_name_description(x) -#' @export -roxy_tag_parse.roxy_tag_description <- function(x) tag_markdown_with_sections(x) -#' @export -roxy_tag_parse.roxy_tag_details <- function(x) tag_markdown_with_sections(x) -#' @export -roxy_tag_parse.roxy_tag_docType <- function(x) tag_name(x) -#' @export -roxy_tag_parse.roxy_tag_encoding <- function(x) tag_value(x) -#' @export -roxy_tag_parse.roxy_tag_evalRd <- function(x) tag_code(x) -#' @export -roxy_tag_parse.roxy_tag_example <- function(x) tag_value(x) -#' @export -roxy_tag_parse.roxy_tag_examples <- function(x) tag_examples(x) -#' @export -roxy_tag_parse.roxy_tag_family <- function(x) tag_value(x) -#' @export -roxy_tag_parse.roxy_tag_field <- function(x) tag_name_description(x) -#' @export -roxy_tag_parse.roxy_tag_format <- function(x) tag_markdown(x) -#' @export -roxy_tag_parse.roxy_tag_includeRmd <- function(x) tag_value(x) -#' @export -roxy_tag_parse.roxy_tag_inherit <- function(x) tag_inherit(x) -#' @export -roxy_tag_parse.roxy_tag_inheritParams <- function(x) tag_value(x) -#' @export -roxy_tag_parse.roxy_tag_inheritDotParams <- function(x) tag_two_part(x, "source", "args", required = FALSE, markdown = FALSE) -#' @export -roxy_tag_parse.roxy_tag_inheritSection <- function(x) tag_name_description(x) -#' @export -roxy_tag_parse.roxy_tag_keywords <- function(x) tag_value(x) -#' @export -roxy_tag_parse.roxy_tag_method <- function(x) tag_words(x, 2, 2) -#' @export -roxy_tag_parse.roxy_tag_name <- function(x) tag_value(x) -#' @export -roxy_tag_parse.roxy_tag_md <- function(x) tag_toggle(x) -#' @export -roxy_tag_parse.roxy_tag_noMd <- function(x) tag_toggle(x) -#' @export -roxy_tag_parse.roxy_tag_noRd <- function(x) tag_toggle(x) -#' @export -roxy_tag_parse.roxy_tag_note <- function(x) tag_markdown(x) -#' @export -roxy_tag_parse.roxy_tag_param <- function(x) tag_name_description(x) -#' @export -roxy_tag_parse.roxy_tag_rdname <- function(x) tag_value(x) -#' @export -roxy_tag_parse.roxy_tag_rawRd <- function(x) tag_value(x) -#' @export -roxy_tag_parse.roxy_tag_references <- function(x) tag_markdown(x) -#' @export -roxy_tag_parse.roxy_tag_return <- function(x) tag_markdown(x) -#' @export -roxy_tag_parse.roxy_tag_section <- function(x) tag_markdown(x) -#' @export -roxy_tag_parse.roxy_tag_seealso <- function(x) tag_markdown(x) -#' @export -roxy_tag_parse.roxy_tag_slot <- function(x) tag_name_description(x) -#' @export -roxy_tag_parse.roxy_tag_source <- function(x) tag_markdown(x) -#' @export -roxy_tag_parse.roxy_tag_template <- function(x) tag_value(x) -#' @export -roxy_tag_parse.roxy_tag_templateVar <- function(x) tag_name_description(x) -#' @export -roxy_tag_parse.roxy_tag_title <- function(x) tag_markdown(x) -#' @export -roxy_tag_parse.roxy_tag_usage <- function(x) tag_value(x) - +# Special cases ----------------------------------------------------------- topics_add_default_description <- function(topics) { for (topic in topics$topics) { @@ -253,45 +154,6 @@ topics_add_default_description <- function(topics) { invisible() } - -topic_add_backref <- function(topic, block) { - tags <- block_get_tags(block, "backref") - for (tag in tags) { - topic$add_simple_field("backref", tag$val) - } -} - -# Simple tags can be converted directly to fields -topic_add_simple_tags <- function(topic, block) { - simple_tags <- block_get_tags(block, - c( - "author", "concept", "description", "details", "encoding", "family", - "format", "note", "rawRd", "references", - "seealso", "source", "title" - ) - ) - - for (tag in simple_tags) { - if (length(tag$val) && nchar(tag$val[[1]])) { - topic$add_simple_field(tag$tag, tag$val[[1]]) - } - for (extra in tag$val[-1]) { - topic$add_simple_field("rawRd", extra) - } - } -} - -topic_add_params <- function(topic, block) { - # Used in process_inherit_params() - value <- block$object$value - if (is.function(value)) { - formals <- formals(value) - topic$add_simple_field("formals", names(formals)) - } - - process_def_tag(topic, block, "param") -} - topic_add_name_aliases <- function(topic, block, name) { tags <- block_get_tags(block, "aliases") @@ -314,16 +176,48 @@ topic_add_name_aliases <- function(topic, block, name) { topic$add_simple_field("alias", aliases) } +# Prefer explicit \code{@@usage} to a \code{@@formals} list. +topic_add_usage <- function(topic, block, old_usage = FALSE) { + tag <- block_get_tag(block, "usage") -topic_add_methods <- function(topic, block) { - obj <- block$object - if (!inherits(obj, "rcclass")) return() + if (is.null(tag)) { + usage <- object_usage(block$object, old_usage = old_usage) + } else if (tag$val == "NULL") { + usage <- NULL + } else { + # Treat user input as already escaped, otherwise they have no way + # to enter \S4method etc. + usage <- rd(tag$val) + } + topic$add_simple_field("usage", usage) +} + +# Tag-wise processing ----------------------------------------------------- + +roxy_tag_rd <- function(x, topic, base_path, env) { + UseMethod("roxy_tag_rd") +} + +roxy_tag_rd.default <- function(x, topic, base_path, env) { +} + + +# Internal tags ----------------------------------------------------------- + +#' @export +roxy_tag_rd.roxy_tag_.reexport <- function(x, topic, base_path, env) { + topic$add(roxy_field_reexport(x$val$pkg, x$val$fun)) +} - methods <- obj$methods - if (is.null(obj$methods)) return() +#' @export +roxy_tag_rd.roxy_tag_.formals <- function(x, topic, base_path, env) { + topic$add_simple_field("formals", x$val) +} - desc <- lapply(methods, function(x) docstring(x$value@.Data)) - usage <- map_chr(methods, function(x) { +#' @export +roxy_tag_rd.roxy_tag_.methods <- function(x, topic, base_path, env) { + desc <- lapply(x$val, function(x) docstring(x$value@.Data)) + usage <- map_chr(x$val, function(x) { function_usage(x$value@name, formals(x$value@.Data)) }) @@ -334,148 +228,216 @@ topic_add_methods <- function(topic, block) { topic$add_simple_field("rcmethods", setNames(desc, usage)) } -topic_add_inherit <- function(topic, block) { - tags <- block_get_tags(block, "inherit") - for (tag in tags) { - field <- roxy_field_inherit(tag$val$source, list(tag$val$fields)) - topic$add_field(field) - } +# Regular tags ------------------------------------------------------------ - tags <- block_get_tags(block, "inheritParams") - for (tag in tags) { - field <- roxy_field_inherit(tag$val, list("params")) - topic$add_field(field) - } +#' @export +roxy_tag_parse.roxy_tag_aliases <- function(x) tag_value(x) - tags <- block_get_tags(block, "inheritSection") - for (tag in tags) { - field <- roxy_field_inherit_section(tag$val$name, tag$val$description) - topic$add_field(field) - } +#' @export +roxy_tag_parse.roxy_tag_author <- function(x) tag_markdown(x) +#' @export +roxy_tag_rd.roxy_tag_author <- function(x, topic, base_path, env) { + topic$add_markdown_field(x$tag, x$val) +} - tags <- block_get_tags(block, "inheritDotParams") - for (tag in tags) { - field <- roxy_field_inherit_dot_params(tag$val$source, tag$val$args) - topic$add_field(field) - } +#' @export +roxy_tag_parse.roxy_tag_backref <- function(x) tag_value(x) +#' @export +roxy_tag_rd.roxy_tag_backref <- function(x, topic, base_path, env) { + topic$add_simple_field("backref", x$val) } +#' @export +roxy_tag_parse.roxy_tag_concept <- function(x) tag_markdown(x) +#' @export +roxy_tag_rd.roxy_tag_concept <- function(x, topic, base_path, env) { + topic$add_markdown_field(x$tag, x$val) +} -topic_add_value <- function(topic, block) { - tags <- block_get_tags(block, "return") +#' @export +roxy_tag_parse.roxy_tag_describeIn <- function(x) tag_name_description(x) - for (tag in tags) { - topic$add_simple_field("value", tag$val) - } +#' @export +roxy_tag_parse.roxy_tag_description <- function(x) tag_markdown_with_sections(x) +#' @export +roxy_tag_rd.roxy_tag_description <- function(x, topic, base_path, env) { + topic$add_markdown_field(x$tag, x$val) } -topic_add_keyword <- function(topic, block) { - tags <- block_get_tags(block, "keywords") - - vals <- map_chr(tags, "val") - keywords <- unlist(str_split(vals, "\\s+")) +#' @export +roxy_tag_parse.roxy_tag_details <- function(x) tag_markdown_with_sections(x) +#' @export +roxy_tag_rd.roxy_tag_details <- function(x, topic, base_path, env) { + topic$add_markdown_field(x$tag, x$val) +} - topic$add_simple_field("keyword", keywords) +#' @export +roxy_tag_parse.roxy_tag_docType <- function(x) tag_name(x) +#' @export +roxy_tag_rd.roxy_tag_docType <- function(x, topic, base_path, env) { + topic$add_simple_field("docType", x$val) } -# Prefer explicit \code{@@usage} to a \code{@@formals} list. -topic_add_usage <- function(topic, block, old_usage = FALSE) { - tag <- block_get_tag(block, "usage") +#' @export +roxy_tag_parse.roxy_tag_encoding <- function(x) tag_value(x) +#' @export +roxy_tag_rd.roxy_tag_encoding <- function(x, topic, base_path, env) { + topic$add_markdown_field(x$tag, x$val) +} - if (is.null(tag)) { - usage <- object_usage(block$object, old_usage = old_usage) - } else if (tag$val == "NULL") { - usage <- NULL - } else { - # Treat user input as already escaped, otherwise they have no way - # to enter \S4method etc. - usage <- rd(tag$val) - } - topic$add_simple_field("usage", usage) +#' @export +roxy_tag_parse.roxy_tag_evalRd <- function(x) tag_code(x) +#' @export +roxy_tag_rd.roxy_tag_evalRd <- function(x, topic, base_path, env) { + topic$add_simple_field("rawRd", roxy_tag_eval(x, env)) } -topic_add_slots <- function(topic, block) { - process_def_tag(topic, block, "slot") +#' @export +roxy_tag_parse.roxy_tag_family <- function(x) tag_value(x) +#' @export +roxy_tag_rd.roxy_tag_family <- function(x, topic, base_path, env) { + topic$add_markdown_field(x$tag, x$val) } -topic_add_fields <- function(topic, block) { - process_def_tag(topic, block, "field") +#' @export +roxy_tag_parse.roxy_tag_field <- function(x) tag_name_description(x) +#' @export +roxy_tag_rd.roxy_tag_field <- function(x, topic, base_path, env) { + value <- setNames(x$val$description, x$val$name) + topic$add_simple_field(x$tag, value) } -topic_add_eval_rd <- function(topic, block, env) { - tags <- block_get_tags(block, "evalRd") +#' @export +roxy_tag_parse.roxy_tag_format <- function(x) tag_markdown(x) +#' @export +roxy_tag_rd.roxy_tag_format <- function(x, topic, base_path, env) { + topic$add_markdown_field(x$tag, x$val) +} - for (tag in tags) { - out <- roxy_tag_eval(tag, env) - topic$add_simple_field("rawRd", out) +#' @export +roxy_tag_parse.roxy_tag_includeRmd <- function(x) { + if (!is_installed("rmarkdown")) { + roxy_tag_warning(x, "Needs the rmarkdown package") + return() } + + tag_value(x) +} +#' @export +roxy_tag_rd.roxy_tag_includeRmd <- function(x, topic, base_path, env) { + topic$add_markdown_field("details", roxy_tag_include_rmd(x, base_path)) +} + +#' @export +roxy_tag_parse.roxy_tag_keywords <- function(x) tag_value(x) +#' @export +roxy_tag_rd.roxy_tag_keywords <- function(x, topic, base_path, env) { + topic$add_simple_field("keyword", str_split(x$val, "\\s+")[[1]]) } -topic_add_include_rmd <- function(topic, block, base_path) { - tags <- block_get_tags(block, "includeRmd") +#' @export +roxy_tag_parse.roxy_tag_method <- function(x) tag_words(x, 2, 2) - for (tag in tags) { - if (!is_installed("rmarkdown")) { - roxy_tag_warning(tag, "Needs the rmarkdown package") - } - out <- block_include_rmd(tag, block, base_path) - if (!is.null(out[[1]])) { - topic$add_simple_field("details", out[[1]]) - } - lapply(out[-1], function(s) { - topic$add_simple_field("rawRd", s) - }) - } +#' @export +roxy_tag_parse.roxy_tag_name <- function(x) tag_value(x) + +#' @export +roxy_tag_parse.roxy_tag_md <- function(x) tag_toggle(x) + +#' @export +roxy_tag_parse.roxy_tag_noMd <- function(x) tag_toggle(x) + +#' @export +roxy_tag_parse.roxy_tag_noRd <- function(x) tag_toggle(x) + +#' @export +roxy_tag_parse.roxy_tag_note <- function(x) tag_markdown(x) +#' @export +roxy_tag_rd.roxy_tag_note <- function(x, topic, base_path, env) { + topic$add_markdown_field(x$tag, x$val) } -topic_add_sections <- function(topic, block) { - tags <- block_get_tags(block, "section") +#' @export +roxy_tag_parse.roxy_tag_param <- function(x) tag_name_description(x) +#' @export +roxy_tag_rd.roxy_tag_param <- function(x, topic, base_path, env) { + value <- setNames(x$val$description, x$val$name) + topic$add_simple_field(x$tag, value) +} - for (tag in tags) { - pieces <- str_split(tag$val, ":", n = 2)[[1]] +#' @export +roxy_tag_parse.roxy_tag_rdname <- function(x) tag_value(x) - title <- str_split(pieces[1], "\n")[[1]] - if (length(title) > 1) { - roxy_tag_warning(tag, - "Section title spans multiple lines: \n", "@section ", title[1] - ) - return() - } +#' @export +roxy_tag_parse.roxy_tag_rawRd <- function(x) tag_value(x) +#' @export +roxy_tag_rd.roxy_tag_rawRd <- function(x, topic, base_path, env) { + topic$add_markdown_field(x$tag, x$val) +} - topic$add_field(roxy_field_section(pieces[1], pieces[2])) - } +#' @export +roxy_tag_parse.roxy_tag_references <- function(x) tag_markdown(x) +#' @export +roxy_tag_rd.roxy_tag_references <- function(x, topic, base_path, env) { + topic$add_markdown_field(x$tag, x$val) } -topic_add_doc_type <- function(topic, block) { - tag <- block_get_tag(block, "docType") - if (is.null(tag)) { +#' @export +roxy_tag_parse.roxy_tag_return <- function(x) tag_markdown(x) +#' @export +roxy_tag_rd.roxy_tag_return <- function(x, topic, base_path, env) { + topic$add_simple_field("value", x$val) +} + +#' @export +roxy_tag_parse.roxy_tag_section <- function(x) tag_markdown(x) +#' @export +roxy_tag_rd.roxy_tag_section <- function(x, topic, base_path, env) { + pieces <- str_split(x$val, ":", n = 2)[[1]] + title <- str_split(pieces[1], "\n")[[1]] + + if (length(title) > 1) { + roxy_tag_warning(x, "Section title spans multiple lines") return() } - topic$add_simple_field("docType", tag$val) + topic$add_field(roxy_field_section(pieces[1], pieces[2])) +} - if (tag$val == "package") { - name <- block_get_tag(block, "name") - if (!str_detect(name$val, "-package")) { - topic$add_simple_field("alias", package_suffix(name$val)) - } - } +#' @export +roxy_tag_parse.roxy_tag_seealso <- function(x) tag_markdown(x) +#' @export +roxy_tag_rd.roxy_tag_seealso <- function(x, topic, base_path, env) { + topic$add_markdown_field(x$tag, x$val) } -package_suffix <- function(name) { - paste0(name, "-package") +#' @export +roxy_tag_parse.roxy_tag_slot <- function(x) tag_name_description(x) +#' @export +roxy_tag_rd.roxy_tag_slot <- function(x, topic, base_path, env) { + value <- setNames(x$val$description, x$val$name) + topic$add_simple_field(x$tag, value) } -# Name + description tags ------------------------------------------------------ +#' @export +roxy_tag_parse.roxy_tag_source <- function(x) tag_markdown(x) +#' @export +roxy_tag_rd.roxy_tag_source <- function(x, topic, base_path, env) { + topic$add_markdown_field(x$tag, x$val) +} -process_def_tag <- function(topic, block, tag) { - tags <- block_get_tags(block, tag) - if (length(tags) == 0) { - return() - } +#' @export +roxy_tag_parse.roxy_tag_template <- function(x) tag_value(x) - desc <- str_trim(map_chr(tags, c("val", "description"))) - names(desc) <- map_chr(tags, c("val", "name")) +#' @export +roxy_tag_parse.roxy_tag_templateVar <- function(x) tag_name_description(x) - topic$add_simple_field(tag, desc) +#' @export +roxy_tag_parse.roxy_tag_title <- function(x) tag_markdown(x) +#' @export +roxy_tag_rd.roxy_tag_title <- function(x, topic, base_path, env) { + topic$add_markdown_field(x$tag, x$val) } + +#' @export +roxy_tag_parse.roxy_tag_usage <- function(x) tag_value(x) diff --git a/R/tag.R b/R/tag.R index 8cf7f56e1..ea71fbf5c 100644 --- a/R/tag.R +++ b/R/tag.R @@ -9,7 +9,8 @@ #' #' @keywords internal #' @export -#' @param tag Tag name +#' @param tag Tag name. Arguments starting with `.` are reserved for internal +#' usage. #' @param raw Raw tag value, a string. #' @param val Parsed tag value, typically a character vector, but sometimes #' a list. Usually filled in by `tag_parsers` @@ -61,7 +62,7 @@ format.roxy_tag <- function(x, ..., file = NULL) { raw <- x$raw } } else { - raw <- "<generated by roxygen2>" + raw <- "<generated>" } parsed <- if (is.null(x$val)) "{unparsed}" else "{parsed}" @@ -88,10 +89,6 @@ roxy_tag_warning <- function(x, ...) { NULL } -roxy_tag_is_field <- function(tag) { - inherits(tag$val, "roxy_field") -} - roxy_tag_eval <- function(tag, env) { tryCatch({ expr <- parse(text = tag$val) diff --git a/R/topic.R b/R/topic.R index f83430c76..5fa89718d 100644 --- a/R/topic.R +++ b/R/topic.R @@ -81,6 +81,16 @@ RoxyTopic <- R6::R6Class("RoxyTopic", public = list( invisible() }, + add_markdown_field = function(name, values) { + if (length(values) && nchar(values[[1]])) { + self$add_simple_field(name, values[[1]]) + } + # Any additional components are sections + for (extra in values[-1]) { + self$add_simple_field("rawRd", extra) + } + }, + add = function(x, overwrite = FALSE) { if (inherits(x, "RoxyTopic")) { self$add(x$fields, overwrite = overwrite) diff --git a/man/roclet.Rd b/man/roclet.Rd index 640adb725..c7eebbce4 100644 --- a/man/roclet.Rd +++ b/man/roclet.Rd @@ -26,13 +26,13 @@ roclet_tags(x) \item{blocks}{A list of \link{roxy_block} objects.} +\item{results}{Value returned from your \code{roclet_process()} method.} + \item{base_path}{Path to root of source package.} \item{global_options}{List of roxygen2 options.} \item{env}{Package environment.} - -\item{results}{Value returned from your \code{roclet_process()} method.} } \description{ To create a new roclet, you will need to create a constructor function diff --git a/man/roxy_tag.Rd b/man/roxy_tag.Rd index f0e652a16..32ae1a8c1 100644 --- a/man/roxy_tag.Rd +++ b/man/roxy_tag.Rd @@ -13,7 +13,8 @@ roxy_tag_parse(x) roxy_tag_warning(x, ...) } \arguments{ -\item{tag}{Tag name} +\item{tag}{Tag name. Arguments starting with \code{.} are reserved for internal +usage.} \item{raw}{Raw tag value, a string.} diff --git a/tests/testthat/test-block-print.txt b/tests/testthat/test-block-print.txt index 1dcf3c3d1..7df455a7e 100644 --- a/tests/testthat/test-block-print.txt +++ b/tests/testthat/test-block-print.txt @@ -4,6 +4,7 @@ [line: 1] @title 'This is a title' {parsed} [line: 5] @param 'x,y A number' {parsed} [line: 6] @export '' {parsed} + [????:???] @.formals '<generated>' {parsed} [????:???] @backref '<text>' {parsed} $call f <- function(x, y) x + y $object <function> diff --git a/tests/testthat/test-rd-doctype.R b/tests/testthat/test-rd-doctype.R index d271b6287..42224a88c 100644 --- a/tests/testthat/test-rd-doctype.R +++ b/tests/testthat/test-rd-doctype.R @@ -1,27 +1,5 @@ context("Rd: docType") -# Package -------------------------------------------------------------------- - -test_that("@docType package automatically adds package alias when needed", { - out <- roc_proc_text(rd_roclet(), " - #' @name a - #' @title a - #' @docType package - NULL - - #' @name a-package - #' @title a - #' @docType package - NULL") - - alias_1 <- get_tag(out[[1]], "alias")$values - expect_equal(alias_1, c("a", "a-package")) - - alias_2 <- get_tag(out[[2]], "alias")$values - expect_equal(alias_2, c("a-package")) -}) - - # Data -------------------------------------------------------------------- test_that("@docType data automatically adds sensible defaults", {