From 43cf5b3cbf2dde50ed46d4818e0a54422d53bfd5 Mon Sep 17 00:00:00 2001 From: Taimur Khan <59065240+thisistaimur@users.noreply.github.com> Date: Tue, 31 Mar 2026 16:04:03 +0500 Subject: [PATCH 1/2] CI: ArgumentUsage updated automatically in each CI run --- .github/ArgumentUsage.xlsx | Bin 12963 -> 11220 bytes .github/scripts/update_argument_usage.R | 645 ++++++++++++++++++++++++ .github/workflows/ci-pipeline.yml | 33 +- 3 files changed, 673 insertions(+), 5 deletions(-) create mode 100644 .github/scripts/update_argument_usage.R diff --git a/.github/ArgumentUsage.xlsx b/.github/ArgumentUsage.xlsx index 6a644578888402d461b02da7f6762275b19d8187..f7a1d5814ccc627f5d9d117d99f0d5ad73a88c17 100644 GIT binary patch literal 11220 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 GeEWaF$2#!< diff --git a/.github/scripts/update_argument_usage.R b/.github/scripts/update_argument_usage.R new file mode 100644 index 0000000..c6317ce --- /dev/null +++ b/.github/scripts/update_argument_usage.R @@ -0,0 +1,645 @@ +#!/usr/bin/env Rscript + +args <- commandArgs(trailingOnly = TRUE) +xlsx_path <- if (length(args) >= 1) args[[1]] else ".github/ArgumentUsage.xlsx" + +normalize_ws <- function(x) { + trimws(gsub("[[:space:]]+", " ", x)) +} + +normalize_arg_key <- function(x) { + x <- normalize_ws(x) + x[x %in% c("...", "\u2026", "\\dots", "\\dots{}")] <- "..." + x +} + +xml_escape <- function(x) { + x <- gsub("&", "&", x, fixed = TRUE) + x <- gsub("<", "<", x, fixed = TRUE) + x <- gsub(">", ">", x, fixed = TRUE) + x <- gsub("\"", """, x, fixed = TRUE) + x <- gsub("'", "'", x, fixed = TRUE) + x +} + +xml_unescape <- function(x) { + x <- gsub("<", "<", x, fixed = TRUE) + x <- gsub(">", ">", x, fixed = TRUE) + x <- gsub(""", "\"", x, fixed = TRUE) + x <- gsub("'", "'", x, fixed = TRUE) + x <- gsub("&", "&", x, fixed = TRUE) + x +} + +read_text <- function(path) { + paste(readLines(path, warn = FALSE, encoding = "UTF-8"), collapse = "") +} + +write_text <- function(path, text) { + writeLines(text, path, useBytes = TRUE) +} + +extract_matches <- function(text, pattern) { + hits <- gregexpr(pattern, text, perl = TRUE) + if (hits[[1]][1] == -1) { + return(character()) + } + regmatches(text, hits)[[1]] +} + +extract_first <- function(text, pattern) { + match <- regexec(pattern, text, perl = TRUE) + groups <- regmatches(text, match)[[1]] + if (length(groups) < 2) { + return(NA_character_) + } + groups[[2]] +} + +col_from_number <- function(index) { + stopifnot(index >= 1) + result <- character() + while (index > 0) { + remainder <- (index - 1) %% 26 + result <- c(intToUtf8(65 + remainder), result) + index <- (index - 1) %/% 26 + } + paste(result, collapse = "") +} + +col_to_number <- function(label) { + chars <- utf8ToInt(label) + total <- 0L + for (char in chars) { + total <- total * 26L + (char - 64L) + } + total +} + +get_named_value <- function(values, name) { + if (name %in% names(values)) { + return(unname(values[[name]])) + } + "" +} + +rd_flatten <- function(node) { + if (is.null(node)) { + return("") + } + if (is.character(node)) { + return(paste(node, collapse = "")) + } + if (!is.list(node)) { + return(as.character(node)) + } + paste(vapply(node, rd_flatten, character(1)), collapse = "") +} + +parse_function_specs <- function(r_dir = "R") { + files <- sort(list.files(r_dir, pattern = "[.][Rr]$", full.names = TRUE)) + specs <- list() + + for (file in files) { + expressions <- as.list(parse(file = file, keep.source = FALSE)) + for (expr in expressions) { + if (!is.call(expr) || length(expr) < 3) { + next + } + if (!identical(as.character(expr[[1]]), "<-")) { + next + } + if (!is.symbol(expr[[2]]) || !is.call(expr[[3]])) { + next + } + if (!identical(expr[[3]][[1]], as.name("function"))) { + next + } + + function_name <- as.character(expr[[2]]) + if (startsWith(function_name, ".")) { + next + } + + function_object <- eval(expr[[3]], envir = baseenv()) + formal_names <- names(formals(function_object)) + if (is.null(formal_names)) { + formal_names <- character() + } + specs[[function_name]] <- formal_names + } + } + + specs +} + +parse_rd_arguments <- function(function_names, man_dir = "man") { + docs <- list() + + for (function_name in function_names) { + rd_path <- file.path(man_dir, paste0(function_name, ".Rd")) + if (!file.exists(rd_path)) { + stop("Missing Rd file for function: ", function_name) + } + + rd <- suppressWarnings(tools::parse_Rd(rd_path)) + rd_tags <- vapply(rd, function(node) attr(node, "Rd_tag"), character(1)) + argument_section_index <- which(rd_tags == "\\arguments") + + if (!length(argument_section_index)) { + docs[[function_name]] <- stats::setNames(character(), character()) + next + } + + argument_section <- rd[[argument_section_index[[1]]]] + item_tags <- vapply(argument_section, function(node) attr(node, "Rd_tag"), character(1)) + items <- argument_section[item_tags == "\\item"] + + descriptions <- character() + argument_names <- character() + + for (item in items) { + argument_name <- normalize_arg_key(rd_flatten(item[[1]])) + description <- normalize_ws(rd_flatten(item[[2]])) + + if (!nzchar(argument_name)) { + next + } + + argument_names <- c(argument_names, argument_name) + descriptions <- c(descriptions, description) + } + + if (anyDuplicated(argument_names)) { + stop("Duplicate argument documentation in ", rd_path) + } + + docs[[function_name]] <- stats::setNames(descriptions, argument_names) + } + + docs +} + +validate_documentation <- function(function_specs, rd_docs) { + for (function_name in names(function_specs)) { + formal_keys <- normalize_arg_key(function_specs[[function_name]]) + doc_keys <- names(rd_docs[[function_name]]) + + missing_docs <- setdiff(formal_keys, doc_keys) + extra_docs <- setdiff(doc_keys, formal_keys) + + if (length(missing_docs) || length(extra_docs)) { + message("Formal arguments: ", paste(formal_keys, collapse = ", ")) + message("Documented args: ", paste(doc_keys, collapse = ", ")) + stop( + "Function documentation mismatch for ", function_name, + if (length(missing_docs)) paste0("; missing docs: ", paste(missing_docs, collapse = ", ")) else "", + if (length(extra_docs)) paste0("; extra docs: ", paste(extra_docs, collapse = ", ")) else "" + ) + } + } +} + +parse_shared_strings <- function(path) { + xml <- read_text(path) + string_nodes <- extract_matches(xml, "(?s).*?") + + vapply(string_nodes, function(node) { + text_nodes <- extract_matches(node, "(?s).*?") + pieces <- vapply(text_nodes, function(text_node) { + xml_unescape(extract_first(text_node, "(?s)^(.*?)$")) + }, character(1)) + paste(pieces, collapse = "") + }, character(1)) +} + +parse_sheet_template <- function(path, shared_strings) { + sheet_xml <- read_text(path) + row_nodes <- extract_matches(sheet_xml, "(?s)]*>.*?") + rows <- list() + + for (row_node in row_nodes) { + row_number <- as.integer(extract_first(row_node, 'r="([0-9]+)"')) + cell_nodes <- extract_matches(row_node, "(?s)]*>.*?") + row_values <- character() + + for (cell_node in cell_nodes) { + ref <- extract_first(cell_node, 'r="([A-Z]+[0-9]+)"') + cell_type <- extract_first(cell_node, 't="([^"]+)"') + raw_value <- extract_first(cell_node, "(?s)(.*?)") + + if (is.na(ref) || is.na(raw_value)) { + next + } + + value <- if (!is.na(cell_type) && identical(cell_type, "s")) { + shared_strings[as.integer(raw_value) + 1L] + } else { + xml_unescape(raw_value) + } + + row_values[ref] <- value + } + + rows[[as.character(row_number)]] <- row_values + } + + header_values <- rows[["1"]] + header_refs <- names(header_values) + header_cols <- sub("[0-9]+$", "", header_refs) + header_order <- order(vapply(header_cols, col_to_number, integer(1))) + header_values <- header_values[header_order] + header_cols <- header_cols[header_order] + + data_row_numbers <- sort(setdiff(as.integer(names(rows)), 1L)) + argument_rows <- lapply(data_row_numbers, function(row_number) { + row_values <- rows[[as.character(row_number)]] + list( + row_number = row_number, + display = get_named_value(row_values, paste0("A", row_number)), + usage = get_named_value(row_values, paste0("B", row_number)) + ) + }) + + list( + xml = sheet_xml, + headers = unname(header_values[!(header_cols %in% c("A", "B"))]), + argument_rows = argument_rows + ) +} + +legacy_function_aliases <- c( + "Helper_InputCheck" = "Helper_InputChecker", + "Metrics_ETTCDI" = "Metrics_ETCCDI" +) + +argument_description_overrides <- c( + extent = paste( + "Optional. The extent to subset the data to, in coordinates of the projection", + "or decimal degrees of longitude and latitude. A numeric vector of length 4", + "with values minimum and maximum X/longitude and minimum and maximum", + "Y/latitude, in that order. Valid ranges depend on the dataset being queried.", + "Defaults to NULL, returning full spatial range of data." + ) +) + +order_functions <- function(existing_headers, current_functions) { + ordered <- character() + used <- character() + removed_headers <- character() + replaced_headers <- character() + discovery_functions <- current_functions[grepl("^Discovery_", current_functions)] + + add_function <- function(function_name) { + if (function_name %in% current_functions && !(function_name %in% used)) { + ordered <<- c(ordered, function_name) + used <<- c(used, function_name) + } + } + + for (header in existing_headers) { + if (header %in% current_functions) { + add_function(header) + next + } + + if (header %in% names(legacy_function_aliases)) { + mapped_name <- unname(legacy_function_aliases[[header]]) + if (mapped_name %in% current_functions) { + add_function(mapped_name) + replaced_headers <- c(replaced_headers, paste0(header, " -> ", mapped_name)) + } else { + removed_headers <- c(removed_headers, header) + } + next + } + + if (identical(header, "Discovery_*")) { + remaining_discovery <- discovery_functions[!(discovery_functions %in% used)] + if (length(remaining_discovery)) { + for (function_name in remaining_discovery) { + add_function(function_name) + } + replaced_headers <- c( + replaced_headers, + paste0("Discovery_* -> ", paste(remaining_discovery, collapse = ", ")) + ) + } else { + removed_headers <- c(removed_headers, header) + } + next + } + + removed_headers <- c(removed_headers, header) + } + + appended <- current_functions[!(current_functions %in% used)] + for (function_name in appended) { + add_function(function_name) + } + + list( + ordered = ordered, + removed_headers = removed_headers, + replaced_headers = replaced_headers, + appended = appended + ) +} + +build_existing_argument_map <- function(argument_rows) { + argument_map <- list() + + for (row in argument_rows) { + key <- normalize_arg_key(row$display) + if (key %in% names(argument_map)) { + stop("Duplicate argument row in workbook template after normalization: ", key) + } + argument_map[[key]] <- row + } + + argument_map +} + +build_argument_order <- function(existing_argument_rows, function_specs, ordered_functions) { + source_order <- unique(unlist(lapply(ordered_functions, function(function_name) { + normalize_arg_key(function_specs[[function_name]]) + }), use.names = FALSE)) + + ordered_keys <- character() + used <- character() + + for (row in existing_argument_rows) { + key <- normalize_arg_key(row$display) + if (key %in% source_order && !(key %in% used)) { + ordered_keys <- c(ordered_keys, key) + used <- c(used, key) + } + } + + c(ordered_keys, source_order[!(source_order %in% used)]) +} + +build_doc_index <- function(ordered_functions, rd_docs) { + doc_index <- list() + + for (function_name in ordered_functions) { + docs <- rd_docs[[function_name]] + for (arg_key in names(docs)) { + doc_index[[arg_key]] <- c(doc_index[[arg_key]], docs[[arg_key]]) + } + } + + doc_index +} + +resolve_argument_description <- function(arg_key, existing_argument_map, doc_index) { + if (!is.null(existing_argument_map[[arg_key]])) { + existing_usage <- existing_argument_map[[arg_key]]$usage + if (nzchar(normalize_ws(existing_usage))) { + return(existing_usage) + } + } + + if (arg_key %in% names(argument_description_overrides)) { + return(unname(argument_description_overrides[[arg_key]])) + } + + candidates <- unique(normalize_ws(unlist(doc_index[[arg_key]]))) + candidates <- candidates[nzchar(candidates)] + + if (!length(candidates)) { + stop("No documented description found for argument: ", arg_key) + } + + if (length(candidates) > 1) { + stop( + "Conflicting descriptions found for new argument ", arg_key, ": ", + paste(candidates, collapse = " | ") + ) + } + + candidates[[1]] +} + +resolve_argument_display <- function(arg_key, existing_argument_map) { + if (!is.null(existing_argument_map[[arg_key]]) && nzchar(existing_argument_map[[arg_key]]$display)) { + return(existing_argument_map[[arg_key]]$display) + } + arg_key +} + +new_string_pool <- function() { + values <- character() + + list( + index = function(value) { + match_index <- match(value, values) + if (is.na(match_index)) { + values <<- c(values, value) + match_index <- length(values) + } + match_index - 1L + }, + values = function() values + ) +} + +make_shared_strings_xml <- function(values, total_count) { + si_nodes <- vapply(values, function(value) { + preserve_space <- grepl("(^\\s)|(\\s$)", value) + sprintf( + "%s", + if (preserve_space) ' xml:space="preserve"' else "", + xml_escape(value) + ) + }, character(1)) + + paste0( + '', + '', + paste(si_nodes, collapse = ""), + "" + ) +} + +make_sheet_data_xml <- function(argument_rows, ordered_functions, function_specs) { + pool <- new_string_pool() + total_cells <- 0L + total_columns <- length(ordered_functions) + 2L + + make_cell <- function(ref, value, style = NULL) { + total_cells <<- total_cells + 1L + attributes <- c(sprintf('r="%s"', ref)) + if (!is.null(style)) { + attributes <- c(attributes, sprintf('s="%s"', style)) + } + attributes <- c(attributes, 't="s"') + sprintf( + "%d", + paste(attributes, collapse = " "), + pool$index(value) + ) + } + + header_cells <- c( + make_cell("A1", "Argument", style = 2), + make_cell("B1", "Usage", style = 2), + vapply(seq_along(ordered_functions), function(index) { + make_cell(paste0(col_from_number(index + 2L), "1"), ordered_functions[[index]], style = 2) + }, character(1)) + ) + + row_nodes <- character(length(argument_rows) + 1L) + row_nodes[[1]] <- sprintf( + '%s', + total_columns, + paste(header_cells, collapse = "") + ) + + function_arguments <- lapply(function_specs[ordered_functions], normalize_arg_key) + + for (row_index in seq_along(argument_rows)) { + sheet_row <- row_index + 1L + argument_row <- argument_rows[[row_index]] + cells <- c( + make_cell(paste0("A", sheet_row), argument_row$display, style = 1), + make_cell(paste0("B", sheet_row), argument_row$usage) + ) + + marker_cells <- vapply(seq_along(ordered_functions), function(function_index) { + function_name <- ordered_functions[[function_index]] + if (!(argument_row$key %in% function_arguments[[function_name]])) { + return("") + } + make_cell(paste0(col_from_number(function_index + 2L), sheet_row), "X") + }, character(1)) + marker_cells <- marker_cells[nzchar(marker_cells)] + + row_nodes[[row_index + 1L]] <- sprintf( + '%s', + sheet_row, + total_columns, + paste(c(cells, marker_cells), collapse = "") + ) + } + + list( + shared_strings = pool$values(), + shared_string_count = total_cells, + sheet_data = paste0("", paste(row_nodes, collapse = ""), ""), + total_columns = total_columns, + total_rows = length(argument_rows) + 1L + ) +} + +update_sheet_xml <- function(template_xml, new_dimension, new_cols_xml, new_sheet_data_xml) { + updated <- template_xml + updated <- sub('', sprintf('', new_dimension), updated) + updated <- sub("(?s).*?", new_cols_xml, updated, perl = TRUE) + updated <- sub("(?s).*?", new_sheet_data_xml, updated, perl = TRUE) + updated +} + +repack_xlsx <- function(tempdir, destination) { + destination <- normalizePath(destination, winslash = "/", mustWork = FALSE) + zip_path <- tempfile(fileext = ".xlsx") + files <- list.files(tempdir, recursive = TRUE, all.files = TRUE, no.. = TRUE) + old_wd <- setwd(tempdir) + on.exit(setwd(old_wd), add = TRUE) + + utils::zip(zipfile = zip_path, files = files, flags = "-q -r9X") + + if (!file.exists(zip_path)) { + stop("Failed to rebuild workbook at ", destination) + } + + if (!file.copy(zip_path, destination, overwrite = TRUE)) { + stop("Failed to copy rebuilt workbook to ", destination) + } +} + +if (!file.exists(xlsx_path)) { + stop("Workbook does not exist: ", xlsx_path) +} + +tempdir <- tempfile("argument-usage-") +dir.create(tempdir) +on.exit(unlink(tempdir, recursive = TRUE), add = TRUE) + +utils::unzip(xlsx_path, exdir = tempdir) + +shared_strings_path <- file.path(tempdir, "xl", "sharedStrings.xml") +sheet_path <- file.path(tempdir, "xl", "worksheets", "sheet1.xml") + +if (!file.exists(shared_strings_path) || !file.exists(sheet_path)) { + stop("Workbook template is missing required OpenXML parts") +} + +existing_shared_xml <- read_text(shared_strings_path) +existing_sheet_xml <- read_text(sheet_path) +shared_strings <- parse_shared_strings(shared_strings_path) +sheet_template <- parse_sheet_template(sheet_path, shared_strings) + +function_specs <- parse_function_specs("R") +ordered_source_functions <- names(function_specs) +rd_docs <- parse_rd_arguments(ordered_source_functions, "man") +validate_documentation(function_specs, rd_docs) + +existing_argument_map <- build_existing_argument_map(sheet_template$argument_rows) +function_order <- order_functions(sheet_template$headers, ordered_source_functions) +ordered_functions <- function_order$ordered +ordered_argument_keys <- build_argument_order(sheet_template$argument_rows, function_specs, ordered_functions) +doc_index <- build_doc_index(ordered_functions, rd_docs) + +argument_rows <- lapply(ordered_argument_keys, function(arg_key) { + list( + key = arg_key, + display = resolve_argument_display(arg_key, existing_argument_map), + usage = resolve_argument_description(arg_key, existing_argument_map, doc_index) + ) +}) + +sheet_data <- make_sheet_data_xml(argument_rows, ordered_functions, function_specs) +last_column <- col_from_number(sheet_data$total_columns) +new_dimension <- sprintf("A1:%s%d", last_column, sheet_data$total_rows) +new_cols_xml <- sprintf( + '', + sheet_data$total_columns +) +new_sheet_xml <- update_sheet_xml( + template_xml = sheet_template$xml, + new_dimension = new_dimension, + new_cols_xml = new_cols_xml, + new_sheet_data_xml = sheet_data$sheet_data +) +new_shared_xml <- make_shared_strings_xml( + values = sheet_data$shared_strings, + total_count = sheet_data$shared_string_count +) + +message("Workbook function columns: ", length(ordered_functions)) +message("Workbook argument rows: ", length(argument_rows)) + +if (length(function_order$replaced_headers)) { + message("Replaced legacy headers: ", paste(function_order$replaced_headers, collapse = "; ")) +} +if (length(function_order$removed_headers)) { + message("Removed stale headers: ", paste(function_order$removed_headers, collapse = ", ")) +} +if (length(function_order$appended)) { + message("Appended new function columns: ", paste(function_order$appended, collapse = ", ")) +} + +if (identical(existing_shared_xml, new_shared_xml) && identical(existing_sheet_xml, new_sheet_xml)) { + message("ArgumentUsage workbook is already up to date.") + quit(status = 0) +} + +write_text(shared_strings_path, new_shared_xml) +write_text(sheet_path, new_sheet_xml) +repack_xlsx(tempdir, xlsx_path) +message("Updated ", xlsx_path) diff --git a/.github/workflows/ci-pipeline.yml b/.github/workflows/ci-pipeline.yml index 6c71411..fadc58f 100644 --- a/.github/workflows/ci-pipeline.yml +++ b/.github/workflows/ci-pipeline.yml @@ -58,6 +58,15 @@ jobs: fetch-depth: 0 fetch-tags: true + - name: Set up R + uses: r-lib/actions/setup-r@v2 + + - name: Validate ArgumentUsage workbook + run: | + set -euo pipefail + Rscript .github/scripts/update_argument_usage.R + git diff --exit-code -- .github/ArgumentUsage.xlsx + - name: Set up environment / dependencies run: | # install dependencies, etc. @@ -71,7 +80,9 @@ jobs: release: name: Create GitHub Release runs-on: ubuntu-latest - needs: versioning + needs: + - versioning + - build # only create a release on pushes and when a version was produced if: ${{ github.event_name == 'push' && needs.versioning.outputs.version != '' }} permissions: @@ -84,6 +95,9 @@ jobs: fetch-depth: 0 fetch-tags: true + - name: Set up R + uses: r-lib/actions/setup-r@v2 + - name: Update DESCRIPTION file with new version run: | set -euo pipefail @@ -96,15 +110,24 @@ jobs: echo "New DESCRIPTION header:" head -n 5 DESCRIPTION - # Commit only if it actually changed + - name: Refresh ArgumentUsage workbook + run: | + set -euo pipefail + Rscript .github/scripts/update_argument_usage.R + + - name: Commit release metadata + run: | + set -euo pipefail + VERSION="${{ needs.versioning.outputs.version }}" + git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - git add DESCRIPTION + git add DESCRIPTION .github/ArgumentUsage.xlsx if ! git diff --cached --quiet; then - git commit -m "[skip ci] chore: bump DESCRIPTION Version to ${VERSION}" + git commit -m "[skip ci] chore: bump DESCRIPTION Version to ${VERSION} and refresh ArgumentUsage workbook" git push else - echo "DESCRIPTION already at ${VERSION}; nothing to commit." + echo "DESCRIPTION and ArgumentUsage workbook already up to date; nothing to commit." fi - name: Create GitHub Release From 96511ca190240cf2518be8e070f75f3f52a19d4a Mon Sep 17 00:00:00 2001 From: Taimur Khan <59065240+thisistaimur@users.noreply.github.com> Date: Tue, 31 Mar 2026 16:14:32 +0500 Subject: [PATCH 2/2] CI: documentation added for excel file autopmation --- .github/ArgumentUsage.xlsx | Bin 11220 -> 11220 bytes .github/scripts/update_argument_usage.R | 30 ++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/.github/ArgumentUsage.xlsx b/.github/ArgumentUsage.xlsx index f7a1d5814ccc627f5d9d117d99f0d5ad73a88c17..9fb1cf40fef94eaa2829395c739a2cda6500f895 100644 GIT binary patch delta 163 zcmcZ-ekGhIz?+#xgn@~HgJEIgMjjzXriG206&P#4^gO0zVA_o(3ruffRRYu3*gUwv z>I7MtLA1Jv06U1bl->*G|Bwp;$xrstGzU?wn)V>-n5GSgV$re%QO;U!AgWu-0YshE TvIJ2a+F+HQ+SVXmo3I7MtLA1Jv06U1bl->*G|Bwp;$xrstGzU?wn)V>-n5GSgV$re%QO;U!AgWu-0YshE TvIJ2a+F+HQ+SVXmo3= 1) args[[1]] else ".github/ArgumentUsage.xlsx" @@ -97,6 +102,8 @@ rd_flatten <- function(node) { } parse_function_specs <- function(r_dir = "R") { + # Discover top-level package functions directly from source without + # evaluating their bodies. Dot-prefixed lifecycle hooks are skipped. files <- sort(list.files(r_dir, pattern = "[.][Rr]$", full.names = TRUE)) specs <- list() @@ -134,6 +141,7 @@ parse_function_specs <- function(r_dir = "R") { } parse_rd_arguments <- function(function_names, man_dir = "man") { + # Extract the canonical argument text from each generated Rd file. docs <- list() for (function_name in function_names) { @@ -181,6 +189,7 @@ parse_rd_arguments <- function(function_names, man_dir = "man") { } validate_documentation <- function(function_specs, rd_docs) { + # Fail fast if a function signature and its Rd file disagree. for (function_name in names(function_specs)) { formal_keys <- normalize_arg_key(function_specs[[function_name]]) doc_keys <- names(rd_docs[[function_name]]) @@ -201,6 +210,8 @@ validate_documentation <- function(function_specs, rd_docs) { } parse_shared_strings <- function(path) { + # The workbook uses shared strings for all populated cells, so these need + # to be decoded before the existing sheet can be matched and rebuilt. xml <- read_text(path) string_nodes <- extract_matches(xml, "(?s).*?") @@ -214,6 +225,8 @@ parse_shared_strings <- function(path) { } parse_sheet_template <- function(path, shared_strings) { + # Read the existing sheet so current argument rows and function columns can + # be preserved instead of re-sorted from scratch on each run. sheet_xml <- read_text(path) row_nodes <- extract_matches(sheet_xml, "(?s)]*>.*?") rows <- list() @@ -284,6 +297,8 @@ argument_description_overrides <- c( ) order_functions <- function(existing_headers, current_functions) { + # Preserve the current workbook column order, apply known legacy header + # mappings, expand the old grouped Discovery column, and append anything new. ordered <- character() used <- character() removed_headers <- character() @@ -361,6 +376,8 @@ build_existing_argument_map <- function(argument_rows) { } build_argument_order <- function(existing_argument_rows, function_specs, ordered_functions) { + # Keep existing argument row order for surviving arguments, then append + # newly introduced arguments in first-seen source order. source_order <- unique(unlist(lapply(ordered_functions, function(function_name) { normalize_arg_key(function_specs[[function_name]]) }), use.names = FALSE)) @@ -393,6 +410,8 @@ build_doc_index <- function(ordered_functions, rd_docs) { } resolve_argument_description <- function(arg_key, existing_argument_map, doc_index) { + # Existing workbook text wins to avoid churn. New arguments must either + # have a single documented description or an explicit override above. if (!is.null(existing_argument_map[[arg_key]])) { existing_usage <- existing_argument_map[[arg_key]]$usage if (nzchar(normalize_ws(existing_usage))) { @@ -467,6 +486,9 @@ make_shared_strings_xml <- function(values, total_count) { } make_sheet_data_xml <- function(argument_rows, ordered_functions, function_specs) { + # Rebuild the single worksheet as the same argument/function matrix: + # row 1 = headers, column A = argument names, column B = descriptions, + # remaining cells = X markers for argument usage. pool <- new_string_pool() total_cells <- 0L total_columns <- length(ordered_functions) + 2L @@ -545,6 +567,8 @@ update_sheet_xml <- function(template_xml, new_dimension, new_cols_xml, new_shee } repack_xlsx <- function(tempdir, destination) { + # Keep the rest of the xlsx package untouched and only replace the rebuilt + # OpenXML payload after the new parts have been written to the temp tree. destination <- normalizePath(destination, winslash = "/", mustWork = FALSE) zip_path <- tempfile(fileext = ".xlsx") files <- list.files(tempdir, recursive = TRUE, all.files = TRUE, no.. = TRUE) @@ -566,6 +590,12 @@ if (!file.exists(xlsx_path)) { stop("Workbook does not exist: ", xlsx_path) } +# Main flow: +# 1. unpack the current workbook +# 2. read existing layout from sheet1/sharedStrings +# 3. derive current functions and docs from source +# 4. reconcile old layout with new source-of-truth +# 5. write the workbook back only if the XML payload changed tempdir <- tempfile("argument-usage-") dir.create(tempdir) on.exit(unlink(tempdir, recursive = TRUE), add = TRUE)