#' srr_stats_roclet
#'
#' Get values of all `srrstats` tags in function documentation
#'
#' Note that this function should never need to be called directly. It only
#' exists to enable "@srrstats" tags to be parsed from \pkg{roxygen2}
#' documentation.
#'
#' @importFrom roxygen2 roclet
#' @return A \pkg{roxygen2} roclet
#' @family roxygen
#'
#' @examples
#' srr_stats_roclet ()
#' @export
srr_stats_roclet <- function () {
roxygen2::roclet ("srr_stats")
}
#' @importFrom roxygen2 roclet_process
#'
#' @export
roclet_process.roclet_srr_stats <- function (x, blocks, env, base_path) { # nolint
if (!get_verbose_flag (blocks)) {
return (NULL)
}
blocks <- collect_blocks (blocks, base_path)
# ------ @srrstats tags:
msgs <- collect_one_tag (base_path, blocks, tag = "srrstats")
msgs_na <- collect_one_tag (base_path, blocks, tag = "srrstatsNA")
msgs_todo <- collect_one_tag (base_path, blocks,
tag = "srrstatsTODO"
)
num_stds <- function (m) {
stds <- regmatches (m, gregexpr ("\\[(.*?)\\]", m))
stds <- lapply (stds, function (i) {
strsplit (gsub ("^\\[|\\]$", "", i), ",\\s?") [[1]]
})
length (unlist (stds))
}
num_mgs <- num_stds (msgs)
num_mgs_na <- num_stds (msgs_na)
num_mgs_todo <- num_stds (msgs_todo)
num_total <- num_mgs + num_mgs_na + num_mgs_todo
check_no_mixed_tags (msgs, msgs_na, msgs_todo)
has_output <- (length (msgs) > 0L |
length (msgs_na) > 0L |
length (msgs_todo) > 0L)
if (has_output) {
txt <- "rOpenSci Statistical Software Standards"
message (cli::rule (center = cli::col_green (txt), line_col = "green"))
}
if (length (msgs) > 0L) {
cli::cli_h3 ("@srrstats standards ({num_mgs} / {num_total}):")
print_one_msg_list (msgs)
}
if (length (msgs_na) > 0L) {
cli::cli_h3 ("@srrstatsNA standards ({num_mgs_na} / {num_total}):")
print_one_msg_list (msgs_na)
}
if (length (msgs_todo) > 0L) {
cli::cli_h3 ("@srrstatsTODO standards ({num_mgs_todo} / {num_total}):")
print_one_msg_list (msgs_todo)
}
if (has_output) {
message (cli::rule (line_col = "green"))
}
return (NULL)
}
collect_blocks <- function (blocks, base_path) {
rcpp <- vapply (
blocks, function (block) {
basename (block$file) == "RcppExports.R"
},
logical (1)
)
rcpp_blocks <- blocks [which (rcpp)]
blocks <- blocks [which (!rcpp)]
file_paths <- vapply (blocks, function (i) {
gsub (base_path, "", i$file)
}, character (1), USE.NAMES = FALSE)
re <- regexpr ("^.*\\/", file_paths)
file_dirs <- rep (".", length (file_paths))
index <- which (re > 0)
file_dirs [index] <- regmatches (file_paths, re)
file_dirs <- gsub ("^\\/", "", file_dirs)
file_dirs <- gsub ("\\/.*$", "", file_dirs)
readme_blocks <- blocks [which (file_dirs == "")]
test_blocks <- blocks [which (file_dirs == "tests")]
r_blocks <- blocks [which (file_dirs == "R")]
vignette_blocks <- blocks [which (file_dirs == "vignettes")]
inst_blocks <- blocks [which (file_dirs == "inst")]
blocks <- list (
R = r_blocks,
src = rcpp_blocks,
tests = test_blocks,
inst = inst_blocks,
vignettes = vignette_blocks,
readme = readme_blocks
)
return (blocks)
}
get_verbose_flag <- function (blocks) {
n <- vapply (
blocks, function (i) {
length (roxygen2::block_get_tags (i, "srrstatsVerbose"))
},
integer (1)
)
if (sum (n) > 1) {
stop (
"There must be only one @srrstatsVerbose flag ",
"in your documentation"
)
}
if (sum (n) == 0) {
return (TRUE)
}
block <- blocks [[which (n == 1)]]
flag <- roxygen2::block_get_tags (block, "srrstatsVerbose") [[1]]$val
if (is.na (as.logical (flag))) {
stop (
"The @srrstatsVerbose tag should only have ",
"'TRUE' or 'FALSE' after it"
)
}
return (as.logical (flag))
}
parse_one_msg_list <- function (msgs, block, tag, fn_name = TRUE, dir = "R") {
if (length (roxygen2::block_get_tags (block, tag)) > 0L) {
msgs <- c (
msgs,
process_srrstats_tags (
tag = tag,
block = block,
fn_name = fn_name,
dir = dir
)
)
}
return (msgs)
}
print_one_msg_list <- function (msgs) {
if (length (msgs) > 0L) {
message (paste0 (" * ", msgs, collapse = "\n"), sep = "")
}
}
# Collect all messages for one tag
collect_one_tag <- function (base_path, blocks, tag = "srrstats") {
msgs <- list ()
for (block in blocks$R) {
msgs <- parse_one_msg_list (msgs, block, tag = tag, fn_name = TRUE)
}
msgs <- c (
msgs,
get_other_tags (blocks$tests, tag = tag, dir = "tests/testthat"),
get_other_tags (blocks$inst, tag = tag, dir = "inst")
)
msgs <- c (msgs, get_src_tags (blocks$src, base_path, tag = tag))
msgs <- c (msgs, get_other_tags (blocks$readme, tag = tag, dir = "."))
msgs <- c (msgs, get_other_tags (blocks$vignettes, tag = tag, dir = "vignettes"))
return (msgs)
}
#' check_block_title
#'
#' Ensure that standards with either 'srrstats' or 'srrstatsTODO' are NOT in a
#' block with a title of 'NA_standards'
#'
#' @noRd
check_block_title <- function (block, tag) {
block_title <- roxygen2::block_get_tag_value (block, "title")
block_title <- ifelse (is.null (block_title), "", block_title)
if (tag != "srrstatsNA" && grepl ("^NA\\_st", block_title)) {
stop (paste0 (
"An NA_standards block should only contain ",
"'@srrstatsNA' tags, and no '@",
tag, "' tags."
))
} else if (tag == "srrstatsNA" & !block_title == "NA_standards") {
stop (
"@srrstatsNA tags should only appear in ",
"a block with a title of NA_standards"
)
}
}
get_fn_name_from_block <- function (block) {
func_name <- block$object$alias
if (is.null (func_name)) {
pd <- tryCatch (
utils::getParseData (parse (text = deparse (block$call))),
error = function (e) NULL
)
if (!is.null (pd)) {
if (nrow (pd) > 0L) {
pd <- pd [-(which (pd$token == "expr")), ]
if (nrow (pd) >= 3L) {
if (pd$token [1] == "SYMBOL" &&
pd$token [2] == "LEFT_ASSIGN" &&
pd$token [3] == "FUNCTION") {
func_name <- pd$text [1]
}
}
}
}
}
return (func_name)
}
#' process_srrstats_tags
#'
#' @param fn_name Include name of calling function in message?
#' @noRd
process_srrstats_tags <- function (tag = "srrstats", block,
fn_name = TRUE, dir = "R") {
check_block_title (block, tag)
standards <- roxygen2::block_get_tags (block, tag)
standards <- unlist (lapply (standards, function (i) i$val))
snum <- extract_standard_numbers (standards)
block_backref <- get_block_backref (block)
block_line <- block$line
msg <- paste0 ("[", paste0 (snum, collapse = ", "), "]")
if (fn_name) {
func_name <- get_fn_name_from_block (block)
if (!is.null (func_name)) {
msg <- paste0 (msg, " in function '", func_name, "()'")
}
}
ptn <- paste0 ("^.*", dir, "\\/")
if (grepl (ptn, block$file)) {
fpath <- regmatches (block$file, regexpr (ptn, block$file))
term_ptn <- "/"
} else {
# Generally only 'tests/testthat.R' where 'dir = tests/testthat'
term_ptn <- paste0 ("\\.", tools::file_ext (block$file))
ptn <- paste0 ("^.*", dir, term_ptn, "$")
fpath <- regmatches (block$file, regexpr (ptn, block$file))
}
fpath_full <- gsub (fpath, paste0 (dir, term_ptn), block$file)
msg <- paste0 (
msg, " on line#", block_line,
" of file [",
fpath_full,
"]"
)
return (msg)
}
# extract the actual standards numbers from arbitrary text strings, first
# capturing everything inside first "[...]":
extract_standard_numbers <- function (standards) {
# roxygen parses markdown "**A**" as "\\strong{A}", and the curly braces
# muck up standards ID, so have to be removed here:
g <- gregexpr ("\\\\(strong|emph)\\{[A-Z]+[0-9]+(\\.[0-9]+)?\\}", standards)
m <- lapply (regmatches (standards, g), function (i) {
res <- paste0 (i, collapse = "|")
res <- gsub ("\\\\(strong|emph)", "\\\\\\\\(strong|emph)", res)
res <- gsub ("\\{", "\\\\{", res)
return (gsub ("\\}", "\\\\}", res))
})
for (i in seq_along (m)) {
standards [i] <- gsub (m [[i]], "", standards [i])
}
# These use regexpr and not gregexpr to only match first '{...}' while
# ignoring all subsequent ones
g_open <- regexpr ("\\{[A-Z]+[0-9]+\\.[0-9]+([a-z]?)", standards)
g_close <- regexpr ("[A-Z]+[0-9]+\\.[0-9]+([a-z]?)\\}", standards)
g_close <- g_close + attr (g_close, "match.length") - 1
standards <- gsub ("\\{|\\}", "", substring (standards, g_open, g_close))
standards <- gsub ("\\s*", "", unlist (strsplit (standards, ",")))
if (length (standards) < 1) {
stop ("srrstats tags found but no correctly-formatted standard numbers")
}
return (standards)
}
get_block_backref <- function (block, base_path = NULL) {
block_backref <- roxygen2::block_get_tag_value (block, "backref")
if (is.null (block_backref)) {
block_backref <- block$file
}
if (!is.null (base_path)) {
block_backref <- gsub (base_path, "", block_backref)
} else {
block_backref <- basename (block_backref)
}
return (block_backref)
}
get_src_tags <- function (blocks, base_path, tag = "srrstats") {
n <- vapply (
blocks, function (i) {
length (roxygen2::block_get_tags (i, tag))
},
integer (1)
)
blocks <- blocks [which (n > 0)]
msgs <- list ()
src_files <- list.files (file.path (base_path, "src"),
pattern = "\\.cpp$|\\.hpp$|.h$",
full.names = TRUE
)
src_files <- src_files [-grep ("RcppExports.cpp", src_files)]
for (block in blocks) { # usually only 1 block for "RcppExports.R"
block_tags <- roxygen2::block_get_tags (block, tag)
for (tag in block_tags) {
tag_txt <- paste0 (tag$tag, "\\s+", tag$val)
tag_txt <- gsub ("\\{", "\\\\{", tag_txt)
tag_txt <- gsub ("\\}", "\\\\}", tag_txt)
which_file <- vapply (
src_files, function (f) {
any (grepl (tag_txt, readLines (f)))
},
logical (1)
)
this_src <- src_files [which (which_file)]
if (length (this_src) > 1) {
this_src <- grep ("\\.cpp", this_src, value = TRUE)
}
src_lines <- readLines (this_src)
line_num <- grep (tag_txt, src_lines)
roxy_lines <- grep ("\\/\\/\\'", src_lines)
index <- cumsum (c (FALSE, diff (roxy_lines) > 1))
roxy_lines <- split (roxy_lines, index)
this_group <- which (vapply (
roxy_lines, function (i) {
line_num %in% i
},
logical (1)
))
roxy_lines <- roxy_lines [[this_group]]
src_lines <- src_lines [(max (roxy_lines) + 1):length (src_lines)]
while (src_lines [1] == "" ||
grepl ("Rcpp::export", src_lines [1])) {
src_lines <- src_lines [-1]
}
this_fn <- strsplit (src_lines [1], "\\s") [[1]] [2]
snum <- extract_standard_numbers (tag$val)
this_src <- fs::path ("src", basename (this_src))
msgs <- c (msgs, paste0 (
"[", paste0 (snum, collapse = ", "),
"] in function '", this_fn,
"()' on line#", line_num, " of file [",
fs::path ("src", basename (this_src)),
"]"
))
} # end for tag in block_tags
} # end for block in blocks
return (msgs)
}
get_other_tags <- function (blocks, tag = "srrstats", dir = "tests") {
msgs <- list ()
for (block in blocks) {
msgs <- parse_one_msg_list (
msgs,
block,
tag = tag,
fn_name = FALSE,
dir = dir
)
}
return (msgs)
}
#' @importFrom roxygen2 roclet_output
#'
#' @export
roclet_output.roclet_srr_stats <- function (x, results, base_path, ...) { # nolint
return (NULL)
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.