blob: 294c24bad7a4bbb79a33f39ec8b8934a86cf657f [file] [log] [blame]
# File src/library/tools/R/Vignettes.R
# Part of the R package, https://www.R-project.org
#
# Copyright (C) 1995-2019 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/
vignette_is_tex <- function(file, ...) {
(regexpr("[.]tex$", file, ignore.case = TRUE) != -1L)
}
# Infers the vignette type (PDF or HTML) from the filename of the
# final vignette product.
vignette_type <- function(file) {
ext <- tolower(file_ext(file))
type <- c(pdf="PDF", html="HTML")[ext]
if (is.na(type))
stop(gettextf("Vignette product %s does not have a known filename extension (%s)",
sQuote(file), paste(sQuote(names(type)), collapse=", ")),
domain = NA)
unname(type)
}
# Locates the vignette weave, tangle and texi2pdf product(s) based on the
# vignette name. All such products must have the same name as their filename
# prefix (i.e. "^<name>").
# For weave, final = TRUE will look for <name>.pdf and <name>.html, whereas
# with final = FALSE it also looks for <name>.tex (if <name>.pdf is also
# found, it will be returned). For tangle, main = TRUE will look for <name>.R,
# whereas main = FALSE will look for <name><anything>*.R.
# For texi2pdf, <name>.pdf is located.
find_vignette_product <-
function(name, by = c("weave", "tangle", "texi2pdf"),
final = FALSE, main = TRUE, dir = ".", engine, ...)
{
stopifnot(length(name) == 1L, dir.exists(dir))
by <- match.arg(by)
exts <- ## (lower case here):
switch(by,
"weave" = if (final) c("pdf", "html") else c("pdf", "html", "tex"),
"tangle" = c("r", "s"),
"texi2pdf" = "pdf")
exts <- c(exts, toupper(exts))
pattern1 <- sprintf("^%s[.](%s)$", name, paste(exts, collapse = "|"))
output0 <- list.files(path = dir, all.files = FALSE, full.names = FALSE,
no.. = TRUE)
output0 <- output0[file_test("-f", file.path(dir, output0))]
output <- grep(pattern1, output0, value = TRUE)
# If main is FALSE, we want to find all other files with related
# names. We make sure that the main file is in position 1.
# FIXME: we should check a timestamp or something to see that
# these were produced by tangling for the "name" vignette,
# they aren't just coincidentally similar names.
if (!main) {
pattern2 <- sprintf("^%s.*[.](%s)$", name, paste(exts, collapse = "|"))
output2 <- grep(pattern2, output0, value = TRUE)
output <- c(output, setdiff(output2, output))
}
if (by == "weave") {
if (length(output) == 0L)
stop(gettextf("Failed to locate the %s output file (by engine %s) for vignette with name %s. The following files exist in directory %s: %s",
sQuote(by),
sQuote(sprintf("%s::%s", engine$package, engine$name)),
sQuote(name), sQuote(dir),
paste(sQuote(output0), collapse=", ")),
domain = NA)
if (length(output) > 2L || (final && length(output) > 1L))
stop(gettextf("Located more than one %s output file (by engine %s) for vignette with name %s: %s", sQuote(by),
sQuote(sprintf("%s::%s", engine$package, engine$name)),
sQuote(name), paste(sQuote(output), collapse=", ")),
domain = NA)
# If weave produced a TeX and then a PDF without cleaning out
# the TeX, consider the newer one (PDF wins a tie) as the weave product
if (length(output) == 2L) {
idxs <- match(tolower(file_ext(output)), exts)
output <- output[order(idxs)]
if (file_test("-nt", output[2L], output[1L])) output <- output[2L]
else output <- output[1L]
}
} else if (by == "tangle") {
if (main)
stopifnot(length(output) <= 1L)
} else if (by == "texi2pdf") {
if (length(output) == 0L)
stop(gettextf("Failed to locate the %s output file (by engine %s) for vignette with name %s. The following files exist in directory %s: %s",
sQuote(by),
sQuote(sprintf("%s::%s", engine$package, engine$name)),
sQuote(name), sQuote(dir),
paste(sQuote(output0), collapse=", ")),
domain = NA)
if (length(output) > 1L)
stop(gettextf("Located more than one %s output file (by engine %s) for vignette with name %s: %s",
sQuote(by),
sQuote(sprintf("%s::%s", engine$package, engine$name)),
sQuote(name), paste(sQuote(output), collapse=", ")),
domain = NA)
}
## return :
if (length(output) > 0L) {
if (dir == ".")
basename(output)
else
file.path(dir, output)
} ## else NULL
}
### * checkVignettes
###
### Run a tangle+source and a weave on all vignettes of a package.
checkVignettes <-
function(package, dir, lib.loc = NULL,
tangle = TRUE, weave = TRUE, latex = FALSE,
workdir = c("tmp", "src", "cur"),
keepfiles = FALSE)
{
vigns <- pkgVignettes(package = package, dir = dir, lib.loc = lib.loc)
if(is.null(vigns)) return(NULL)
workdir <- match.arg(workdir)
wd <- getwd()
if (is.null(wd))
stop("current working directory cannot be ascertained")
if(workdir == "tmp") {
tmpd <- tempfile("Sweave") ## <= Rename?
if(!dir.create(tmpd))
stop(gettextf("unable to create temp directory %s ", sQuote(tmpd)),
domain = NA)
setwd(tmpd)
}
else {
keepfiles <- TRUE
if(workdir == "src") setwd(vigns$dir)
}
on.exit({
setwd(wd)
if(!keepfiles) unlink(tmpd, recursive = TRUE)
})
file.create(".check.timestamp")
result <- list(tangle = list(), weave = list(),
source = list(), latex = list())
## pkgVignettes has already done this
loadVignetteBuilder(vigns$pkgdir)
startdir <- getwd()
for(i in seq_along(vigns$docs)) {
file <- vigns$docs[i]
file <- basename(file)
name <- vigns$names[i]
engine <- vignetteEngine(vigns$engines[i])
enc <- vigns$encodings[i]
if (enc == "non-ASCII")
stop(gettextf("Vignette '%s' is non-ASCII but has no declared encoding", name),
domain = NA)
if(tangle) {
message(" Running ", sQuote(file))
.eval_with_capture({
result$tangle[[file]] <- tryCatch({
engine$tangle(file, quiet = TRUE, encoding = enc)
setwd(startdir) # in case a vignette changes the working dir
find_vignette_product(name, by = "tangle", main = FALSE, engine = engine)
}, error = function(e) e)
})
}
if(weave) {
setwd(startdir) # in case a vignette changes the working dir then errored out
.eval_with_capture({
result$weave[[file]] <- tryCatch({
engine$weave(file, quiet = TRUE, encoding = enc)
setwd(startdir)
find_vignette_product(name, by = "weave", engine = engine)
}, error = function(e) e)
})
}
setwd(startdir) # in case a vignette changes the working dir then errored out
}
# Assert that output files were not overwritten
for (name in c("weave", "tangle")) {
resultsT <- result[[name]]
if (length(resultsT) <= 1L)
next
for (i in 1L:(length(resultsT)-1L)) {
outputsI <- resultsT[[i]]
if (inherits(outputsI, "error"))
next;
outputsI <- normalizePath(outputsI)
for (j in (i+1L):length(resultsT)) {
outputsJ <- resultsT[[j]]
if (inherits(outputsJ, "error"))
next;
outputsJ <- normalizePath(outputsJ)
bad <- intersect(outputsJ, outputsI)
if (length(bad) > 0L) {
stop(gettextf("Vignette %s overwrites the following %s output by vignette %s: %s",
sQuote(basename(names(resultsT)[j])),
sQuote(name),
sQuote(basename(names(resultsT)[i])),
paste(basename(bad), collapse=", ")),
domain = NA)
}
}
}
}
if(tangle) {
## Tangling can create several source files if splitting is on,
## and these can be .R or .S (at least). However, there is
## no guarantee that running them in alphabetical order in a
## session will work -- with named chunks it normally will not.
cwd <- getwd()
if (is.null(cwd))
stop("current working directory cannot be ascertained")
for(i in seq_along(result$tangle)) {
sources <- result$tangle[[i]]
if (inherits(sources, "error"))
next
sources <- sources[file_test("-nt", sources, ".check.timestamp")]
for(file in sources) {
.eval_with_capture({
result$source[[file]] <- tryCatch({
source(file)
}, error = function(e) e)
})
setwd(startdir)
}
}
}
if(weave && latex) {
if("Makefile" %notin% list.files(vigns$dir)) {
## <NOTE>
## This used to run texi2pdf on *all* vignettes, including
## the ones already known from the above to give trouble.
## In addition, texi2pdf errors were not caught, so that in
## particular the results of the previous QC analysis were
## *not* returned in case of such errors ...
## Hence, let us
## * Only run texi2pdf() on previously unproblematic vignettes
## * Catch texi2pdf() errors similar to the above.
## * Do *not* immediately show texi2pdf() output as part of
## running checkVignettes().
## (For the future, maybe keep this output and provide it as
## additional diagnostics ...)
## </NOTE>
for (i in seq_along(result$weave)) {
file <- names(result$weave)[i]
output <- result$weave[i]
if (inherits(output, "error"))
next
if (!vignette_is_tex(output))
next
.eval_with_capture({
result$latex[[file]] <- tryCatch({
texi2pdf(file = output, clean = FALSE, quiet = TRUE)
find_vignette_product(name, by = "texi2pdf", engine = engine)
}, error = function(e) e)
})
}
}
}
# Cleanup results
for (name in c("tangle", "weave", "source", "latex")) {
resultsT <- result[[name]]
resultsT <- lapply(resultsT, FUN = function(res) {
if (inherits(res, "error"))
conditionMessage(res)
else
NULL
})
resultsT <- resultsT[!vapply(resultsT, is.null, NA)]
result[[name]] <- resultsT
}
file.remove(".check.timestamp")
class(result) <- "checkVignettes"
result
}
print.checkVignettes <-
function(x, ...)
{
mycat <- function(y, title) {
if(length(y)){
cat("\n", title, "\n\n", sep = "")
for(k in seq_along(y)) {
cat("File", names(y)[k], ":\n")
cat(as.character(y[[k]]), "\n")
}
}
}
mycat(x$tangle, "*** Tangle Errors ***")
mycat(x$source, "*** Source Errors ***")
mycat(x$weave, "*** Weave Errors ***")
mycat(x$latex, "*** PDFLaTeX Errors ***")
invisible(x)
}
### get the engine from a file
getVignetteEngine <- function(filename, lines = readLines(filename, warn=FALSE)) {
c(.get_vignette_metadata(lines, "Engine"), "utils::Sweave")[1L]
}
### * engineMatches
###
### does the engine from a vignette match one of the registered ones?
###
engineMatches <- function(regengine, vigengine) {
if (!grepl("::", vigengine))
regengine <- sub("^.*::", "", regengine)
regengine == vigengine
}
### * pkgVignettes
###
### Get an object of class pkgVignettes which contains a list of
### vignette source files, the registered vignette engine for
### each of them, and the name of the directory which contains them.
### A vector of 'subdirs' is allowed for historical reasons but the
### first which exists is used.
pkgVignettes <-
function(package, dir, subdirs = NULL, lib.loc = NULL, output = FALSE,
source = FALSE, check = FALSE)
{
## Argument handling.
if(!missing(package)) {
if(length(package) != 1L)
stop("argument 'package' must be of length 1")
dir <- find.package(package, lib.loc)
}
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)
if (is.null(subdirs))
subdirs <- if (missing(package)) "vignettes" else "doc"
for (subdir in subdirs) {
docdir <- file.path(dir, subdir)
if(dir.exists(docdir))
break
}
}
if(!dir.exists(docdir)) return(NULL)
# Locate all vignette files
buildPkgs <- loadVignetteBuilder(dir, mustwork = FALSE)
engineList <- vignetteEngine(package = buildPkgs)
docs <- names <- engines <- patterns <- character()
allFiles <- list.files(docdir, all.files = FALSE, full.names = TRUE)
exclude <- inRbuildignore(sub(paste0(dir, "/"), "", allFiles, fixed = TRUE), dir)
allFiles <- allFiles[!exclude]
matchedPattern <- rep.int(FALSE, length(allFiles))
msg <- character()
if (length(allFiles) > 0L) {
for (name in names(engineList)) {
engine <- engineList[[name]]
for (pattern in engine$pattern) {
idxs <- grep(pattern, allFiles)
matchedPattern[idxs] <- TRUE
keep <- vapply(allFiles[idxs], function(.d.)
engineMatches(name, getVignetteEngine(.d.)), NA)
if (any(keep)) {
idxs <- idxs[keep]
if (is.function(engine$weave)) {
docsT <- allFiles[idxs]
docs <- c(docs, docsT)
names <- c(names, gsub(pattern, "", basename(docsT)))
engines <- c(engines, rep.int(name, length(idxs)))
patterns <- c(patterns, rep.int(pattern, length(idxs)))
}
matchedPattern <- matchedPattern[-idxs]
allFiles <- allFiles[-idxs]
if (length(allFiles) == 0L)
break
}
}
}
if (check && any(matchedPattern)) {
files <- substring(allFiles[matchedPattern], nchar(dir) + 2)
msg <- c("Files named as vignettes but with no recognized vignette engine:",
paste(" ", sQuote(files)),
"(Is a VignetteBuilder field missing?)")
}
}
# Assert
stopifnot(length(names) == length(docs),
length(engines) == length(docs),
length(patterns) == length(docs), !anyDuplicated(docs))
defaultEncoding <- .get_package_metadata(dir)["Encoding"]
encodings <- vapply(docs, getVignetteEncoding, "", default = defaultEncoding)
z <- list(docs = docs, names = names, engines = engines,
patterns = patterns, encodings = encodings,
dir = docdir, pkgdir = dir, msg = msg)
if (output) {
outputs <- character(length(docs))
for (i in seq_along(docs)) {
file <- docs[i]
name <- names[i]
outputI <- find_vignette_product(name, by = "weave", dir = docdir, engine = engine)
outputs[i] <- outputI
}
z$outputs <- outputs
}
if (source) {
sources <- list()
for (i in seq_along(docs)) {
file <- docs[i]
name <- names[i]
sourcesI <- find_vignette_product(name, by = "tangle", main = FALSE, dir = docdir, engine = engine)
sources[[file]] <- sourcesI
}
z$sources <- sources
}
class(z) <- "pkgVignettes"
z
}
### * buildVignettes
###
### Run a weave and pdflatex on all vignettes of a package and try to
### remove all temporary files that were created.
### Exported version, used in R CMD build/check
buildVignettes <-
function(package, dir, lib.loc = NULL, quiet = TRUE, clean = TRUE,
tangle = FALSE, ser_elibs = NULL)
{
separate <- !is.null(ser_elibs)
if (separate) elibs <- readRDS(ser_elibs)
## This has side effects, including loading vignette-buider pkgs
vigns <- pkgVignettes(package = package, dir = dir, lib.loc = lib.loc,
check = TRUE)
if (is.null(vigns)) return(invisible())
if (length(vigns$docs) <= 1L) separate <- FALSE
if (length(vigns$msg))
warning(paste(vigns$msg, collapse = "\n"), domain = NA)
## Check that duplicated vignette names do not exist, e.g.
## 'vig' and 'vig' from 'vig.Rnw' and 'vig.Snw'.
dups <- duplicated(vigns$names)
if (any(dups)) {
names <- unique(vigns$names[dups])
docs <- sort(basename(vigns$docs[vigns$names %in% names]))
stop(gettextf("Detected vignette source files (%s) with shared names (%s) and therefore risking overwriting each other's output files",
paste(sQuote(docs), collapse = ", "),
paste(sQuote(names), collapse = ", ")),
domain = NA)
}
## Check for duplicated titles (which look silly on CRAN pages)
titles <- character()
for (d in vigns$docs) {
this <- c(.get_vignette_metadata(readLines(d, warn = FALSE),
"IndexEntry"), "")[1L]
titles <- c(titles, this)
}
have_dup_titles <-
if (any(dup <- duplicated(titles))) {
dups <- unique(titles[dup])
message(ngettext(length(dups),
"duplicated vignette title:",
"duplicated vignette titles:"))
message(paste(.pretty_format(dups), collapse = "\n"))
message()
TRUE
} else FALSE
## unset SWEAVE_STYLEPATH_DEFAULT here to avoid problems
Sys.unsetenv("SWEAVE_STYLEPATH_DEFAULT")
op <- options(warn = 1) # we run may run vignettes in this process
wd <- getwd()
if (is.null(wd))
stop("current working directory cannot be ascertained")
on.exit({
setwd(wd)
options(op)
})
setwd(vigns$dir)
## FIXME: should this recurse into subdirs?
origfiles <- list.files(all.files = TRUE)
## Note, as from 2.13.0, only this case
have.makefile <- "Makefile" %in% origfiles
file.create(".build.timestamp")
## pkgVignettes has already done this
## loadVignetteBuilder(vigns$pkgdir)
outputs <- character()
sourceList <- list()
startdir <- getwd()
fails <- character()
for(i in seq_along(vigns$docs)) {
thisOK <- TRUE
file <- basename(vigns$docs[i])
enc <- vigns$encodings[i]
if (enc == "non-ASCII") {
message(gettextf("Error: Vignette '%s' is non-ASCII but has no declared encoding",
file))
fails <- c(fails, file)
next
}
name <- vigns$names[i]
engine <- vignetteEngine(vigns$engines[i])
if (separate) { # --- run in separate process
tf2 <- gsub("\\", "/", tempfile(fileext = ".rds"), fixed = TRUE)
saveRDS(engine, tf2)
Rcmd <- sprintf('tools:::.buildOneVignette("%s", "%s", %s, %s, "%s", "%s", "%s")',
file, vigns$pkgdir, quiet, have.makefile,
name, enc, tf2)
tlim <- get_timeout(Sys.getenv("_R_CHECK_ONE_VIGNETTE_ELAPSED_TIMEOUT_",
Sys.getenv("_R_CHECK_ELAPSED_TIMEOUT_")))
tf <- tempfile()
status <- R_runR(Rcmd, "--vanilla --slave", elibs,
stdout = tf, stderr = tf, timeout = tlim)
unlink(tf2)
##print(status)
if (!status) {
this <- readLines(tf)
patt <- "^[+]-[+]"
l <- grepl(patt, this)
output <- gsub(patt, "", this[l])
outputs <- c(outputs, output)
cat(this[!l], sep = "\n")
} else {
fails <- c(fails, file)
cat(readLines(tf), sep = "\n")
}
unlink(tf)
} else { # --- run in this process
message(gettextf("--- re-building %s using %s",
sQuote(file), engine$name))
tryCatch({
engine$weave(file, quiet = quiet, encoding = enc)
setwd(startdir) # In case weave/vignette changed it
output <- find_vignette_product(name, by = "weave", engine = engine)
if (!have.makefile && vignette_is_tex(output)) {
## This can fail if run in a directory whose path contains spaces.
texi2pdf(file = output, clean = FALSE, quiet = quiet)
output <- find_vignette_product(name, by = "texi2pdf",
engine = engine)
}
outputs <- c(outputs, output)
}, error = function(e) {
thisOK <<- FALSE
fails <<- c(fails, file)
message(gettextf("Error: processing vignette '%s' failed with diagnostics:\n%s",
file, conditionMessage(e)))
})
} # end if (separate)
if (tangle && !separate) { # This is set for all engines as of 3.0.2
## It is unlikely that weave succeeds but tangle fails,
## so we don't bother to report tangle failures specifically.
output <- tryCatch({
engine$tangle(file, quiet = quiet, encoding = enc)
setwd(startdir) # In case tangle/vignette changed it
find_vignette_product(name, by = "tangle", main = FALSE, engine = engine)
}, error = function(e) {
thisOK <<- FALSE
fails <<- c(fails, file)
message(gettextf("Error: tangling vignette '%s' failed with diagnostics:\n%s",
file, conditionMessage(e)))
})
sourceList[[file]] <- output
}
if (!separate) {
if (thisOK)
message(gettextf("--- finished re-building %s\n", sQuote(file)))
else
message(gettextf("--- failed re-building %s\n", sQuote(file)))
}
} # end loop over vignettes
if (have.makefile) {
WINDOWS <- .Platform$OS.type == "windows"
if (WINDOWS) {
## Some people have *assumed* that R_HOME uses / in Makefiles
## Spaces in paths might still cause trouble.
rhome <- chartr("\\", "/", R.home())
Sys.setenv(R_HOME = rhome)
}
make <- Sys.getenv("MAKE", "make")
if (!nzchar(make)) make <- "make"
yy <- system(make)
if (yy > 0) stop("running 'make' failed")
## See if Makefile has a clean: target, and if so run it.
if (clean &&
any(startsWith(readLines("Makefile", warn = FALSE), "clean:")))
system(paste(make, "clean"))
} else {
## Badly-written vignettes open a pdf() device on Rplots.pdf and
## fail to close it.
grDevices::graphics.off()
keep <- c(outputs, unlist(sourceList))
if (clean) {
f <- setdiff(list.files(all.files = TRUE, no.. = TRUE), keep)
newer <- file_test("-nt", f, ".build.timestamp")
## some packages, e.g. SOAR, create directories
unlink(f[newer], recursive = TRUE)
f <- setdiff(list.files(all.files = TRUE, no.. = TRUE),
c(keep, origfiles))
f <- f[file_test("-f", f)]
file.remove(f)
}
}
if (file.exists(".build.timestamp")) file.remove(".build.timestamp")
## Might have been in origfiles ...
if (length(fails)) {
message(ngettext(length(fails),
"SUMMARY: processing the following file failed:",
"SUMMARY: processing the following files failed:"))
message(paste(.pretty_format(fails), collapse = "\n"))
message()
}
msg2 <- paste("Duplicate vignette titles.",
" Ensure that the %\\VignetteIndexEntry lines in the vignette sources",
" correspond to the vignette titles.",
sep = "\n")
## Assert
if (length(fails) || (length(outputs) != length(vigns$docs))) {
msg <- "Vignette re-building failed."
if (have_dup_titles) msg <- paste0(msg, "\nError: ", msg2)
stop(msg, domain = NA, call. = FALSE)
}
if (have_dup_titles)
stop(msg2, domain = NA, call. = FALSE)
vigns$outputs <- outputs
vigns$sources <- sourceList
invisible(vigns) ## not documented on the help page.
}
### * buildVignette
###
### Run a weave and/or tangle on one vignette and try to
### remove all temporary files that were created.
### Also called from 'R CMD Sweave' via .Sweave() in ../../utils/R/Sweave.R
buildVignette <-
function(file, dir = ".", weave = TRUE, latex = TRUE, tangle = TRUE,
quiet = TRUE, clean = TRUE, keep = character(),
engine = NULL, buildPkg = NULL,
encoding = getVignetteEncoding(file), ...)
{
if (!file_test("-f", file))
stop(gettextf("file '%s' not found", file), domain = NA)
if (!dir.exists(dir))
stop(gettextf("directory '%s' does not exist", dir), domain = NA)
if (!is.null(buildPkg))
for (pkg in buildPkg)
suppressPackageStartupMessages(loadNamespace(pkg))
if (is.null(engine))
# Infer vignette engine from vignette content
engine <- getVignetteEngine(file)
# Get the vignette engine
if (is.character(engine))
engine <- vignetteEngine(engine, package = buildPkg)
# Infer the vignette name
names <- sapply(engine$pattern, FUN = sub, "", file)
name <- basename(names[(names != file)][1L])
# A non-matching filename?
if (is.na(name))
stop(gettextf("vignette filename '%s' does not match any of the '%s' filename patterns",
file, paste(engine$package, engine$name, sep="::")),
domain = NA)
if (encoding == "non-ASCII")
stop(gettextf("Vignette '%s' is non-ASCII but has no declared encoding", name))
# Set output directory temporarily
file <- file_path_as_absolute(file)
olddir <- setwd(dir)
if (!is.null(olddir)) on.exit(setwd(olddir))
## # Record existing files
## origfiles <- list.files(all.files = TRUE)
if (is.na(clean) || clean) {
file.create(".build.timestamp")
}
tdir <- getwd()# if 'dir' was relative, resetting to tdir will work
output <- NULL
# Weave
final <- if (weave) {
engine$weave(file, quiet = quiet, encoding = encoding, ...)
setwd(tdir) # In case weave/vignette changed it
output <- find_vignette_product(name, by = "weave", engine = engine)
# Compile TeX to PDF?
if(latex && vignette_is_tex(output)) {
texi2pdf(file = output, clean = FALSE, quiet = quiet)
find_vignette_product(name, by = "texi2pdf", engine = engine)
} else
output
} # else NULL
# Tangle
sources <- if (tangle) {
engine$tangle(file, quiet = quiet, encoding = encoding, ...)
setwd(tdir) # In case tangle changed it
find_vignette_product(name, by = "tangle", main = FALSE, engine = engine)
} # else NULL
## Cleanup newly created files unless those in 'keep'
keep <- c(sources, final, keep)
if (is.na(clean)) { # Use NA to signal we want .tex (or .md) files kept.
keep <- c(keep, output)
clean <- TRUE
}
if (clean) {
f <- setdiff(list.files(all.files = TRUE, no.. = TRUE), keep)
newer <- file_test("-nt", f, ".build.timestamp")
## some packages create directories
unlink(f[newer], recursive = TRUE)
}
### huh? 2nd round of cleaning even if clean is FALSE ??
## f <- setdiff(list.files(all.files = TRUE, no.. = TRUE), c(keep, origfiles))
## f <- f[file_test("-f", f)]
## file.remove(f)
## #}
if((is.na(clean) || clean) && file.exists(".build.timestamp")) {
file.remove(".build.timestamp")
}
unique(keep)
}
### * .buildOneVignette
## helper to be run in a separate process
.buildOneVignette <-
function(file, pkgdir, quiet = TRUE, have.makefile = FALSE,
name, enc, ser_engine)
{
op <- options(warn = 1) # we run vignettes in this process
engine <- readRDS(ser_engine)
## This is more than we need, but we cannot be sure that 'engine'
## has references to all the namespaces required. But packages
## typically only have one engine, at most two.
loadVignetteBuilder(pkgdir)
OK <- TRUE
startdir <- getwd()
output <- character()
message(gettextf("--- re-building %s using %s",
sQuote(file), engine$name))
tryCatch({
engine$weave(file, quiet = quiet, encoding = enc)
setwd(startdir) # In case weave/vignette changed it
output <- find_vignette_product(name, by = "weave", engine = engine)
if(!have.makefile && vignette_is_tex(output)) {
texi2pdf(file = output, clean = FALSE, quiet = quiet)
output <- find_vignette_product(name, by = "texi2pdf",
engine = engine)
}
}, error = function(e) {
OK <<- FALSE
message(gettextf("Error: processing vignette '%s' failed with diagnostics:\n%s",
file, conditionMessage(e)))
})
if (OK)
message(gettextf("--- finished re-building %s\n", sQuote(file)))
else {
message(gettextf("--- failed re-building %s\n", sQuote(file)))
q("no", status = 9L)
}
message("+-+", output)
invisible(output)
}
### * getVignetteEncoding
getVignetteEncoding <- function(file, ...)
{
lines <- readLines(file, warn = FALSE)
.getVignetteEncoding(lines, ...)
}
.getVignetteEncoding <- function(lines, default = NA)
{
res <- .get_vignette_metadata(lines, "Encoding")[1L]
if(is.na(res)) {
poss <- grep("^[[:space:]]*%+[[:space:]]*\\\\SweaveUTF8[[:space:]]*$", lines, useBytes = TRUE)
if (length(poss))
"UTF-8"
else {
## Look for input enc lines using inputenc or inputenx
## Note, multiple encodings are excluded.
poss <-
grep("^[[:space:]]*\\\\usepackage\\[([[:alnum:]]+)\\]\\{inputen[cx]\\}",
lines, useBytes = TRUE)
## Check it is in the preamble
start <- grep("^[[:space:]]*\\\\begin\\{document\\}",
lines, useBytes = TRUE)
if(length(start))
poss <- poss[poss < start[1L]]
if(length(poss)) {
poss <- lines[poss[1L]]
res <- gsub("^[[:space:]]*\\\\usepackage\\[([[:alnum:]]+)\\].*", "\\1",
poss) # This line should be ASCII.
## see Rd2latex.R.
## Currently utf8, utf8x, latin1, latin9 and ansinew are in use.
switch(res,
"utf8" =, "utf8x" = "UTF-8",
"latin1" =, "iso-8859-1" = "latin1",
"latin2" =, "iso-8859-2" = "latin2",
"latin9" =, "iso-8859-15" = "latin-9", # only form known to GNU libiconv
"latin10" =, "iso-8859-16" = "latin10",
"cyrillic" =, "iso-8859-5" = "ISO-8859-5", # inputenx
"koi8-r" = "KOI8-R", # inputenx
"arabic" = "ISO-8859-6", # Not clear next 3 are known to latex
"greek" =, "iso-8859-7" = "ISO-8859-7",
"hebrew" =, "iso-8859-8" = "ISO-8859-8",
"ansinew" = "CP1252",
"applemac" = "macroman",
## assume these only get used on Windows
"cp1250" = "CP1250",
"cp1252" = "CP1252",
"cp1257" = "CP1257",
"unknown")
} else if (!is.na(default)) {
default
} else { # Nothing else has indicated an encoding, maybe it's just ASCII
asc <- iconv(lines, "latin1", "ASCII")
if(anyNA(asc) || any(asc != lines)) "non-ASCII" else "" # or "ASCII"
}
}
} else
res
}
### * .build_vignette_index
.get_vignette_metadata <-
function(lines, tag)
{
## <FIXME>
## Why don't we anchor this to the beginning of a line?
meta_RE <- paste0("[[:space:]]*%+[[:space:]]*\\\\Vignette",
tag, "\\{([^}]*(\\{[^}]*\\})*[^}]*)\\}.*")
## </FIXME>
meta <- grep(meta_RE, lines, value = TRUE, useBytes = TRUE)
trimws(gsub(meta_RE, "\\1", meta))
}
vignetteInfo <- function(file)
{
lines <- readLines(file, warn = FALSE)
## <FIXME>
## Can only proceed with lines which are valid in the current locale.
## Unfortunately, vignette encodings are a mess: package encodings
## might apply, but be overridden by \inputencoding commands.
## For now, assume that vignette metadata occur in all ASCII lines.
## (Could also iconv() using sub = "byte".)
lines[is.na(nchar(lines, "c", TRUE))] <- ""
## </FIXME>
## \VignetteIndexEntry
title <- c(.get_vignette_metadata(lines, "IndexEntry"), "")[1L]
## \VignetteDepends
depends <- .get_vignette_metadata(lines, "Depends")
if(length(depends))
depends <- unlist(strsplit(depends[1L], ", *"))
## \VignetteKeyword and old-style \VignetteKeywords
keywords <- .get_vignette_metadata(lines, "Keywords")
keywords <- if(!length(keywords)) {
## No old-style \VignetteKeywords entries found.
.get_vignette_metadata(lines, "Keyword")
} else unlist(strsplit(keywords[1L], ", *"))
## no point in recording the file path since this is called on
## package installation.
engine <- getVignetteEngine(lines=lines)
list(file = basename(file), title = title, depends = depends,
keywords = keywords, engine = engine)
}
## builds vignette indices from 'vigns', a pkgVignettes() result
.build_vignette_index <- function(vigns)
{
stopifnot(inherits(vigns, "pkgVignettes"))
files <- vigns$docs
names <- vigns$names
dir <- vigns$dir
sources <- vigns$sources
if(!dir.exists(dir))
stop(gettextf("directory '%s' does not exist", dir), domain = NA)
nvigns <- length(files)
if(nvigns == 0L) {
out <- data.frame(File = character(),
Title = character(),
PDF = character(),
R = character(),
stringsAsFactors = FALSE)
out$Depends <- list()
out$Keywords <- list()
return(out)
}
# Check for duplicated vignette names
if (any(dups <- duplicated(names))) {
dupname <- names[dups][1L]
dup <- basename(files[dups][1L])
orig <- basename(files[ names == dupname ][1L])
stop(gettextf("In '%s' vignettes '%s' and '%s' have the same vignette name",
basename(dirname(dir)), orig, dup),
domain = NA)
}
# Read vignette annotation from vignette source files
contents <- vector("list", length = nvigns * 5L)
dim(contents) <- c(nvigns, 5L)
for(i in seq_along(files))
contents[i, ] <- vignetteInfo(files[i])
colnames(contents) <- c("File", "Title", "Depends", "Keywords", "Engine")
## This is to cover a temporary package installation
## by 'R CMD build' (via 'R CMD INSTALL -l <lib>)
## which in case vignettes have not been built.
outputs <- vigns$outputs
outputs <- if(!is.null(outputs)) basename(outputs) else character(nvigns)
out <- data.frame(File = unlist(contents[, "File"]),
Title = unlist(contents[, "Title"]),
PDF = outputs, # Not necessarily PDF, but name it that for back compatibility
R = "", # May or may not be present
row.names = NULL, # avoid trying to compute row
# names
stringsAsFactors = FALSE)
# Optional
for (i in seq_along(sources))
if (length(s <- sources[[i]]))
out$R[which(names(sources)[i] == files)] <- basename(s[1L])
out$Depends <- contents[, "Depends"]
out$Keywords <- contents[, "Keywords"]
stopifnot(NROW(out) == nvigns)
out
}
### * .check_vignette_index
.check_vignette_index <-
function(vignetteDir, pkgdir = ".")
{
dir <- file.path(pkgdir, vignetteDir)
if(!dir.exists(dir))
stop(gettextf("directory '%s' does not exist", dir), domain = NA)
subdir <- gsub(pkgdir, "", dir, fixed=TRUE)
vigns <- pkgVignettes(dir = pkgdir, subdirs = subdir)
vignetteIndex <- .build_vignette_index(vigns)
badEntries <-
vignetteIndex[grep("^[[:space:]]*$", vignetteIndex[, "Title"]), "File"]
class(badEntries) <- "check_vignette_index"
badEntries
}
print.check_vignette_index <-
function(x, ...)
{
if(length(x)) {
writeLines(c("Vignettes with missing or empty \\VignetteIndexEntry:",
paste0(" ", basename(unclass(x)))))
}
invisible(x)
}
### * .writeVignetteHtmlIndex
## NB SamplerCompare has a .Rnw file which produces no R code.
.writeVignetteHtmlIndex <-
function(pkg, con, vignetteIndex = NULL)
{
## FIXME: in principle we could need to set an encoding here
html <- c(HTMLheader("Vignettes and other documentation"),
paste0("<h2>Vignettes from package '", pkg,"'</h2>"),
if(NROW(vignetteIndex) == 0L) ## NROW(NULL) = 0
"The package contains no vignette meta-information."
else {
vignetteIndex <- cbind(Package = pkg,
as.matrix(vignetteIndex[, c("File", "Title", "PDF", "R")]))
makeVignetteTable(vignetteIndex, depth = 3L)
})
otherfiles <- list.files(system.file("doc", package = pkg))
if(NROW(vignetteIndex))
otherfiles <- setdiff(otherfiles,
c(vignetteIndex[, c("PDF", "File", "R")], "index.html"))
if (length(otherfiles)) {
otherfiles <- ifelse(dir.exists(system.file(file.path("doc", otherfiles), package = pkg)),
paste0(otherfiles, "/"),
otherfiles)
urls <- paste0('<a href="', otherfiles, '">', otherfiles, '</a>')
html <- c(html, '<h2>Other files in the <span class="samp">doc</span> directory</h2>',
'<table width="100%">',
'<col style="width: 24%;" />',
'<col style="width: 50%;" />',
'<col style="width: 24%;" />',
paste0('<tr><td></td><td><span class="samp">',
iconv(urls, "", "UTF-8"), "</span></td></tr>"),
"</table>")
}
html <- c(html, "</body></html>")
writeLines(html, con=con)
}
getVigDepMtrx <-
function(vigDeps)
{
## Taken almost directly out of 'package.dependencies'
if (length(vigDeps)) {
z <- unlist(strsplit(vigDeps, ",", fixed=TRUE))
z <- sub("^[[:space:]]*(.*)", "\\1", z)
z <- sub("(.*)[[:space:]]*$", "\\1", z)
pat <- "^([^\\([:space:]]+)[[:space:]]*\\(([^\\)]+)\\).*"
depMtrx <- cbind(sub(pat, "\\1", z),
sub(pat, "\\2", z),
NA)
noversion <- depMtrx[, 1L] == depMtrx[, 2L]
depMtrx[noversion, 2L] <- NA
pat <- "[[:space:]]*([[<>=]+)[[:space:]]+(.*)"
depMtrx[!noversion, 2:3] <-
c(sub(pat, "\\1", depMtrx[!noversion, 2L]),
sub(pat, "\\2", depMtrx[!noversion, 2L]))
depMtrx
}
else
NA
}
### * .run_one_vignette
### helper for R CMD check
.run_one_vignette <-
function(vig_name, docDir, encoding = "", pkgdir)
{
## The idea about encodings here is that Stangle reads the
## file, converts on read and outputs in the current encoding.
## Then source() can assume the current encoding.
td <- tempfile()
dir.create(td)
file.copy(docDir, td, recursive = TRUE)
setwd(file.path(td, basename(docDir)))
subdir <- gsub(pkgdir, "", docDir, fixed=TRUE)
vigns <- pkgVignettes(dir=pkgdir, subdirs=subdir)
if (is.null(vigns)) {
cat("\n When running vignette ", sQuote(vig_name), ":\n", sep="")
stop("No vignettes available", call. = FALSE, domain = NA)
}
i <- which(basename(vigns$docs) == vig_name)
if (length(i) == 0L) {
cat("\n When running vignette ", sQuote(vig_name), ":\n", sep="")
stop("No such vignette ", sQuote(vig_name), call. = FALSE, domain = NA)
}
stopifnot(length(i) == 1L)
loadVignetteBuilder(pkgdir)
file <- basename(vigns$docs[i])
name <- vigns$names[i]
engine <- vignetteEngine(vigns$engines[i])
output <- tryCatch({
engine$tangle(file, quiet = TRUE, encoding = encoding)
find_vignette_product(name, by = "tangle", engine = engine)
}, error = function(e) {
cat("\n When tangling ", sQuote(file), ":\n", sep="")
stop(conditionMessage(e), call. = FALSE, domain = NA)
})
if(length(output) == 1L) {
tryCatch({
source(output, echo = TRUE)
}, error = function(e) {
cat("\n When sourcing ", sQuote(output), ":\n", sep="")
stop(conditionMessage(e), call. = FALSE, domain = NA)
})
}
cat("\n *** Run successfully completed ***\n")
}
vignetteEngine <- local({
registry <- new.env(parent = emptyenv())
engineKey <- function(name, package) {
key <- strsplit(name, split = "::", fixed = TRUE)[[1L]]
if (length(key) == 1L) {
if (missing(package))
stop("Vignette engine package not specified", call.=FALSE)
key[2L] <- key[1L]
key[1L] <- package
} else if (length(key) != 2L) {
stop("Unsupported engine name ", sQuote(name))
}
key
}
getEngine <- function(name, package) {
if (missing(name)) {
result <- as.list(registry)
if (length(result) > 0L && !is.null(package)) {
package <- unique(package)
pkgs <- sapply(result, function(engine) engine$package)
keep <- is.element(pkgs, package)
if (!any(keep)) {
stop(gettextf("None of packages %s have registered vignette engines",
paste(sQuote(package), collapse = ", ")),
domain = NA)
}
result <- result[keep]
pkgs <- pkgs[keep]
if (length(package) > 1L) {
result <- result[order(match(pkgs, package))]
}
}
} else {
result <- NULL
if (is.null(package)) {
if (name == "Sweave") {
key <- engineKey(name, package = "utils")
} else {
key <- engineKey(name)
}
suppressPackageStartupMessages(loadNamespace(key[1]))
name <- paste(key, collapse = "::")
result <- registry[[name]]
if (is.null(result))
stop(gettextf("Vignette engine %s is not registered",
sQuote(name)), domain = NA)
} else {
for (pkg in package) {
key <- engineKey(name, pkg)
try(suppressPackageStartupMessages(loadNamespace(key[1])),
silent = TRUE)
nameT <- paste(key, collapse = "::")
result <- registry[[nameT]]
if (!is.null(result))
break
}
if (is.null(result))
stop(gettextf("Vignette engine %s is not registered by any of the packages %s",
sQuote(name),
paste(sQuote(package), collapse = ", ")),
domain = NA)
}
if (!is.null(package) && !is.element(result$package, package))
stop(gettextf("Vignette engine %s is not registered by any of the packages %s",
sQuote(name),
paste(sQuote(package), collapse = ", ")),
domain = NA)
}
result
}
setEngine <- function(name, package, pattern, weave, tangle,
aspell = list()) {
key <- engineKey(name, package)
if (!is.null(package) && key[1L] != package)
stop(gettextf("Engine name %s and package %s do not match",
sQuote(name), sQuote(package)), domain = NA)
rname <- paste(key, collapse = "::")
if (is.null(weave)) {
result <- NULL
if (exists(rname, envir = registry))
rm(list = rname, envir = registry)
} else {
if (!is.function(weave) && is.na(weave)) {
if (missing(tangle))
tangle <- NA
} else {
if (!is.function(weave))
stop(gettextf("Argument %s must be a function and not %s",
sQuote("weave"), sQuote(class(weave)[1L])),
domain = NA)
if (!is.function(tangle))
stop(gettextf("Argument %s must be a function and not %s",
sQuote("tangle"), sQuote(class(tangle)[1L])),
domain = NA)
}
if (is.null(pattern))
pattern <- "[.][rRsS](nw|tex)$"
else if (!is.character(pattern))
stop(gettextf("Argument %s must be a character vector or NULL and not %s",
sQuote("pattern"), sQuote(class(pattern)[1L])),
domain = NA)
result <-
list(name = key[2L], package = key[1L], pattern = pattern,
weave = weave, tangle = tangle, aspell = aspell)
assign(rname, result, registry)
}
result
}
setEngine(name = "Sweave", package = "utils", pattern = NULL,
weave = function(...) utils::Sweave(...),
tangle = function(...) utils::Stangle(...),
aspell = list(filter = "Sweave", control = "-t"))
function(name, weave, tangle, pattern = NULL, package = NULL,
aspell = list()) {
if (missing(weave)) { # we're getting the engine
getEngine(name, package)
} else { # we're setting a new engine
if (is.element(name, c("Sweave", "utils::Sweave"))) {
stop(gettextf("Cannot change the %s engine or use an engine of that name",
sQuote("Sweave")), domain = NA)
}
if (missing(package))
package <- utils::packageName(parent.frame())
result <-
setEngine(name, package, pattern = pattern,
weave = weave, tangle = tangle, aspell = aspell)
invisible(result)
}
}
})
loadVignetteBuilder <-
function(pkgdir, mustwork = TRUE)
{
pkgs <- .get_package_metadata(pkgdir)["VignetteBuilder"]
if (is.na(pkgs))
pkgs <- NULL
else if (length(pkgs)) {
pkgs <- unlist(strsplit(pkgs, ","))
pkgs <- gsub('[[:space:]]', '', pkgs)
}
pkgs <- unique(c(pkgs, "utils"))
for (pkg in pkgs) {
res <- tryCatch(suppressPackageStartupMessages(loadNamespace(pkg)),
error = identity)
if (mustwork && inherits(res, "error"))
stop(gettextf("vignette builder '%s' not found", pkg), domain = NA)
}
pkgs
}
# This gets the info for installed packages
getVignetteInfo <- function(package = NULL, lib.loc = NULL, all = TRUE)
{
paths <-
if (is.null(package)) {
package <- .packages(all.available = all, lib.loc)
## allow for misnamed dirs
find.package(package, lib.loc, quiet = TRUE)
} else
find.package(package, lib.loc)
## Find the directories with a 'doc' subdirectory *possibly*
## containing vignettes.
paths <- paths[dir.exists(file.path(paths, "doc"))]
empty <- cbind(Package = character(0),
Dir = character(0),
Topic = character(0),
File = character(0),
Title = character(0),
R = character(0),
PDF = character(0))
getVinfo <- function(dir) {
entries <- NULL
if (file.exists(INDEX <- file.path(dir, "Meta", "vignette.rds")))
entries <- readRDS(INDEX)
if (NROW(entries) > 0) {
# FIXME: this test is unnecessary?
R <- if (is.null(entries$R)) rep.int("", NROW(entries)) else entries$R
file <- basename(entries$File)
pdf <- entries$PDF
topic <- file_path_sans_ext(ifelse(R == "", ifelse(pdf == "", file, pdf), R))
cbind(Package = basename(dir),
Dir = dir,
Topic = topic,
File = file,
Title = entries$Title,
R = R,
PDF = pdf)[order(entries$Title), , drop=FALSE]
}
else empty
}
if (length(paths))
do.call(rbind, lapply(paths, getVinfo))
else
empty
}
### Local variables: ***
### mode: outline-minor ***
### outline-regexp: "### [*]+" ***
### End: ***