Repository: CRAN
Date/Publication: 2024-05-06 17:30:02 UTC
tinytex/tests/ 0000755 0001762 0000144 00000000000 14477353116 013133 5 ustar ligges users tinytex/tests/test-travis.R 0000644 0001762 0000144 00000000243 14017545604 015535 0 ustar ligges users # run tests on Travis (these tests depend on TeX Live)
if (!is.na(Sys.getenv('CI', NA)) && tinytex:::tlmgr_available()) testit::test_pkg('tinytex', 'test-travis')
tinytex/tests/test-cran.R 0000644 0001762 0000144 00000000051 13211546267 015146 0 ustar ligges users testit::test_pkg('tinytex', 'test-cran')
tinytex/tests/test-travis/ 0000755 0001762 0000144 00000000000 14616211632 015407 5 ustar ligges users tinytex/tests/test-travis/test-tlmgr.R 0000644 0001762 0000144 00000002255 14124474270 017643 0 ustar ligges users library(testit)
assert('tlmgr is available', {
tlmgr_available()
})
assert('tlmgr_search() searches the online TeX Live database', {
res = tlmgr_search('/framed', stdout = TRUE)
('framed:' %in% res)
(any(grepl('/framed[.]sty$', res)))
})
assert('`tlmgr info` can list the installed packages', {
res = tlmgr(c('info', '--list', '--only-installed', '--data', 'name'), stdout = TRUE)
# only check a few basic packages
(c('xetex', 'luatex', 'graphics') %in% res)
})
assert('`tl_size()` can correctly list name and size', {
res = tl_sizes(pkgs = "luatex")$package
# only check a few basic packages
('luatex' %==% tl_sizes(pkgs = "luatex")$package)
})
assert('fonts package are correctly identified', {
p_q = function(...) parse_packages(..., quiet = c(TRUE, TRUE, TRUE))
(p_q(text = "! Font U/psy/m/n/10=psyr at 10.0pt not loadable: Metric (TFM) file not found") %==% 'symbol')
(p_q(text = '! The font "FandolSong-Regular" cannot be found.') %==% 'fandol')
(p_q(text = '!pdfTeX error: /usr/local/bin/pdflatex (file tcrm0700): Font tcrm0700 at 600 not found') %==% 'ec')
(p_q(text = '! Package fontspec Error: The font "Caladea" cannot be found.') %==% 'caladea')
})
tinytex/tests/test-travis/test-install.R 0000644 0001762 0000144 00000000571 14153473103 020157 0 ustar ligges users library(testit)
assert('normalize_repo() creates a valid url', {
(normalize_repo('https://ctan.math.illinois.edu/') %==%
'https://ctan.math.illinois.edu/systems/texlive/tlnet')
(normalize_repo('https://ftp.tu-chemnitz.de/pub/tug/historic/systems/texlive/2020/tlnet-final/') %==%
'https://ftp.tu-chemnitz.de/pub/tug/historic/systems/texlive/2020/tlnet-final')
})
tinytex/tests/test-cran/ 0000755 0001762 0000144 00000000000 14616211632 015022 5 ustar ligges users tinytex/tests/test-cran/test-latex.R 0000644 0001762 0000144 00000003370 14207444233 017243 0 ustar ligges users library(testit)
assert('detect_files() can detect filenames from LaTeX log', {
# Fonts are tested in test-tlmgr.R also
(detect_files("! Font U/psy/m/n/10=psyr at 10.0pt not loadable: Metric (TFM) file not found") %==% font_ext("psyr"))
(detect_files('! The font "FandolSong-Regular" cannot be found.') %==% font_ext("FandolSong-Regular"))
(detect_files('!pdfTeX error: /usr/local/bin/pdflatex (file tcrm0700): Font tcrm0700 at 600 not found') %==% font_ext('tcrm0700'))
(length(detect_files("asdf qwer")) == 0)
(detect_files("! LaTeX Error: File `framed.sty' not found.") %==% 'framed.sty')
(detect_files("/usr/local/bin/mktexpk: line 123: mf: command not found") %==% 'mf')
(detect_files("or the language definition file ngerman.ldf was not found") %==% 'ngerman.ldf')
(detect_files("!pdfTeX error: pdflatex (file 8r.enc): cannot open encoding file for reading") %==% '8r.enc')
(detect_files("! CTeX fontset `fandol' is unavailable in current mode") %==% 'fandol')
(detect_files('Package widetext error: Install the flushend package which is a part of sttools') %==% 'flushend.sty')
(detect_files('! Package isodate.sty Error: Package file substr.sty not found.') %==% 'substr.sty')
(detect_files("! Package fontenc Error: Encoding file `t2aenc.def' not found.") %==% 't2aenc.def')
(detect_files("! I can't find file `hyph-de-1901.ec.tex'.") %==% 'hyph-de-1901.ec.tex')
(detect_files("luaotfload-features.lua:835: module 'lua-uni-normalize' not found:") %==% 'lua-uni-algos.lua')
})
assert('rerun are correctly detected', {
(needs_rerun(text = "Package biblatex Warning: Please rerun LaTeX."))
(needs_rerun(text = "Please (re)run the file"))
(needs_rerun(text = "error: Rerun LaTeX."))
(needs_rerun(text = "Rerun to get the final file"))
})
tinytex/R/ 0000755 0001762 0000144 00000000000 14574207444 012172 5 ustar ligges users tinytex/R/utils.R 0000644 0001762 0000144 00000002111 14520055437 013442 0 ustar ligges users # for xfun::session_info('tinytex')
xfun_session_info = function() {
try_null = function(...) tryCatch(..., error = function(e) NULL)
version_info = function(cmd) try_null(system2(cmd, '--version', stdout = TRUE))
tweak_path()
pdftex_info = version_info('pdflatex')[1]
info = if (is.null(pdftex_info)) {
version_info('tectonic')[1] # try tectonic engine?
} else if (grepl('TeX Live', pdftex_info, fixed = TRUE)) {
# we get more information on tlmgr in that case
try_null(tlmgr_version('string'))
} else {
# for other distributions, e.g., MiKTeX-pdfTeX 4.8 (MiKTeX 21.8)
xfun::grep_sub('^.*\\((.*)\\)$', '\\1', pdftex_info)
}
if (!length(info)) return(invisible(NULL))
paste(c('LaTeX version used: ', paste0(' ', info)), collapse = '\n')
}
# read a file without warning, and discard lines with invalid characters to
# avoid warnings in the grep() family (invalid lines in log files should be safe
# to discard in this package, although it isn't so in general)
read_lines = function(...) {
x = readLines(..., warn = FALSE)
x[!validEnc(x)] = ''
x
}
tinytex/R/install.R 0000644 0001762 0000144 00000060573 14574207444 013776 0 ustar ligges users #' Install/Uninstall TinyTeX
#'
#' The function \code{install_tinytex()} downloads and installs TinyTeX, a
#' custom LaTeX distribution based on TeX Live. The function
#' \code{uninstall_tinytex()} removes TinyTeX; \code{reinstall_tinytex()}
#' reinstalls TinyTeX as well as previously installed LaTeX packages by default;
#' \code{tinytex_root()} returns the root directory of TinyTeX if found.
#' @param force Whether to force to install or uninstall TinyTeX. For
#' \code{install_tinytex()}, \code{force = FALSE} will stop this function from
#' installing TinyTeX if another LaTeX distribution is detected, or the
#' directory specified via the \code{dir} argument exists.
#' @param dir The directory to install (should not exist unless \code{force =
#' TRUE}) or uninstall TinyTeX.
#' @param version The version of TinyTeX, e.g., \code{"2020.09"} (see all
#' available versions at \url{https://github.com/rstudio/tinytex-releases}, or
#' via \code{xfun::github_releases('rstudio/tinytex-releases')}). By default,
#' it installs the latest daily build of TinyTeX. If \code{version =
#' 'latest'}, it installs the latest monthly Github release of TinyTeX.
#' @param bundle The bundle name of TinyTeX (which determines the collection of
#' LaTeX packages to install). See
#' \url{https://github.com/rstudio/tinytex-releases#releases} for all possible
#' bundles and their meanings.
#' @param repository The CTAN repository to set. By default, it is the
#' repository automatically chosen by \code{https://mirror.ctan.org} (which is
#' usually the fastest one to your location). You can find available
#' repositories at \code{https://ctan.org/mirrors}), e.g.,
#' \code{'http://mirrors.tuna.tsinghua.edu.cn/CTAN/'}, or
#' \code{'https://mirror.las.iastate.edu/tex-archive/'}. In theory, this
#' argument should end with the path \file{/systems/texlive/tlnet}, and if it
#' does not, the path will be automatically appended. You can get a full list
#' of CTAN mirrors via \code{tinytex:::ctan_mirrors()}.
#' @param extra_packages A character vector of extra LaTeX packages to be
#' installed. By default, a vector of all currently installed LaTeX packages
#' if an existing installation of TinyTeX is found. If you want a fresh
#' installation, you may use \code{extra_packages = NULL}.
#' @param add_path Whether to run the command \command{tlmgr path add} to add
#' the bin path of TeX Live to the system environment variable \var{PATH}.
#' @references See the TinyTeX documentation (\url{https://yihui.org/tinytex/})
#' for the default installation directories on different platforms.
#' @note If you really want to disable the installation, you may set the
#' environment variable \var{TINYTEX_PREVENT_INSTALL} to \code{true}. Then
#' \code{install_tinytex()} will fail immediately. This can be useful to
#' sysadmins who want to prevent the accidental installation of TinyTeX.
#'
#' Installing TinyTeX requires perl (on Linux, perl-base is insufficient).
#' @export
install_tinytex = function(
force = FALSE, dir = 'auto', version = 'daily', bundle = 'TinyTeX-1', repository = 'auto',
extra_packages = if (is_tinytex()) tl_pkgs(), add_path = TRUE
) {
if (tolower(Sys.getenv('TINYTEX_PREVENT_INSTALL')) == 'true') stop(
"The environment variable 'TINYTEX_PREVENT_INSTALL' was set to 'true', so ",
"the installation is aborted."
)
if (!is.logical(force)) stop('The argument "force" must take a logical value.')
continue_inst = function() {
tolower(substr(readline('Continue the installation anyway? (Y/N) '), 1, 1)) == 'y'
}
# if tlmgr is detected in the system, ask in interactive mode whether to
# continue the installation, and stop in non-interactive() mode
p = which_bin(c('tlmgr', 'pdftex', 'xetex', 'luatex'))
p = p[p != '']
if (!force && length(p)) {
message("Found '", p[1], "', which indicates a LaTeX distribution may have existed in the system.")
if (interactive()) {
if (!continue_inst()) return(invisible(''))
} else stop(
'If you want to force installing TinyTeX anyway, use tinytex::install_tinytex(force = TRUE).'
)
}
force(extra_packages) # evaluate it before TinyTeX is removed or reinstalled next
check_dir = function(dir) {
if (dir_exists(dir) && !force) stop(
'The directory "', dir, '" exists. Please either delete it, ',
'or use tinytex::install_tinytex(force = TRUE).'
)
}
if (missing(dir)) dir = ''
user_dir = ''
if (dir != '') {
dir = gsub('[/\\]+$', '', dir) # remove trailing slashes
check_dir(dir)
dir = xfun::normalize_path(dir)
if (is_windows() && !valid_path(dir)) {
warning(
"The directory path '", dir, "' contains spaces or non-ASCII characters, ",
"and TinyTeX may not work. Please use a path with pure ASCII characters and no spaces.",
immediate. = TRUE
)
if (!force && !(interactive() && continue_inst())) return(invisible(dir))
}
unlink(dir, recursive = TRUE)
user_dir = dir
}
repository = normalize_repo(repository)
not_ctan = repository != 'ctan'
https = grepl('^https://', repository)
if (!grepl('TinyTeX', bundle)) message(
"The bundle name '", bundle, "' has been automatically corrected to '",
bundle <- gsub('tinytex', 'TinyTeX', bundle, ignore.case = TRUE), "'."
)
owd = setwd(tempdir()); on.exit(setwd(owd), add = TRUE)
if ((texinput <- Sys.getenv('TEXINPUT')) != '') message(
'Your environment variable TEXINPUT is "', texinput,
'". Normally you should not set this variable, because it may lead to issues like ',
'https://github.com/rstudio/tinytex/issues/92.'
)
switch(
os,
'unix' = {
check_local_bin()
if (os_index != 3 && !any(dir_exists(c('~/bin', '~/.local/bin')))) on.exit(message(
'You may have to restart your system after installing TinyTeX to make sure ',
'~/bin appears in your PATH variable (https://github.com/rstudio/tinytex/issues/16).'
), add = TRUE)
},
'windows' = {},
stop('Sorry, but tinytex::install_tinytex() does not support this platform: ', os)
)
src_install = getOption('tinytex.source.install', need_source_install())
# if needs to install from source, set `extra_packages` according to `bundle`
if (src_install && missing(extra_packages)) {
extra_packages = switch(
bundle,
'TinyTeX-2' = 'scheme-full',
'TinyTeX' = read_lines('https://yihui.org/gh/tinytex/tools/pkgs-custom.txt'),
'TinyTeX-0' = {
warning("bundle = 'TinyTeX-0' is not supported for your system"); NULL
}
)
}
install = function(...) {
if (src_install) {
install_tinytex_source(repository, ...)
} else {
install_prebuilt(bundle, ..., repo = repository)
}
}
if (version == 'daily') {
version = ''
# test if https://yihui.org or github.com is accessible because the daily
# version is downloaded from there
determine_version = function() {
if (xfun::url_accessible('https://yihui.org')) return('')
if (xfun::url_accessible('https://github.com')) return('daily-github')
warning(
"The daily version of TinyTeX does not appear to be accessible. ",
"Switching to version = 'latest' instead. If you are sure to install ",
"the daily version, call tinytex::install_tinytex(version = 'daily') ",
"(which may fail)."
)
'latest'
}
if (missing(version) && !src_install) version = determine_version()
}
user_dir = install(user_dir, version, add_path, extra_packages)
opts = options(tinytex.tlmgr.path = find_tlmgr(user_dir))
on.exit(options(opts), add = TRUE)
if (not_ctan) {
# install tlgpg for Windows and macOS users if an HTTPS repo is preferred
if (os_index %in% c(1, 3) && https) {
tlmgr(c('--repository', 'http://www.preining.info/tlgpg/', 'install', 'tlgpg'))
}
tlmgr_repo(repository)
if (tlmgr(c('update', '--list')) != 0) {
warning('The repository ', repository, ' does not seem to be accessible. Reverting to the default CTAN mirror.')
tlmgr(c('option', 'repository', 'ctan'))
}
}
invisible(user_dir)
}
# TinyTeX has to be installed from source for OSes that are not Linux or
# non-x86_64 Linux machines
need_source_install = function() {
os_index == 0 || (os_index == 2 && !identical(Sys.info()[['machine']], 'x86_64'))
}
# append /systems/texlive/tlnet to the repo url if necessary
normalize_repo = function(url) {
# don't normalize the url if users passes I(url) or 'ctan' or NULL
if (is.null(url) || url == 'ctan' || inherits(url, 'AsIs')) return(url)
if (url == 'auto') return(auto_repo())
if (url == 'illinois') return('https://ctan.math.illinois.edu/systems/texlive/tlnet')
url = sub('/+$', '', url)
if (!grepl('/tlnet$', url)) {
url2 = paste0(url, '/systems/texlive/tlnet')
# return the amended url if it works
if (xfun::url_accessible(url2)) return(url2)
}
url
}
# get the automatic CTAN mirror returned from mirror.ctan.org
auto_repo = function() {
# curlGetHeaders() may time out, hence tryCatch() here
x = tryCatch(
curlGetHeaders('https://mirror.ctan.org/systems/texlive/tlnet'),
error = function(e) character()
)
x = xfun::grep_sub('^location: (https://[^[:space:]]+)\\s*$', '\\1', x, ignore.case = TRUE)
x = tail(x, 1)
if (length(x) == 1) x else 'ctan'
}
# retrieve all CTAN (https) mirrors
ctan_mirrors = function() {
x = readLines('https://ctan.org/mirrors/mirmon')
u = xfun::grep_sub('.* 0) {
dir.create(texmf_tmp <- tempfile(), recursive = TRUE)
message(
'The directory ', texmf, ' is not empty. It will be backed up to ',
texmf_tmp, ' and restored later.\n'
)
file.copy(texmf, texmf_tmp, recursive = TRUE)
on.exit(
file.copy(file.path(texmf_tmp, basename(texmf)), dirname(texmf), recursive = TRUE),
add = TRUE
)
}
uninstall_tinytex()
install_tinytex(extra_packages = pkgs, dir = dir, ...)
}
#' @param error Whether to signal an error if TinyTeX is not found.
#' @rdname install_tinytex
#' @export
tinytex_root = function(error = TRUE) {
path = which_bin('tlmgr')
if (path == '') return('')
root_dir = function(path, ...) {
dir = normalizePath(file.path(dirname(path), ...), mustWork = TRUE)
if (!'bin' %in% list.files(dir)) if (error) stop(
dir, ' does not seem to be the root directory of TeX Live (no "bin/" dir under it)'
) else return('')
dir
}
if (os == 'windows') return(root_dir(path, '..', '..'))
if (Sys.readlink(path) == '') if (error) stop(
'Cannot figure out the root directory of TeX Live from ', path,
' (not a symlink on ', os, ')'
) else return('')
path = symlink_root(path)
root_dir(normalizePath(path), '..', '..', '..')
}
# return paths to TinyTeX's executables even if TinyTeX was not added to PATH
which_bin = function(exec) {
tweak_path()
Sys.which(exec)
}
# trace a symlink to its final destination
symlink_root = function(path) {
path = normalizePath(path, mustWork = TRUE)
path2 = Sys.readlink(path)
if (path2 == '') return(path) # no longer a symlink; must be resolved now
# path2 may still be a _relative_ symlink
in_dir(dirname(path), symlink_root(path2))
}
# a helper function to open tlmgr.pl (on *nix)
open_tlmgr = function() {
file.edit(symlink_root(Sys.which('tlmgr')))
}
#' Check if the LaTeX installation is TinyTeX
#'
#' First find the root directory of the installation via
#' \code{\link{tinytex_root}()}. Then check if the directory name is
#' \code{"tinytex"} (case-insensitive). If not, further check if the first line
#' of the file \file{texmf-dist/web2c/fmtutil.cnf} under the directory contains
#' \code{"TinyTeX"} or \code{".TinyTeX"}. If the binary version of TinyTeX was
#' installed, \file{fmtutil.cnf} should contain a line like \samp{Generated by
#' */TinyTeX/bin/x86_64-darwin/tlmgr on Thu Sep 17 07:13:28 2020}.
#' @return A logical value indicating if the LaTeX installation is TinyTeX.
#' @export
#' @examples tinytex::is_tinytex()
is_tinytex = function() tryCatch({
root = tinytex_root()
root != '' && (
grepl('^[.]?tinytex$', tolower(basename(root))) ||
file.exists(file.path(root, '.tinytex'))
)
}, error = function(e) FALSE)
dir_rename = function(from, to) {
# cannot rename '/foo' to '/bar' because of 'Invalid cross-device link'
suppressWarnings(file.rename(from, to)) || dir_copy(from, to)
}
dir_copy = function(from, to) {
dir.create(to, showWarnings = FALSE, recursive = TRUE)
all(file.copy(list.files(from, full.names = TRUE), to, recursive = TRUE)) &&
unlink(from, recursive = TRUE) == 0
}
# LaTeX packages that I use
install_yihui_pkgs = function() {
pkgs = read_lines('https://yihui.org/gh/tinytex/tools/pkgs-yihui.txt')
tlmgr_install(pkgs)
}
# install a prebuilt version of TinyTeX
install_prebuilt = function(
pkg = '', dir = '', version = '', add_path = TRUE, extra_packages = NULL,
repo = 'ctan', hash = FALSE, cache = NA
) {
if (need_source_install()) stop(
'There is no prebuilt version of TinyTeX for this platform: ',
paste(Sys.info()[c('sysname', 'machine')], collapse = ' '), '.'
)
dir0 = default_inst(); b = basename(dir0)
dir1 = xfun::normalize_path(dir) # expected installation dir
if (dir1 == '') dir1 = dir0
# the archive is extracted to this target dir
target = dirname(dir1)
dir2 = file.path(target, b) # path to (.)TinyTeX/ after extraction
if (xfun::file_ext(pkg) == '') {
if (version == 'latest') {
version = xfun::github_releases('rstudio/tinytex-releases', version)
} else if (version == 'daily-github') {
version = ''
opts = options(tinytex.install.url = 'https://github.com/rstudio/tinytex-releases/releases/download/daily/')
on.exit(options(opts), add = TRUE)
}
version = gsub('^v', '', version)
installer = if (pkg == '') 'TinyTeX' else pkg
# e.g., TinyTeX-0.zip, TinyTeX-1-v2020.10.tar.gz, ...
pkg = paste0(
installer, if (version != '') paste0('-v', version), '.',
c('zip', 'tar.gz', 'tgz')[os_index]
)
# Full scheme is bundled as a self extracting archive on Windows
if (os_index == 1 && installer == 'TinyTeX-2') pkg = xfun::with_ext(pkg, "exe")
if (file.exists(pkg) && is.na(cache)) {
# invalidate cache (if unspecified) when the installer is more than one day old
if (as.numeric(difftime(Sys.time(), file.mtime(pkg), units = 'days')) > 1)
cache = FALSE
}
if (identical(cache, FALSE)) {
file.remove(pkg); on.exit(file.remove(pkg), add = TRUE)
}
if (!file.exists(pkg)) download_installer(pkg, version)
}
pkg = path.expand(pkg)
# installation dir shouldn't be a file but a directory
file.remove(exist_files(c(dir1, dir2)))
if (grepl('[.]exe$', pkg)) {
system2(pkg, args = c('-y', paste0('-o', path.expand(target))))
} else {
extract = if (grepl('[.]zip$', pkg)) unzip else untar
extract(pkg, exdir = path.expand(target))
}
# TinyTeX (or .TinyTeX) is extracted to the parent dir of `dir`; may need to rename
if (dir != '') {
if (basename(dir1) != b) file.rename(dir2, dir1)
opts = options(tinytex.tlmgr.path = find_tlmgr(dir1))
on.exit(options(opts), add = TRUE)
}
post_install_config(add_path, extra_packages, repo, hash)
invisible(dir1)
}
# post-install configurations
post_install_config = function(add_path = TRUE, extra_packages = NULL, repo = 'ctan', hash = FALSE) {
if (os_index == 2) {
if (!dir_exists(bin_dir <- '~/.local/bin')) dir.create(bin_dir <- '~/bin', FALSE, TRUE)
tlmgr(c('option', 'sys_bin', bin_dir))
}
# fix fonts.conf: https://github.com/rstudio/tinytex/issues/313
tlmgr(c('postaction', 'install', 'script', 'xetex'), .quiet = TRUE)
# do not wrap lines in latex log (#322)
tlmgr_conf(c('texmf', 'max_print_line', '10000'), .quiet = TRUE, stdout = FALSE)
if (add_path) tlmgr_path()
r_texmf(.quiet = TRUE)
# don't use the default random ctan mirror when installing on CI servers
if (repo != 'ctan' || tolower(Sys.getenv('CI')) != 'true')
tlmgr_repo(repo, stdout = FALSE, .quiet = TRUE)
tlmgr_install(setdiff(extra_packages, tl_pkgs()))
if (hash) {
texhash(); fmtutil(stdout = FALSE); updmap(); fc_cache()
}
}
download_installer = function(file, version) {
url = if (version != '') sprintf(
'https://github.com/rstudio/tinytex-releases/releases/download/v%s/%s', version, file
) else paste0(getOption('tinytex.install.url', 'https://yihui.org/tinytex/'), file)
download_file(url, file, mode = 'wb')
}
#' Copy TinyTeX to another location and use it in another system
#'
#' The function \code{copy_tinytex()} copies the existing TinyTeX installation
#' to another directory (e.g., a portable device like a USB stick). The function
#' \code{use_tinytex()} runs \command{tlmgr path add} to add the copy of TinyTeX
#' in an existing folder to the \code{PATH} variable of the current system, so
#' that you can use utilities such as \command{tlmgr} and \command{pdflatex},
#' etc.
#' @param from The root directory of the TinyTeX installation. For
#' \code{copy_tinytex()}, the default value \code{tinytex_root()} should be a
#' reasonable guess if you installed TinyTeX via \code{install_tinytex()}. For
#' \code{use_tinytex()}, if \code{from} is not provided, a dialog for choosing
#' the directory interactively will pop up.
#' @param to The destination directory where you want to make a copy of TinyTeX.
#' Like \code{from} in \code{use_tinytex()}, a dialog will pop up if \code{to}
#' is not provided in \code{copy_tinytex()}.
#' @param move Whether to use the new copy and delete the original copy of
#' TinyTeX after copying it.
#' @note You can only copy TinyTeX and use it in the same system, e.g., the
#' Windows version of TinyTeX only works on Windows.
#' @export
copy_tinytex = function(
from = tinytex_root(), to = select_dir('Select Destination Directory'), move = FALSE
) {
op = options(tinytex.warn.appdata = FALSE); on.exit(options(op), add = TRUE)
if (!dir_exists(from)) stop('TinyTeX does not seem to be installed.')
if (length(to) != 1 || !dir_exists(to))
stop("The destination directory '", to, "' does not exist.")
target = file.path(to, basename(from))
if (!move || !{tlmgr_path('remove'); res <- file.rename(from, target)}) {
res = file.copy(from, to, recursive = TRUE)
if (res && move) {
tlmgr_path('remove')
unlink(from, recursive = TRUE)
}
}
if (res && move) use_tinytex(target)
res
}
#' @rdname copy_tinytex
#' @export
use_tinytex = function(from = select_dir('Select TinyTeX Directory')) {
if (length(from) != 1) stop('Please provide a valid path to the TinyTeX directory.')
d = list.files(file.path(from, 'bin'), full.names = TRUE)
d = d[dir_exists(d)]
if (length(d) != 1) stop("The directory '", from, "' does not contain TinyTeX.")
p = file.path(d, 'tlmgr')
if (os == 'windows') p = paste0(p, '.bat')
if (system2(p, c('path', 'add')) != 0) stop(
"Failed to add '", d, "' to your system's environment variable PATH. You may ",
"consider the fallback approach, i.e., set options(tinytex.tlmgr.path = '", p, "')."
)
op = options(tinytex.tlmgr.path = p); on.exit(options(op), add = TRUE)
post_install_config(FALSE)
message('Restart R and your editor and check if tinytex::tinytex_root() points to ', from)
}
select_dir = function(caption = 'Select Directory') {
d = tryCatch(rstudioapi::selectDirectory(caption), error = function(e) {
if (os == 'windows') utils::choose.dir(caption = caption) else {
tcltk::tk_choose.dir(caption = caption)
}
})
if (!is.null(d) && !is.na(d)) d
}
tinytex/R/latex.R 0000644 0001762 0000144 00000067733 14526723450 013447 0 ustar ligges users #' Compile a LaTeX document
#'
#' The function \code{latexmk()} emulates the system command \command{latexmk}
#' (\url{https://ctan.org/pkg/latexmk}) to compile a LaTeX document. The
#' functions \code{pdflatex()}, \code{xelatex()}, and \code{lualatex()} are
#' wrappers of \code{latexmk(engine =, emulation = TRUE)}.
#'
#' The \command{latexmk} emulation works like this: run the LaTeX engine once
#' (e.g., \command{pdflatex}), run \command{makeindex} to make the index if
#' necessary (the \file{*.idx} file exists), run the bibliography engine
#' \command{bibtex} or \command{biber} to make the bibliography if necessary
#' (the \file{*.aux} or \file{*.bcf} file exists), and finally run the LaTeX
#' engine a number of times (the maximum is 10 by default) to resolve all
#' cross-references.
#'
#' By default, LaTeX warnings will be converted to R warnings. To suppress these
#' warnings, set \code{options(tinytex.latexmk.warning = FALSE)}.
#'
#' If \code{emulation = FALSE}, you need to make sure the executable
#' \command{latexmk} is available in your system, otherwise \code{latexmk()}
#' will fall back to \code{emulation = TRUE}. You can set the global option
#' \code{options(tinytex.latexmk.emulation = FALSE)} to always avoid emulation
#' (i.e., always use the executable \command{latexmk}).
#'
#' The default command to generate the index (if necessary) is
#' \command{makeindex}. To change it to a different command (e.g.,
#' \command{zhmakeindex}), you may set the global option
#' \code{tinytex.makeindex}. To pass additional command-line arguments to the
#' command, you may set the global option \code{tinytex.makeindex.args} (e.g.,
#' \code{options(tinytex.makeindex = 'zhmakeindex', tinytex.makeindex.args =
#' c('-z', 'pinyin'))}).
#'
#' If you are using the LaTeX distribution TinyTeX, but its path is not in the
#' \code{PATH} variable of your operating system, you may set the global option
#' \code{tinytex.tlmgr.path} to the full path of the executable \command{tlmgr},
#' so that \code{latexmk()} knows where to find executables like
#' \command{pdflatex}. For example, if you are using Windows and your TinyTeX is
#' on an external drive \file{Z:/} under the folder \file{TinyTeX}, you may set
#' \code{options(tinytex.tlmgr.path = "Z:/TinyTeX/bin/windows/tlmgr.bat")}.
#' Usually you should not need to set this option because TinyTeX can add itself
#' to the \code{PATH} variable during installation or via
#' \code{\link{use_tinytex}()}. In case both methods fail, you can use this
#' manual approach.
#' @param file A LaTeX file path.
#' @param engine A LaTeX engine (can be set in the global option
#' \code{tinytex.engine}, e.g., \code{options(tinytex.engine = 'xelatex')}).
#' @param bib_engine A bibliography engine (can be set in the global option
#' \code{tinytex.bib_engine}).
#' @param engine_args Command-line arguments to be passed to \code{engine} (can
#' be set in the global option \code{tinytex.engine_args}, e.g.,
#' \code{options(tinytex.engine_args = '-shell-escape'}).
#' @param emulation Whether to emulate the executable \command{latexmk} using R.
#' Note that this is unused when \code{engine == 'tectonic'}.
#' @param min_times,max_times The minimum and maximum number of times to rerun
#' the LaTeX engine when using emulation. You can set the global options
#' \code{tinytex.compile.min_times} or \code{tinytex.compile.max_times}, e.g.,
#' \code{options(tinytex.compile.max_times = 3)}.
#' @param install_packages Whether to automatically install missing LaTeX
#' packages found by \code{\link{parse_packages}()} from the LaTeX log. This
#' argument is only for the emulation mode and TeX Live. Its value can also be
#' set via the global option \code{tinytex.install_packages}, e.g.,
#' \code{options(tinytex.install_packages = FALSE)}.
#' @param pdf_file Path to the PDF output file. By default, it is under the same
#' directory as the input \code{file} and also has the same base name. When
#' \code{engine == 'latex'}, this will be a DVI file.
#' @param clean Whether to clean up auxiliary files after compilation (can be
#' set in the global option \code{tinytex.clean}, which defaults to
#' \code{TRUE}).
#' @export
#' @return A character string of the path of the output file (i.e., the value of
#' the \code{pdf_file} argument).
latexmk = function(
file, engine = c('pdflatex', 'xelatex', 'lualatex', 'latex', 'tectonic'),
bib_engine = c('bibtex', 'biber'), engine_args = NULL, emulation = TRUE,
min_times = 1, max_times = 10, install_packages = emulation && tlmgr_writable(),
pdf_file = gsub('tex$', 'pdf', file), clean = TRUE
) {
if (!grepl('[.]tex$', file))
stop("The input file '", file, "' does not have the .tex extension")
file = path.expand(file)
if (missing(engine)) engine = getOption('tinytex.engine', engine)
engine = gsub('^(pdf|xe|lua)(tex)$', '\\1la\\2', engine) # normalize *tex to *latex
engine = match.arg(engine)
is_latex = engine == 'latex'
tweak_path()
if (missing(emulation)) emulation = getOption('tinytex.latexmk.emulation', emulation)
if (!emulation) {
if (Sys.which('latexmk') == '') {
warning('The executable "latexmk" not found in your system')
emulation = TRUE
} else if (system2_quiet('latexmk', '-v') != 0) {
warning('The executable "latexmk" was found but does not work')
emulation = TRUE
}
}
if (missing(min_times)) min_times = getOption('tinytex.compile.min_times', min_times)
if (missing(max_times)) max_times = getOption('tinytex.compile.max_times', max_times)
if (missing(install_packages))
install_packages = getOption('tinytex.install_packages', install_packages)
if (missing(bib_engine)) bib_engine = getOption('tinytex.bib_engine', bib_engine)
if (missing(engine_args)) engine_args = getOption('tinytex.engine_args', engine_args)
if (missing(clean)) clean = getOption('tinytex.clean', TRUE)
pdf = gsub('tex$', if (is_latex) 'dvi' else 'pdf', basename(file))
if (!is.null(output_dir <- getOption('tinytex.output_dir'))) {
output_dir_arg = shQuote(paste0(if (emulation) '-', '-output-directory=', output_dir))
if (length(grep(output_dir_arg, engine_args, fixed = TRUE)) == 0) stop(
"When you set the global option 'tinytex.output_dir', the argument 'engine_args' ",
"must contain this value: ", capture.output(dput(output_dir_arg))
)
pdf = file.path(output_dir, pdf)
if (missing(pdf_file)) pdf_file = file.path(output_dir, basename(pdf_file))
}
if (is_latex) pdf_file = with_ext(pdf_file, 'dvi')
check_pdf = function() {
if (!file.exists(pdf)) show_latex_error(file, with_ext(pdf, 'log'), TRUE)
file_rename(pdf, pdf_file)
pdf_file
}
if (engine == 'tectonic') {
system2_quiet('tectonic', c(engine_args, shQuote(file)))
return(check_pdf())
}
if (emulation) {
latexmk_emu(
file, engine, bib_engine, engine_args, min_times, max_times,
install_packages, clean
)
return(check_pdf())
}
system2_quiet('latexmk', c(
'-latexoption=-halt-on-error -interaction=batchmode',
if (is_latex) '-latex=latex' else paste0('-pdf -pdflatex=', engine),
engine_args, shQuote(file)
), error = {
if (install_packages) warning(
'latexmk(install_packages = TRUE) does not work when emulation = FALSE'
)
check_latexmk_version()
})
if (clean) system2('latexmk', c('-c', engine_args), stdout = FALSE)
check_pdf()
}
#' @param ... Arguments to be passed to \code{latexmk()} (other than
#' \code{engine} and \code{emulation}).
#' @rdname latexmk
#' @export
pdflatex = function(...) latexmk(engine = 'pdflatex', emulation = TRUE, ...)
#' @rdname latexmk
#' @export
xelatex = function(...) latexmk(engine = 'xelatex', emulation = TRUE, ...)
#' @rdname latexmk
#' @export
lualatex = function(...) latexmk(engine = 'lualatex', emulation = TRUE, ...)
# a quick and dirty version of latexmk (should work reasonably well unless the
# LaTeX document is extremely complicated)
latexmk_emu = function(
file, engine, bib_engine = c('bibtex', 'biber'), engine_args = NULL, min_times = 1, max_times = 10,
install_packages = FALSE, clean
) {
aux = c(
'log', 'idx', 'aux', 'bcf', 'blg', 'bbl', 'fls', 'out', 'lof', 'lot', 'toc',
'nav', 'snm', 'vrb', 'ilg', 'ind', 'xwm', 'brf', 'run.xml'
)
base = gsub('[.]tex$', '', basename(file))
aux_files = paste(base, aux, sep = '.')
aux_files = c(aux_files, 'preview.aux') # generated by the preview package
if (!is.null(output_dir <- getOption('tinytex.output_dir')))
aux_files = file.path(output_dir, aux_files)
names(aux_files)[seq_along(aux)] = aux
logfile = aux_files['log']; unlink(logfile) # clean up the log before compilation
# clean up aux files from LaTeX compilation
files1 = exist_files(aux_files)
keep_log = FALSE
on.exit({
files2 = exist_files(aux_files)
files3 = setdiff(files2, files1)
if (keep_log || length(latex_warning(logfile))) files3 = setdiff(files3, logfile)
if (clean) unlink(files3)
.global$update_noted = NULL
}, add = TRUE)
pkgs_last = character()
filep = sub('.log$', if (engine == 'latex') '.dvi' else '.pdf', logfile)
verbose = getOption('tinytex.verbose', FALSE)
# install commands like pdflatex, bibtex, biber, and makeindex if necessary
install_cmd = function(cmd) {
if (install_packages && Sys.which(cmd) == '') parse_install(file = cmd)
}
install_cmd(engine)
run_engine = function() {
on_error = function() {
if (install_packages && file.exists(logfile)) {
pkgs = parse_packages(logfile, quiet = !verbose)
if (length(pkgs) && !identical(pkgs, pkgs_last)) {
if (verbose) message('Trying to automatically install missing LaTeX packages...')
if (tlmgr_install(pkgs, .quiet = !verbose) == 0) {
pkgs_last <<- pkgs
return(run_engine())
}
} else if (tlmgr_writable()) {
# chances are you are the sysadmin, and don't need ~/.TinyTeX
if (delete_texmf_user()) return(run_engine())
}
}
keep_log <<- TRUE
show_latex_error(file, logfile)
}
res = system2_quiet(
engine, c('-halt-on-error', '-interaction=batchmode', engine_args, shQuote(file)),
error = {
if (install_packages) tlmgr_update(run_fmtutil = FALSE, .quiet = TRUE)
on_error()
}, logfile = logfile, fail_rerun = verbose
)
# PNAS you are the worst! Why don't you signal an error in case of missing packages?
if (res == 0 && !file.exists(filep)) on_error()
invisible(res)
}
run_engine()
# some problems only trigger warnings but not errors, e.g.,
# https://github.com/rstudio/tinytex/issues/311 fix them and re-run engine
if (install_packages && check_extra(logfile)) run_engine()
# generate index
if (file.exists(idx <- aux_files['idx'])) {
idx_engine = getOption('tinytex.makeindex', 'makeindex')
install_cmd(idx_engine)
run_engine() # run the engine one more time (cf rstudio/bookdown#1274)
system2_quiet(idx_engine, c(getOption('tinytex.makeindex.args'), shQuote(idx)), error = {
stop("Failed to build the index via ", idx_engine, call. = FALSE)
})
}
# generate bibliography
bib_engine = match.arg(bib_engine)
install_cmd(bib_engine)
pkgs_last = character()
aux = aux_files[if ((biber <- bib_engine == 'biber')) 'bcf' else 'aux']
if (file.exists(aux)) {
if (biber || require_bibtex(aux)) {
blg = aux_files['blg'] # bibliography log file
build_bib = function() system2_quiet(bib_engine, shQuote(aux), error = {
check_blg = function() {
if (!file.exists(blg)) return(TRUE)
x = read_lines(blg)
if (length(grep('error message', x)) == 0) return(TRUE)
warn = function() {
warning(
bib_engine, ' seems to have failed:\n\n', paste(x, collapse = '\n'),
call. = FALSE
)
TRUE
}
if (!tlmgr_available() || !install_packages) return(warn())
# install the possibly missing .bst package and rebuild bib
pkgs = parse_packages(text = x, quiet = !verbose)
if (length(pkgs) == 0 || identical(pkgs, pkgs_last)) return(warn())
pkgs_last <<- pkgs
tlmgr_install(pkgs); build_bib()
FALSE
}
if (check_blg())
stop("Failed to build the bibliography via ", bib_engine, call. = FALSE)
})
build_bib()
}
}
for (i in seq_len(max_times)) {
if (i > min_times) {
if (file.exists(logfile)) {
if (!needs_rerun(logfile)) break
} else warning('The LaTeX log file "', logfile, '" is not found')
}
run_engine()
}
}
require_bibtex = function(aux) {
x = read_lines(aux)
r = length(grep('^\\\\citation\\{', x)) && length(grep('^\\\\bibdata\\{', x)) &&
length(grep('^\\\\bibstyle\\{', x))
if (r && !tlmgr_available() && os == 'windows') tweak_aux(aux, x)
r
}
# remove the .bib extension in \bibdata{} in the .aux file, because bibtex on
# Windows requires no .bib extension (sigh)
tweak_aux = function(aux, x = read_lines(aux)) {
r = '^\\\\bibdata\\{.+\\}\\s*$'
if (length(i <- grep(r, x)) == 0) return()
x[i] = gsub('[.]bib([,}])', '\\1', x[i])
writeLines(x, aux)
}
needs_rerun = function(log, text = read_lines(log)) {
any(grepl(
'(Rerun to get |Please \\(?re\\)?run | Rerun LaTeX\\.)', text,
useBytes = TRUE
))
}
system2_quiet = function(..., error = NULL, logfile = NULL, fail_rerun = TRUE) {
# system2(stdout = FALSE) fails on Windows with MiKTeX's pdflatex in the R
# console in RStudio: https://github.com/rstudio/rstudio/issues/2446 so I have
# to redirect stdout and stderr to files instead
f1 = tempfile('stdout'); f2 = tempfile('stderr')
on.exit(unlink(c(f1, f2)), add = TRUE)
# run the command quietly if possible
res = system2(..., stdout = if (use_file_stdout()) f1 else FALSE, stderr = f2)
if (is.character(logfile) && file.exists(f2) && length(e <- read_lines(f2))) {
i = grep('^\\s*$', e, invert = TRUE)
e[i] = paste('!', e[i]) # prepend ! to non-empty error messages
cat('', e, file = logfile, sep = '\n', append = TRUE)
}
# if failed, use the normal mode
if (fail_rerun && res != 0) res = system2(...)
# if still fails, run the error callback
if (res != 0) error # lazy evaluation
invisible(res)
}
use_file_stdout = function() {
getOption('tinytex.stdout.file', {
os == 'windows' && interactive() && !is.na(Sys.getenv('RSTUDIO', NA))
})
}
# parse the LaTeX log and show error messages
show_latex_error = function(
file, logfile = gsub('[.]tex$', '.log', basename(file)), force = FALSE
) {
e = c('LaTeX failed to compile ', file, '. See https://yihui.org/tinytex/r/#debugging for debugging tips.')
if (!file.exists(logfile)) stop(e, call. = FALSE)
x = read_lines(logfile)
b = grep('^\\s*$', x) # blank lines
b = c(b, which(x == "Here is how much of TeX's memory you used:"))
m = NULL
for (i in grep('^! ', x)) {
# ignore the last error message about the fatal error
if (grepl('==> Fatal error occurred', x[i], fixed = TRUE)) next
n = b[b > i]
n = if (length(n) == 0) i else min(n) - 1L
m = c(m, x[i:n], '')
}
if (length(m)) {
message(paste(m, collapse = '\n'))
latex_hints(m, file)
stop(e, ' See ', logfile, ' for more info.', call. = FALSE)
} else if (force) stop(e, call. = FALSE)
}
latex_hints = function(x, f) {
check_inline_math(x, f)
check_unicode(x)
}
check_inline_math = function(x, f) {
r = 'l[.][0-9]+\\s*|\\s*[0-9.]+\\\\times.*'
if (!any('! Missing $ inserted.' == x) || !length(i <- grep(r, x))) return()
m = gsub(r, '', x[i]); m = m[m != '']
s = with_ext(f, 'Rmd')
if (file.exists(s)) message(
if (length(m)) c('Try to find the following text in ', s, ':\n', paste(' ', m, '\n'), '\n'),
'You may need to add $ $ around a certain inline R expression `r ` in ', s,
if (length(m)) ' (see the above hint)',
'. See https://github.com/rstudio/rmarkdown/issues/385 for more info.'
)
}
check_unicode = function(x) {
if (length(grep('! (Package inputenc|LaTeX) Error: Unicode character', x))) message(
'Try other LaTeX engines instead (e.g., xelatex) if you are using pdflatex.',
if ('rmarkdown' %in% loadedNamespaces())
' See https://bookdown.org/yihui/rmarkdown-cookbook/latex-unicode.html'
)
}
# whether a LaTeX log file contains LaTeX or package (e.g. babel) warnings
latex_warning = function(file, show = getOption('tinytex.latexmk.warning', TRUE)) {
if (!file.exists(file)) return()
# if the option tinytex.latexmk.warning = FALSE, delete the log in latexmk_emu()
if (!show && missing(show)) return()
x = read_lines(file)
if (length(i <- grep('^(LaTeX|Package [[:alnum:]]+) Warning:', x)) == 0) return()
# these warnings may be okay (our Pandoc LaTeX template in rmarkdown may need an update)
i = i[grep('^Package (fixltx2e|caption|hyperref) Warning:', x[i], invert = TRUE)]
if (length(i) == 0) return()
b = grep('^\\s*$', x)
i = unlist(lapply(i, function(j) {
n = b[b > j]
n = if (length(n) == 0) i else min(n) - 1L
j:n
}))
i = sort(unique(i))
if (show) for(msg in x[i]) warning(msg, call. = FALSE, immediate. = TRUE)
x[i]
}
# check if any babel/glossaries/... packages are missing
check_extra = function(file) {
length(m <- latex_warning(file, FALSE)) > 0 &&
length(grep('^Package ([^ ]+) Warning:', m)) > 0 &&
any(
check_babel(m),
check_glossaries(m),
check_datetime2(m),
check_polyglossia(m)
)
}
check_babel = function(text) {
r = "^(\\(babel\\).* |Package babel Warning: No hyphenation patterns were preloaded for the )language [`']([^']+)'.*$"
if (length(m <- grep_sub(r, 'hyphen-\\2', text)) == 0) return(FALSE)
# (babel) the language `German (new orthography)' into the format
m = gsub('\\s.*', '', m)
m[m == 'hyphen-pinyin'] = 'hyphen-chinese'
tlmgr_install(tolower(m)) == 0
}
# Package glossaries Warning: No language module detected for `english'.
# (glossaries) Language modules need to be installed separately.
# (glossaries) Please check on CTAN for a bundle called
# (glossaries) `glossaries-english' or similar.
check_glossaries = function(text) {
r = "^\\(glossaries).* [`']([^']+)'.*$"
if (length(m <- grep_sub(r, '\\1', text)) == 0) return(FALSE)
tlmgr_install(m) == 0
}
# Package polyglossia Warning: No hyphenation patterns were loaded for `hungarian'
# Package polyglossia Warning: No hyphenation patterns were loaded for British English
check_polyglossia = function(text) {
r = "^Package polyglossia Warning: No hyphenation patterns were loaded for ([`'][^']+'|British English).*"
if (length(m <- grep_sub(r, '\\1', text)) == 0) return(FALSE)
m[m == 'British English'] = 'english'
m = gsub("[`']", '', m)
tlmgr_install(paste0('hyphen-', m)) == 0
}
# Package datetime2 Warning: Date-Time Language Module `english' not installed on
# input line xxx.
check_datetime2 = function(text) {
r = "^Package datetime2 Warning: Date-Time Language Module [`']([^']+)' not installed.*$"
if (length(m <- grep_sub(r, '\\1', text)) == 0) return(FALSE)
tlmgr_install(paste0('datetime2-', m)) == 0
}
# check the version of latexmk
check_latexmk_version = function() {
out = system2('latexmk', '-v', stdout = TRUE)
reg = '^.*Version (\\d+[.]\\d+).*$'
out = grep_sub(reg, '\\1', out)
if (length(out) == 0) return()
ver = as.numeric_version(out[1])
if (ver >= '4.43') return()
system2('latexmk', '-v')
warning(
'Your latexmk version seems to be too low. ',
'You may need to update the latexmk package or your LaTeX distribution.',
call. = FALSE
)
}
# return file paths that exist
exist_files = function(files) {
files[utils::file_test('-f', files)]
}
# use file.copy() if file.rename() fails
file_rename = function(from, to) {
if (from == to) return(TRUE)
if (!suppressWarnings(file.rename(from, to))) {
if (file.copy(from, to, overwrite = TRUE)) file.remove(from)
}
}
#' Find missing LaTeX packages from a LaTeX log file
#'
#' Analyze the error messages in a LaTeX log file to figure out the names of
#' missing LaTeX packages that caused the errors. These packages can be
#' installed via \code{\link{tlmgr_install}()}. Searching for missing packages
#' is based on \code{\link{tlmgr_search}()}.
#' @param log Path to the LaTeX log file (typically named \file{*.log}).
#' @param text A character vector of the error log (read from the file provided
#' by the \code{log} argument by default).
#' @param files A character vector of names of the missing files (automatically
#' detected from the \code{log} by default).
#' @param quiet Whether to suppress messages when finding packages. It should be
#' a logical vector of length 3: the first element indicates whether to
#' suppress the message when no missing LaTeX packages could be detected from
#' the log, the second element indicate whether to suppress the message when
#' searching for packages via \code{tlmgr_search()}, and the third element
#' indicates whether to warn if no packages could be found via
#' \code{tlmgr_search()}.
#' @return A character vector of LaTeX package names.
#' @export
parse_packages = function(
log, text = read_lines(log), files = detect_files(text), quiet = rep(FALSE, 3)
) {
pkgs = character(); quiet = rep_len(quiet, length.out = 3)
x = unique(c(files, miss_font()))
if (length(x) == 0) {
if (!quiet[1]) message(
'I was unable to find any missing LaTeX packages from the error log',
if (missing(log)) '.' else c(' ', log, '.')
)
return(invisible(pkgs))
}
for (j in seq_along(x)) {
l = tlmgr_search(paste0('/', x[j]), stdout = TRUE, .quiet = quiet[2])
if (length(l) == 0) next
if (x[j] == 'fandol') return(x[j]) # a known package
# why $? e.g. searching for mf returns a list like this
# metafont.x86_64-darwin:
# bin/x86_64-darwin/mf <- what we want
# metapost.x86_64-darwin:
# bin/x86_64-darwin/mfplain <- but this also matches /mf
k = grep(paste0('/', x[j], '$'), l) # only match /mf exactly
if (length(k) == 0) {
if (!quiet[3]) warning('Failed to find a package that contains ', x[j])
next
}
k = k[k > 2]
p = grep(':$', l)
if (length(p) == 0) next
for (i in k) {
pkg = gsub(':$', '', l[max(p[p < i])]) # find the package name
pkgs = c(pkgs, setNames(pkg, x[j]))
}
}
pkgs = gsub('[.].*', '', pkgs) # e.g., 'metafont.x86_64-darwin'
unique(pkgs)
}
regex_errors = function() {
# possible errors are like:
# ! LaTeX Error: File `framed.sty' not found.
# /usr/local/bin/mktexpk: line 123: mf: command not found
# ! Font U/psy/m/n/10=psyr at 10.0pt not loadable: Metric (TFM) file not found
# !pdfTeX error: /usr/local/bin/pdflatex (file tcrm0700): Font tcrm0700 at 600 not found
# xdvipdfmx:fatal: Unable to find TFM file "rsfs10"
# ! The font "FandolSong-Regular" cannot be found.
# ! Package babel Error: Unknown option `ngerman'. Either you misspelled it
# (babel) or the language definition file ngerman.ldf was not found.
# !pdfTeX error: pdflatex (file 8r.enc): cannot open encoding file for reading
# ! CTeX fontset `fandol' is unavailable in current mode
# Package widetext error: Install the flushend package which is a part of sttools
# Package biblatex Info: ... file 'trad-abbrv.bbx' not found
# ! Package pdftex.def Error: File `logo-mdpi-eps-converted-to.pdf' not found
# ! xdvipdfmx:fatal: pdf_ref_obj(): passed invalid object.
# ! Package tikz Error: I did not find the tikz library 'hobby'... named tikzlibraryhobby.code.tex
# support file `supp-pdf.mkii' (supp-pdf.tex) is missing
# ! I can't find file `hyph-de-1901.ec.tex'.
# ! Package pdfx Error: No color profile sRGB_IEC61966-2-1_black_scaled.icc found
# No file LGRcmr.fd. ! LaTeX Error: This NFSS system isn't set up properly.
list(
font = c(
# error messages about missing fonts (don't move the first item below, as
# it is special and emitted by widetext; the rest can be freely reordered)
'.*Package widetext error: Install the ([^ ]+) package.*',
".*! Font [^=]+=([^ ]+).+ not loadable.*",
'.*! .*The font "([^"]+)" cannot be found.*',
'.*!.+ error:.+\\(file ([^)]+)\\): .*',
'.*Unable to find TFM file "([^"]+)".*'
),
fd = c(
# font definition files
".*No file ([^`'. ]+[.]fd)[.].*"
),
epstopdf = c(
# possible errors when epstopdf is missing
".* File [`'](.+eps-converted-to.pdf)'.*",
".*xdvipdfmx:fatal: pdf_ref_obj.*"
),
colorprofiles.sty = c(
'.* Package pdfx Error: No color profile ([^ ]+).*'
),
`lua-uni-algos.lua` = c(
".* module '(lua-uni-normalize)' not found:.*"
),
tikz = c(
# when a required tikz library is missing
'.* (tikzlibrary[^ ]+?[.]code[.]tex).*'
),
l3backend = c(
# L3 programming layer mismatch (#424)
'^File: ([^ ]+) \\d{4,}-\\d{2}-\\d{2} .*$'
),
style = c(
# missing .sty or commands
".* Loading '([^']+)' aborted!",
".*! LaTeX Error: File [`']([^']+)' not found.*",
".* [fF]ile ['`]?([^' ]+)'? not found.*",
'.*the language definition file ([^ ]+) .*',
'.* \\(file ([^)]+)\\): cannot open .*',
'.* open style file ([^ ]+).*',
".*file [`']([^']+)' .*is missing.*",
".*! CTeX fontset [`']([^']+)' is unavailable.*",
".*: ([^:]+): command not found.*",
".*! I can't find file [`']([^']+)'.*"
)
)
}
# find filenames (could also be font names) from LaTeX error logs
detect_files = function(text) {
r = regex_errors()
x = grep(paste(unlist(r), collapse = '|'), text, value = TRUE)
if (length(x) > 0) unique(unlist(lapply(unlist(r), function(p) {
v = grep_sub(p, '\\1', x)
if (length(v) == 0) return(v)
if (p == r$tikz && length(grep('! Package tikz Error:', text)) == 0) return()
# if the problem is caused by the L3 programming layer mismatch, use the
# last found file before the error line, which should be from l3backend
if (p == r$l3backend) return(
if (length(grep('^! Undefined control sequence', text)) > 0) tail(v, 1)
)
# these are some known filenames
for (i in c('epstopdf', grep('[.]', names(r), value = TRUE))) {
if (p %in% r[[i]]) return(i)
}
if (p == r$fd) v = tolower(v) # LGRcmr.fd -> lgrcmr.fd
if (!(p %in% r$font)) return(v)
if (p == r$font[1]) paste0(v, '.sty') else font_ext(v)
})))
}
#' Parse the LaTeX log and install missing LaTeX packages if possible
#'
#' This is a helper function that combines \code{\link{parse_packages}()} and
#' \code{\link{tlmgr_install}()}.
#' @param ... Arguments passed to \code{\link{parse_packages}()}.
#' @export
parse_install = function(...) {
tlmgr_install(parse_packages(...))
}
# check missfont.log and detect the missing font packages; missfont.log
# typically looks like this:
# mktexpk --mfmode / --bdpi 600 --mag 1+0/600 --dpi 600 ecrm0900
miss_font = function() {
if (!file.exists(f <- 'missfont.log')) return()
on.exit(unlink(f), add = TRUE)
x = gsub('\\s*$', '', read_lines(f))
x = grep('.+\\s+.+', x, value = TRUE)
if (length(x) == 0) return()
x1 = gsub('.+\\s+', '', x) # possibly missing fonts
x2 = gsub('\\s+.+', '', x) # the command to make fonts
unique(c(font_ext(x1), x2))
}
font_ext = function(x) {
i = !grepl('[.]', x)
x[i] = paste0(x[i], '(-(Bold|Italic|Regular).*)?[.](tfm|afm|mf|otf|ttf)')
x
}
# it should be rare that we need to manually run texhash
texhash = function() {
tweak_path()
system2('texhash')
}
updmap = function(usermode = FALSE) {
tweak_path()
system2('updmap', if (usermode) '--user' else '--sys')
}
fmtutil = function(usermode = FALSE, ...) {
tweak_path()
system2('fmtutil', c(if (usermode) '--user' else '--sys', '--all'), ...)
}
fc_cache = function(args = c('-v', '-r')) {
tweak_path()
# run fc-cache on default dirs, then on the TinyTeX root dir
for (i in unique(c('', tinytex_root(error = FALSE))))
system2('fc-cache', shQuote(c(args, if (i != '') i)))
}
# refresh/update/regenerate everything
refresh_all = function(...) {
fc_cache(); fmtutil(...); updmap(...); texhash()
}
# look up files in the Kpathsea library, e.g., kpsewhich('Sweave.sty')
kpsewhich = function(filename, options = character(), ...) {
tweak_path()
system2('kpsewhich', c(options, shQuote(filename)), ...)
}
tinytex/R/package.R 0000644 0001762 0000144 00000000273 14366520027 013704 0 ustar ligges users #' @import stats utils tools
#' @importFrom xfun grep_sub dir_exists download_file in_dir
#' @keywords internal
'_PACKAGE'
os = .Platform$OS.type
.global = new.env(parent = emptyenv())
tinytex/R/tlmgr.R 0000644 0001762 0000144 00000041525 14616205351 013441 0 ustar ligges users #' Run the TeX Live Manager
#'
#' Execute the \command{tlmgr} command to search for LaTeX packages, install
#' packages, update packages, and so on.
#'
#' The \code{tlmgr()} function is a wrapper of \code{system2('tlmgr')}. All
#' other \code{tlmgr_*()} functions are based on \code{tlmgr} for specific
#' tasks. For example, \code{tlmgr_install()} runs the command \command{tlmgr
#' install} to install LaTeX packages, and \code{tlmgr_update} runs the command
#' \command{tlmgr update}, etc. Note that \code{tlmgr_repo} runs \command{tlmgr
#' options repository} to query or set the CTAN repository. Please consult the
#' \pkg{tlmgr} manual for full details.
#' @param args A character vector of arguments to be passed to the command
#' \command{tlmgr}.
#' @param usermode (For expert users only) Whether to use TeX Live's
#' \href{https://www.tug.org/texlive/doc/tlmgr.html#USER-MODE}{user mode}. If
#' \code{TRUE}, you must have run \code{tlmgr('init-usertree')} once before.
#' This option allows you to manage a user-level texmf tree, e.g., install a
#' LaTeX package to your home directory instead of the system directory, to
#' which you do not have write permission. This option should not be needed on
#' personal computers, and has some limitations, so please read the
#' \pkg{tlmgr} manual very carefully before using it.
#' @param ... For \code{tlmgr()}, additional arguments to be passed to
#' \code{\link{system2}()} (e.g., \code{stdout = TRUE} to capture stdout). For
#' other functions, arguments to be passed to \code{tlmgr()}.
#' @param .quiet Whether to hide the actual command before executing it.
#' @references The \pkg{tlmgr} manual:
#' \url{https://www.tug.org/texlive/doc/tlmgr.html}
#' @export
#' @examplesIf interactive()
#' # search for a package that contains titling.sty
#' tlmgr_search('titling.sty')
#'
#' # to match titling.sty exactly, add a slash before the keyword, e.g.
#' tlmgr_search('/titling.sty')
#'
#' # use a regular expression if you want to be more precise, e.g.
#' tlmgr_search('/titling\\.sty$')
#'
#' # list all installed LaTeX packages
#' tlmgr(c('info', '--list', '--only-installed', '--data', 'name'))
tlmgr = function(args = character(), usermode = FALSE, ..., .quiet = FALSE) {
tweak_path()
if (!.quiet && !tlmgr_available()) warning(
'\nTeX Live does not seem to be installed. See https://yihui.org/tinytex/.\n'
)
if (usermode) args = c('--usermode', args)
if (!.quiet) message(paste(c('tlmgr', args), collapse = ' '))
# use TeX Live's own binaries (e.g., curl): https://github.com/rstudio/tinytex/issues/354
vars = xfun::set_envvar(c(TEXLIVE_PREFER_OWN = 1))
on.exit(xfun::set_envvar(vars), add = TRUE)
system2('tlmgr', args, ...)
}
# add ~/bin to PATH if necessary on Linux, because sometimes PATH may not be
# inherited (https://github.com/rstudio/rstudio/issues/1878), and TinyTeX is
# installed to ~/bin by default; on Windows, prioritize win_app_dir('TinyTeX')
# if it exists (so TinyTeX can be used even when MiKTeX is installed); on macOS,
# check if it is necessary to add ~/Library/TinyTeX/bin/*/ to PATH
#' @importFrom xfun is_linux is_unix is_macos is_windows with_ext
tweak_path = function() {
# check tlmgr exists under the default installation dir of TinyTeX, or the
# global option tinytex.tlmgr.path
f = getOption('tinytex.tlmgr.path', find_tlmgr())
if (length(f) == 0 || !file_test('-x', f)) return()
bin = normalizePath(dirname(f))
# if the pdftex from TinyTeX is already on PATH, no need to adjust the PATH
if ((p <- Sys.which('pdftex')) != '') {
p2 = with_ext(file.path(bin, 'pdftex'), xfun::file_ext(p))
if (xfun::same_path(p, p2)) return()
}
old = Sys.getenv('PATH')
one = unlist(strsplit(old, s <- .Platform$path.sep, fixed = TRUE))
Sys.setenv(PATH = paste(c(bin, setdiff(one, bin)), collapse = s))
do.call(
on.exit, list(substitute(Sys.setenv(PATH = x), list(x = old)), add = TRUE),
envir = parent.frame()
)
}
tlmgr_available = function() Sys.which('tlmgr') != ''
#' @param what A search keyword as a (Perl) regular expression.
#' @param file Whether to treat \code{what} as a filename (pattern).
#' @param all For \code{tlmgr_search()}, whether to search in everything,
#' including package names, descriptions, and filenames. For
#' \code{tlmgr_update()}, whether to update all installed packages.
#' @param global Whether to search the online TeX Live Database or locally.
#' @param word Whether to restrict the search of package names and descriptions
#' to match only full words.
#' @rdname tlmgr
#' @export
tlmgr_search = function(what, file = TRUE, all = FALSE, global = TRUE, word = FALSE, ...) {
tlmgr(c(
'search', if (file) '--file', if (all) '--all', if (global) '--global',
if (word) '--word', shQuote(what)
), ...)
}
#' @param pkgs A character vector of LaTeX package names.
#' @param path Whether to run \code{tlmgr_path('add')} after installing packages
#' (\code{path = TRUE} is a conservative default: it is only necessary to do
#' this after a binary package is installed, such as the \pkg{metafont}
#' package, which contains the executable \command{mf}, but it does not hurt
#' even if no binary packages were installed).
#' @rdname tlmgr
#' @export
tlmgr_install = function(pkgs = character(), usermode = FALSE, path = !usermode && os != 'windows', ...) {
if (length(pkgs) == 0) return(invisible(0L))
update_pkgs = function(...) tlmgr_update(..., usermode = usermode)
# if any packages have been installed, update packages first
if (getOption('tinytex.tlmgr_update', TRUE) && any(check_installed(pkgs))) update_pkgs()
res = tlmgr(c('install', pkgs), usermode, ...)
if (res != 0 || any(!check_installed(pkgs))) {
update_pkgs(all = FALSE) # update tlmgr itself since it might be outdated
res = tlmgr(c('install', pkgs), usermode, ...)
}
if ('epstopdf' %in% pkgs && is_unix() && Sys.which('gs') == '') {
if (is_macos() && Sys.which('brew') != '') {
message('Trying to install GhostScript via Homebrew for the epstopdf package.')
system('brew install ghostscript')
}
if (Sys.which('gs') == '') warning('GhostScript is required for the epstopdf package.')
}
if (missing(path)) path = path && need_add_path()
if (path) tlmgr_path('add')
invisible(res)
}
# we should run `tlmgr path add` after `tlmgr install` only when the `tlmgr`
# found from PATH is a symlink that links to another symlink (typically under
# TinyTeX/bin/platform/tlmgr, which is typically a symlink to tlmgr.pl)
need_add_path = function() {
is_writable(p <- Sys.which('tlmgr')) &&
(p2 <- Sys.readlink(p)) != '' && basename(Sys.readlink(p2)) == 'tlmgr.pl' &&
basename(dirname(dirname(p2))) == 'bin'
}
is_writable = function(p) file.access(p, 2) == 0
tlmgr_writable = function() is_writable(Sys.which('tlmgr'))
#' Check if certain LaTeX packages are installed
#'
#' If a package has been installed in TinyTeX or TeX Live, the command
#' \command{tlmgr info PKG} should return \code{PKG} where \code{PKG} is the
#' package name.
#' @param pkgs A character vector of LaTeX package names.
#' @return A logical vector indicating if packages specified in \code{pkgs} are
#' installed.
#' @note This function only works with LaTeX distributions based on TeX Live,
#' such as TinyTeX.
#' @export
#' @examplesIf interactive()
#' tinytex::check_installed('framed')
check_installed = function(pkgs) {
if (length(pkgs) == 0) return(TRUE)
res = suppressWarnings(tryCatch(
tl_list(pkgs, stdout = TRUE, stderr = FALSE, .quiet = TRUE),
error = function(e) NULL
))
pkgs %in% res
}
#' @rdname tlmgr
#' @export
tlmgr_remove = function(pkgs = character(), usermode = FALSE) {
if (length(pkgs)) tlmgr(c('remove', pkgs), usermode)
}
#' @param format The data format to be returned: \code{raw} means the raw output
#' of the command \command{tlmgr --version}, \code{string} means a character
#' string of the format \samp{TeX Live YEAR (TinyTeX) with tlmgr DATE}, and
#' \code{list} means a list of the form \code{list(texlive = YEAR, tlmgr =
#' DATE, tinytex = TRUE/FALSE)}.
#' @rdname tlmgr
#' @importFrom xfun raw_string
#' @export
tlmgr_version = function(format = c('raw', 'string', 'list')) {
vers = tlmgr('--version', stdout = TRUE, .quiet = TRUE)
format = match.arg(format)
if (format != 'raw') {
year = xfun::grep_sub('^TeX Live.* version (\\d+).*$', '\\1', vers)[1]
tinytex = is_tinytex()
date = xfun::grep_sub('^tlmgr revision \\d+ \\(([0-9-]+) .*$', '\\1', vers)[1]
vers = if (format == 'list') {
list(texlive = as.integer(year), tlmgr = as.Date(date), tinytex = tinytex)
} else {
sprintf('TeX Live %s %swith tlmgr %s', year, if (tinytex) '(TinyTeX) ' else '', date)
}
}
if (is.character(vers)) xfun::raw_string(vers) else vers
}
#' @param self Whether to update the TeX Live Manager itself.
#' @param more_args A character vector of more arguments to be passed to the
#' command \command{tlmgr update} or \command{tlmgr conf}.
#' @param run_fmtutil Whether to run \command{fmtutil-sys --all} to (re)create
#' format and hyphenation files after updating \pkg{tlmgr}.
#' @param delete_tlpdb Whether to delete the \file{texlive.tlpdb.HASH} files
#' (where \verb{HASH} is an MD5 hash) under the \file{tlpkg} directory of the
#' root directory of TeX Live after updating.
#' @rdname tlmgr
#' @export
tlmgr_update = function(
all = TRUE, self = TRUE, more_args = character(), usermode = FALSE,
run_fmtutil = TRUE, delete_tlpdb = getOption('tinytex.delete_tlpdb', FALSE), ...
) {
# if unable to update due to a new release of TeX Live, skip the update
if (isTRUE(.global$update_noted)) return(invisible(NULL))
res = suppressWarnings(tlmgr(
c('update', if (all) '--all', if (self && !usermode) '--self', more_args),
usermode, ..., stdout = TRUE, stderr = TRUE
))
check_tl_version(res)
if (run_fmtutil) fmtutil(usermode, stdout = FALSE)
if (delete_tlpdb) delete_tlpdb_files()
invisible()
}
# check if a new version of TeX Live has been released and give instructions on
# how to upgrade
check_tl_version = function(x) {
i = grep('Local TeX Live \\([0-9]+) is older than remote repository \\([0-9]+)', x)
if (length(i) > 0) auto_upgrade()
.global$update_noted = TRUE
}
# provide a way options(tinytex.upgrade = TRUE) to automatically upgrade TinyTeX
# (this is an ugly workaround for rstudio/revdepcheck-cloud#115)
auto_upgrade = function() {
up = is_tinytex() && getOption(
'tinytex.upgrade',
all(Sys.getenv(c('DEV_PACKAGE_TARBALL', 'OUTPUT_S3_PATH')) != '')
)
if (!up) return(message(
'A new version of TeX Live has been released. If you need to install or update ',
'any LaTeX packages, you have to upgrade ', if (!is_tinytex()) 'TeX Live.' else c(
'TinyTeX with tinytex::reinstall_tinytex(repository = "illinois").'
)
))
root = tinytex_root()
message('Trying to upgrade TinyTeX automatically now...')
reinstall_tinytex(force = TRUE, dir = if (file.access(root, 2) == 0) root else default_inst())
}
delete_tlpdb_files = function() {
if ((root <- tinytex_root(FALSE)) != '') file.remove(list.files(
file.path(root, 'tlpkg'), '^texlive[.]tlpdb.*[.][0-9a-f]{32}$', full.names = TRUE
))
}
#' @param action On Unix, add/remove symlinks of binaries to/from the system's
#' \code{PATH}. On Windows, add/remove the path to the TeXLive binary
#' directory to/from the system environment variable \code{PATH}.
#' @rdname tlmgr
#' @export
tlmgr_path = function(action = c('add', 'remove'))
tlmgr(c('path', match.arg(action)), .quiet = TRUE)
#' @rdname tlmgr
#' @export
tlmgr_conf = function(more_args = character(), ...) {
tlmgr(c('conf', more_args), ...)
}
#' @param url The URL of the CTAN mirror. If \code{NULL}, show the current
#' repository, otherwise set the repository. See the \code{repository}
#' argument of \code{\link{install_tinytex}()} for examples.
#' @rdname tlmgr
#' @export
tlmgr_repo = function(url = NULL, ...) {
tlmgr(c('option', 'repository', shQuote(normalize_repo(url))), ...)
}
#' Add/remove R's texmf tree to/from TeX Live
#'
#' R ships a custom texmf tree containing a few LaTeX style and class files,
#' which are required when compiling R packages manuals (\file{Rd.sty}) or
#' Sweave documents (\file{Sweave.sty}). This tree can be found under the
#' directory \code{file.path(R.home('share'), 'texmf')}. This function can be
#' used to add/remove R's texmf tree to/from TeX Live via
#' \code{\link{tlmgr_conf}('auxtrees')}.
#' @param action Add/remove R's texmf tree to/from TeX Live.
#' @param ... Arguments passed to \code{\link{tlmgr}()}.
#' @references See the \pkg{tlmgr} manual for detailed information about
#' \command{tlmgr conf auxtrees}. Check out
#' \url{https://tex.stackexchange.com/q/77720/9128} if you don't know what
#' \code{texmf} means.
#' @export
#' @examples
#' # running the code below will modify your texmf tree; please do not run
#' # unless you know what it means
#'
#' # r_texmf('remove')
#' # r_texmf('add')
#'
#' # all files under R's texmf tree
#' list.files(file.path(R.home('share'), 'texmf'), recursive = TRUE, full.names = TRUE)
r_texmf = function(action = c('add', 'remove'), ...) {
tlmgr_conf(c('auxtrees', match.arg(action), shQuote(r_texmf_path())), ...)
}
r_texmf_path = function() {
d = file.path(R.home('share'), 'texmf')
if (dir_exists(d)) return(d)
# retry another directory: https://github.com/rstudio/tinytex/issues/60
if ('Rd.sty' %in% basename(list.files(d2 <- '/usr/share/texmf', recursive = TRUE))) {
return(d2)
}
warning("Cannot find R's texmf tree; returning '", d, "'")
d
}
#' Sizes of LaTeX packages in TeX Live
#'
#' Use the command \command{tlmgr info --list} to obtain the sizes of LaTeX
#' packages.
#' @param show_total Whether to show the total size.
#' @param pkgs A character vector of package names (by default, all packages).
#' @param field A character vector of field names in the package information.
#' See \url{https://www.tug.org/texlive/doc/tlmgr.html#info} for more info.
#' @inheritParams tl_pkgs
#' @export
#' @return By default, a data frame of three columns: \code{package} is the
#' package names, \code{size} is the sizes in bytes, and \code{size_h} is the
#' human-readable version of sizes. If different field names are provided in
#' the \code{field} argument, the returned data frame will contain these
#' columns.
tl_sizes = function(show_total = TRUE, pkgs = NULL, only_installed = TRUE, field = 'size') {
info = tl_list(pkgs, paste(c('name', field), collapse = ','), only_installed, stdout = TRUE)
info = read.table(sep = ',', text = info, stringsAsFactors = FALSE, col.names = c('package', field))
info = info[info$package %in% tl_names(info$package), , drop = FALSE]
if ('size' %in% names(info)) {
info = info[order(info[, 'size'], decreasing = TRUE), , drop = FALSE]
info$size_h = xfun::format_bytes(info[, 'size'])
if (show_total) message('The total size is ', xfun::format_bytes(sum(info$size)))
}
rownames(info) = NULL
info
}
#' List the names of installed TeX Live packages
#'
#' Calls \command{tlmgr info --list --data name} to obtain the names of all
#' (installed) TeX Live packages. Platform-specific strings in package names are
#' removed, e.g., \code{"tex"} is returned for the package
#' \pkg{tex.x86_64-darwin}.
#' @param only_installed Whether to list installed packages only.
#' @export
#' @return A character vector of package names.
tl_pkgs = function(only_installed = TRUE) {
x = tl_list(NULL, 'name', only_installed, stdout = TRUE, .quiet = TRUE)
tl_names(x, NULL)
}
tl_list = function(pkgs = NULL, field = 'name', only_installed = TRUE, ...) {
tlmgr(c('info', '--list', if (only_installed) '--only-installed', '--data', shQuote(field), pkgs), ...)
}
tl_platform = function() tlmgr('print-platform', stdout = TRUE, .quiet = TRUE)
# get all supported platforms (this needs Internet connection since the info is
# fetched from CTAN)
tl_platforms = function(print = FALSE) {
x = tlmgr(c('platform', 'list'), stdout = TRUE, .quiet = TRUE)
x = sub('^\\(i)', ' ', x)
x = sort(trimws(grep('^ ', x, value = TRUE)))
if (print) {
cat(sprintf("'%s'", x), sep = ', ')
invisible(x)
} else x
}
# a copy of the returned result from tl_platform() is saved here because
# tl_platform() is a little slow and requires Internet connection
.tl_platforms = c(
'aarch64-linux', 'amd64-freebsd', 'amd64-netbsd', 'armhf-linux',
'i386-freebsd', 'i386-linux', 'i386-netbsd', 'i386-solaris', 'universal-darwin',
'windows', 'x86_64-cygwin', 'x86_64-darwinlegacy', 'x86_64-linux',
'x86_64-linuxmusl', 'x86_64-solaris'
)
# remove the platform suffixes from texlive package names, and optionally keep
# the suffixes for certain platforms
tl_names = function(x, platform = tl_platform()) {
unique(sub(paste0(
'[.](', paste(setdiff(.tl_platforms, platform), collapse = '|'), ')$'
), '', x))
}
# get the names of packages that are not relocatable
tl_unrelocatable = function() {
x = tl_list(NULL, 'name,relocatable', FALSE, stdout = TRUE, .quiet = TRUE)
x = grep_sub(',0$', '', x)
tl_names(x)
}
tinytex/MD5 0000644 0001762 0000144 00000002624 14616211632 012274 0 ustar ligges users d1ea1300082ce1dd13988fdd04c02e00 *DESCRIPTION
214aad66205c39813fa32d36c37a64c9 *LICENSE
3fed972b9d8fbe8160ac430077bc4258 *NAMESPACE
f17a892f25f12a8499002a83dc371d93 *R/install.R
0f7c616dfc3219b7b1130e41e15e1fde *R/latex.R
0a363e5d065005b52e4d93270ddaf416 *R/package.R
d459ef7bbab626fd8d4ee0370e45c105 *R/tlmgr.R
a94156ccfbc5d10fd9f90ea685954d1e *R/utils.R
6bb251e68e88b7b36342b62c0b67f030 *README.md
757e76a4de0b6b3cc0549f6c08e84fe8 *inst/CITATION
8319d1d7c068cb31fa38c74cdcd2b830 *inst/NEWS.Rd
beb72b729727b5d7e94b9ba60ecaa66a *man/check_installed.Rd
51be9176879b070ef6d0e0a3e92d463d *man/copy_tinytex.Rd
9fa5ddd0d5a5cba87b9e979a8fa5d7ea *man/install_tinytex.Rd
2856e7abb8fa85e11e67b8ab98928604 *man/is_tinytex.Rd
e8a825623e22c69e3291e1c48af34d49 *man/latexmk.Rd
6e322ddcc88698ded4976ae80cc3788d *man/parse_install.Rd
dee1900604e4dac0d368df63650b11e9 *man/parse_packages.Rd
618fbe58b795fa525d7a59c0339f7b53 *man/r_texmf.Rd
a5242c76dddeaeb81e6be6b82d0c3bf2 *man/tinytex-package.Rd
179f7f5074f6498051c9f9ff570b3e1c *man/tl_pkgs.Rd
58ee80fb118f9e8acb8050f8d3c1b499 *man/tl_sizes.Rd
11609ea6698f90a0f34232637e50d5ef *man/tlmgr.Rd
adafda5ea0b62571a6766d4c95aaa065 *tests/test-cran.R
67e664e0b0f058f3d9288ffefeeb3017 *tests/test-cran/test-latex.R
fe61c3b9a1a1e5a590c79e02644aa45e *tests/test-travis.R
3566092b9846c158fed92c0536cfa798 *tests/test-travis/test-install.R
598698a9ddda44f24fa635c94ff92c17 *tests/test-travis/test-tlmgr.R
tinytex/inst/ 0000755 0001762 0000144 00000000000 14477353115 012745 5 ustar ligges users tinytex/inst/CITATION 0000644 0001762 0000144 00000001241 14366525535 014104 0 ustar ligges users year = sub('.*(2[[:digit:]]{3})-.*', '\\1', meta$Date, perl = TRUE)
vers = paste('R package version', meta$Version)
if (length(year) == 0) year = format(Sys.Date(), '%Y')
bibentry(
'manual',
title = paste('tinytex:', meta$Title),
author = Filter(function(p) 'aut' %in% p$role, as.person(meta$Author)),
year = year,
note = vers,
url = meta$URL
)
bibentry(
'article',
title = 'TinyTeX: A lightweight, cross-platform, and easy-to-maintain LaTeX distribution based on TeX Live',
author = 'Yihui Xie',
journal = 'TUGboat',
year = '2019',
volume = '40',
number = '1',
pages = '30--32',
url = 'https://tug.org/TUGboat/Contents/contents40-1.html'
)
tinytex/inst/NEWS.Rd 0000644 0001762 0000144 00000000441 14240530641 013774 0 ustar ligges users \name{NEWS}
\title{News for Package 'tinytex'}
\section{CHANGES IN tinytex VERSION 999.999}{
\itemize{
\item This NEWS file is only a placeholder. The version 999.999 does not really
exist. Please read the NEWS on Github: \url{https://github.com/rstudio/tinytex/releases}
}
}
|