blob: 639d72375ebbc8456b047b935dce959f433e3711 [file] [log] [blame]
# File src/library/tools/R/Rd.R
# Part of the R package, https://www.R-project.org
#
# Copyright (C) 1995-2015 The R Core Team
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# A copy of the GNU General Public License is available at
# https://www.R-project.org/Licenses/
### * Rd_info
Rd_info <-
function(file, encoding = "unknown")
{
## <FIXME>
## This used to work only for a given Rd file.
## now only for a parsed Rd object.
if(inherits(file, "Rd")) {
Rd <- file
description <- attr(attr(Rd, "srcref"), "srcfile")$filename
} else
stop("Rd object required")
aliases <- .Rd_get_metadata(Rd, "alias")
concepts <- .Rd_get_metadata(Rd, "concept")
keywords <- .Rd_get_metadata(Rd, "keyword") %w/o% .Rd_keywords_auto
## Could be none or more than one ... argh.
Rd_type <- .Rd_get_doc_type(Rd)
encoding <- c(.Rd_get_metadata(Rd, "encoding"), "")[1L]
Rd_name <- .Rd_get_name(Rd)
if(!length(Rd_name)) {
msg <-
c(gettextf("missing/empty %s field in '%s'",
"\\name",
description),
gettextf("Rd files must have a non-empty %s.",
"\\name"),
gettext("See chapter 'Writing R documentation' in manual 'Writing R Extensions'."))
stop(paste(msg, collapse = "\n"), domain = NA)
}
Rd_title <- .Rd_get_title(Rd)
if(!nchar(Rd_title)) {
msg <-
c(gettextf("missing/empty \\title field in '%s'",
description),
gettext("Rd files must have a non-empty \\title."),
gettext("See chapter 'Writing R documentation' in manual 'Writing R Extensions'."))
stop(paste(msg, collapse = "\n"), domain = NA)
}
list(name = Rd_name, type = Rd_type, title = Rd_title,
aliases = aliases, concepts = concepts, keywords = keywords,
encoding = encoding)
}
### * Rd_contents
Rd_contents <-
function(db)
{
## Compute contents db from Rd db.
## NB: Encoding is the encoding declared in the file, not
## that after parsing.
if(!length(db)) {
out <- data.frame(File = character(),
Name = character(),
Type = character(),
Title = character(),
Encoding = character(),
stringsAsFactors = FALSE)
out$Aliases <- list()
out$Concepts <- list()
out$Keywords <- list()
return(out)
}
entries <- c("Name", "Type", "Title", "Aliases", "Concepts",
"Keywords", "Encoding")
contents <- vector("list", length(db) * length(entries))
dim(contents) <- c(length(db), length(entries))
for(i in seq_along(db)) {
contents[i, ] <- Rd_info(db[[i]])
}
colnames(contents) <- entries
title <- .Rd_format_title(unlist(contents[ , "Title"]))
out <- data.frame(File = basename(names(db)),
Name = unlist(contents[ , "Name"]),
Type = unlist(contents[ , "Type"]),
Title = title,
Encoding = unlist(contents[ , "Encoding"]),
row.names = NULL, # avoid trying to compute row
# names
stringsAsFactors = FALSE)
out$Aliases <- contents[ , "Aliases"]
out$Concepts <- contents[ , "Concepts"]
out$Keywords <- contents[ , "Keywords"]
out
}
### * .write_Rd_contents_as_RDS
.write_Rd_contents_as_RDS <-
function(contents, outFile)
{
## Save Rd contents db to @file{outFile}.
## <NOTE>
## To deal with possible changes in the format of the contents db
## in the future, use a version attribute and/or a formal class.
saveRDS(contents, file = outFile, compress = TRUE)
## </NOTE>
}
### * .write_Rd_contents_as_DCF
if(FALSE) {
.write_Rd_contents_as_DCF <-
function(contents, packageName, outFile)
{
## Write a @file{CONTENTS} DCF file from an Rd contents db.
## Note that these files currently have @samp{URL:} entries which
## contain the package name, whereas @code{Rd_contents()} works on
## collections of Rd files which do not necessarily all come from
## the same package ...
## If the contents is 'empty', return immediately. (Otherwise,
## e.g. URLs would not be right ...)
if(!NROW(contents)) return()
## <NOTE>
## This has 'html' hard-wired.
## Note that slashes etc. should be fine for URLs.
URLs <- paste0("../../../library/", packageName, "/html/",
file_path_sans_ext(contents[ , "File"]),
".html")
## </NOTE>
if(is.data.frame(contents))
contents <-
cbind(contents$Name,
vapply(contents$Aliases, paste, "", collapse = " "),
vapply(contents$Keywords, paste, "", collapse = " "),
contents$Title)
else
contents <-
contents[, c("Name", "Aliases", "Keywords", "Title"),
drop = FALSE]
cat(paste(c("Entry:", "Aliases:", "Keywords:", "Description:",
"URL:"),
t(cbind(contents, URLs))),
sep = c("\n", "\n", "\n", "\n", "\n\n"),
file = outFile)
}
}
### * .build_Rd_index
.build_Rd_index <-
function(contents, type = NULL)
{
## Build an Rd 'index' containing Rd "names" (see below) and titles,
## maybe subscripted according to the Rd type (\docType).
keywords <- contents[ , "Keywords"]
if(!is.null(type)) {
idx <- contents[ , "Type"] %in% type
## Argh. Ideally we only want to subscript according to
## \docType. Maybe for 2.0 ...
if(type == "data")
idx <- idx | keywords == "datasets"
## (Note: we really only want the Rd objects which have
## 'datasets' as their *only* keyword.)
contents <- contents[idx, , drop = FALSE]
keywords <- keywords[idx]
}
## Drop all Rd objects marked as 'internal' from the index.
idx <- (vapply(keywords,
function(x) match("internal", x, 0L),
0L) == 0L)
index <- contents[idx, c("Name", "Title"), drop = FALSE]
if(nrow(index)) {
## If a \name is not a valid \alias, replace it by the first
## alias.
aliases <- contents[idx, "Aliases"]
bad <- which(!mapply("%in%", index[, 1L], aliases))
if(any(bad)) {
## was [[, but that applies to lists not char vectors
tmp <- sapply(aliases[bad], "[", 1L)
tmp[is.na(tmp)] <- ""
index[bad, 1L] <- tmp
}
## and sort it by name
index <- index[sort.list(index[, 1L]), ]
}
index
}
### * Rdindex
Rdindex <-
function(RdFiles, outFile = "", type = NULL,
width = 0.9 * getOption("width"), indent = NULL)
{
## Create @file{INDEX} or @file{data/00Index} style files from Rd
## files.
##
## R version of defunct @code{R CMD Rdindex} (now removed).
##
## called from R CMD build
if((length(RdFiles) == 1L) && dir.exists(RdFiles)) {
## Compatibility code for the former @code{R CMD Rdindex}
## interface.
docsDir <- RdFiles
if(dir.exists(file.path(docsDir, "man")))
docsDir <- file.path(docsDir, "man")
RdFiles <- list_files_with_type(docsDir, "docs")
}
if(outFile == "")
outFile <- stdout()
else if(is.character(outFile)) {
outFile <- file(outFile, "w")
on.exit(close(outFile))
}
if(!inherits(outFile, "connection"))
stop("argument 'outFile' must be a character string or connection")
db <- .build_Rd_db(files = RdFiles, stages="build")
index <- .build_Rd_index(Rd_contents(db), type = type)
writeLines(formatDL(index, width = width, indent = indent), outFile)
}
### * Rd_db
Rd_db <-
function(package, dir, lib.loc = NULL, stages = "build")
{
## Build an Rd 'data base' from an installed package or the unpacked
## package sources as a list containing the parsed Rd objects.
## <NOTE>
## We actually also process platform conditionals.
## If this was to be changed, we could also need to arrange that Rd
## objects in *all* platform specific subdirectories are included.
## </NOTE>
## Argument handling.
if(!missing(package)) {
if(length(package) != 1L)
stop("argument 'package' must be of length 1")
dir <- find.package(package, lib.loc)
## Using package installed in @code{dir} ...
docs_dir <- file.path(dir, "man")
## For an installed package, we might have
##
## 1) pre-2.10.0-style man/package.Rd.gz
## file with suitable concatenated Rd sources,
##
## 2) help/package.rd[bx]
## with a DB of the parsed (and platform processed, see
## above) Rd objects.
db_file <- file.path(dir, "help", package)
if(file_test("-f", paste0(db_file, ".rdx"))) {
db <- fetchRdDB(db_file)
pathfile <- file.path(dir, "help", "paths.rds")
if(file.exists(pathfile)) {
paths <- readRDS(pathfile)
if(!is.null(first <- attr(paths, "first")))
paths <- substring(paths, first)
names(db) <- paths
}
return(db)
}
db_file <- file.path(docs_dir, sprintf("%s.Rd.gz", package))
if(file_test("-f", db_file)) {
lines <- .read_Rd_lines_quietly(db_file)
eof_pos <-
grep("^\\\\eof$", lines, perl = TRUE, useBytes = TRUE)
db <- split(lines[-eof_pos],
rep.int(seq_along(eof_pos),
diff(c(0, eof_pos)))[-eof_pos])
} else return(structure(list(), names = character()))
## NB: we only get here for pre-2.10.0 installs
## If this was installed using a recent enough version of R CMD
## INSTALL, information on source file names is available, and
## we use it for the names of the Rd db. Otherwise, remove the
## artificial names attribute.
paths <- as.character(sapply(db, "[", 1L))
names(db) <-
if(length(paths)
&& all(grepl("^% --- Source file: (.+) ---$", paths)))
sub("^% --- Source file: (.+) ---$", "\\1", paths)
else
NULL
## Determine package encoding.
encoding <- .get_package_metadata(dir, TRUE)["Encoding"]
if(is.na(encoding)) encoding <- "unknown"
db <- suppressWarnings(lapply(db,
prepare_Rd_from_Rd_lines,
encoding = encoding,
defines = .Platform$OS.type,
stages = "install"))
}
else {
if(missing(dir))
stop("you must specify 'package' or 'dir'")
## Using sources from directory @code{dir} ...
if(!dir.exists(dir))
stop(gettextf("directory '%s' does not exist", dir),
domain = NA)
else
dir <- file_path_as_absolute(dir)
built_file <- file.path(dir, "build", "partial.rdb")
db <- .build_Rd_db(dir,
stages = stages,
built_file = built_file)
if(length(db)) {
first <- nchar(file.path(dir, "man")) + 2L
names(db) <- substring(names(db), first)
}
}
db
}
prepare_Rd_from_Rd_lines <-
function(x, ...)
{
con <- textConnection(x, "rt")
on.exit(close(con))
prepare_Rd(con, ...)
}
.build_Rd_db <-
function(dir = NULL, files = NULL,
encoding = "unknown", db_file = NULL,
stages = c("build", "install"), os = .OStype(), step = 3L,
built_file = NULL, macros = character())
{
if(!is.null(dir)) {
dir <- file_path_as_absolute(dir)
macros0 <- loadPkgRdMacros(dir)
man_dir <- file.path(dir, "man")
if(!dir.exists(man_dir))
return(structure(list(), names = character()))
if(is.null(files))
files <- list_files_with_type(man_dir, "docs", OS_subdirs=os)
encoding <- .get_package_metadata(dir, FALSE)["Encoding"]
if(is.na(encoding)) encoding <- "unknown"
} else if(!is.null(files))
macros0 <- initialRdMacros()
else
stop("you must specify 'dir' or 'files'")
if(length(macros)) {
con <- textConnection(macros)
macros <- loadRdMacros(con, macros0)
close(con)
} else {
macros <- macros0
}
.fetch_Rd_object <- function(f) {
## This calls parse_Rd if f is a filename
Rd <- prepare_Rd(f, encoding = encoding,
defines = os,
stages = stages, warningCalls = FALSE,
stage2 = step > 1L, stage3 = step > 2L,
macros = macros)
structure(Rd, prepared = step)
}
if(!is.null(db_file) && file_test("-f", db_file)) {
## message("updating database of parsed Rd files")
db <- fetchRdDB(sub("\\.rdx$", "", db_file))
db_names <- names(db) <-
readRDS(file.path(dirname(db_file), "paths.rds"))
## Files in the db in need of updating:
indf <- (files %in% db_names) & file_test("-nt", files, db_file)
## Also files not in the db:
indf <- indf | (files %notin% db_names)
## Db elements missing from files:
ind <- (db_names %notin% files) | (db_names %in% files[indf])
if(any(ind))
db <- db[!ind]
files <- files[indf]
} else
db <- list()
# The built_file is a file of partially processed Rd objects, where build time
# \Sexprs have been evaluated. We'll put the object in place of its
# filename to continue processing.
names(files) <- files
if(!is.null(built_file) && file_test("-f", built_file)) {
basenames <- basename(files)
built <- readRDS(built_file)
names_built <- names(built)
if ("install" %in% stages) {
this_os <- grepl(paste0("^", os, "/"), names_built)
name_only <- basename(names_built[this_os])
built[name_only] <- built[this_os]
some_os <- grepl("/", names(built))
built <- built[!some_os]
names_built <- names(built)
}
built[names_built %notin% basenames] <- NULL
if (length(built)) {
which <- match(names(built), basenames)
if (all(file_test("-nt", built_file, files[which]))) {
files <- as.list(files)
files[which] <- built
}
}
}
if(length(files)) {
## message("building database of parsed Rd files")
db1 <- lapply(files, .fetch_Rd_object)
names(db1) <- names(files)
db <- c(db, db1)
}
db
}
### * Rd_aliases
## Called from undoc and .check_Rd_xrefs
Rd_aliases <-
function(package, dir, lib.loc = NULL)
{
## Get the Rd aliases (topics) from an installed package or the
## unpacked package sources.
if(!missing(package)) {
dir <- find.package(package, lib.loc)
rds <- file.path(dir, "Meta", "Rd.rds")
if(file_test("-f", rds)) {
aliases <- readRDS(rds)$Aliases
if(length(aliases)) sort(unlist(aliases)) else character()
} else
character()
## <NOTE>
## Alternatively, we could get the aliases from the help index
## (and in fact, earlier versions of this code, then part of
## undoc(), did so), along the lines of
## <CODE>
## help_index <- file.path(dir, "help", "AnIndex")
## all_doc_topics <- if(!file_test("-f", help_index))
## character()
## else
## sort(scan(file = helpIndex, what = list("", ""),
## sep = "\t", quote = "", quiet = TRUE,
## na.strings = character())[[1L]])
## </CODE>
## This gets all topics the same way as index.search() would
## find individual ones.
## </NOTE>
}
else {
if(dir.exists(file.path(dir, "man"))) {
db <- Rd_db(dir = dir)
aliases <- lapply(db, .Rd_get_metadata, "alias")
if(length(aliases))
sort(unique(unlist(aliases, use.names = FALSE)))
else character()
}
else
character()
}
}
### .build_Rd_xref_db
.build_Rd_xref_db <-
function(package, dir, lib.loc = NULL)
{
db <- if(!missing(package))
Rd_db(package, lib.loc = lib.loc)
else
Rd_db(dir = dir)
lapply(db, .Rd_get_xrefs)
}
### * .Rd_get_metadata
.Rd_get_metadata <-
function(x, kind)
{
x <- x[RdTags(x) == sprintf("\\%s", kind)]
if(!length(x))
character()
else
unique(trimws(sapply(x, as.character)))
}
### * .Rd_keywords_auto
.Rd_keywords_auto <-
c("~kwd1", "~kwd2", "~~ other possible keyword(s) ~~")
### * .Rd_get_section
.Rd_get_section <-
function(x, which, predefined = TRUE)
{
if(predefined)
x <- x[RdTags(x) == paste0("\\", which)]
else {
## User-defined sections are parsed into lists of length 2, with
## the elements the title and the body, respectively.
x <- x[RdTags(x) == "\\section"]
if(length(x)) {
ind <- sapply(x, function(e) .Rd_get_text(e[[1L]])) == which
x <- lapply(x[ind], `[[`, 2L)
}
}
if(!length(x)) x else structure(x[[1L]], class = "Rd")
}
### * .Rd_deparse
.Rd_deparse <-
function(x, tag = TRUE)
{
## <NOTE>
## This should eventually get an option controlling whether to
## escape Rd special characters as needed (thus providing valid Rd)
## or not.
## It might also be useful to have an option for dropping comments.
## </NOTE>
if(!tag)
attr(x, "Rd_tag") <- "Rd"
paste(as.character.Rd(x), collapse = "")
}
### * .Rd_drop_comments
.Rd_drop_comments <-
function(x)
.Rd_drop_nodes_with_tags(x, "COMMENT")
### * .Rd_drop_nodes_with_tags
.Rd_drop_nodes_with_tags <-
function(x, tags)
{
recurse <- function(e) {
if(is.list(e))
structure(lapply(e[is.na(match(RdTags(e), tags))], recurse),
Rd_tag = attr(e, "Rd_tag"))
else
e
}
recurse(x)
}
### * .Rd_get_argument_names
.Rd_get_argument_names <-
function(x)
{
x <- .Rd_get_section(x, "arguments")
if(!length(x)) return(character())
txt <- .Rd_get_item_tags(x)
txt <- unlist(strsplit(txt, ", *"))
txt <- gsub("\\\\l?dots", "...", txt)
txt <- gsub("\\\\_", "_", txt)
trimws(txt)
}
### * .Rd_get_argument_table
.Rd_get_argument_table <-
function(x)
{
x <- .Rd_get_section(x, "arguments")
if(!length(x)) return(matrix(character(), 0L, 2L))
## Extract two-arg \item tags at top level ... non-recursive.
x <- x[RdTags(x) == "\\item"]
if(!length(x)) return(matrix(character(), 0L, 2L))
x <- lapply(x[lengths(x) == 2L], sapply, .Rd_deparse)
matrix(unlist(x), ncol = 2L, byrow = TRUE)
}
### * .Rd_get_item_tags
.Rd_get_item_tags <-
function(x)
{
## Extract two-arg \item tags at top level ... non-recursive.
x <- x[RdTags(x) == "\\item"]
out <- lapply(x[lengths(x) == 2L],
function(e) .Rd_deparse(e[[1L]]))
as.character(unlist(out))
}
### * .Rd_get_example_code
.Rd_get_example_code <-
function(x)
{
x <- .Rd_get_section(x, "examples")
if(!length(x)) return(character())
## Need to remove everything inside \dontrun (and drop comments),
## and "undefine" \dontshow and \testonly (which is achieved by
## changing the Rd tag to "Rd").
## <FIXME>
## Remove eventually.
x <- .Rd_drop_comments(x)
## </FIXME>
recurse <- function(e) {
if(!is.null(tag <- attr(e, "Rd_tag"))
&& tag %in% c("\\dontshow", "\\testonly"))
attr(e, "Rd_tag") <- "Rd"
if(is.list(e)) {
structure(lapply(e[is.na(match(RdTags(e), "\\dontrun"))],
recurse),
Rd_tag = attr(e, "Rd_tag"))
}
else e
}
.Rd_deparse(recurse(x), tag = FALSE)
}
### * .Rd_get_methods_description_table
.Rd_get_methods_description_table <-
function(x)
{
y <- matrix(character(), 0L, 2L)
x <- .Rd_get_section(x, "Methods", FALSE)
if(!length(x)) return(y)
x <- .Rd_get_section(x, "describe")
if(!length(x)) return(y)
x <- x[RdTags(x) == "\\item"]
if(!length(x)) return(y)
x <- lapply(x[lengths(x) == 2L], sapply, .Rd_deparse)
matrix(unlist(x), ncol = 2L, byrow = TRUE)
}
### * .Rd_get_doc_type
.Rd_get_doc_type <-
function(x)
{
c(attr(x, "meta")$docType, .Rd_get_metadata(x, "docType"), "")[1L]
}
### * .Rd_get_name
.Rd_get_name <-
function(x)
{
x <- .Rd_get_section(x, "name")
## The name should really be plain text, so as.character() should be
## fine as well ...
if(length(x))
trimws(.Rd_deparse(x, tag = FALSE))
else
character()
}
### * .Rd_get_title
.Rd_get_title <-
function(x)
{
title <- .Rd_get_section(x, "title")
result <- character()
if(length(title)) {
result <- .Rd_get_text(title)
result <- result[nzchar(result)]
}
paste(result, collapse=" ")
}
### * .Rd_get_text
# Return display form of text, encoded in UTF-8. Note that
# textConnection converts to the local encoding, and we convert back,
# so unrepresentable characters will be lost
## FIXME: use out = tempfile(), like .Rd_get_latex.
.Rd_get_text <-
function(x) {
# Handle easy cases first
if (is.character(x)) return(c(x))
# We'd like to use capture.output here, but don't want to depend
# on utils, so we duplicate some of it
rval <- NULL
file <- textConnection("rval", "w", local = TRUE)
save <- options(useFancyQuotes = FALSE)
Rdsave <- Rd2txt_options(underline_titles = FALSE)
sink(file)
tryCatch(Rd2txt(x, fragment=TRUE),
finally = {sink()
options(save)
Rd2txt_options(Rdsave)
close(file)})
if (is.null(rval)) rval <- character()
else enc2utf8(rval)
}
### * .Rd_get_xrefs
.Rd_get_xrefs <-
function(x)
{
out <- matrix(character(), nrow = 0L, ncol = 2L)
recurse <- function(e) {
tag <- attr(e, "Rd_tag")
if(identical(tag, "\\link")) {
val <- if(length(e)) { # mvbutils has empty links
arg <- as.character(e[[1L]])
opt <- attr(e, "Rd_option")
c(arg, if(is.null(opt)) "" else as.character(opt))
} else c("", "")
out <<- rbind(out, val)
} else if(identical(tag, "\\linkS4class")) {
arg <- as.character(e[[1L]])
val <- c(arg, sprintf("=%s-class", arg))
out <<- rbind(out, val)
}
if(is.list(e)) lapply(e, recurse)
}
lapply(x, recurse)
dimnames(out) <- list(NULL, c("Target", "Anchor"))
out
}
### * .Rd_get_names_from_Rd_db
.Rd_get_names_from_Rd_db <-
function(db)
{
Rd_names <- lapply(db, .Rd_get_name)
## If the Rd db was obtained from an installed package, we know that
## all Rd objects must have a \name entry---otherwise, Rd_info() and
## hence installing the package Rd contents db would have failed.
## For Rd dbs created from a package source directory, we now add
## the Rd file paths as the names attribute, so that we can point to
## the files with missing \name entries.
idx <- as.integer(lengths(Rd_names)) == 0L
if(any(idx)) {
Rd_paths <- names(db)
if(is.null(Rd_paths)) {
## This should not happen.
## We cannot refer to the bad Rd objects because we do not
## know their names, and have no idea which file they came
## from ...)
stop("cannot deal with Rd objects with missing/empty names")
}
else {
stop(sprintf(ngettext(sum(idx),
"missing/empty \\name field in Rd file\n%s",
"missing/empty \\name field in Rd files\n%s"),
paste0(" ", Rd_paths[idx], collapse = "\n")),
call. = FALSE, domain = NA)
}
}
unlist(Rd_names)
}
### * .Rd_format_title
.Rd_format_title <-
function(x)
{
## Although R-exts says about the Rd title slot that
## <QUOTE>
## This should be capitalized, not end in a period, and not use
## any markup (which would cause problems for hypertext search).
## </QUOTE>
## some Rd files have LaTeX-style markup, including
## * LaTeX-style single and double quotation
## * Medium and punctuation dashes
## * Escaped ampersand.
## Hence we try getting rid of these ...
x <- gsub("(``|'')", "\"", x)
x <- gsub("`", "'", x)
x <- gsub("([[:alnum:]])--([[:alnum:]])", "\\1-\\2", x)
x <- gsub("\\\\&", "&", x)
x <- gsub("---", "--", x)
## Also remove leading and trailing whitespace.
trimws(x)
}
### * fetchRdDB
fetchRdDB <-
function(filebase, key = NULL)
{
fun <- function(db) {
vals <- db$vals
vars <- db$vars
datafile <- db$datafile
compressed <- db$compressed
envhook <- db$envhook
fetch <- function(key)
lazyLoadDBfetch(vals[key][[1L]], datafile, compressed, envhook)
if(length(key)) {
if(key %notin% vars)
stop(gettextf("No help on %s found in RdDB %s",
sQuote(key), sQuote(filebase)),
domain = NA)
fetch(key)
} else {
res <- lapply(vars, fetch)
names(res) <- vars
res
}
}
res <- lazyLoadDBexec(filebase, fun)
if (length(key))
res
else
invisible(res)
}
# The macros argument can be TRUE, in which case a new environment is created with an empty parent,
# or the result of a previous call to this function, in which case it becomes the parent,
# or a filename, in which case that file is loaded first, then the new file into a child environment.
# It is not safe to save this environment, as changes to the parser may invalidate its contents.
loadRdMacros <- function(file, macros = TRUE) {
# New macros are loaded into a clean environment
if (is.logical(macros) && !macros)
stop("'macros' must be TRUE or must specify existing macros")
Rd <- parse_Rd(file, fragment = TRUE, macros = macros, warningCalls = FALSE)
for(entry in Rd) {
bad <- TRUE
if (is.list(entry)) break
tag <- attr(entry, "Rd_tag")
switch(tag,
TEXT = if (any(grepl("[^[:space:]]", entry, perl = TRUE, useBytes=TRUE)))
break
else
bad <- FALSE,
USERMACRO =,
"\\newcommand" =,
"\\renewcommand" =,
COMMENT = bad <- FALSE,
break
)
}
if (bad)
warning(gettextf("Macro file %s should only contain Rd macro definitions and comments",
file))
attr(Rd, "macros")
}
initialRdMacros <- function(pkglist = NULL,
macros = file.path(R.home("share"), "Rd", "macros", "system.Rd")
) {
if (length(pkglist)) {
others <- trimws(unlist(strsplit(pkglist, ",")))
for (p in others) {
if((fp <- system.file(package = p)) == "")
warning(gettextf("Rd macro package '%s' is not installed.",
p),
call. = FALSE)
else if(dir.exists(file.path(fp, "help", "macros")))
macros <- loadPkgRdMacros(system.file(package = p), macros)
else
warning(gettextf("No Rd macros in package '%s'.", p),
call. = FALSE)
}
} else if (is.character(macros))
macros <- loadRdMacros(file = macros)
macros
}
loadPkgRdMacros <- function(pkgdir, macros = NULL) {
## this does get called on any directory,
## e.g. a man directory in package 'diveMove'.
pkglist <- try(.read_description(file.path(pkgdir, "DESCRIPTION")),
silent = TRUE)
if (inherits(pkglist, "try-error"))
pkglist <- try(.read_description(file.path(pkgdir, "DESCRIPTION.in")),
silent = TRUE)
## may check for 'macros' subdirectory?
if (inherits(pkglist, "try-error")) return(macros)
pkglist <- pkglist["RdMacros"]
if (is.na(pkglist))
pkglist <- NULL
if (is.null(macros))
macros <- initialRdMacros(pkglist)
else
macros <- initialRdMacros(pkglist, macros)
files <- c(list.files(file.path(pkgdir, "man", "macros"), pattern = "\\.Rd$", full.names = TRUE),
list.files(file.path(pkgdir, "help", "macros"), pattern = "\\.Rd$", full.names = TRUE))
for (f in files)
macros <- loadRdMacros(f, macros)
macros
}
### Local variables: ***
### mode: outline-minor ***
### outline-regexp: "### [*]+" ***
### End: ***