From 54c1ddf52d6eb818797d7e03d304e99aff64e480 Mon Sep 17 00:00:00 2001 From: Kyle Baron Date: Thu, 15 Feb 2024 10:54:28 -0600 Subject: [PATCH 01/29] adding rd files for glossary feature --- NAMESPACE | 4 + R/AAAA.R | 2 +- R/table-object.R | 72 ++++++++++++++++ R/utils.R | 35 ++++++++ inst/tex/glossary.tex | 190 ++++++++++++++++++++++++++++++++++++++++++ man/glossary_notes.Rd | 30 +++++++ man/read_glossary.Rd | 29 +++++++ man/st_notes_glo.Rd | 47 +++++++++++ 8 files changed, 408 insertions(+), 1 deletion(-) create mode 100644 inst/tex/glossary.tex create mode 100644 man/glossary_notes.Rd create mode 100644 man/read_glossary.Rd create mode 100644 man/st_notes_glo.Rd diff --git a/NAMESPACE b/NAMESPACE index fc3f4f92..01ee5fe4 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -59,6 +59,7 @@ export(digit1) export(ensure_parens) export(find_bq_col) export(get_stable_data) +export(glossary_notes) export(is.colgroup) export(is.colsplit) export(is.rowpanel) @@ -80,6 +81,7 @@ export(pt_data_study) export(pt_demographics) export(pt_wrap) export(ptdata) +export(read_glossary) export(rnd) export(rowpanel) export(sig) @@ -122,6 +124,7 @@ export(st_notes) export(st_notes_app) export(st_notes_conf) export(st_notes_detach) +export(st_notes_glo) export(st_notes_rm) export(st_notes_str) export(st_notes_sub) @@ -204,6 +207,7 @@ importFrom(purrr,walk) importFrom(rlang,":=") importFrom(rlang,.data) importFrom(rlang,.env) +importFrom(rlang,abort) importFrom(rlang,as_string) importFrom(rlang,enquo) importFrom(rlang,enquos) diff --git a/R/AAAA.R b/R/AAAA.R index 37c6060d..71e2d6ce 100644 --- a/R/AAAA.R +++ b/R/AAAA.R @@ -12,7 +12,7 @@ #' @importFrom forcats fct_inorder #' @importFrom rlang sym syms quo_get_expr as_string := .data .env is_empty #' @importFrom rlang enquo enquos is_named is_atomic have_name -#' @importFrom rlang env_clone +#' @importFrom rlang env_clone abort #' @importFrom glue glue #' @importFrom tibble tibble as_tibble is_tibble #' @importFrom stats median rnorm sd na.omit setNames update diff --git a/R/table-object.R b/R/table-object.R index cd6f36b4..7de5dda9 100644 --- a/R/table-object.R +++ b/R/table-object.R @@ -388,6 +388,78 @@ st_noteconf <- function(x,...) { #' @export st_notes_conf <- st_noteconf +#' Add notes based on tex glossary definitions +#' +#' @param x an stobject. +#' @param glossary a named list generated from [read_glossary()]. +#' @param ... unquoted names matching those names in `glossary`. +#' @param sep character to separate name and value. +#' @param collapse a character used to collapse definitions into a +#' single string. +#' @param width if numeric, [st_notes_detach()] will be called with `width` +#' argument. +#' @param terms a character vector or comma-separates string of definition +#' names to get appended to the end of names passed in `...`. +#' +#' @examples +#' file <- system.file("tex", "glossary.tex", package = "pmtables") +#' +#' x <- read_glossary(file) +#' +#' stdata() %>% +#' st_notes_glo(x, WT, CRCL, SCR, width = 1) %>% +#' stable() +#' +#' @export +st_notes_glo <- function(x, glossary, ..., sep = ": ", collapse = "; ", + terms = NULL, width = NULL) { + terms <- cvec_cs(terms) + what <- c(new_names(enquos(...)), terms) + if(!length(what)) what <- names(glossary) + notes <- build_glossary_notes(glossary, what, sep, collapse) + if(is.numeric(width)) { + x <- st_notes_detach(x, width = width) + } + st_notes(x, notes) +} + +build_glossary_notes <- function(glossary, what, sep, collapse) { + if(!all(what %in% names(glossary))) { + bad <- setdiff(what, names(glossary)) + names(bad) <- "x" + msg <- c("Requested definitions not found in glossary file", bad) + abort(msg) + } + glossary <- glossary[what] + cols <- names(glossary) + notes <- unlist(glossary, use.names = FALSE) + notes <- paste0(cols, sep, notes) + if(is.character(collapse)) { + notes <- paste0(notes, collapse = collapse) + } + notes +} + +#' Return formatted notes from a tex glossary file +#' +#' @inheritParams read_glossary +#' @inheritParams st_notes_glo +#' +#' @examples +#' file <- system.file("tex", "glossary.tex", package = "pmtables") +#' +#' glossary_notes(file, WT, CLCR) +#' +#' @export +glossary_notes <- function(file, ..., sep = ": ", collapse = "; ", terms = NULL) { + glossary <- read_glossary(file) + terms <- cvec_cs(terms) + what <- c(new_names(enquos(...)), terms) + if(!length(what)) what <- names(glossary) + build_glossary_notes(glossary, what, sep, collapse) +} + + #' Add column alignment information to st object #' #' See the `align` argument to [stable()]. This function may be called several diff --git a/R/utils.R b/R/utils.R index 8ffbfea6..5f5c4225 100644 --- a/R/utils.R +++ b/R/utils.R @@ -175,3 +175,38 @@ ensure_parens <- function(x) { x[where] <- paste0("(", x[where], ")") x } + +#' Read and parse a tex glossary file +#' +#' @param file path to the glossary file +#' +#' @return +#' A named list. +#' +#' @examples +#' file <- system.file("tex", "glossary.tex", package = "pmtables") +#' +#' x <- read_glossary(file) +#' +#' names(x) +#' +#' x$WT +#' +#' x$SC +#' +#' @export +read_glossary <- function(file) { + if(!file.exists(file)) { + rlang::abort(glue::glue("glossary file {file} does not exist.")) + } + txt <- readLines(file) + txt <- trimws(txt) + txt <- txt[grepl("^\\\\newacronym", txt)] + m <- regexec("\\{(.+)\\}\\{(.+)\\}\\{(.+)\\}$", txt) + x <- regmatches(txt, m) + description <- vapply(x, FUN="[", 4L, FUN.VALUE = "a") + col <- vapply(x, FUN="[", 2L, FUN.VALUE = "a") + ans <- as.list(description) + names(ans) <- col + ans +} diff --git a/inst/tex/glossary.tex b/inst/tex/glossary.tex new file mode 100644 index 00000000..43551b1e --- /dev/null +++ b/inst/tex/glossary.tex @@ -0,0 +1,190 @@ +%%%%%%%%%%%% Program Specific Acronyms: %%%%%%%%%%%%%%% +% Program specific acronyms, such as disease, population, drugs, etc. are to be defined here +% Keep them separate from the general acronyms enables cleansing of project-specific info, if needed + +%%%%%%%%%%%% General Acronyms: %%%%%%%%%%%%%%% +%General acronyms are to be defined here +%%%%%%%%% A +\newacronym{ADA}{ADA}{anti-drug antibodies} +\newacronym{AE}{AE}{adverse event} +\newacronym{AIC}{AIC}{Akaike information criterion} +\newacronym{ALAG}{ALAG}{oral absorption lag time} +\newacronym{ASCII}{ASCII}{American Standard Code for Information Interchange} +\newacronym{AST}{AST}{aspartate transaminase} +\newacronym[sort=AUC]{AUC}{\ensuremath{\mathit{AUC}}}{area under the concentration-time curve} +\newacronym[sort=AUCss]{AUCss}{\ensuremath{\mathit{AUC_{\mathrm{ss}}}}}{area under the concentration-time curve for a dosing interval at steady state} +\newacronym[sort=AUCC]{AUCC}{\ensuremath{AUC_{\mathrm{cumulative}}}}{cumulative area under the concentration-time curve since first dose} +\newacronym[sort=AUC50]{AUCC50}{\ensuremath{AUC50}}{area under the concentration-time curve at 50\% of the maximum effect} + +%%%%%%%%% B +\newacronym{BIC}{BIC}{Bayesian information criterion} +\newacronym{BID}{BID}{twice daily} +\newacronym{BL}{BL}{baseline} +\newacronym{BLQ}{BLQ}{below limit of quantification} +\newacronym{BMI}{BMI}{body mass index} +\newacronym{BSA}{BSA}{body surface area} +\newacronym{BTR}{BTR}{best tumor response} + +%%%%%%%%% C +\newacronym{CATD}{CATD}{computer assisted clinical trial design} +\newacronym[sort=Cavg]{Cavg}{\ensuremath{C_{\mathrm{avg}}}}{average concentration during the dosing interval} +\newacronym[sort=Cavgc]{Cavgc}{\ensuremath{C_{\mathrm{avg,\sim cumulative}}}}{average concentration since first dose calculated as $AUC_{\mathrm{cumulative}}$/TAFD} +\newacronym[sort=Cavgss]{Cavgss}{\ensuremath{C_{\mathit{avg,ss}}}}{average concentration during the dosing interval at steady state} +\newacronym[plural=CIs, firstplural=confidence intervals (CIs)]{CI}{CI}{confidence interval} +\newacronym{CKD}{CKD}{chronic kidney disease} +\newacronym[sort=CLi]{CLi}{\ensuremath{\mathit{CL_i}}}{individual-specific systemic clearance} +\newacronym[sort=CLF]{CLF}{\ensuremath{\mathit{CL/F}}}{apparent clearance after oral dosing} +\newacronym[sort=Cmax]{Cmax}{\ensuremath{C_{\mathrm{max}}}}{maximum concentration in the dosing interval} +\newacronym[sort=Cmaxss]{Cmaxss}{\ensuremath{C_{\mathrm{max,ss}}}}{maximum concentration in the dosing interval at steady state} +\newacronym[sort=Cmin]{Cmin}{\ensuremath{C_{\mathrm{min}}}}{minimum concentration in the dosing interval} +\newacronym[sort=Cminss]{Cminss}{\ensuremath{C_{\mathrm{min,ss}}}}{minimum concentration in the dosing interval at steady state} +\newacronym{CRCL}{CRCL}{Cockcroft-Gault calculated creatinine clearance based on total body weight} +\newacronym{CSV}{CSV}{comma separated values} +\newacronym{CTCAE}{CTCAE}{Common Terminology Criteria for Adverse Events} +\newacronym{CUI}{CUI}{clinical utility index} +\newacronym{CVP}{CV\%}{percent coefficient of variation} +\newacronym{CWRES}{CWRES}{conditional weighted residual} +\newacronym{CWRESI}{CWRESI}{conditional weighted residual with interaction} + +%%%%%%%%% D +\newacronym{D1}{D1}{zero order absorption duration} +\newacronym{df}{df}{degrees of freedom} + +%%%%%%%%% E +\newacronym[plural=EBEs, firstplural=empirical Bayes estimates (EBEs)]{EBE}{EBE}{empirical Bayes estimates} +\newacronym{ECOG}{ECOG}{Eastern Cooperative Oncology Group} +\newacronym{eGFR}{eGFR}{estimated glomerular filtration rate} +\newacronym{EMA}{EMA}{European Medicines Agency} +\newacronym{Emax}{Emax}{maximum effect} +\newacronym{EM}{EM}{expectation maximization} +\newacronym{ER}{ER}{exposure-response} +\newacronym{ES}{ES}{exposure-safety} +\newacronym{ETA}{ETA}{interindividual random effect} +\newacronym{ETHN}{ETHN}{ethnicity} +\newacronym{EVID}{EVID}{event ID} +\newacronym{EWRES}{EWRES}{expected weighted residual} + +%%%%%%%%% F +\newacronym[sort=f]{F}{F}{absolute bioavailability} +\newacronym{FDA}{FDA}{United States Food and Drug Administration} +\newacronym{FO}{FO}{first-order} +\newacronym{FOCE}{FOCE}{first-order conditional estimation} +\newacronym{FOCEI}{FOCEI}{first-order conditional estimation with $\eta$--$\epsilon$\ interaction} +\newacronym{FPG}{FPG}{fasting plasma glucose} + +%%%%%%%%% G +\newacronym{gam}{GAM}{generalized additive model} +\newacronym{gh}{HbA1c}{glycosylated hemoglobin} +\newacronym{glm}{GLM}{generalized linear model} +\newacronym{GOF}{GOF}{goodness of fit} + +%%%%%%%%% H +\newacronym{hr}{hr}{hours} + +%%%%%%%%% I +\newacronym{IBW}{IBW}{ideal body weight} +\newacronym{IGF-1}{IGF-1}{insulin-like growth factor 1} +\newacronym{IIV}{IIV}{interindividual variability} +\newacronym{IM}{IM}{intramuscular} +\newacronym{Imax}{Imax}{maximum inhibition} +\newacronym{IMP}{IMP}{Monte Carlo importance sampling} +\newacronym{IOV}{IOV}{interoccasion variability} +\newacronym{ITS}{ITS}{iterative two stage} +\newacronym{IV}{IV}{intravenous} + +%%%%%%%%% J + +%%%%%%%%% K +\newacronym[sort=ka]{ka}{\ensuremath{k_a}}{absorption rate constant} +\newacronym{kg}{kg}{kilograms} + +%%%%%%%%% L +\newacronym{L}{L}{liters} +\newacronym{LASSO}{LASSO}{least absolute shrinkage and selection operator} +\newacronym{LL}{LL}{log likelihood} +\newacronym{LLQ}{LLQ}{lower limit of quantification} +\newacronym{LRT}{LRT}{likelihood ratio test} + +%%%%%%%%% M +\newacronym{MAP}{MAP}{maximum \textit{a posteriori}} +\newacronym{mcg}{mcg}{micrograms} +\newacronym{MCMC}{MCMC}{Markov chain Monte Carlo} +\newacronym{MDRD}{MDRD}{modification of diet in renal disease} +\newacronym{mg}{mg}{milligrams} +\newacronym{mL}{mL}{milliliters} +\newacronym{MS}{M\&S}{modeling and simulation} +\newacronym{MSP}{MSP}{modeling and simulation plan} + +%%%%%%%%% N +\newacronym{NCA}{NCA}{noncompartmental analysis} +\newacronym{NCI}{NCI}{National Cancer Institute} +\newacronym{NCI-ODWG}{NCI-ODWG}{National Cancer Institute organ dysfunction working group} +\newacronym{Neff}{Neff}{effective sample size} +\newacronym{ng}{ng}{nanograms} +\newacronym[sort=N]{etai}{\ensuremath{\eta i}}{individual random effects} +\newacronym{NPDE}{NPDE}{normalized prediction distribution error} + + +%%%%%%%%% O +\newacronym{ODWG}{ODWG}{organ dysfunction working group} +\newacronym{OFV}{OFV}{objective function value} + +%%%%%%%%% P +\newacronym{PD}{PD}{pharmacodynamic} +\newacronym{PK}{PK}{pharmacokinetic} +\newacronym{pg}{pg}{picograms} +\newacronym{PO}{PO}{orally} +\newacronym[plural=PPCs, firstplural=posterior predictive checks (PPCs)]{PPC}{PPC}{posterior predictive check} + + +%%%%%%%%% Q +\newacronym{QD}{QD}{once daily} +\newacronym[sort=QF]{QF}{\ensuremath{\mathit{Q/F}}}{apparent (oral) intercompartmental clearance} +\newacronym{QQ}{QQ}{quantile-quantile} + +%%%%%%%%% R +\newacronym{RES}{RES}{residuals} +\newacronym{Rhat}{R-hat}{Gelman-Rubin diagnostics} +\newacronym{ROPE}{ROPE}{region of practical equivalence} + +%%%%%%%%% S +\newacronym{SAEM}{SAEM}{stochastic approximation expectation maximization} +\newacronym{SAS}{SAS XPORT}{Statistical Analysis System Transport File Format} +\newacronym{SC}{SC}{subcutaneous} +\newacronym{SCR}{SCR}{serum creatinine} +\newacronym{SD}{SD}{standard deviation} +\newacronym{SE}{SE}{standard error} +\newacronym{SDTM}{SDTM}{study data tabulation model} +\newacronym{ss}{ss}{steady state} +\newacronym{SXPORT}{SAS XPORT}{Statistical Analysis System Transport File Format} + + +%%%%%%%%% T +\newacronym{TAD}{TAD}{time after (most recent) dose} +\newacronym{TAFD}{TAFD}{time after first dose} +\newacronym[sort=TVP]{TVP}{\ensuremath{\mathit{TVP}}}{typical value of a model parameter $P$} +\newacronym{T2DM}{T2DM}{type 2 diabetes mellitus} +\newacronym[sort=t]{hl}{\ensuremath{t_{1/2}}}{elimination half-life} +\newacronym{TMDD}{TMDD}{target-mediated drug disposition} + + +%%%%%%%%% U +\newacronym{UKPDS}{UKPDS}{United Kingdom prospective diabetes study} + +%%%%%%%%% V +\newacronym[sort=VF]{VF}{\ensuremath{V/F}}{apparent volume of distribution after oral dosing} +\newacronym[sort=V2F]{V2F}{\ensuremath{V_2/F}}{apparent central volume of distribution after oral dosing} +\newacronym[sort=V3F]{V3F}{\ensuremath{\mathit{V_3/F}}}{apparent peripheral volume of distribution after oral dosing} +\newacronym{VPC}{VPC}{visual predictive check} + +%%%%%%%%% W +\newacronym{WRES}{WRES}{weighted residuals} +\newacronym{WT}{WT}{subject weight} + +%%%%%%%%% X +\newacronym[sort=X]{chi2}{\ensuremath{\chi^2}}{chi-squared distribution} + + +%%%%%%%%% Y + +%%%%%%%%% Z diff --git a/man/glossary_notes.Rd b/man/glossary_notes.Rd new file mode 100644 index 00000000..9089cc88 --- /dev/null +++ b/man/glossary_notes.Rd @@ -0,0 +1,30 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/table-object.R +\name{glossary_notes} +\alias{glossary_notes} +\title{Return formatted notes from a tex glossary file} +\usage{ +glossary_notes(file, ..., sep = ": ", collapse = "; ", terms = NULL) +} +\arguments{ +\item{file}{path to the glossary file} + +\item{...}{unquoted names matching those names in \code{glossary}.} + +\item{sep}{character to separate name and value.} + +\item{collapse}{a character used to collapse definitions into a +single string.} + +\item{terms}{a character vector or comma-separates string of definition +names to get appended to the end of names passed in \code{...}.} +} +\description{ +Return formatted notes from a tex glossary file +} +\examples{ +file <- system.file("tex", "glossary.tex", package = "pmtables") + +glossary_notes(file, WT, CLCR) + +} diff --git a/man/read_glossary.Rd b/man/read_glossary.Rd new file mode 100644 index 00000000..96cfb0b6 --- /dev/null +++ b/man/read_glossary.Rd @@ -0,0 +1,29 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils.R +\name{read_glossary} +\alias{read_glossary} +\title{Read and parse a tex glossary file} +\usage{ +read_glossary(file) +} +\arguments{ +\item{file}{path to the glossary file} +} +\value{ +A named list. +} +\description{ +Read and parse a tex glossary file +} +\examples{ +file <- system.file("tex", "glossary.tex", package = "pmtables") + +x <- read_glossary(file) + +names(x) + +x$WT + +x$SC + +} diff --git a/man/st_notes_glo.Rd b/man/st_notes_glo.Rd new file mode 100644 index 00000000..1c290904 --- /dev/null +++ b/man/st_notes_glo.Rd @@ -0,0 +1,47 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/table-object.R +\name{st_notes_glo} +\alias{st_notes_glo} +\title{Add notes based on tex glossary definitions} +\usage{ +st_notes_glo( + x, + glossary, + ..., + sep = ": ", + collapse = "; ", + terms = NULL, + width = NULL +) +} +\arguments{ +\item{x}{an stobject.} + +\item{glossary}{a named list generated from \code{\link[=read_glossary]{read_glossary()}}.} + +\item{...}{unquoted names matching those names in \code{glossary}.} + +\item{sep}{character to separate name and value.} + +\item{collapse}{a character used to collapse definitions into a +single string.} + +\item{terms}{a character vector or comma-separates string of definition +names to get appended to the end of names passed in \code{...}.} + +\item{width}{if numeric, \code{\link[=st_notes_detach]{st_notes_detach()}} will be called with \code{width} +argument.} +} +\description{ +Add notes based on tex glossary definitions +} +\examples{ +file <- system.file("tex", "glossary.tex", package = "pmtables") + +x <- read_glossary(file) + +stdata() \%>\% + st_notes_glo(x, WT, CRCL, SCR, width = 1) \%>\% + stable() + +} From d67df6e98a478d262f207cabd7b6b578ce0e60b5 Mon Sep 17 00:00:00 2001 From: Kyle Baron Date: Thu, 15 Feb 2024 11:12:41 -0600 Subject: [PATCH 02/29] clean up check --- R/table-object.R | 29 ++++++++++++++++++----------- R/utils.R | 20 +++++++++++++++----- man/glossary_notes.Rd | 13 ++++++++----- man/read_glossary.Rd | 6 ++++-- man/st_notes_glo.Rd | 13 +++++++++---- 5 files changed, 54 insertions(+), 27 deletions(-) diff --git a/R/table-object.R b/R/table-object.R index 7de5dda9..aa92e52d 100644 --- a/R/table-object.R +++ b/R/table-object.R @@ -398,23 +398,29 @@ st_notes_conf <- st_noteconf #' single string. #' @param width if numeric, [st_notes_detach()] will be called with `width` #' argument. -#' @param terms a character vector or comma-separates string of definition -#' names to get appended to the end of names passed in `...`. +#' @param labels a character vector or comma-separates string of definition +#' labels to get appended to the end of names passed in `...`. #' #' @examples +#' library(dplyr) +#' #' file <- system.file("tex", "glossary.tex", package = "pmtables") #' #' x <- read_glossary(file) #' -#' stdata() %>% +#' st_new(stdata()) %>% #' st_notes_glo(x, WT, CRCL, SCR, width = 1) %>% #' stable() #' +#' @seealso [glossary_notes()], [read_glossary()] #' @export st_notes_glo <- function(x, glossary, ..., sep = ": ", collapse = "; ", - terms = NULL, width = NULL) { - terms <- cvec_cs(terms) - what <- c(new_names(enquos(...)), terms) + labels = NULL, width = NULL) { + if(!is.list(glossary) || !is_named(glossary)) { + abort("`glossary` must be a named list.") + } + labels <- cvec_cs(labels) + what <- c(new_names(enquos(...)), labels) if(!length(what)) what <- names(glossary) notes <- build_glossary_notes(glossary, what, sep, collapse) if(is.numeric(width)) { @@ -448,18 +454,19 @@ build_glossary_notes <- function(glossary, what, sep, collapse) { #' @examples #' file <- system.file("tex", "glossary.tex", package = "pmtables") #' -#' glossary_notes(file, WT, CLCR) +#' glossary_notes(file, WT, CRCL) #' +#' @seealso [st_notes_glo()], [read_glossary()] #' @export -glossary_notes <- function(file, ..., sep = ": ", collapse = "; ", terms = NULL) { +glossary_notes <- function(file, ..., sep = ": ", collapse = "; ", + labels = NULL) { glossary <- read_glossary(file) - terms <- cvec_cs(terms) - what <- c(new_names(enquos(...)), terms) + labels <- cvec_cs(labels) + what <- c(new_names(enquos(...)), labels) if(!length(what)) what <- names(glossary) build_glossary_notes(glossary, what, sep, collapse) } - #' Add column alignment information to st object #' #' See the `align` argument to [stable()]. This function may be called several diff --git a/R/utils.R b/R/utils.R index 5f5c4225..22baa964 100644 --- a/R/utils.R +++ b/R/utils.R @@ -178,10 +178,12 @@ ensure_parens <- function(x) { #' Read and parse a tex glossary file #' -#' @param file path to the glossary file +#' @param file path to the tex glossary file. #' #' @return -#' A named list. +#' A named list of glossary definitions with class `tex_glossary` and +#' `glossary`. The list names are acronym entry labels (i.e., what you would +#' pass to `\gls{}` when writing a tex document). #' #' @examples #' file <- system.file("tex", "glossary.tex", package = "pmtables") @@ -197,16 +199,24 @@ ensure_parens <- function(x) { #' @export read_glossary <- function(file) { if(!file.exists(file)) { - rlang::abort(glue::glue("glossary file {file} does not exist.")) + abort(glue("glossary file {file} does not exist.")) } txt <- readLines(file) + if(!length(txt)) abort("glossary file was empty.") txt <- trimws(txt) txt <- txt[grepl("^\\\\newacronym", txt)] m <- regexec("\\{(.+)\\}\\{(.+)\\}\\{(.+)\\}$", txt) x <- regmatches(txt, m) - description <- vapply(x, FUN="[", 4L, FUN.VALUE = "a") - col <- vapply(x, FUN="[", 2L, FUN.VALUE = "a") + if(!length(x)) { + abort("no acronym entries were found in `file`.") + } + description <- vapply(x, FUN = "[", 4L, FUN.VALUE = "a") + col <- vapply(x, FUN = "[", 2L, FUN.VALUE = "a") + if(length(description) != length(col)) { + abort("there was a problem parsing the glossary file.") + } ans <- as.list(description) names(ans) <- col + class(ans) <- c("glossary", "tex") ans } diff --git a/man/glossary_notes.Rd b/man/glossary_notes.Rd index 9089cc88..9b4ae073 100644 --- a/man/glossary_notes.Rd +++ b/man/glossary_notes.Rd @@ -4,10 +4,10 @@ \alias{glossary_notes} \title{Return formatted notes from a tex glossary file} \usage{ -glossary_notes(file, ..., sep = ": ", collapse = "; ", terms = NULL) +glossary_notes(file, ..., sep = ": ", collapse = "; ", labels = NULL) } \arguments{ -\item{file}{path to the glossary file} +\item{file}{path to the tex glossary file.} \item{...}{unquoted names matching those names in \code{glossary}.} @@ -16,8 +16,8 @@ glossary_notes(file, ..., sep = ": ", collapse = "; ", terms = NULL) \item{collapse}{a character used to collapse definitions into a single string.} -\item{terms}{a character vector or comma-separates string of definition -names to get appended to the end of names passed in \code{...}.} +\item{labels}{a character vector or comma-separates string of definition +labels to get appended to the end of names passed in \code{...}.} } \description{ Return formatted notes from a tex glossary file @@ -25,6 +25,9 @@ Return formatted notes from a tex glossary file \examples{ file <- system.file("tex", "glossary.tex", package = "pmtables") -glossary_notes(file, WT, CLCR) +glossary_notes(file, WT, CRCL) } +\seealso{ +\code{\link[=st_notes_glo]{st_notes_glo()}}, \code{\link[=read_glossary]{read_glossary()}} +} diff --git a/man/read_glossary.Rd b/man/read_glossary.Rd index 96cfb0b6..017f9b33 100644 --- a/man/read_glossary.Rd +++ b/man/read_glossary.Rd @@ -7,10 +7,12 @@ read_glossary(file) } \arguments{ -\item{file}{path to the glossary file} +\item{file}{path to the tex glossary file.} } \value{ -A named list. +A named list of glossary definitions with class \code{tex_glossary} and +\code{glossary}. The list names are acronym entry labels (i.e., what you would +pass to \verb{\\gls\{\}} when writing a tex document). } \description{ Read and parse a tex glossary file diff --git a/man/st_notes_glo.Rd b/man/st_notes_glo.Rd index 1c290904..ef97a560 100644 --- a/man/st_notes_glo.Rd +++ b/man/st_notes_glo.Rd @@ -10,7 +10,7 @@ st_notes_glo( ..., sep = ": ", collapse = "; ", - terms = NULL, + labels = NULL, width = NULL ) } @@ -26,8 +26,8 @@ st_notes_glo( \item{collapse}{a character used to collapse definitions into a single string.} -\item{terms}{a character vector or comma-separates string of definition -names to get appended to the end of names passed in \code{...}.} +\item{labels}{a character vector or comma-separates string of definition +labels to get appended to the end of names passed in \code{...}.} \item{width}{if numeric, \code{\link[=st_notes_detach]{st_notes_detach()}} will be called with \code{width} argument.} @@ -36,12 +36,17 @@ argument.} Add notes based on tex glossary definitions } \examples{ +library(dplyr) + file <- system.file("tex", "glossary.tex", package = "pmtables") x <- read_glossary(file) -stdata() \%>\% +st_new(stdata()) \%>\% st_notes_glo(x, WT, CRCL, SCR, width = 1) \%>\% stable() } +\seealso{ +\code{\link[=glossary_notes]{glossary_notes()}}, \code{\link[=read_glossary]{read_glossary()}} +} From 665169161bde493e6a929aa2e3853aa0c23f4aa2 Mon Sep 17 00:00:00 2001 From: Kyle Baron Date: Thu, 15 Feb 2024 11:28:53 -0600 Subject: [PATCH 03/29] adding test glossary file --- R/utils.R | 4 +-- tests/testthat/test-glossary.R | 45 ++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 tests/testthat/test-glossary.R diff --git a/R/utils.R b/R/utils.R index 22baa964..96e95d85 100644 --- a/R/utils.R +++ b/R/utils.R @@ -201,7 +201,7 @@ read_glossary <- function(file) { if(!file.exists(file)) { abort(glue("glossary file {file} does not exist.")) } - txt <- readLines(file) + txt <- readLines(file, warn = FALSE) if(!length(txt)) abort("glossary file was empty.") txt <- trimws(txt) txt <- txt[grepl("^\\\\newacronym", txt)] @@ -217,6 +217,6 @@ read_glossary <- function(file) { } ans <- as.list(description) names(ans) <- col - class(ans) <- c("glossary", "tex") + class(ans) <- c("glossary", "tex", "list") ans } diff --git a/tests/testthat/test-glossary.R b/tests/testthat/test-glossary.R new file mode 100644 index 00000000..76094a4a --- /dev/null +++ b/tests/testthat/test-glossary.R @@ -0,0 +1,45 @@ +library(testthat) +library(dplyr) + +context("test-glossary") + +glofile <- system.file("tex", "glossary.tex", package = "pmtables") + +test_that("read a glossary file", { + tex <- read_glossary(glofile) + expect_named(tex) + expect_is(tex, "list") + expect_true("WT" %in% names(tex)) + expect_true("QF" %in% names(tex)) + + foo <- tempfile() + expect_error(read_glossary(foo), "does not exist.") + + cat("a\n", file = foo) + expect_error(read_glossary(foo), "no acronym entries were found") + +}) + +test_that("load glossary notes", { + notes <- glossary_notes(glofile, WT, CLF) + expect_is(notes, "character") + expect_length(notes, 1) + expect_true(grepl("WT: ", notes, fixed = TRUE)) + expect_true(grepl("CLF: ", notes, fixed = TRUE)) + + notes <- glossary_notes(glofile, WT, CLF, collapse = NULL) + expect_is(notes, "character") + expect_length(notes, 2) + expect_true(any(grepl("WT", notes))) + + notes <- glossary_notes(glofile, WT, CLF, sep = "& ") + expect_is(notes, "character") + expect_true(any(grepl("WT& ", notes, fixed = TRUE))) + + notes <- glossary_notes(glofile, WT, CLF, collapse = NULL, labels = "VF") + expect_is(notes, "character") + expect_length(notes, 3) + expect_true(any(grepl("WT: ", notes, fixed = TRUE))) + expect_true(any(grepl("CLF: ", notes, fixed = TRUE))) + expect_true(any(grepl("VF: ", notes, fixed = TRUE))) +}) From 8e8386ae8440d599e0dfb35b030868abf21ce160 Mon Sep 17 00:00:00 2001 From: Kyle Baron Date: Thu, 15 Feb 2024 12:41:48 -0600 Subject: [PATCH 04/29] split up functionality a bit --- R/utils.R | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/R/utils.R b/R/utils.R index 96e95d85..df9a9437 100644 --- a/R/utils.R +++ b/R/utils.R @@ -203,20 +203,25 @@ read_glossary <- function(file) { } txt <- readLines(file, warn = FALSE) if(!length(txt)) abort("glossary file was empty.") + ans <- parse_glossary(txt) + class(ans) <- c("glossary", "tex", "list") + ans +} + +parse_glossary <- function(txt) { txt <- trimws(txt) txt <- txt[grepl("^\\\\newacronym", txt)] m <- regexec("\\{(.+)\\}\\{(.+)\\}\\{(.+)\\}$", txt) - x <- regmatches(txt, m) - if(!length(x)) { + parsed <- regmatches(txt, m) + if(!length(parsed)) { abort("no acronym entries were found in `file`.") } - description <- vapply(x, FUN = "[", 4L, FUN.VALUE = "a") - col <- vapply(x, FUN = "[", 2L, FUN.VALUE = "a") + description <- lapply(parsed, FUN = "[", 4L) + col <- vapply(parsed, FUN = "[", 2L, FUN.VALUE = "a") if(length(description) != length(col)) { abort("there was a problem parsing the glossary file.") } ans <- as.list(description) names(ans) <- col - class(ans) <- c("glossary", "tex", "list") ans } From e8f82438db14a9a3ef278f4fd97fba6b0c643b96 Mon Sep 17 00:00:00 2001 From: Kyle Baron Date: Thu, 15 Feb 2024 12:51:27 -0600 Subject: [PATCH 05/29] glossary text parse --- R/utils.R | 2 +- tests/testthat/test-glossary.R | 27 ++++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/R/utils.R b/R/utils.R index df9a9437..025df1a2 100644 --- a/R/utils.R +++ b/R/utils.R @@ -211,7 +211,7 @@ read_glossary <- function(file) { parse_glossary <- function(txt) { txt <- trimws(txt) txt <- txt[grepl("^\\\\newacronym", txt)] - m <- regexec("\\{(.+)\\}\\{(.+)\\}\\{(.+)\\}$", txt) + m <- regexec("\\{(.+)\\}\\{(.+)\\}\\{(.+)\\}.*$", txt) parsed <- regmatches(txt, m) if(!length(parsed)) { abort("no acronym entries were found in `file`.") diff --git a/tests/testthat/test-glossary.R b/tests/testthat/test-glossary.R index 76094a4a..7fe2aba9 100644 --- a/tests/testthat/test-glossary.R +++ b/tests/testthat/test-glossary.R @@ -17,7 +17,6 @@ test_that("read a glossary file", { cat("a\n", file = foo) expect_error(read_glossary(foo), "no acronym entries were found") - }) test_that("load glossary notes", { @@ -43,3 +42,29 @@ test_that("load glossary notes", { expect_true(any(grepl("CLF: ", notes, fixed = TRUE))) expect_true(any(grepl("VF: ", notes, fixed = TRUE))) }) + +test_that("parse a glossary entry", { + txt <- "\\newacronym{a}{b}{c} % comment" + x <- pmtables:::parse_glossary(txt) + expect_length(x, 1) + expect_named(x) + expect_identical(names(x), "a") + expect_identical(x$a, "c") + + txt <- "\\newacronym[options]{a}{b}{c} % comment" + x <- pmtables:::parse_glossary(txt) + expect_length(x, 1) + expect_named(x) + expect_identical(names(x), "a") + expect_identical(x$a, "c") + + txt <- "\\newacronym[options]{a}{b_\\mathrm{d}}{\\mathit{c}} % comment" + x <- pmtables:::parse_glossary(txt) + expect_length(x, 1) + expect_named(x) + expect_identical(names(x), "a") + expect_identical(x$a, "\\mathit{c}") + + txt <- "%\\newacronym{a}{b}{c}" + expect_error(pmtables:::parse_glossary(txt), "no acronym entries") +}) From 04e344a1fd6248870d110d99804cd3c2541eeb27 Mon Sep 17 00:00:00 2001 From: Kyle Baron Date: Thu, 15 Feb 2024 13:04:17 -0600 Subject: [PATCH 06/29] add test for st_notes_glo --- tests/testthat/test-table-object.R | 32 ++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/testthat/test-table-object.R b/tests/testthat/test-table-object.R index 6423a7d0..82169044 100644 --- a/tests/testthat/test-table-object.R +++ b/tests/testthat/test-table-object.R @@ -13,6 +13,8 @@ inspect2 <- function(x) { x %>% st_make(inspect=TRUE) %>% get_stable_data() } +glofile <- system.file("tex", "glossary.tex", package = "pmtables") + test_that("stobject equivalent hline [PMT-TEST-0215]", { mt <- mtcars[1:20,] x <- inspect(mt, hline_at = c(1,5,8)) @@ -432,6 +434,36 @@ test_that("substitute lines in table notes", { ) }) +test_that("get notes from glossary file with st_notes_glo", { + glossary <- read_glossary(glofile) + data <- stdata() + + x <- st_new(data) + y <- st_notes_glo(x, glossary, WT, CLF, V2F)$notes + expect_length(y, 1) + expect_match(y, "subject weight", fixed = TRUE) + expect_match(y, "CLF: ", fixed = TRUE) + + x <- st_new(data) + y <- st_notes_glo(x, glossary, WT, CLF, collapse = NULL)$notes + expect_length(y, 2) + + x <- st_new(data) + y <- st_notes_glo(x, glossary, WT, CLF, sep = "@ ")$notes + expect_length(y, 1) + expect_match(y, "CLF@ ") + + x <- st_new(data) + y <- st_notes_glo(x, glossary, WT, CLF, labels = "SCR", collapse = NULL)$notes + expect_length(y, 3) + expect_match(y[[3]], "SCR: ") + + x <- st_new(data) + y <- st_notes_glo(x, glossary, WT, CLF, width = 1)$note_config + expect_identical(y$type, "minipage") + expect_identical(y$width, 1) +}) + test_that("st_filter filters data in pmtable object [PMT-STFUN-0001]", { data <- stdata() x <- st_new(data) From 2d23dfd97d13b81447dfe1ef428412a97f21f85a Mon Sep 17 00:00:00 2001 From: Kyle Baron Date: Thu, 15 Feb 2024 13:11:29 -0600 Subject: [PATCH 07/29] test parsing labels --- tests/testthat/test-glossary.R | 5 +++-- tests/testthat/test-table-object.R | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/testthat/test-glossary.R b/tests/testthat/test-glossary.R index 7fe2aba9..8754c717 100644 --- a/tests/testthat/test-glossary.R +++ b/tests/testthat/test-glossary.R @@ -35,12 +35,13 @@ test_that("load glossary notes", { expect_is(notes, "character") expect_true(any(grepl("WT& ", notes, fixed = TRUE))) - notes <- glossary_notes(glofile, WT, CLF, collapse = NULL, labels = "VF") + notes <- glossary_notes(glofile, WT, CLF, collapse = NULL, labels = "VF,NPDE") expect_is(notes, "character") - expect_length(notes, 3) + expect_length(notes, 4) expect_true(any(grepl("WT: ", notes, fixed = TRUE))) expect_true(any(grepl("CLF: ", notes, fixed = TRUE))) expect_true(any(grepl("VF: ", notes, fixed = TRUE))) + expect_true(any(grepl("NPDE: ", notes, fixed = TRUE))) }) test_that("parse a glossary entry", { diff --git a/tests/testthat/test-table-object.R b/tests/testthat/test-table-object.R index 82169044..acd5cb51 100644 --- a/tests/testthat/test-table-object.R +++ b/tests/testthat/test-table-object.R @@ -454,8 +454,8 @@ test_that("get notes from glossary file with st_notes_glo", { expect_match(y, "CLF@ ") x <- st_new(data) - y <- st_notes_glo(x, glossary, WT, CLF, labels = "SCR", collapse = NULL)$notes - expect_length(y, 3) + y <- st_notes_glo(x, glossary, WT, CLF, labels = "SCR,V2F", collapse = NULL)$notes + expect_length(y, 4) expect_match(y[[3]], "SCR: ") x <- st_new(data) From 8b608c5d5015c5fd7e29a14172237009709f3942 Mon Sep 17 00:00:00 2001 From: Kyle Baron Date: Thu, 15 Feb 2024 13:22:27 -0600 Subject: [PATCH 08/29] minor documentation fix --- R/table-object.R | 2 +- man/st_notes_glo.Rd | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/R/table-object.R b/R/table-object.R index aa92e52d..d6e49c84 100644 --- a/R/table-object.R +++ b/R/table-object.R @@ -388,7 +388,7 @@ st_noteconf <- function(x,...) { #' @export st_notes_conf <- st_noteconf -#' Add notes based on tex glossary definitions +#' Add table notes based on acronyms from a tex glossary file #' #' @param x an stobject. #' @param glossary a named list generated from [read_glossary()]. diff --git a/man/st_notes_glo.Rd b/man/st_notes_glo.Rd index ef97a560..4e2ad0bf 100644 --- a/man/st_notes_glo.Rd +++ b/man/st_notes_glo.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/table-object.R \name{st_notes_glo} \alias{st_notes_glo} -\title{Add notes based on tex glossary definitions} +\title{Add table notes based on acronyms from a tex glossary file} \usage{ st_notes_glo( x, @@ -33,7 +33,7 @@ labels to get appended to the end of names passed in \code{...}.} argument.} } \description{ -Add notes based on tex glossary definitions +Add table notes based on acronyms from a tex glossary file } \examples{ library(dplyr) From 26578c9aaa3dc6681b66ab5acdce7126e48a75a6 Mon Sep 17 00:00:00 2001 From: Kyle Baron Date: Thu, 15 Feb 2024 13:41:13 -0600 Subject: [PATCH 09/29] make_glossaries is generic --- NAMESPACE | 2 ++ R/table-object.R | 22 ++++++++++++++++------ R/utils.R | 14 ++++++++++++++ man/glossary_notes.Rd | 10 ++++++++-- man/read_glossary.Rd | 11 +++++++++++ tests/testthat/test-glossary.R | 6 ++++++ 6 files changed, 57 insertions(+), 8 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 01ee5fe4..c964bda2 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -8,6 +8,8 @@ S3method(as.panel,rowpanel) S3method(as_stable,pmtable) S3method(as_stable,stable) S3method(as_stable,stobject) +S3method(glossary_notes,character) +S3method(glossary_notes,list) S3method(names,stobject) S3method(new_names,character) S3method(new_names,list) diff --git a/R/table-object.R b/R/table-object.R index d6e49c84..fff38502 100644 --- a/R/table-object.R +++ b/R/table-object.R @@ -415,7 +415,7 @@ st_notes_conf <- st_noteconf #' @seealso [glossary_notes()], [read_glossary()] #' @export st_notes_glo <- function(x, glossary, ..., sep = ": ", collapse = "; ", - labels = NULL, width = NULL) { + labels = NULL, width = NULL) { if(!is.list(glossary) || !is_named(glossary)) { abort("`glossary` must be a named list.") } @@ -458,13 +458,23 @@ build_glossary_notes <- function(glossary, what, sep, collapse) { #' #' @seealso [st_notes_glo()], [read_glossary()] #' @export -glossary_notes <- function(file, ..., sep = ": ", collapse = "; ", - labels = NULL) { - glossary <- read_glossary(file) +glossary_notes <- function(x, ...) UseMethod("glossary_notes") + +#' @rdname glossary_notes +#' @export +glossary_notes.character <- function(x, ...) { + glossary <- read_glossary(x) + glossary_notes(glossary, ...) +} + +#' @rdname glossary_notes +#' @export +glossary_notes.list <- function(x, ..., sep = ": ", collapse = "; ", + labels = NULL) { labels <- cvec_cs(labels) what <- c(new_names(enquos(...)), labels) - if(!length(what)) what <- names(glossary) - build_glossary_notes(glossary, what, sep, collapse) + if(!length(what)) what <- names(x) + build_glossary_notes(x, what, sep, collapse) } #' Add column alignment information to st object diff --git a/R/utils.R b/R/utils.R index 025df1a2..cc23f573 100644 --- a/R/utils.R +++ b/R/utils.R @@ -185,6 +185,20 @@ ensure_parens <- function(x) { #' `glossary`. The list names are acronym entry labels (i.e., what you would #' pass to `\gls{}` when writing a tex document). #' +#' +#' @details +#' The glossary file is expected to contain acronym entries with the form +#' +#' ``` +#' \newacronym{label}{abbreviation}{definition} +#' ``` +#' +#' or +#' +#' ``` +#' \newacronym[options]{label}{abbreviation}{definition} +#' ``` +#' #' @examples #' file <- system.file("tex", "glossary.tex", package = "pmtables") #' diff --git a/man/glossary_notes.Rd b/man/glossary_notes.Rd index 9b4ae073..d975256d 100644 --- a/man/glossary_notes.Rd +++ b/man/glossary_notes.Rd @@ -2,12 +2,18 @@ % Please edit documentation in R/table-object.R \name{glossary_notes} \alias{glossary_notes} +\alias{glossary_notes.character} +\alias{glossary_notes.list} \title{Return formatted notes from a tex glossary file} \usage{ -glossary_notes(file, ..., sep = ": ", collapse = "; ", labels = NULL) +glossary_notes(x, ...) + +\method{glossary_notes}{character}(x, ...) + +\method{glossary_notes}{list}(x, ..., sep = ": ", collapse = "; ", labels = NULL) } \arguments{ -\item{file}{path to the tex glossary file.} +\item{x}{an stobject.} \item{...}{unquoted names matching those names in \code{glossary}.} diff --git a/man/read_glossary.Rd b/man/read_glossary.Rd index 017f9b33..5912f1b7 100644 --- a/man/read_glossary.Rd +++ b/man/read_glossary.Rd @@ -17,6 +17,17 @@ pass to \verb{\\gls\{\}} when writing a tex document). \description{ Read and parse a tex glossary file } +\details{ +The glossary file is expected to contain acronym entries with the form + +\if{html}{\out{
}}\preformatted{\\newacronym\{label\}\{abbreviation\}\{definition\} +}\if{html}{\out{
}} + +or + +\if{html}{\out{
}}\preformatted{\\newacronym[options]\{label\}\{abbreviation\}\{definition\} +}\if{html}{\out{
}} +} \examples{ file <- system.file("tex", "glossary.tex", package = "pmtables") diff --git a/tests/testthat/test-glossary.R b/tests/testthat/test-glossary.R index 8754c717..e195cab2 100644 --- a/tests/testthat/test-glossary.R +++ b/tests/testthat/test-glossary.R @@ -44,6 +44,12 @@ test_that("load glossary notes", { expect_true(any(grepl("NPDE: ", notes, fixed = TRUE))) }) +test_that("make glossary notes from glossary list", { + x <- read_glossary(glofile) + notes <- glossary_notes(x, WT, NPDE, CWRES, collapse = NULL) + expect_length(notes, 3) +}) + test_that("parse a glossary entry", { txt <- "\\newacronym{a}{b}{c} % comment" x <- pmtables:::parse_glossary(txt) From 59dc8e13bf202f511d56af79aa01ca9564978632 Mon Sep 17 00:00:00 2001 From: Kyle Baron Date: Thu, 15 Feb 2024 17:44:30 -0600 Subject: [PATCH 10/29] move code to glossary.R file; refactor for more control --- DESCRIPTION | 1 + NAMESPACE | 2 + R/glossary.R | 98 ++++++++++++++++++++++++++++++++++++++++++++ R/utils.R | 63 ---------------------------- man/read_glossary.Rd | 5 ++- 5 files changed, 105 insertions(+), 64 deletions(-) create mode 100644 R/glossary.R diff --git a/DESCRIPTION b/DESCRIPTION index c1615a6b..ce0f9046 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -52,6 +52,7 @@ Collate: 'data_inventory_table.R' 'demographics-table.R' 'discrete_table.R' + 'glossary.R' 'knit-dependencies.R' 'preview-standalone.R' 'preview.R' diff --git a/NAMESPACE b/NAMESPACE index c964bda2..734343d5 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -16,6 +16,7 @@ S3method(new_names,list) S3method(new_names,quosures) S3method(new_names,rowpanel) S3method(print,digits) +S3method(print,glossary_entry) S3method(print,st_caption) S3method(print,stable_data) S3method(print,stobject) @@ -40,6 +41,7 @@ export(.cols) export(as.caption) export(as.panel) export(as.span) +export(as_glossary) export(as_lscape) export(as_stable) export(cat_data) diff --git a/R/glossary.R b/R/glossary.R new file mode 100644 index 00000000..472cb531 --- /dev/null +++ b/R/glossary.R @@ -0,0 +1,98 @@ +#' Read and parse a tex glossary file +#' +#' @param file path to the tex glossary file. +#' +#' @return +#' A named list of glossary definitions with class `tex_glossary` and +#' `glossary`. The list names are acronym entry labels (i.e., what you would +#' pass to `\gls{}` when writing a tex document). +#' +#' +#' @details +#' The glossary file is expected to contain acronym entries with the form +#' +#' ``` +#' \newacronym{label}{abbreviation}{definition} +#' ``` +#' +#' or +#' +#' ``` +#' \newacronym[options]{label}{abbreviation}{definition} +#' ``` +#' +#' @examples +#' file <- system.file("tex", "glossary.tex", package = "pmtables") +#' +#' x <- read_glossary(file) +#' +#' names(x) +#' +#' x$WT +#' +#' x$SC +#' +#' @export +read_glossary <- function(file) { + if(!file.exists(file)) { + abort(glue("glossary file {file} does not exist.")) + } + txt <- readLines(file, warn = FALSE) + if(!length(txt)) abort("glossary file was empty.") + ans <- parse_glossary(txt) + class(ans) <- c("tex_glossary", "glossary", "list") + ans +} + +#' @rdname read_glossary +#' @export +as_glossary <- function(glossary, ...) { + if(!is.list(glossary) || !is_named(glossary)) { + abort("`glossary` must be a named list") + } + type_chr <- vapply(glossary, inherits, what = "character", FUN.VALUE = TRUE) + len <- vapply(glossary, length, FUN.VALUE = 1) + if(!all(type_chr & len==1)) { + abort("Each `glossary` entry must have type character and length 1.") + } + glossary <- Map(glossary, names(glossary), f = function(def, label) { + as_glossary_entry(c(definition = def, abbreviation = label)) + }) + abbrev <- new_names(enquos(...)) + for(a in seq_along(abbrev)) { + + } + class(glossary) <- c("tex_glossary", "glossary", "list") + glossary +} + +parse_glossary <- function(txt) { + txt <- trimws(txt) + txt <- txt[grepl("^\\\\newacronym", txt)] + m <- regexec("\\{(.+)\\}\\{(.+)\\}\\{(.+)\\}.*$", txt) + parsed <- regmatches(txt, m) + if(!length(parsed)) { + abort("no acronym entries were found in `file`.") + } + if(!all(vapply(parsed, length, 1L)==4)) { + abort("there was a problem parsing the glossary file.") + } + label <- vapply(parsed, FUN = "[", 2L, FUN.VALUE = "a") + data <- lapply(parsed, FUN = "[", c(3L, 4L)) + data <- lapply(data, as_glossary_entry) + data <- lapply(data, setNames, c("abbreviation", "definition")) + names(data) <- label + data +} + +as_glossary_entry <- function(x) { + x <- as.list(x) + class(x) <- c("glossary_entry", "list") + x +} + + +#' @export +print.glossary_entry <- function(x, ...) { + print(paste0(x$definition, " (", x$abbreviation, ")")) +} diff --git a/R/utils.R b/R/utils.R index cc23f573..04daf834 100644 --- a/R/utils.R +++ b/R/utils.R @@ -176,66 +176,3 @@ ensure_parens <- function(x) { x } -#' Read and parse a tex glossary file -#' -#' @param file path to the tex glossary file. -#' -#' @return -#' A named list of glossary definitions with class `tex_glossary` and -#' `glossary`. The list names are acronym entry labels (i.e., what you would -#' pass to `\gls{}` when writing a tex document). -#' -#' -#' @details -#' The glossary file is expected to contain acronym entries with the form -#' -#' ``` -#' \newacronym{label}{abbreviation}{definition} -#' ``` -#' -#' or -#' -#' ``` -#' \newacronym[options]{label}{abbreviation}{definition} -#' ``` -#' -#' @examples -#' file <- system.file("tex", "glossary.tex", package = "pmtables") -#' -#' x <- read_glossary(file) -#' -#' names(x) -#' -#' x$WT -#' -#' x$SC -#' -#' @export -read_glossary <- function(file) { - if(!file.exists(file)) { - abort(glue("glossary file {file} does not exist.")) - } - txt <- readLines(file, warn = FALSE) - if(!length(txt)) abort("glossary file was empty.") - ans <- parse_glossary(txt) - class(ans) <- c("glossary", "tex", "list") - ans -} - -parse_glossary <- function(txt) { - txt <- trimws(txt) - txt <- txt[grepl("^\\\\newacronym", txt)] - m <- regexec("\\{(.+)\\}\\{(.+)\\}\\{(.+)\\}.*$", txt) - parsed <- regmatches(txt, m) - if(!length(parsed)) { - abort("no acronym entries were found in `file`.") - } - description <- lapply(parsed, FUN = "[", 4L) - col <- vapply(parsed, FUN = "[", 2L, FUN.VALUE = "a") - if(length(description) != length(col)) { - abort("there was a problem parsing the glossary file.") - } - ans <- as.list(description) - names(ans) <- col - ans -} diff --git a/man/read_glossary.Rd b/man/read_glossary.Rd index 5912f1b7..3a34a786 100644 --- a/man/read_glossary.Rd +++ b/man/read_glossary.Rd @@ -1,10 +1,13 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils.R +% Please edit documentation in R/glossary.R \name{read_glossary} \alias{read_glossary} +\alias{as_glossary} \title{Read and parse a tex glossary file} \usage{ read_glossary(file) + +as_glossary(glossary, ...) } \arguments{ \item{file}{path to the tex glossary file.} From 03a20eff31d514d4ee8a2afa6c46f537e7e777e5 Mon Sep 17 00:00:00 2001 From: Kyle Baron Date: Thu, 15 Feb 2024 23:53:25 -0600 Subject: [PATCH 11/29] continue refactor; tests --- NAMESPACE | 4 + R/glossary.R | 135 ++++++++++++++++++++++++++--- R/table-object.R | 54 +----------- man/as_glossary.Rd | 28 ++++++ man/glossary_notes.Rd | 14 +-- man/read_glossary.Rd | 7 +- tests/testthat/test-glossary.R | 36 +++++--- tests/testthat/test-table-object.R | 7 +- 8 files changed, 199 insertions(+), 86 deletions(-) create mode 100644 man/as_glossary.Rd diff --git a/NAMESPACE b/NAMESPACE index 734343d5..93933e77 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,6 +1,8 @@ # Generated by roxygen2: do not edit by hand S3method(as.list,digits) +S3method(as.list,glossary) +S3method(as.list,glossary_entry) S3method(as.panel,"NULL") S3method(as.panel,character) S3method(as.panel,quosures) @@ -9,6 +11,7 @@ S3method(as_stable,pmtable) S3method(as_stable,stable) S3method(as_stable,stobject) S3method(glossary_notes,character) +S3method(glossary_notes,glossary) S3method(glossary_notes,list) S3method(names,stobject) S3method(new_names,character) @@ -89,6 +92,7 @@ export(read_glossary) export(rnd) export(rowpanel) export(sig) +export(simplify_abbrev) export(st2article) export(st2doc) export(st2pdf) diff --git a/R/glossary.R b/R/glossary.R index 472cb531..02ed6aaf 100644 --- a/R/glossary.R +++ b/R/glossary.R @@ -32,50 +32,77 @@ #' #' x$SC #' +#' @seealso [as_glossary()] #' @export read_glossary <- function(file) { if(!file.exists(file)) { - abort(glue("glossary file {file} does not exist.")) + abort(glue("Glossary file {file} does not exist.")) } txt <- readLines(file, warn = FALSE) - if(!length(txt)) abort("glossary file was empty.") + if(!length(txt)) abort("The glossary file was empty.") ans <- parse_glossary(txt) class(ans) <- c("tex_glossary", "glossary", "list") ans } -#' @rdname read_glossary +#' Coerce a named list to glossary +#' +#' @param x a named list. +#' @param ... unquoted `new abbreviation = old abbreviation` pairs. +#' @seealso [read_glossary()] +#' +#' @examples +#' +#' l <- list(VPC = "visual predictive check", tz = "timezone") +#' +#' as_glossary(l) +#' +#' as_glossary(l, tzone = tz) +#' #' @export -as_glossary <- function(glossary, ...) { - if(!is.list(glossary) || !is_named(glossary)) { - abort("`glossary` must be a named list") +as_glossary <- function(x, ...) { + if(!is.list(x) || !is_named(x)) { + abort("`x` must be a named list") } - type_chr <- vapply(glossary, inherits, what = "character", FUN.VALUE = TRUE) - len <- vapply(glossary, length, FUN.VALUE = 1) + type_chr <- vapply(x, inherits, what = "character", FUN.VALUE = TRUE) + len <- vapply(x, length, FUN.VALUE = 1) if(!all(type_chr & len==1)) { - abort("Each `glossary` entry must have type character and length 1.") + abort("Every `glossary` entry must have type character and length 1.") } - glossary <- Map(glossary, names(glossary), f = function(def, label) { - as_glossary_entry(c(definition = def, abbreviation = label)) + glossary <- Map(x, names(x), f = function(def, label) { + data <- c(definition = def, abbreviation = label) + as_glossary_entry(data) }) abbrev <- new_names(enquos(...)) for(a in seq_along(abbrev)) { - + glossary[[abbrev[a]]]$abbreviation <- names(abbrev[a]) } class(glossary) <- c("tex_glossary", "glossary", "list") glossary } +#' @rdname read_glossary +#' @export +simplify_abbrev <- function(x, ...) { + require_glossary(x) + items <- new_names(enquos(...)) + abort_bad_glo_labels(x, items) + for(m in seq_along(items)) { + x[[items[[m]]]]$abbreviation <- names(items[m]) + } + x +} + parse_glossary <- function(txt) { txt <- trimws(txt) txt <- txt[grepl("^\\\\newacronym", txt)] m <- regexec("\\{(.+)\\}\\{(.+)\\}\\{(.+)\\}.*$", txt) parsed <- regmatches(txt, m) if(!length(parsed)) { - abort("no acronym entries were found in `file`.") + abort("No acronym entries were found in `file`.") } if(!all(vapply(parsed, length, 1L)==4)) { - abort("there was a problem parsing the glossary file.") + abort("There was a problem parsing the glossary file.") } label <- vapply(parsed, FUN = "[", 2L, FUN.VALUE = "a") data <- lapply(parsed, FUN = "[", c(3L, 4L)) @@ -91,8 +118,88 @@ as_glossary_entry <- function(x) { x } +abort_bad_glo_labels <- function(x, what) { + if(!all(what %in% names(x))) { + bad <- setdiff(what, names(x)) + names(bad) <- rep("x", length(bad)) + msg <- c("Requested definitions not found in glossary object", bad) + abort(msg) + } +} + +#' Return formatted notes from a tex glossary file or glossary object +#' +#' @param x path to a tex glossary file, a glossary object, or a list that can +#' be coerced to a glossary object with [as_glossary()]. +#' +#' @inheritParams read_glossary +#' @inheritParams st_notes_glo +#' +#' @examples +#' file <- system.file("tex", "glossary.tex", package = "pmtables") +#' +#' glossary_notes(file, WT, CRCL) +#' +#' @seealso [st_notes_glo()], [read_glossary()] +#' @export +glossary_notes <- function(x, ...) UseMethod("glossary_notes") + +#' @rdname glossary_notes +#' @export +glossary_notes.character <- function(x, ...) { + x <- read_glossary(x) + glossary_notes(x, ...) +} + +#' @rdname glossary_notes +#' @export +glossary_notes.list <- function(x, ...) { + x <- as_glossary(x) + glossary_notes(x, ...) +} + +#' @rdname glossary_notes +#' @export +glossary_notes.glossary <- function(x, ..., sep = ": ", collapse = "; ", + labels = NULL) { + labels <- cvec_cs(labels) + labels <- c(new_names(enquos(...)), labels) + if(!length(labels)) labels <- names(x) + build_glossary_notes(x, labels, sep, collapse) +} + +build_glossary_notes <- function(glossary, labels, sep, collapse) { + abort_bad_glo_labels(glossary, labels) + glossary <- glossary[labels] + abb <- vapply(glossary, "[[", "abbreviation", FUN.VALUE = "a") + notes <- lapply(glossary, "[[", "definition") + notes <- paste0(abb, sep, notes) + if(is.character(collapse)) { + notes <- paste0(notes, collapse = collapse) + } + notes +} #' @export print.glossary_entry <- function(x, ...) { print(paste0(x$definition, " (", x$abbreviation, ")")) } + +#' @export +as.list.glossary <- function(x, ...) { + class(x) <- "list" + x <- lapply(x, as.list) + x +} + +#' @export +as.list.glossary_entry <- function(x, ...) { + class(x) <- "list" + x +} + +require_glossary <- function(x) { + if(!inherits(x, "glossary")) { + abort("`x` is not a glossary object") + } +} diff --git a/R/table-object.R b/R/table-object.R index fff38502..3583d19a 100644 --- a/R/table-object.R +++ b/R/table-object.R @@ -420,63 +420,15 @@ st_notes_glo <- function(x, glossary, ..., sep = ": ", collapse = "; ", abort("`glossary` must be a named list.") } labels <- cvec_cs(labels) - what <- c(new_names(enquos(...)), labels) - if(!length(what)) what <- names(glossary) - notes <- build_glossary_notes(glossary, what, sep, collapse) + labels <- c(new_names(enquos(...)), labels) + if(!length(labels)) labels <- names(glossary) + notes <- build_glossary_notes(glossary, labels, sep, collapse) if(is.numeric(width)) { x <- st_notes_detach(x, width = width) } st_notes(x, notes) } -build_glossary_notes <- function(glossary, what, sep, collapse) { - if(!all(what %in% names(glossary))) { - bad <- setdiff(what, names(glossary)) - names(bad) <- "x" - msg <- c("Requested definitions not found in glossary file", bad) - abort(msg) - } - glossary <- glossary[what] - cols <- names(glossary) - notes <- unlist(glossary, use.names = FALSE) - notes <- paste0(cols, sep, notes) - if(is.character(collapse)) { - notes <- paste0(notes, collapse = collapse) - } - notes -} - -#' Return formatted notes from a tex glossary file -#' -#' @inheritParams read_glossary -#' @inheritParams st_notes_glo -#' -#' @examples -#' file <- system.file("tex", "glossary.tex", package = "pmtables") -#' -#' glossary_notes(file, WT, CRCL) -#' -#' @seealso [st_notes_glo()], [read_glossary()] -#' @export -glossary_notes <- function(x, ...) UseMethod("glossary_notes") - -#' @rdname glossary_notes -#' @export -glossary_notes.character <- function(x, ...) { - glossary <- read_glossary(x) - glossary_notes(glossary, ...) -} - -#' @rdname glossary_notes -#' @export -glossary_notes.list <- function(x, ..., sep = ": ", collapse = "; ", - labels = NULL) { - labels <- cvec_cs(labels) - what <- c(new_names(enquos(...)), labels) - if(!length(what)) what <- names(x) - build_glossary_notes(x, what, sep, collapse) -} - #' Add column alignment information to st object #' #' See the `align` argument to [stable()]. This function may be called several diff --git a/man/as_glossary.Rd b/man/as_glossary.Rd new file mode 100644 index 00000000..50111c96 --- /dev/null +++ b/man/as_glossary.Rd @@ -0,0 +1,28 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/glossary.R +\name{as_glossary} +\alias{as_glossary} +\title{Coerce a named list to glossary} +\usage{ +as_glossary(x, ...) +} +\arguments{ +\item{x}{a named list.} + +\item{...}{unquoted \verb{new abbreviation = old abbreviation} pairs.} +} +\description{ +Coerce a named list to glossary +} +\examples{ + +l <- list(VPC = "visual predictive check", tz = "timezone") + +as_glossary(l) + +as_glossary(l, tzone = tz) + +} +\seealso{ +\code{\link[=read_glossary]{read_glossary()}} +} diff --git a/man/glossary_notes.Rd b/man/glossary_notes.Rd index d975256d..b46e31e8 100644 --- a/man/glossary_notes.Rd +++ b/man/glossary_notes.Rd @@ -1,19 +1,23 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/table-object.R +% Please edit documentation in R/glossary.R \name{glossary_notes} \alias{glossary_notes} \alias{glossary_notes.character} \alias{glossary_notes.list} -\title{Return formatted notes from a tex glossary file} +\alias{glossary_notes.glossary} +\title{Return formatted notes from a tex glossary file or glossary object} \usage{ glossary_notes(x, ...) \method{glossary_notes}{character}(x, ...) -\method{glossary_notes}{list}(x, ..., sep = ": ", collapse = "; ", labels = NULL) +\method{glossary_notes}{list}(x, ...) + +\method{glossary_notes}{glossary}(x, ..., sep = ": ", collapse = "; ", labels = NULL) } \arguments{ -\item{x}{an stobject.} +\item{x}{path to a tex glossary file, a glossary object, or a list that can +be coerced to a glossary object with \code{\link[=as_glossary]{as_glossary()}}.} \item{...}{unquoted names matching those names in \code{glossary}.} @@ -26,7 +30,7 @@ single string.} labels to get appended to the end of names passed in \code{...}.} } \description{ -Return formatted notes from a tex glossary file +Return formatted notes from a tex glossary file or glossary object } \examples{ file <- system.file("tex", "glossary.tex", package = "pmtables") diff --git a/man/read_glossary.Rd b/man/read_glossary.Rd index 3a34a786..05678d94 100644 --- a/man/read_glossary.Rd +++ b/man/read_glossary.Rd @@ -2,12 +2,12 @@ % Please edit documentation in R/glossary.R \name{read_glossary} \alias{read_glossary} -\alias{as_glossary} +\alias{simplify_abbrev} \title{Read and parse a tex glossary file} \usage{ read_glossary(file) -as_glossary(glossary, ...) +simplify_abbrev(x, ...) } \arguments{ \item{file}{path to the tex glossary file.} @@ -43,3 +43,6 @@ x$WT x$SC } +\seealso{ +\code{\link[=as_glossary]{as_glossary()}} +} diff --git a/tests/testthat/test-glossary.R b/tests/testthat/test-glossary.R index e195cab2..46aefc4d 100644 --- a/tests/testthat/test-glossary.R +++ b/tests/testthat/test-glossary.R @@ -8,23 +8,28 @@ glofile <- system.file("tex", "glossary.tex", package = "pmtables") test_that("read a glossary file", { tex <- read_glossary(glofile) expect_named(tex) + expect_is(tex, "tex_glossary") + expect_is(tex, "glossary") expect_is(tex, "list") expect_true("WT" %in% names(tex)) expect_true("QF" %in% names(tex)) + type_list <- vapply(tex, inherits, what = "list", FUN.VALUE = TRUE) + expect_true(all(type_list)) + foo <- tempfile() expect_error(read_glossary(foo), "does not exist.") cat("a\n", file = foo) - expect_error(read_glossary(foo), "no acronym entries were found") + expect_error(read_glossary(foo), "No acronym entries were found") }) test_that("load glossary notes", { - notes <- glossary_notes(glofile, WT, CLF) + notes <- glossary_notes(glofile, WT, NPDE) expect_is(notes, "character") expect_length(notes, 1) expect_true(grepl("WT: ", notes, fixed = TRUE)) - expect_true(grepl("CLF: ", notes, fixed = TRUE)) + expect_true(grepl("NPDE: ", notes, fixed = TRUE)) notes <- glossary_notes(glofile, WT, CLF, collapse = NULL) expect_is(notes, "character") @@ -35,12 +40,12 @@ test_that("load glossary notes", { expect_is(notes, "character") expect_true(any(grepl("WT& ", notes, fixed = TRUE))) - notes <- glossary_notes(glofile, WT, CLF, collapse = NULL, labels = "VF,NPDE") + notes <- glossary_notes(glofile, WT, CLF, collapse = NULL, labels = "QD,NPDE") expect_is(notes, "character") expect_length(notes, 4) expect_true(any(grepl("WT: ", notes, fixed = TRUE))) - expect_true(any(grepl("CLF: ", notes, fixed = TRUE))) - expect_true(any(grepl("VF: ", notes, fixed = TRUE))) + expect_true(any(grepl("CL/F", notes, fixed = TRUE))) + expect_true(any(grepl("QD: ", notes, fixed = TRUE))) expect_true(any(grepl("NPDE: ", notes, fixed = TRUE))) }) @@ -56,22 +61,33 @@ test_that("parse a glossary entry", { expect_length(x, 1) expect_named(x) expect_identical(names(x), "a") - expect_identical(x$a, "c") + expect_equivalent(x$a, list(abbreviation = "b", definition = "c")) txt <- "\\newacronym[options]{a}{b}{c} % comment" x <- pmtables:::parse_glossary(txt) expect_length(x, 1) expect_named(x) expect_identical(names(x), "a") - expect_identical(x$a, "c") + expect_equivalent(x$a, list(abbreviation = "b", definition = "c")) txt <- "\\newacronym[options]{a}{b_\\mathrm{d}}{\\mathit{c}} % comment" x <- pmtables:::parse_glossary(txt) expect_length(x, 1) expect_named(x) expect_identical(names(x), "a") - expect_identical(x$a, "\\mathit{c}") + expect_equivalent( + x$a, list(abbreviation = "b_\\mathrm{d}", definition = "\\mathit{c}") + ) txt <- "%\\newacronym{a}{b}{c}" - expect_error(pmtables:::parse_glossary(txt), "no acronym entries") + expect_error(pmtables:::parse_glossary(txt), "No acronym entries") +}) + +test_that("corece list to glossary object", { + g <- as_glossary(list(a = "b", c = "d")) + expect_length(g, 2) + expect_is(g, "glossary") + + expect_error(as_glossary(list("a", c = "d")), "must be a named list") + expect_error(as_glossary(c(a = "b", c = "d")), "must be a named list") }) diff --git a/tests/testthat/test-table-object.R b/tests/testthat/test-table-object.R index acd5cb51..54e973f9 100644 --- a/tests/testthat/test-table-object.R +++ b/tests/testthat/test-table-object.R @@ -362,7 +362,6 @@ test_that("call st_units() on pmtable", { ) }) - test_that("remove notes from a st object", { x <- pt_data_inventory(pmt_obs) expect_true(is.character(x$notes)) @@ -442,16 +441,16 @@ test_that("get notes from glossary file with st_notes_glo", { y <- st_notes_glo(x, glossary, WT, CLF, V2F)$notes expect_length(y, 1) expect_match(y, "subject weight", fixed = TRUE) - expect_match(y, "CLF: ", fixed = TRUE) + expect_match(y, "CL/F", fixed = TRUE) x <- st_new(data) y <- st_notes_glo(x, glossary, WT, CLF, collapse = NULL)$notes expect_length(y, 2) x <- st_new(data) - y <- st_notes_glo(x, glossary, WT, CLF, sep = "@ ")$notes + y <- st_notes_glo(x, glossary, WT, QD, sep = "@ ")$notes expect_length(y, 1) - expect_match(y, "CLF@ ") + expect_match(y, "QD@ ") x <- st_new(data) y <- st_notes_glo(x, glossary, WT, CLF, labels = "SCR,V2F", collapse = NULL)$notes From e1695ab4d034942620dec3a3cbf62e2ae275c693 Mon Sep 17 00:00:00 2001 From: Kyle Baron Date: Thu, 22 Feb 2024 15:58:31 -0600 Subject: [PATCH 12/29] documentation; update_abbrev function --- NAMESPACE | 2 +- R/glossary.R | 61 +++++++++++++++++++++++++++++++------------ man/as_glossary.Rd | 7 +++-- man/glossary_notes.Rd | 6 ++++- man/read_glossary.Rd | 5 ++-- man/update_abbrev.Rd | 33 +++++++++++++++++++++++ 6 files changed, 91 insertions(+), 23 deletions(-) create mode 100644 man/update_abbrev.Rd diff --git a/NAMESPACE b/NAMESPACE index 93933e77..9bd8657f 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -92,7 +92,6 @@ export(read_glossary) export(rnd) export(rowpanel) export(sig) -export(simplify_abbrev) export(st2article) export(st2doc) export(st2pdf) @@ -166,6 +165,7 @@ export(tab_spanners) export(tex_bold) export(tex_it) export(triage_data) +export(update_abbrev) export(yaml_as_df) importFrom(assertthat,assert_that) importFrom(assertthat,validate_that) diff --git a/R/glossary.R b/R/glossary.R index 02ed6aaf..d786f91c 100644 --- a/R/glossary.R +++ b/R/glossary.R @@ -1,6 +1,7 @@ #' Read and parse a tex glossary file #' #' @param file path to the tex glossary file. +#' @param ... `