xfun/ 0000755 0001762 0000144 00000000000 13607036513 011236 5 ustar ligges users xfun/NAMESPACE 0000644 0001762 0000144 00000002315 13606702706 012461 0 ustar ligges users # Generated by roxygen2: do not edit by hand
S3method("$",xfun_strict_list)
S3method(print,xfun_raw_string)
S3method(print,xfun_strict_list)
export(Rcmd)
export(Rscript)
export(as_strict_list)
export(attr)
export(compare_Rcheck)
export(download_file)
export(embed_dir)
export(embed_file)
export(embed_files)
export(file_ext)
export(file_string)
export(gsub_dir)
export(gsub_ext)
export(gsub_file)
export(gsub_files)
export(in_dir)
export(install_dir)
export(install_github)
export(isFALSE)
export(is_ascii)
export(is_linux)
export(is_macos)
export(is_unix)
export(is_windows)
export(json_vector)
export(loadable)
export(n2w)
export(native_encode)
export(normalize_path)
export(numbers_to_words)
export(optipng)
export(parse_only)
export(pkg_attach)
export(pkg_attach2)
export(pkg_load)
export(pkg_load2)
export(prose_index)
export(protect_math)
export(raw_string)
export(read_utf8)
export(rename_seq)
export(rev_check)
export(rstudio_type)
export(same_path)
export(sans_ext)
export(session_info)
export(split_lines)
export(strict_list)
export(stringsAsStrings)
export(strings_please)
export(tojson)
export(try_silent)
export(upload_ftp)
export(upload_win_builder)
export(with_ext)
export(write_utf8)
import(stats)
import(utils)
xfun/LICENSE 0000644 0001762 0000144 00000000047 13223200735 012235 0 ustar ligges users YEAR: 2018
COPYRIGHT HOLDER: Yihui Xie
xfun/README.md 0000644 0001762 0000144 00000001076 13563361006 012520 0 ustar ligges users # xfun
[](https://travis-ci.org/yihui/xfun)
[](https://codecov.io/github/yihui/xfun?branch=master)
[](https://cran.r-project.org/package=xfun)
This package contains several utility functions that I frequently use in other packages, and also miscellaneous functions that I use by myself from time to time. For more information, see https://yihui.org/xfun/.
xfun/man/ 0000755 0001762 0000144 00000000000 13606702660 012013 5 ustar ligges users xfun/man/rename_seq.Rd 0000644 0001762 0000144 00000003323 13606702707 014424 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/paths.R
\name{rename_seq}
\alias{rename_seq}
\title{Rename files with a sequential numeric prefix}
\usage{
rename_seq(
pattern = "^[0-9]+-.+[.]Rmd$",
format = "auto",
replace = TRUE,
start = 1,
dry_run = TRUE
)
}
\arguments{
\item{pattern}{A regular expression for \code{\link{list.files}()} to obtain
the files to be renamed. For example, to rename \code{.jpeg} files, use
\code{pattern = "[.]jpeg$"}.}
\item{format}{The format for the numeric prefix. This is passed to
\code{\link{sprintf}()}. The default format is \code{"\%0Nd"} where \code{N
= floor(log10(n)) + 1} and \code{n} is the number of files, which means the
prefix may be padded with zeros. For example, if there are 150 files to be
renamed, the format will be \code{"\%03d"} and the prefixes will be
\code{001}, \code{002}, ..., \code{150}.}
\item{replace}{Whether to remove existing numeric prefixes in filenames.}
\item{start}{The starting number for the prefix (it can start from 0).}
\item{dry_run}{Whether to not really rename files. To be safe, the default is
\code{TRUE}. If you have looked at the new filenames and are sure the new
names are what you want, you may rerun \code{rename_seq()} with
\code{dry_run = FALSE)} to actually rename files.}
}
\value{
A named character vector. The names are original filenames, and the
vector itself is the new filenames.
}
\description{
Rename a series of files and add an incremental numeric prefix to the
filenames. For example, files \file{a.txt}, \file{b.txt}, and \file{c.txt}
can be renamed to \file{1-a.txt}, \file{2-b.txt}, and \file{3-c.txt}.
}
\examples{
xfun::rename_seq()
xfun::rename_seq("[.](jpeg|png)$", format = "\%04d")
}
xfun/man/os.Rd 0000644 0001762 0000144 00000001056 13606702707 012727 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/os.R
\name{is_windows}
\alias{is_windows}
\alias{is_unix}
\alias{is_macos}
\alias{is_linux}
\title{Test for types of operating systems}
\usage{
is_windows()
is_unix()
is_macos()
is_linux()
}
\description{
Functions based on \code{.Platform$OS.type} and \code{Sys.info()} to test if
the current operating system is Windows, macOS, Unix, or Linux.
}
\examples{
library(xfun)
# only one of the following statements should be true
is_windows()
is_unix() && is_macos()
is_linux()
}
xfun/man/optipng.Rd 0000644 0001762 0000144 00000000631 13606702707 013764 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/command.R
\name{optipng}
\alias{optipng}
\title{Run OptiPNG on all PNG files under a directory}
\usage{
optipng(dir = ".")
}
\arguments{
\item{dir}{Path to a directory.}
}
\description{
Calls the command \command{optipng} to optimize all PNG files under a
directory.
}
\references{
OptiPNG: \url{http://optipng.sourceforge.net}.
}
xfun/man/read_utf8.Rd 0000644 0001762 0000144 00000001660 13606702707 014170 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/io.R
\name{read_utf8}
\alias{read_utf8}
\alias{write_utf8}
\title{Read / write files encoded in UTF-8}
\usage{
read_utf8(con, error = FALSE)
write_utf8(text, con, ...)
}
\arguments{
\item{con}{A connection or a file path.}
\item{error}{Whether to signal an error when non-UTF8 characters are detected
(if \code{FALSE}, only a warning message is issued).}
\item{text}{A character vector (will be converted to UTF-8 via
\code{\link{enc2utf8}()}).}
\item{...}{Other arguments passed to \code{\link{writeLines}()} (except
\code{useBytes}, which is \code{TRUE} in \code{write_utf8()}).}
}
\description{
Read or write files, assuming they are encoded in UTF-8. \code{read_utf8()}
is roughly \code{readLines(encoding = 'UTF-8')} (a warning will be issued if
non-UTF8 lines are found), and \code{write_utf8()} calls
\code{writeLines(enc2utf8(text), useBytes = TRUE)}.
}
xfun/man/install_dir.Rd 0000644 0001762 0000144 00000001466 13606702706 014616 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/packages.R
\name{install_dir}
\alias{install_dir}
\title{Install a source package from a directory}
\usage{
install_dir(src, build = TRUE, build_opts = NULL, install_opts = NULL)
}
\arguments{
\item{src}{The package source directory.}
\item{build}{Whether to build a tarball from the source directory. If
\code{FALSE}, run \command{R CMD INSTALL} on the directory directly (note
that vignettes will not be automatically built).}
\item{build_opts}{The options for \command{R CMD build}.}
\item{install_opts}{The options for \command{R CMD INSTALL}.}
}
\value{
Invisible status from \command{R CMD INSTALL}.
}
\description{
Run \command{R CMD build} to build a tarball from a source directory, and run
\command{R CMD INSTALL} to install it.
}
xfun/man/isFALSE.Rd 0000644 0001762 0000144 00000000626 13606702707 013476 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/utils.R
\name{isFALSE}
\alias{isFALSE}
\title{Test if an object is identical to \code{FALSE}}
\usage{
isFALSE(x)
}
\arguments{
\item{x}{An R object.}
}
\description{
A simple abbreviation of \code{identical(x, FALSE)}.
}
\examples{
library(xfun)
isFALSE(TRUE) # false
isFALSE(FALSE) # true
isFALSE(c(FALSE, FALSE)) # false
}
xfun/man/tojson.Rd 0000644 0001762 0000144 00000002222 13606702707 013616 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/json.R
\name{tojson}
\alias{tojson}
\alias{json_vector}
\title{A simple JSON serializer}
\usage{
tojson(x)
json_vector(x, to_array = FALSE, quote = TRUE)
}
\arguments{
\item{x}{An R object.}
\item{to_array}{Whether to convert a vector to a JSON array (use \code{[]}).}
\item{quote}{Whether to double quote the elements.}
}
\value{
A character string.
}
\description{
A JSON serializer that only works on a limited types of R data (\code{NULL},
lists, logical scalars, character/numeric vectors). A character string of the
class \code{JS_EVAL} is treated as raw JavaScript, so will not be quoted. The
function \code{json_vector()} converts an atomic R vector to JSON.
}
\examples{
library(xfun)
tojson(NULL)
tojson(1:10)
tojson(TRUE)
tojson(FALSE)
cat(tojson(list(a = 1, b = list(c = 1:3, d = "abc"))))
cat(tojson(list(c("a", "b"), 1:5, TRUE)))
# the class JS_EVAL is originally from htmlwidgets::JS()
JS = function(x) structure(x, class = "JS_EVAL")
cat(tojson(list(a = 1:5, b = JS("function() {return true;}"))))
}
\seealso{
The \pkg{jsonlite} package provides a full JSON serializer.
}
xfun/man/install_github.Rd 0000644 0001762 0000144 00000001331 13606702706 015311 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/packages.R
\name{install_github}
\alias{install_github}
\title{An alias of \code{remotes::install_github()}}
\usage{
install_github(...)
}
\arguments{
\item{...}{Arguments to be passed to
\code{remotes::\link[remotes]{install_github}()}.}
}
\description{
This alias is to make autocomplete faster via \code{xfun::install_github},
because most \code{remotes::install_*} functions are never what I want. I
only use \code{install_github} and it is inconvenient to autocomplete it,
e.g. \code{install_git} always comes before \code{install_github}, but I
never use it. In RStudio, I only need to type \code{xfun::ig} to get
\code{xfun::install_github}.
}
xfun/man/strict_list.Rd 0000644 0001762 0000144 00000003413 13606702707 014650 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/data-structure.R
\name{strict_list}
\alias{strict_list}
\alias{as_strict_list}
\alias{$.xfun_strict_list}
\alias{print.xfun_strict_list}
\title{Strict lists}
\usage{
strict_list(...)
as_strict_list(x)
\method{$}{xfun_strict_list}(x, name)
\method{print}{xfun_strict_list}(x, ...)
}
\arguments{
\item{...}{Objects (list elements), possibly named. Ignored in the
\code{print()} method.}
\item{x}{For \code{as_strict_list()}, the object to be coerced to a strict
list.
For \code{print()}, a strict list.}
\item{name}{The name (a character string) of the list element.}
}
\value{
Both \code{strict_list()} and \code{as_strict_list()} return a list
with the class \code{xfun_strict_list}. Whereas \code{as_strict_list()}
attempts to coerce its argument \code{x} to a list if necessary,
\code{strict_list()} just wraps its argument \code{...} in a list, i.e., it
will add another list level regardless if \code{...} already is of type
list.
}
\description{
A strict list is essentially a normal \code{\link{list}()} but it does not
allow partial matching with \code{$}.
}
\details{
To me, partial matching is often more annoying and surprising than
convenient. It can lead to bugs that are very hard to discover, and I have
been bitten by it many times. When I write \code{x$name}, I always mean
precisely \code{name}. You should use a modern code editor to autocomplete
the \code{name} if it is too long to type, instead of using partial names.
}
\examples{
library(xfun)
(z = strict_list(aaa = "I am aaa", b = 1:5))
z$a # NULL!
z$aaa # I am aaa
z$b
z$c = "create a new element"
z2 = unclass(z) # a normal list
z2$a # partial matching
z3 = as_strict_list(z2) # a strict list again
z3$a # NULL again!
}
xfun/man/gsub_file.Rd 0000644 0001762 0000144 00000003250 13606702706 014242 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/io.R
\name{gsub_file}
\alias{gsub_file}
\alias{gsub_files}
\alias{gsub_dir}
\alias{gsub_ext}
\title{Search and replace strings in files}
\usage{
gsub_file(file, ..., rw_error = TRUE)
gsub_files(files, ...)
gsub_dir(..., dir = ".", recursive = TRUE, ext = NULL, mimetype = ".*")
gsub_ext(ext, ..., dir = ".", recursive = TRUE)
}
\arguments{
\item{file}{Path of a single file.}
\item{...}{For \code{gsub_file()}, arguments passed to \code{gsub()}. For
other functions, arguments passed to \code{gsub_file()}. Note that the
argument \code{x} of \code{gsub()} is the content of the file.}
\item{rw_error}{Whether to signal an error if the file cannot be read or
written. If \code{FALSE}, the file will be ignored (with a warning).}
\item{files}{A vector of file paths.}
\item{dir}{Path to a directory (all files under this directory will be
replaced).}
\item{recursive}{Whether to find files recursively under a directory.}
\item{ext}{A vector of filename extensions (without the leading periods).}
\item{mimetype}{A regular expression to filter files based on their MIME
types, e.g., \code{'^text/'} for plain text files. This requires the
\pkg{mime} package.}
}
\description{
These functions provide the "file" version of \code{\link{gsub}()}, i.e.,
they perform searching and replacement in files via \code{gsub()}.
}
\note{
These functions perform in-place replacement, i.e., the files will be
overwritten. Make sure you backup your files in advance, or use version
control!
}
\examples{
library(xfun)
f = tempfile()
writeLines(c("hello", "world"), f)
gsub_file(f, "world", "woRld", fixed = TRUE)
readLines(f)
}
xfun/man/attr.Rd 0000644 0001762 0000144 00000000776 13606702706 013267 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/utils.R
\name{attr}
\alias{attr}
\title{Obtain an attribute of an object without partial matching}
\usage{
attr(...)
}
\arguments{
\item{...}{Passed to \code{base::\link[base]{attr}()} (without the
\code{exact} argument).}
}
\description{
An abbreviation of \code{base::\link[base]{attr}(exact = TRUE)}.
}
\examples{
z = structure(list(a = 1), foo = 2)
base::attr(z, "f") # 2
xfun::attr(z, "f") # NULL
xfun::attr(z, "foo") # 2
}
xfun/man/Rscript.Rd 0000644 0001762 0000144 00000001133 13606702707 013730 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/command.R
\name{Rscript}
\alias{Rscript}
\alias{Rcmd}
\title{Run the commands \command{Rscript} and \command{R CMD}}
\usage{
Rscript(args, ...)
Rcmd(args, ...)
}
\arguments{
\item{args}{A character vector of command-line arguments.}
\item{...}{Other arguments to be passed to \code{\link{system2}()}.}
}
\value{
A value returned by \code{system2()}.
}
\description{
Wrapper functions to run the commands \command{Rscript} and \command{R CMD}.
}
\examples{
library(xfun)
Rscript(c("-e", "1+1"))
Rcmd(c("build", "--help"))
}
xfun/man/file_string.Rd 0000644 0001762 0000144 00000001007 13606702706 014606 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/io.R
\name{file_string}
\alias{file_string}
\title{Read a text file and concatenate the lines by \code{'\n'}}
\usage{
file_string(file)
}
\arguments{
\item{file}{Path to a text file (should be encoded in UTF-8).}
}
\value{
A character string of text lines concatenated by \code{'\n'}.
}
\description{
The source code of this function should be self-explanatory.
}
\examples{
xfun::file_string(system.file("DESCRIPTION", package = "xfun"))
}
xfun/man/prose_index.Rd 0000644 0001762 0000144 00000001551 13606702707 014625 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/markdown.R
\name{prose_index}
\alias{prose_index}
\title{Find the indices of lines in Markdown that are prose (not code blocks)}
\usage{
prose_index(x, warn = TRUE)
}
\arguments{
\item{x}{A character vector of text in Markdown.}
\item{warn}{Whether to emit a warning when code fences are not balanced.}
}
\value{
An integer vector of indices of lines that are prose in Markdown.
}
\description{
Filter out the indices of lines between code block fences such as \verb{```}
(could be three or four or more backticks).
}
\note{
If the code fences are not balanced (e.g., a starting fence without an
ending fence), this function will treat all lines as prose.
}
\examples{
library(xfun)
prose_index(c("a", "```", "b", "```", "c"))
prose_index(c("a", "````", "```r", "1+1", "```", "````", "c"))
}
xfun/man/download_file.Rd 0000644 0001762 0000144 00000001704 13606702706 015113 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/utils.R
\name{download_file}
\alias{download_file}
\title{Try various methods to download a file}
\usage{
download_file(url, output = basename(url), ...)
}
\arguments{
\item{url}{The URL of the file.}
\item{output}{Path to the output file. If not provided, the base name of the
URL will be used (query parameters and hash in the URL will be removed).}
\item{...}{Other arguments to be passed to \code{\link{download.file}()}
(except \code{method}).}
}
\value{
The integer code \code{0} for success, or an error if none of the
methods work.
}
\description{
Try all possible methods in \code{\link{download.file}()} (e.g.,
\code{libcurl}, \code{curl}, \code{wget}, and \code{wininet}) and see if any
method can succeed. The reason to enumerate all methods is that sometimes the
default method does not work, e.g.,
\url{https://stat.ethz.ch/pipermail/r-devel/2016-June/072852.html}.
}
xfun/man/embed_file.Rd 0000644 0001762 0000144 00000004547 13606702706 014370 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/markdown.R
\name{embed_file}
\alias{embed_file}
\alias{embed_dir}
\alias{embed_files}
\title{Embed a file, multiple files, or directory on an HTML page}
\usage{
embed_file(path, name = basename(path), text = paste("Download", name), ...)
embed_dir(path, name = paste0(normalize_path(path), ".zip"), ...)
embed_files(path, name = with_ext(basename(path[1]), ".zip"), ...)
}
\arguments{
\item{path}{Path to the file(s) or directory.}
\item{name}{The default filename to use when downloading the file. Note that
for \code{embed_dir()}, only the base name (of the zip filename) will be
used.}
\item{text}{The text for the hyperlink.}
\item{...}{For \code{embed_file()}, additional arguments to be passed to
\code{htmltools::a()} (e.g., \code{class = 'foo'}). For \code{embed_dir()}
and \code{embed_files()}, arguments passed to \code{embed_file()}.}
}
\value{
An HTML tag \samp{} with the appropriate attributes.
}
\description{
For a file, first encode it into base64 data (a character string). Then
generate a hyperlink of the form \samp{Download filename}. The file can be downloaded when
the link is clicked in modern web browsers. For a directory, it will be
compressed as a zip archive first, and the zip file is passed to
\code{embed_file()}. For multiple files, they are also compressed to a zip
file first.
}
\details{
These functions can be called in R code chunks in R Markdown documents with
HTML output formats. You may embed an arbitrary file or directory in the HTML
output file, so that readers of the HTML page can download it from the
browser. A common use case is to embed data files for readers to download.
}
\note{
Windows users may need to install Rtools to obtain the \command{zip}
command to use \code{embed_dir()} and \code{embed_files()}.
These functions require R packages \pkg{mime}, \pkg{base64enc}, and
\pkg{htmltools}. If you have installed the \pkg{rmarkdown} package, these
packages should be available, otherwise you need to install them
separately.
Currently Internet Explorer does not support downloading embedded files
(\url{https://caniuse.com/#feat=download}).
}
\examples{
logo = file.path(R.home("doc"), "html", "logo.jpg")
link = xfun::embed_file(logo, "R-logo.jpg", "Download R logo")
link
htmltools::browsable(link)
}
xfun/man/upload_ftp.Rd 0000644 0001762 0000144 00000002657 13606702707 014453 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/command.R
\name{upload_ftp}
\alias{upload_ftp}
\alias{upload_win_builder}
\title{Upload to an FTP server via \command{curl}}
\usage{
upload_ftp(file, server, dir = "")
upload_win_builder(
file,
version = c("R-devel", "R-release", "R-oldrelease", "R-devel_gcc8"),
server = "ftp://win-builder.r-project.org/"
)
}
\arguments{
\item{file}{Path to a local file.}
\item{server}{The address of the FTP server.}
\item{dir}{The remote directory to which the file should be uploaded.}
\item{version}{The R version(s) on win-builder.}
}
\value{
Status code returned from \code{\link{system2}}.
}
\description{
Run the command \command{curl -T file server} to upload a file to an FTP
server. These functions require the system package (\emph{not the R package})
\command{curl} to be installed (which should be available on macOS by
default). The function \code{upload_win_builder()} uses \code{upload_ftp()}
to upload packages to the win-builder server.
}
\details{
These functions were written mainly to save package developers the trouble of
going to the win-builder web page and uploading packages there manually. You
may also consider using \code{devtools::check_win_*}, which currently only
allows you to upload a package to one folder on win-builder each time, and
\code{xfun::upload_win_builder()} uploads to all three folders, which is more
likely to be what you need.
}
xfun/man/pkg_attach.Rd 0000644 0001762 0000144 00000005257 13606702707 014422 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/packages.R
\name{pkg_attach}
\alias{pkg_attach}
\alias{pkg_load}
\alias{loadable}
\alias{pkg_attach2}
\alias{pkg_load2}
\title{Attach or load packages, and automatically install missing packages if
requested}
\usage{
pkg_attach(
...,
install = FALSE,
message = getOption("xfun.pkg_attach.message", TRUE)
)
pkg_load(..., error = TRUE, install = FALSE)
loadable(pkg, strict = TRUE, new_session = FALSE)
pkg_attach2(...)
pkg_load2(...)
}
\arguments{
\item{...}{Package names (character vectors, and must always be quoted).}
\item{install}{Whether to automatically install packages that are not
available using \code{\link{install.packages}()}. You are recommended to
set a CRAN mirror in the global option \code{repos} via
\code{\link{options}()} if you want to automatically install packages.}
\item{message}{Whether to show the package startup messages (if any startup
messages are provided in a package).}
\item{error}{Whether to signal an error when certain packages cannot be loaded.}
\item{pkg}{A single package name.}
\item{strict}{If \code{TRUE}, use \code{\link{requireNamespace}()} to test if
a package is loadable; otherwise only check if the package is in
\code{\link{.packages}(TRUE)} (this does not really load the package, so it
is less rigorous but on the other hand, it can keep the current R session
clean).}
\item{new_session}{Whether to test if a package is loadable in a new R
session. Note that \code{new_session = TRUE} implies \code{strict = TRUE}.}
}
\value{
\code{pkg_attach()} returns \code{NULL} invisibly. \code{pkg_load()}
returns a logical vector, indicating whether the packages can be loaded.
}
\description{
\code{pkg_attach()} is a vectorized version of \code{\link{library}()} over
the \code{package} argument to attach multiple packages in a single function
call. \code{pkg_load()} is a vectorized version of
\code{\link{requireNamespace}()} to load packages (without attaching them).
The functions \code{pkg_attach2()} and \code{pkg_load2()} are wrappers of
\code{pkg_attach(install = TRUE)} and \code{pkg_load(install = TRUE)},
respectively. \code{loadable()} is an abbreviation of
\code{requireNamespace(quietly = TRUE)}.
}
\details{
These are convenience functions that aim to solve these common problems: (1)
We often need to attach or load multiple packages, and it is tedious to type
several \code{library()} calls; (2) We are likely to want to install the
packages when attaching/loading them but they have not been installed.
}
\examples{
library(xfun)
pkg_attach("stats", "graphics")
# pkg_attach2('servr') # automatically install servr if it is not installed
(pkg_load("stats", "graphics"))
}
xfun/man/session_info.Rd 0000644 0001762 0000144 00000003372 13606702707 015007 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/utils.R
\name{session_info}
\alias{session_info}
\title{An alternative to sessionInfo() to print session information}
\usage{
session_info(packages = NULL, dependencies = TRUE)
}
\arguments{
\item{packages}{A character vector of package names, of which the versions
will be printed. If not specified, it means all loaded and attached
packages in the current R session.}
\item{dependencies}{Whether to print out the versions of the recursive
dependencies of packages.}
}
\value{
A character vector of the session information marked as
\code{\link{raw_string}()}.
}
\description{
This function tweaks the output of \code{\link{sessionInfo}()}: (1) It adds
the RStudio version information if running in the RStudio IDE; (2) It removes
the information about matrix products, BLAS, and LAPACK; (3) It removes the
names of base R packages; (4) It prints out package versions in a single
group, and does not differentiate between loaded and attached packages.
}
\details{
It also allows you to only print out the versions of specified packages (via
the \code{packages} argument) and optionally their recursive dependencies.
For these specified packages (if provided), if a function
\code{xfun_session_info()} exists in a package, it will be called and
expected to return a character vector to be appended to the output of
\code{session_info()}. This provides a mechanism for other packages to inject
more information into the \code{session_info} output. For example,
\pkg{rmarkdown} (>= 1.20.2) has a function \code{xfun_session_info()} that
returns the version of Pandoc, which can be very useful information for
diagnostics.
}
\examples{
xfun::session_info()
if (loadable("MASS")) xfun::session_info("MASS")
}
xfun/man/native_encode.Rd 0000644 0001762 0000144 00000001473 13606702707 015114 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/encoding.R
\name{native_encode}
\alias{native_encode}
\title{Try to use the system native encoding to represent a character vector}
\usage{
native_encode(x, windows_only = is_windows())
}
\arguments{
\item{x}{A character vector.}
\item{windows_only}{Whether to make the attempt on Windows only. On Unix,
characters are typically encoded in the native encoding (UTF-8), so there
is no need to do the conversion.}
}
\description{
Apply \code{enc2native()} to the character vector, and check if
\code{enc2utf8()} can convert it back without a loss. If it does, return
\code{enc2native(x)}, otherwise return the original vector with a warning.
}
\examples{
library(xfun)
s = intToUtf8(c(20320, 22909))
Encoding(s)
s2 = native_encode(s)
Encoding(s2)
}
xfun/man/parse_only.Rd 0000644 0001762 0000144 00000000745 13606702707 014465 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/utils.R
\name{parse_only}
\alias{parse_only}
\title{Parse R code and do not keep the source}
\usage{
parse_only(code)
}
\arguments{
\item{code}{A character vector of the R source code.}
}
\value{
R \code{\link{expression}}s.
}
\description{
An abbreviation of \code{parse(keep.source = FALSE)}.
}
\examples{
library(xfun)
parse_only("1+1")
parse_only(c("y~x", "1:5 # a comment"))
parse_only(character(0))
}
xfun/man/rstudio_type.Rd 0000644 0001762 0000144 00000002122 13606702707 015033 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/rstudio.R
\name{rstudio_type}
\alias{rstudio_type}
\title{Type a character vector into the RStudio source editor}
\usage{
rstudio_type(x, pause = function() 0.1, mistake = 0, save = 0)
}
\arguments{
\item{x}{A character vector.}
\item{pause}{A function to return a number in seconds to pause after typing
each character.}
\item{mistake}{The probability of making random mistakes when typing the next
character. A random mistake is a random string typed into the editor and
deleted immediately.}
\item{save}{The probability of saving the document after typing each
character. Note that If a document is not opened from a file, it will never
be saved.}
}
\description{
Use the \pkg{rstudioapi} package to insert characters one by one into the
RStudio source editor, as if they were typed by a human.
}
\examples{
library(xfun)
if (loadable("rstudioapi") && rstudioapi::isAvailable()) {
rstudio_type("Hello, RStudio! xfun::rstudio_type() looks pretty cool!",
pause = function() runif(1, 0, 0.5), mistake = 0.1)
}
}
xfun/man/is_ascii.Rd 0000644 0001762 0000144 00000001056 13606702707 014071 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/encoding.R
\name{is_ascii}
\alias{is_ascii}
\title{Check if a character vector consists of entirely ASCII characters}
\usage{
is_ascii(x)
}
\arguments{
\item{x}{A character vector.}
}
\value{
A logical vector indicating whether each element of the character
vector is ASCII.
}
\description{
Converts the encoding of a character vector to \code{'ascii'}, and check if
the result is \code{NA}.
}
\examples{
library(xfun)
is_ascii(letters) # yes
is_ascii(intToUtf8(8212)) # no
}
xfun/man/protect_math.Rd 0000644 0001762 0000144 00000003243 13606702707 014777 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/markdown.R
\name{protect_math}
\alias{protect_math}
\title{Protect math expressions in pairs of backticks in Markdown}
\usage{
protect_math(x)
}
\arguments{
\item{x}{A character vector of text in Markdown.}
}
\value{
A character vector with math expressions in backticks.
}
\description{
For Markdown renderers that do not support LaTeX math, we need to protect
math expressions as verbatim code (in a pair of backticks), because some
characters in the math expressions may be interpreted as Markdown syntax
(e.g., a pair of underscores may make text italic). This function detects
math expressions in Markdown (by heuristics), and wrap them in backticks.
}
\details{
Expressions in pairs of dollar signs or double dollar signs are treated as
math, if there are no spaces after the starting dollar sign, or before the
ending dollar sign. There should be spaces before the starting dollar sign,
unless the math expression starts from the very beginning of a line. For a
pair of single dollar signs, the ending dollar sign should not be followed by
a number. With these assumptions, there should not be too many false
positives when detecing math expressions.
Besides, LaTeX environments (\verb{\begin{*}} and \verb{\end{*}}) are also
protected in backticks.
}
\note{
If you are using Pandoc or the \pkg{rmarkdown} package, there is no
need to use this function, because Pandoc's Markdown can recognize math
expressions.
}
\examples{
library(xfun)
protect_math(c("hi $a+b$", "hello $$\\\\alpha$$", "no math here: $x is $10 dollars"))
protect_math(c("hi $$", "\\\\begin{equation}", "x + y = z", "\\\\end{equation}"))
}
xfun/man/stringsAsStrings.Rd 0000644 0001762 0000144 00000001320 13606702707 015627 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/utils.R
\name{stringsAsStrings}
\alias{stringsAsStrings}
\alias{strings_please}
\title{Set the global option \code{\link{options}(stringsAsFactors = FALSE)} inside
a parent function and restore the option after the parent function exits}
\usage{
stringsAsStrings()
strings_please()
}
\description{
This is a shorthand of \code{opts = options(stringsAsFactors = FALSE);
on.exit(options(opts), add = TRUE)}; \code{strings_please()} is an alias of
\code{stringsAsStrings()}.
}
\examples{
f = function() {
xfun::strings_please()
data.frame(x = letters[1:4], y = factor(letters[1:4]))
}
str(f()) # the first column should be character
}
xfun/man/try_silent.Rd 0000644 0001762 0000144 00000000602 13606702707 014476 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/utils.R
\name{try_silent}
\alias{try_silent}
\title{Try to evaluate an expression silently}
\usage{
try_silent(expr)
}
\arguments{
\item{expr}{An R expression.}
}
\description{
An abbreviation of \code{try(silent = TRUE)}.
}
\examples{
library(xfun)
z = try_silent(stop("Wrong!"))
inherits(z, "try-error")
}
xfun/man/same_path.Rd 0000644 0001762 0000144 00000001006 13606702707 014242 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/paths.R
\name{same_path}
\alias{same_path}
\title{Test if two paths are the same after they are normalized}
\usage{
same_path(p1, p2, ...)
}
\arguments{
\item{p1, p2}{Two vectors of paths.}
\item{...}{Arguments to be passed to \code{\link{normalize_path}()}.}
}
\description{
Compare two paths after normalizing them with the same separator (\code{/}).
}
\examples{
library(xfun)
same_path("~/foo", file.path(Sys.getenv("HOME"), "foo"))
}
xfun/man/rev_check.Rd 0000644 0001762 0000144 00000014014 13606702707 014235 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/revcheck.R
\name{rev_check}
\alias{rev_check}
\alias{compare_Rcheck}
\title{Run \command{R CMD check} on the reverse dependencies of a package}
\usage{
rev_check(
pkg,
which = "all",
recheck = FALSE,
ignore = NULL,
update = TRUE,
src = file.path(src_dir, pkg),
src_dir = getOption("xfun.rev_check.src_dir")
)
compare_Rcheck(status_only = FALSE, output = "00check_diffs.md")
}
\arguments{
\item{pkg}{The package name.}
\item{which}{Which types of reverse dependencies to check. See
\code{tools::\link[tools]{package_dependencies}()} for possible values. The
special value \code{'hard'} means the hard dependencies, i.e.,
\code{c('Depends', 'Imports', 'LinkingTo')}.}
\item{recheck}{Whether to only check the failed packages from last time. By
default, if there are any \file{*.Rcheck} directories, \code{recheck} will
be automatically set to \code{TRUE} if missing.}
\item{ignore}{A vector of package names to be ignored in \command{R CMD
check}. If this argument is missing and a file \file{00ignore} exists, the
file will be read as a character vector and passed to this argument.}
\item{update}{Whether to update all packages before the check.}
\item{src}{The path of the source package directory.}
\item{src_dir}{The parent directory of the source package directory. This can
be set in a global option if all your source packages are under a common
parent directory.}
\item{status_only}{If \code{TRUE}, only compare the final statuses of the
checks (the last line of \file{00check.log}), and delete \file{*.Rcheck}
and \file{*.Rcheck2} if the statuses are identical, otherwise write out the
full diffs of the logs. If \code{FALSE}, compare the full logs under
\file{*.Rcheck} and \file{*.Rcheck2}.}
\item{output}{The output Markdown file to which the diffs in check logs will
be written. If the \pkg{markdown} package is available, the Markdown file
will be converted to HTML, so you can see the diffs more clearly.}
}
\description{
Install the source package, figure out the reverse dependencies on CRAN,
download all of their source packages, and run \command{R CMD check} on them
in parallel.
}
\details{
Everything occurs under the current working directory, and you are
recommended to call this function under a designated directory, especially
when the number of reverse dependencies is large, because all source packages
will be downloaded to this directory, and all \file{*.Rcheck} directories
will be generated under this directory, too.
If a source tarball of the expected version has been downloaded before (under
the \file{tarball} directory), it will not be downloaded again (to save time
and bandwidth).
After a package has been checked, the associated \file{*.Rcheck} directory
will be deleted if the check was successful (no warnings or errors or notes),
which means if you see a \file{*.Rcheck} directory, it means the check
failed, and you need to take a look at the log files under that directory.
The time to finish the check is recorded for each package. As the check goes
on, the total remaing time will be roughly estimated via \code{n *
mean(times)}, where \code{n} is the number of packages remaining to be
checked, and \code{times} is a vector of elapsed time of packages that have
been checked.
If a check on a reverse dependency failed, its \file{*.Rcheck} directory will
be renamed to \file{*.Rcheck2}, and another check will be run against the
CRAN version of the package. If the logs of the two checks are the same, it
means no new problems were introduced in the package, and you can probably
ignore this particular reverse dependency. The function
\code{compare_Rcheck()} can be used to create a summary of all the
differences in the check logs under \file{*.Rcheck} and \file{*.Rcheck2}.
This will be done automatically if \code{options(xfun.rev_check.summary =
TRUE)} has been set.
A recommended workflow is to use a special directory to run
\code{rev_check()}, set the global \code{\link{options}}
\code{xfun.rev_check.src_dir} and \code{repos} in the R startup (see
\code{?\link{Startup}}) profile file \code{.Rprofile} under this directory,
and (optionally) set \code{R_LIBS_USER} in \file{.Renviron} to use a special
library path (so that your usual library will not be cluttered). Then run
\code{xfun::rev_check(pkg)} once, investigate and fix the problems or (if you
believe it was not your fault) ignore broken packages in the file
\file{00ignore}, and run \code{xfun::rev_check(pkg)} again to recheck the
failed packages. Repeat this process until all \file{*.Rcheck} directories
are gone.
As an example, I set \code{options(repos = c(CRAN =
'https://cran.rstudio.com'), xfun.rev_check.src_dir = '~/Dropbox/repo')} in
\file{.Rprofile}, and \code{R_LIBS_USER=~/R-tmp} in \file{.Renviron}. Then I
can run, for example, \code{xfun::rev_check('knitr')} repeatedly under a
special directory \file{~/Downloads/revcheck}. Reverse dependencies and their
dependencies will be installed to \file{~/R-tmp}, and \pkg{knitr} will be
installed from \file{~/Dropbox/repo/kintr}.
}
\seealso{
\code{devtools::revdep_check()} is more sophisticated, but currently
has a few major issues that affect me: (1) It always deletes the
\file{*.Rcheck} directories
(\url{https://github.com/hadley/devtools/issues/1395}), which makes it
difficult to know more information about the failures; (2) It does not
fully install the source package before checking its reverse dependencies
(\url{https://github.com/hadley/devtools/pull/1397}); (3) I feel it is
fairly difficult to iterate the check (ignore the successful packages and
only check the failed packages); by comparison, \code{xfun::rev_check()}
only requires you to run a short command repeatedly (failed packages are
indicated by the existing \file{*.Rcheck} directories, and automatically
checked again the next time).
\code{xfun::rev_check()} borrowed a very nice feature from
\code{devtools::revdep_check()}: estimating and displaying the remaining
time. This is particularly useful for packages with huge numbers of reverse
dependencies.
}
xfun/man/file_ext.Rd 0000644 0001762 0000144 00000001553 13606702706 014106 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/paths.R
\name{file_ext}
\alias{file_ext}
\alias{sans_ext}
\alias{with_ext}
\title{Manipulate filename extensions}
\usage{
file_ext(x)
sans_ext(x)
with_ext(x, ext)
}
\arguments{
\item{x}{A character of file paths.}
\item{ext}{A vector of new extensions.}
}
\value{
A character vector of the same length as \code{x}.
}
\description{
Functions to obtain (\code{file_ext()}), remove (\code{sans_ext()}), and
change (\code{with_ext()}) extensions in filenames.
}
\details{
\code{file_ext()} is a wrapper of \code{tools::\link{file_ext}()}.
\code{sans_ext()} is a wrapper of \code{tools::\link{file_path_sans_ext}()}.
}
\examples{
library(xfun)
p = c("abc.doc", "def123.tex", "path/to/foo.Rmd")
file_ext(p)
sans_ext(p)
with_ext(p, ".txt")
with_ext(p, c(".ppt", ".sty", ".Rnw"))
with_ext(p, "html")
}
xfun/man/normalize_path.Rd 0000644 0001762 0000144 00000000705 13606702707 015322 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/paths.R
\name{normalize_path}
\alias{normalize_path}
\title{Normalize paths}
\usage{
normalize_path(path, winslash = "/", must_work = FALSE)
}
\arguments{
\item{path, winslash, must_work}{Arguments passed to
\code{\link{normalizePath}()}.}
}
\description{
A wrapper function of \code{normalizePath()} with different defaults.
}
\examples{
library(xfun)
normalize_path("~")
}
xfun/man/numbers_to_words.Rd 0000644 0001762 0000144 00000002363 13606702707 015703 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/string.R
\name{numbers_to_words}
\alias{numbers_to_words}
\alias{n2w}
\title{Convert numbers to English words}
\usage{
numbers_to_words(x, cap = FALSE, hyphen = TRUE, and = FALSE)
n2w(x, cap = FALSE, hyphen = TRUE, and = FALSE)
}
\arguments{
\item{x}{A numeric vector. Values should be integers. The absolute values
should be less than \code{1e15}.}
\item{cap}{Whether to capitalize the first letter of the word. This can be
useful when the word is at the beginning of a sentence. Default is
\code{FALSE}.}
\item{hyphen}{Whether to insert hyphen (-) when the number is between 21 and
99 (except 30, 40, etc.).}
\item{and}{Whether to insert \code{and} between hundreds and tens, e.g.,
write 110 as \dQuote{one hundred and ten} if \code{TRUE} instead of
\dQuote{one hundred ten}.}
}
\value{
A character vector.
}
\description{
This can be helpful when writing reports with \pkg{knitr}/\pkg{rmarkdown} if
we want to print numbers as English words in the output. The function
\code{n2w()} is an alias of \code{numbers_to_words()}.
}
\examples{
library(xfun)
n2w(0, cap = TRUE)
n2w(0:121, and = TRUE)
n2w(1e+06)
n2w(1e+11 + 12345678)
n2w(-987654321)
n2w(1e+15 - 1)
}
\author{
Daijiang Li
}
xfun/man/split_lines.Rd 0000644 0001762 0000144 00000001412 13606702707 014627 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/string.R
\name{split_lines}
\alias{split_lines}
\title{Split a character vector by line breaks}
\usage{
split_lines(x)
}
\arguments{
\item{x}{A character vector.}
}
\value{
All elements of the character vector are split by \code{'\n'} into
lines.
}
\description{
Call \code{unlist(strsplit(x, '\n'))} on the character vector \code{x} and
make sure it works in a few edge cases: \code{split_lines('')} returns
\code{''} instead of \code{character(0)} (which is the returned value of
\code{strsplit('', '\n')}); \code{split_lines('a\n')} returns \code{c('a',
'')} instead of \code{c('a')} (which is the returned value of
\code{strsplit('a\n', '\n')}.
}
\examples{
xfun::split_lines(c("a", "b\nc"))
}
xfun/man/in_dir.Rd 0000644 0001762 0000144 00000000740 13606702706 013550 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/utils.R
\name{in_dir}
\alias{in_dir}
\title{Evaluate an expression under a specified working directory}
\usage{
in_dir(dir, expr)
}
\arguments{
\item{dir}{Path to a directory.}
\item{expr}{An R expression.}
}
\description{
Change the working directory, evaluate the expression, and restore the
working directory.
}
\examples{
library(xfun)
in_dir(tempdir(), {
print(getwd())
list.files()
})
}
xfun/man/raw_string.Rd 0000644 0001762 0000144 00000001741 13606702707 014466 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/data-structure.R
\name{raw_string}
\alias{raw_string}
\alias{print.xfun_raw_string}
\title{Print a character vector in its raw form}
\usage{
raw_string(x)
\method{print}{xfun_raw_string}(x, ...)
}
\arguments{
\item{x}{For \code{raw_string()}, a character vector. For the print method,
the \code{raw_string()} object.}
\item{...}{Other arguments (currently ignored).}
}
\description{
The function \code{raw_string()} assigns the class \code{xfun_raw_string} to
the character vector, and the corresponding printing function
\code{print.xfun_raw_string()} uses \code{cat(x, sep = '\n')} to write the
character vector to the console, which will suppress the leading indices
(such as \code{[1]}) and double quotes, and it may be easier to read the
characters in the raw form (especially when there are escape sequences).
}
\examples{
library(xfun)
raw_string(head(LETTERS))
raw_string(c("a \"b\"", "hello\tworld!"))
}
xfun/DESCRIPTION 0000644 0001762 0000144 00000002154 13607036513 012746 0 ustar ligges users Package: xfun
Type: Package
Title: Miscellaneous Functions by 'Yihui Xie'
Version: 0.12
Authors@R: c(
person("Yihui", "Xie", role = c("aut", "cre", "cph"), email = "xie@yihui.name", comment = c(ORCID = "0000-0003-0645-5666")),
person("Daijiang", "Li", role = "ctb"),
person("Xianying", "Tan", role = "ctb"),
person("Salim", "Brüggemann", role = "ctb", email = "salim-b@pm.me"),
person()
)
Description: Miscellaneous functions commonly used in other packages maintained by 'Yihui Xie'.
Imports: stats, tools
Suggests: testit, parallel, rstudioapi, tinytex, mime, markdown, knitr,
htmltools, base64enc, remotes, rmarkdown
License: MIT + file LICENSE
URL: https://github.com/yihui/xfun
BugReports: https://github.com/yihui/xfun/issues
Encoding: UTF-8
LazyData: true
RoxygenNote: 7.0.2
VignetteBuilder: knitr
NeedsCompilation: no
Packaged: 2020-01-13 04:22:04 UTC; yihui
Author: Yihui Xie [aut, cre, cph] (),
Daijiang Li [ctb],
Xianying Tan [ctb],
Salim Brüggemann [ctb]
Maintainer: Yihui Xie
Repository: CRAN
Date/Publication: 2020-01-13 09:50:03 UTC
xfun/build/ 0000755 0001762 0000144 00000000000 13606770153 012341 5 ustar ligges users xfun/build/vignette.rds 0000644 0001762 0000144 00000000323 13606770153 014676 0 ustar ligges users b```b`fcb`b2 1#'H+MAwS+)O)M.S(W )ES ֞Q&1 a(DXT%iewI-HK î?/S+`zPAհe
,s\ܠL t7`~r=xA $Gs=ʕXVr7 -/ xfun/tests/ 0000755 0001762 0000144 00000000000 13357673770 012416 5 ustar ligges users xfun/tests/test-all.R 0000644 0001762 0000144 00000000041 13326436067 014251 0 ustar ligges users library(testit)
test_pkg('xfun')
xfun/tests/testit/ 0000755 0001762 0000144 00000000000 13544666311 013721 5 ustar ligges users xfun/tests/testit/test-markdown.R 0000644 0001762 0000144 00000003453 13544667771 016663 0 ustar ligges users library(testit)
assert('prose_index() works', {
x = c('a', '```', 'b', '```', 'c')
out = c(1L, 5L)
(prose_index(x) %==% out)
x = c('a', '````', '```r', '1+1', '```', '````', 'c')
out = c(1L, 7L)
(prose_index(x) %==% out)
x = c('a', '``', 'b', '``', 'c')
out = seq_along(x)
(prose_index(x) %==% out)
# a character vector of length zero
x = character()
out = integer()
(prose_index(x) %==% out)
# one backbrick
x = c('`', 'a', '`')
out = seq_along(x)
(prose_index(x) %==% out)
# two backbrick
x = c('``', 'a', '``')
out = seq_along(x)
(prose_index(x) %==% out)
# no code fences
x = c('a', 'b')
out = c(1L, 2L)
(prose_index(x) %==% out)
# two code fences
x = c('```', 'b', '```', '```', 'd', '```')
out = integer()
(prose_index(x) %==% out)
# if the code fences are not balanced
x = c('a', '```', 'b', '``', 'c')
out = seq_along(x)
(has_warning(prose_index(x)))
(prose_index(x) %==% out)
})
assert('protect_math() puts inline math expressions in backticks', {
(protect_math('$x$') %==% '`\\(x\\)`')
(protect_math('hi $x$ a') %==% 'hi `\\(x\\)` a')
(protect_math('$ a $') %==% '$ a $')
(protect_math(' $a$') %==% ' `\\(a\\)`')
(protect_math('$ x$') %==% '$ x$')
(protect_math('$x $') %==% '$x $')
(protect_math('b$a$') %==% 'b$a$') # no space before $; unlikely to be math
(protect_math('`$a$`') %==% '`$a$`')
(protect_math('hi $x$9') %==% 'hi $x$9')
(protect_math('$500 $600') %==% '$500 $600')
(protect_math('$$a$$') %==% '`$$a$$`')
(protect_math('$$a$') %==% '$$a$')
(protect_math('hi $$\alpha$$') %==% 'hi `$$\alpha$$`')
(protect_math('hi $$\alpha $$') %==% 'hi $$\alpha $$')
(protect_math('hi $$ \alpha$$') %==% 'hi $$ \alpha$$')
(protect_math('hi $$\alpha$$ and $$ \alpha$$') %==% 'hi `$$\alpha$$` and $$ \alpha$$')
})
xfun/tests/testit/test-paths.R 0000644 0001762 0000144 00000001415 13377267011 016137 0 ustar ligges users library(testit)
assert('with_ext works for corner cases', {
(with_ext(character(), 'abc') %==% character())
(with_ext('abc', character()) %==% 'abc')
(with_ext(NA_character_, 'abc') %==% NA_character_)
(has_error(with_ext('abc', NA_character_)))
(with_ext('abc', c('d', 'e')) %==% c('abc.d', 'abc.e'))
(has_error(with_ext(c('a', 'b'), c('d', 'e', 'f'))))
(with_ext(c('a', 'b'), c('d', 'e')) %==% c('a.d', 'b.e'))
(with_ext(c('a', 'b'), c('d')) %==% c('a.d', 'b.d'))
(with_ext(c('a', 'b', 'c'), c('', '.d', 'e.e')) %==% c('a', 'b.d', 'c.e.e'))
})
assert('same_path works', {
(is.na(same_path('~/foo', NA_character_)))
(is.na(same_path(NA_character_, '~/foo')))
(same_path('~/foo', file.path(Sys.getenv('HOME'), 'foo')))
(!same_path(tempdir(), 'foo'))
})
xfun/tests/testit/test-string.R 0000644 0001762 0000144 00000002453 13606703165 016331 0 ustar ligges users library(testit)
assert('n2w converts numbers to words', {
(n2w(0) %==% 'zero')
# cap capitalizes the first letter
(n2w(0, cap = TRUE) %==% 'Zero')
# hyphen adds '-' for 21-99 (except 30, 40, ...)
(n2w(21, cap = TRUE, hyphen = TRUE) %==% 'Twenty-one')
(n2w(21, cap = TRUE, hyphen = FALSE) %==% 'Twenty one')
# x can be negative integers
(n2w(-21, cap = TRUE, hyphen = TRUE) %==% 'Minus twenty-one')
(n2w(-21, cap = TRUE, hyphen = FALSE) %==% 'Minus twenty one')
# and controls whether to have 'add' between hundreds and double digits
(n2w(121, and = FALSE) %==% 'one hundred twenty-one')
(n2w(121, and = TRUE) %==% 'one hundred and twenty-one')
# x can be a vector with length > 1
(n2w(c(10, 13, 99, 1e6)) %==% c('ten', 'thirteen', 'ninety-nine', 'one million'))
# the number should be less than 1e15
(has_error(n2w(1e15)))
})
assert('split_lines() splits a character vector into lines', {
(split_lines('a') %==% 'a')
(split_lines('') %==% '')
(split_lines(NULL) %==% NULL)
(split_lines('a\n') %==% c('a', ''))
(split_lines(c('a', 'b\nc')) %==% c('a', 'b', 'c'))
(split_lines(c('', '\n')) %==% c('', '', ''))
(split_lines('a\nb') %==% c('a', 'b'))
(split_lines('a\nb\n\n') %==% c('a', 'b', '', ''))
(split_lines(c('a\nb', '', ' ', 'c')) %==% c('a', 'b', '', ' ', 'c'))
})
xfun/tests/testit/test-encoding.R 0000644 0001762 0000144 00000002130 13377266446 016614 0 ustar ligges users library(testit)
assert('native_encode() warns against characters that cannot be represented in native encoding', {
latin_str = 'fa\u00e7ile'
cn_str = '\u4e2d\u6587\u5b57\u7b26'
# I bet only one or none of latin1 & GBK will be the native encoding in windows
(!is_windows() || has_warning({native_encode(cn_str); native_encode(latin_str)}))
gb2312_raw = as.raw(c(0xd6, 0xd0, 0xce, 0xc4, 0xd7, 0xd6, 0xb7, 0xfb))
is_gb2312_native = identical(gb2312_raw, charToRaw(enc2native(cn_str)))
no_need_test = !(is_windows() && is_gb2312_native)
cn_str_native = native_encode(cn_str)
(no_need_test || Encoding(cn_str_native) %==% 'unknown')
(no_need_test || charToRaw(cn_str_native) %==% gb2312_raw)
})
assert('is_ascii() can identify ascii strings', {
ascii_str = c('aaa', 'bbb', 'ccc')
latin_str = 'fa\u00e7ile'
cn_str = '\u4e2d\u6587\u5b57\u7b26'
mixed_str = c(ascii_str, latin_str)
(is_ascii(ascii_str) %==% c(TRUE, TRUE, TRUE))
(!is_ascii(latin_str))
(!is_ascii(cn_str))
(is_ascii(mixed_str) %==% c(TRUE, TRUE, TRUE, FALSE))
(is_ascii(c(NA_character_, 'a')) %==% c(NA, TRUE))
})
xfun/tests/testit/test-data-structure.R 0000644 0001762 0000144 00000002374 13544660035 017773 0 ustar ligges users library(testit)
assert('strict_list() is really strict', {
s_list = strict_list(aaa = 1:3, bbb = c('hey', 'mom'))
# the class name better not be changed in the future
(inherits(s_list, 'xfun_strict_list'))
# `$` returns the expected value if the name provided is complete
(s_list$aaa %==% 1:3)
# but partial match is prohibited
(s_list$a %==% NULL)
# `[` will return a list - will it be better if returns a strict list?
(inherits(s_list['aaa'], 'list'))
# and add an element won't change the class by accident
s_list$ccc = 4
s_list$ddd = 'abcd'
(inherits(s_list, 'xfun_strict_list'))
})
assert('as_strict_list() converts a list to a strict list', {
normal_list = list(aaa = 1:3, bbb = c('hey', 'mom'))
s_list = as_strict_list(normal_list)
# does the strict list have the same length as the normal list?
(length(normal_list) %==% length(s_list))
# is the converted strict list equal to the same object created by `strict_list()`?
s_list = strict_list(aaa = 1:3, bbb = c('hey', 'mom'))
(as_strict_list(normal_list) %==% s_list)
})
assert('raw_string() prints as expected', {
rs = raw_string(c('a "b"', 'hello\tworld!'))
(inherits(rs, 'xfun_raw_string'))
output = capture.output(rs)
(output %==% c('a "b"', 'hello\tworld!'))
})
xfun/tests/testit/test-packages.R 0000644 0001762 0000144 00000000522 13377264552 016603 0 ustar ligges users library(testit)
assert("loadable() works", {
(loadable("base"))
# (loadable("base", new_session = TRUE))
(loadable("base", strict = FALSE))
(has_error(loadable(c("base", "base2"))))
(has_error(loadable(character())))
(!loadable("#base"))
# (!loadable("#base", new_session = TRUE))
(!loadable("#base", strict = FALSE))
})
xfun/tests/testit/test-json.R 0000644 0001762 0000144 00000001372 13377266145 016002 0 ustar ligges users library(testit)
assert("tojson() works", {
(tojson(NULL) %==% "null")
(tojson(list()) %==% "{}")
(has_error(tojson(Sys.Date())))
(has_error(tojson(NA)))
(tojson(NA_character_) %==% '"NA"')
(tojson(1:10) %==% "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]")
(tojson(TRUE) %==% "true")
(tojson(FALSE) %==% "false")
x = list(a = 1, b = list(c = 1:3, d = "abc"))
out = '{\n"a": 1,\n"b": {\n"c": [1, 2, 3],\n"d": "abc"\n}\n}'
(tojson(x) %==% out)
x = list(c("a", "b"), 1:5, TRUE)
out = '[["a", "b"], [1, 2, 3, 4, 5], true]'
(tojson(x) %==% out)
JS = function(x) structure(x, class = "JS_EVAL")
x = list(a = 1:5, b = JS("function() {return true;}"))
out = '{\n"a": [1, 2, 3, 4, 5],\n"b": function() {return true;}\n}'
(tojson(x) %==% out)
})
xfun/tests/testit/test-io.R 0000644 0001762 0000144 00000002544 13377266511 015437 0 ustar ligges users library(testit)
assert('invalid_utf8() works and respects NA', {
(invalid_utf8(character()) %==% integer())
x = 'fa\xE7ile'; Encoding(x) = 'latin1'
(invalid_utf8(c('aaa', x, NA_character_, '', '\u4e2d\u6587')) %==% 2L)
})
assert('read/write_utf8() works', {
ascii_txt = c('aa', 'bb', 'cc')
utf8_txt = c('\u4e2d\u6587', '\u5927\u5bb6\u597d', '\u65e9\u996d')
latin_txt = local({x = 'fa\xE7ile'; Encoding(x) = 'latin1'; x})
mixed_txt = c(ascii_txt, latin_txt, utf8_txt)
write_utf8(ascii_txt, con = (ascii_file = tempfile()))
write_utf8(utf8_txt, con = (utf8_file = tempfile()))
write_utf8(latin_txt, con = (latin_file = tempfile()))
write_utf8(mixed_txt, con = (mixed_file = tempfile()))
(read_utf8(ascii_file) %==% ascii_txt)
(read_utf8(utf8_file) %==% utf8_txt)
(read_utf8(latin_file) %==% latin_txt) # identical will not compare Encoding
(Encoding(read_utf8(latin_file)) %==% 'UTF-8')
(read_utf8(mixed_file) %==% mixed_txt)
(Encoding(read_utf8(mixed_file)) %==% c(rep('unknown', 3), rep('UTF-8', 4)))
mixed_file2 = tempfile()
local({
opts = options(encoding = 'native.enc'); on.exit(options(opts), add = TRUE)
writeLines(mixed_txt, con = mixed_file2, useBytes = TRUE)
})
(suppressWarnings(read_utf8(mixed_file2)[4] != mixed_txt[4]))
has_warning(read_utf8(mixed_file2))
has_error(read_utf8(mixed_file2, error = TRUE))
})
xfun/tests/testit/test-utils.R 0000644 0001762 0000144 00000000744 13373640067 016166 0 ustar ligges users library(testit)
assert('attr() is strict', {
z = structure(list(a = 1), foo = 2)
(attr(z, 'foo') %==% 2)
(attr(z, 'f') %==% NULL)
})
assert('in_dir() preserves the working directory', {
owd = getwd()
in_dir('.', setwd(tempdir()))
(same_path(owd, getwd()))
})
assert('stringsAsFactors() makes sure strings are not converted to factors', {
f = function() {
strings_please()
data.frame(x = letters[1:4], y = factor(letters[1:4]))
}
is.character(f()[, 1])
})
xfun/vignettes/ 0000755 0001762 0000144 00000000000 13606770153 013252 5 ustar ligges users xfun/vignettes/xfun.Rmd 0000644 0001762 0000144 00000020147 13563361006 014675 0 ustar ligges users ---
title: An Introduction to xfun
subtitle: A Collection of Miscellaneous Functions
author: "Yihui Xie"
date: "`r Sys.Date()`"
slug: xfun
githubEditURL: https://github.com/yihui/xfun/edit/master/vignettes/xfun.Rmd
output:
knitr:::html_vignette:
toc: yes
vignette: >
%\VignetteIndexEntry{An Introduction to xfun}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
```{r setup, include=FALSE}
library(xfun)
```
After writing about 20 R packages, I found I had accumulated several utility functions that I used across different packages, so I decided to extract them into a separate package. Previously I had been using the evil triple-colon `:::` to access these internal utility functions. Now with **xfun**, these functions have been exported, and more importantly, documented. It should be better to use them under the sun instead of in the dark.
This page shows examples of a subset of functions in this package. For a full list of functions, see the help page `help(package = 'xfun')`. The source package is available on Github: https://github.com/yihui/xfun.
## No more partial matching for lists!
I have been bitten many times by partial matching in lists, e.g., when I want `x$a` but the element `a` does not exist in the list `x`, it returns the value `x$abc` if `abc` exists in `x`. This is [very annoying to me](https://twitter.com/xieyihui/status/782462926862954496) which is why I created strict lists. A strict list is a list for which the partial matching of the `$` operator is disabled. The functions `xfun::strict_list()` and `xfun::as_strict_list()` are the equivalents to `base::list()` and `base::as.list()` respectively which always return as strict list, e.g.,
```{r}
library(xfun)
(z = strict_list(aaa = "I am aaa", b = 1:5))
z$a # NULL (strict matching)
z$aaa # I am aaa
z$b
z$c = "you can create a new element"
z2 = unclass(z) # a normal list
z2$a # partial matching
z3 = as_strict_list(z2) # a strict list again
z3$a # NULL (strict matching) again!
```
Similarly, the default partial matching in `attr()` can be annoying, too. The function `xfun::attr()` is simply a shorthand of `attr(..., exact = TRUE)`.
I want it, or I do not want. There is no "I probably want".
## Output character vectors for human eyes
When R prints a character vector, your eyes may be distracted by the indices like `[1]`, double quotes, and escape sequences. To see a character vector in its "raw" form, you can use `cat(..., sep = '\n')`. The function `raw_string()` marks a character vector as "raw", and the corresponding printing function will call `cat(sep = '\n')` to print the character vector to the console.
```{r comment=''}
library(xfun)
raw_string(head(LETTERS))
(x = c("a \"b\"", "hello\tworld!"))
raw_string(x) # this is more likely to be what you want to see
```
## Print the content of a text file
I have used `paste(readLines('foo'), collapse = '\n')` many times before I decided to write a simple wrapper function `xfun::file_string()`. This function also makes use of `raw_string()`, so you can see the content of a file in the console as a side-effect, e.g.,
```{r comment=''}
f = system.file("LICENSE", package = "xfun")
xfun::file_string(f)
as.character(xfun::file_string(f)) # essentially a character string
```
## Search and replace strings in files
I can never remember how to properly use `grep` or `sed` to search and replace strings in multiple files. My favorite IDE, RStudio, has not provided this feature yet (you can only search and replace in the currently opened file). Therefore I did a quick and dirty implementation in R, including functions `gsub_files()`, `gsub_dir()`, and `gsub_ext()`, to search and replace strings in multiple files under a directory. Note that the files are assumed to be encoded in UTF-8. If you do not use UTF-8, we cannot be friends. Seriously.
All functions are based on `gsub_file()`, which performs searching and replacing in a single file, e.g.,
```{r comment=''}
library(xfun)
f = tempfile()
writeLines(c("hello", "world"), f)
gsub_file(f, "world", "woRld", fixed = TRUE)
file_string(f)
```
The function `gsub_dir()` is very flexible: you can limit the list of files by MIME types, or extensions. For example, if you want to do substitution in text files, you may use `gsub_dir(..., mimetype = '^text/')`.
**WARNING**: Before using these functions, make sure that you have backed up your files, or version control your files. The files will be modified in-place. If you do not back up or use version control, there is no chance to regret.
## Manipulate filename extensions
Functions `file_ext()` and `sans_ext()` are based on functions in **tools**. The function `with_ext()` adds or replaces extensions of filenames, and it is vectorized.
```{r}
library(xfun)
p = c("abc.doc", "def123.tex", "path/to/foo.Rmd")
file_ext(p)
sans_ext(p)
with_ext(p, ".txt")
with_ext(p, c(".ppt", ".sty", ".Rnw"))
with_ext(p, "html")
```
## Types of operating systems
The series of functions `is_linux()`, `is_macos()`, `is_unix()`, and `is_windows()` test the types of the OS, using the information from `.Platform` and `Sys.info()`, e.g.,
```{r}
xfun::is_macos()
xfun::is_unix()
xfun::is_linux()
xfun::is_windows()
```
## Loading and attaching packages
Oftentimes I see users attach a series of packages in the beginning of their scripts by repeating `library()` multiple times. This could be easily vectorized, and the function `xfun::pkg_attach()` does this job. For example,
```{r eval=FALSE}
library(testit)
library(parallel)
library(tinytex)
library(mime)
```
is equivalent to
```{r eval=FALSE}
xfun::pkg_attach(c('testit', 'parallel', 'tinytex', 'mime'))
```
I also see scripts that contain code to install a package if it is not available, e.g.,
```{r eval=FALSE}
if (!requireNamespace('tinytex')) install.packages('tinytex')
library(tinytex)
```
This could be done via
```{r eval=FALSE}
xfun::pkg_attach2('tinytex')
```
The function `pkg_attach2()` is a shorthand of `pkg_attach(..., install = TRUE)`, which means if a package is not available, install it. This function can also deal with multiple packages.
The function `loadable()` tests if a package is loadable.
## Read/write files in UTF-8
Functions `read_utf8()` and `write_utf8()` can be used to read/write files in UTF-8. They are simple wrappers of `readLines()` and `writeLines()`.
## Convert numbers to English words
The function `numbers_to_words()` (or `n2w()` for short) converts numbers to English words.
```{r}
n2w(0, cap = TRUE)
n2w(seq(0, 121, 11), and = TRUE)
n2w(1e+06)
n2w(1e+11 + 12345678)
n2w(-987654321)
n2w(1e+15 - 1)
```
## Check reverse dependencies of a package
Running `R CMD check` on the reverse dependencies of **knitr** and **rmarkdown** is my least favorite thing in developing R packages, because the numbers of their reverse dependencies are huge. The function `rev_check()` reflects some of my past experience in this process. I think I have automated it as much as possible, and made it as easy as possible to discover possible new problems introduced by the current version of the package (compared to the CRAN version). Finally I can just sit back and let it run.
## Input a character vector into the RStudio source editor
The function `rstudio_type()` inputs characters in the RStudio source editor as if they were typed by a human. I came up with the idea when preparing my talk for rstudio::conf 2018 ([see this post](https://yihui.org/en/2018/03/blogdown-video-rstudio-conf/) for more details).
## Print session information
Since I have never been fully satisfied by the output of `sessionInfo()`, I tweaked it to make it more useful in my use cases. For example, it is rarely useful to print out the names of base R packages, or information about the matrix products / BLAS / LAPACK. Oftentimes I want additional information in the session information, such as the Pandoc version when **rmarkdown** is used. The function `session_info()` tweaks the output of `sessionInfo()`, and makes it possible for other packages to append information in the output of `session_info()`.
You can choose to print out the versions of only the packages you specify, e.g.,
```{r}
xfun::session_info(c('xfun', 'rmarkdown', 'knitr', 'tinytex'), dependencies = FALSE)
```
xfun/R/ 0000755 0001762 0000144 00000000000 13574507516 011450 5 ustar ligges users xfun/R/revcheck.R 0000644 0001762 0000144 00000047046 13606173027 013370 0 ustar ligges users #' Run \command{R CMD check} on the reverse dependencies of a package
#'
#' Install the source package, figure out the reverse dependencies on CRAN,
#' download all of their source packages, and run \command{R CMD check} on them
#' in parallel.
#'
#' Everything occurs under the current working directory, and you are
#' recommended to call this function under a designated directory, especially
#' when the number of reverse dependencies is large, because all source packages
#' will be downloaded to this directory, and all \file{*.Rcheck} directories
#' will be generated under this directory, too.
#'
#' If a source tarball of the expected version has been downloaded before (under
#' the \file{tarball} directory), it will not be downloaded again (to save time
#' and bandwidth).
#'
#' After a package has been checked, the associated \file{*.Rcheck} directory
#' will be deleted if the check was successful (no warnings or errors or notes),
#' which means if you see a \file{*.Rcheck} directory, it means the check
#' failed, and you need to take a look at the log files under that directory.
#'
#' The time to finish the check is recorded for each package. As the check goes
#' on, the total remaing time will be roughly estimated via \code{n *
#' mean(times)}, where \code{n} is the number of packages remaining to be
#' checked, and \code{times} is a vector of elapsed time of packages that have
#' been checked.
#'
#' If a check on a reverse dependency failed, its \file{*.Rcheck} directory will
#' be renamed to \file{*.Rcheck2}, and another check will be run against the
#' CRAN version of the package. If the logs of the two checks are the same, it
#' means no new problems were introduced in the package, and you can probably
#' ignore this particular reverse dependency. The function
#' \code{compare_Rcheck()} can be used to create a summary of all the
#' differences in the check logs under \file{*.Rcheck} and \file{*.Rcheck2}.
#' This will be done automatically if \code{options(xfun.rev_check.summary =
#' TRUE)} has been set.
#'
#' A recommended workflow is to use a special directory to run
#' \code{rev_check()}, set the global \code{\link{options}}
#' \code{xfun.rev_check.src_dir} and \code{repos} in the R startup (see
#' \code{?\link{Startup}}) profile file \code{.Rprofile} under this directory,
#' and (optionally) set \code{R_LIBS_USER} in \file{.Renviron} to use a special
#' library path (so that your usual library will not be cluttered). Then run
#' \code{xfun::rev_check(pkg)} once, investigate and fix the problems or (if you
#' believe it was not your fault) ignore broken packages in the file
#' \file{00ignore}, and run \code{xfun::rev_check(pkg)} again to recheck the
#' failed packages. Repeat this process until all \file{*.Rcheck} directories
#' are gone.
#'
#' As an example, I set \code{options(repos = c(CRAN =
#' 'https://cran.rstudio.com'), xfun.rev_check.src_dir = '~/Dropbox/repo')} in
#' \file{.Rprofile}, and \code{R_LIBS_USER=~/R-tmp} in \file{.Renviron}. Then I
#' can run, for example, \code{xfun::rev_check('knitr')} repeatedly under a
#' special directory \file{~/Downloads/revcheck}. Reverse dependencies and their
#' dependencies will be installed to \file{~/R-tmp}, and \pkg{knitr} will be
#' installed from \file{~/Dropbox/repo/kintr}.
#' @param pkg The package name.
#' @param which Which types of reverse dependencies to check. See
#' \code{tools::\link[tools]{package_dependencies}()} for possible values. The
#' special value \code{'hard'} means the hard dependencies, i.e.,
#' \code{c('Depends', 'Imports', 'LinkingTo')}.
#' @param recheck Whether to only check the failed packages from last time. By
#' default, if there are any \file{*.Rcheck} directories, \code{recheck} will
#' be automatically set to \code{TRUE} if missing.
#' @param ignore A vector of package names to be ignored in \command{R CMD
#' check}. If this argument is missing and a file \file{00ignore} exists, the
#' file will be read as a character vector and passed to this argument.
#' @param update Whether to update all packages before the check.
#' @param src The path of the source package directory.
#' @param src_dir The parent directory of the source package directory. This can
#' be set in a global option if all your source packages are under a common
#' parent directory.
#' @seealso \code{devtools::revdep_check()} is more sophisticated, but currently
#' has a few major issues that affect me: (1) It always deletes the
#' \file{*.Rcheck} directories
#' (\url{https://github.com/hadley/devtools/issues/1395}), which makes it
#' difficult to know more information about the failures; (2) It does not
#' fully install the source package before checking its reverse dependencies
#' (\url{https://github.com/hadley/devtools/pull/1397}); (3) I feel it is
#' fairly difficult to iterate the check (ignore the successful packages and
#' only check the failed packages); by comparison, \code{xfun::rev_check()}
#' only requires you to run a short command repeatedly (failed packages are
#' indicated by the existing \file{*.Rcheck} directories, and automatically
#' checked again the next time).
#'
#' \code{xfun::rev_check()} borrowed a very nice feature from
#' \code{devtools::revdep_check()}: estimating and displaying the remaining
#' time. This is particularly useful for packages with huge numbers of reverse
#' dependencies.
#' @export
rev_check = function(
pkg, which = 'all', recheck = FALSE, ignore = NULL, update = TRUE,
src = file.path(src_dir, pkg), src_dir = getOption('xfun.rev_check.src_dir')
) {
if (length(src) != 1 || !dir.exists(src)) stop(
'The package source dir (the "src" argument) must be an existing directory'
)
message('Installing the source package ', src)
install_dir(path.expand(src))
db = available.packages(type = 'source')
unlink('*.Rcheck2', recursive = TRUE)
dirs = list.files('.', '.+[.]Rcheck$')
if (missing(recheck)) recheck = length(dirs) > 0
pkgs = if (recheck) {
gsub('.Rcheck$', '', dirs)
} else {
res = check_deps(pkg, db, which)
pkgs_up = NULL
if (update) {
message('Updating all R packages...')
pkgs_up = intersect(old.packages(checkBuilt = TRUE)[, 'Package'], res$install)
pkg_install(pkgs_up)
}
message('Installing dependencies of reverse dependencies')
res$install = setdiff(res$install, ignore_deps())
res$install = setdiff(res$install, pkgs_up) # don't install pkgs that were just updated
print(system.time({
pkg_install(unlist(plapply(res$install, function(p) if (!loadable(p)) p)))
}))
res$check
}
lib_cran = './library-cran'
on.exit(unlink(lib_cran, recursive = TRUE), add = TRUE)
dir.create(lib_cran, showWarnings = FALSE)
pkg_install(pkg, lib = lib_cran) # the CRAN version of the package
f = tempfile('check-done', fileext = '.rds')
l = tempfile('check-lock'); on.exit(unlink(c(f, l)), add = TRUE)
n = length(pkgs)
if (n == 0) {
message('No reverse dependencies to be checked for the package ', pkg); return()
}
if (missing(ignore) && file.exists('00ignore')) ignore = scan('00ignore', 'character')
if (length(ignore)) {
message('Ignoring packages: ', paste(ignore, collapse = ' '))
unlink(sprintf('%s.Rcheck', ignore), recursive = TRUE)
pkgs = setdiff(pkgs, ignore)
}
message('Downloading tarballs')
tars = unlist(lapply(pkgs, function(p) download_tarball(p, db, dir = 'tarball')))
tars = setNames(tars, pkgs)
t0 = Sys.time()
message('Checking ', n, ' packages: ', paste(pkgs, collapse = ' '))
res = plapply(pkgs, function(p) {
d = sprintf('%s.Rcheck', p)
if (!p %in% rownames(db)) {
message('Checking ', p, ' (aborted since it is no longer on CRAN')
unlink(d, recursive = TRUE)
return()
}
timing = function() {
# in case two packages finish at exactly the same time
while (file.exists(l)) Sys.sleep(.1)
file.create(l); on.exit(unlink(l), add = TRUE)
done = c(if (file.exists(f)) readRDS(f), p)
saveRDS(done, f)
n2 = length(setdiff(pkgs, done)) # remaining packages
t1 = Sys.time(); t2 = Sys.time() + n2 * (t1 - t0) / (n - n2)
message(
'Packages remaining: ', n2, '/', n, '; Expect to finish at ', t2,
' (', format(round(difftime(t2, t1))), ')'
)
}
if (!file.exists(z <- tars[p])) {
timing()
return(dir.create(d, showWarnings = FALSE))
}
check_it = function(args = NULL, ...) {
system2(
file.path(R.home('bin'), 'R'),
c(args, 'CMD', 'check', '--no-manual', shQuote(z)), stdout = FALSE, stderr = FALSE, ...
)
}
check_it()
if (!clean_Rcheck(d)) {
if (!dir.exists(d)) {dir.create(d); return(timing())}
# try to install missing LaTeX packages for vignettes if possible, then recheck
vigs = list.files(
file.path(d, 'vign_test', p, 'vignettes'), '[.](Rnw|Rmd)',
ignore.case = TRUE, full.names = TRUE
)
if (length(vigs) && any(file.exists(with_ext(vigs, 'log')))) {
if (!loadable('tinytex')) install.packages('tinytex')
if (tinytex:::is_tinytex()) for (vig in vigs) in_dir(dirname(vig), {
Rscript(shQuote(c('-e', 'if (grepl("[.]Rnw$", f <- commandArgs(T), ignore.case = T)) knitr::knit2pdf(f) else rmarkdown::render(f)', basename(vig))))
})
check_it()
if (clean_Rcheck(d)) return(timing())
}
# clean up the check log, and recheck with the current CRAN version of pkg
cleanup = function() in_dir(d, {
clean_log()
# so that I can easily preview it in the Finder on macOS
file.exists('00install.out') && file.rename('00install.out', '00install.log')
})
cleanup()
file.rename(d, d2 <- paste0(d, '2'))
check_it('--no-environ', env = tweak_r_libs(lib_cran))
if (!dir.exists(d)) file.rename(d2, d) else {
cleanup()
if (identical_logs(c(d, d2))) unlink(c(d, d2), recursive = TRUE)
}
}
timing()
NULL
})
if (getOption('xfun.rev_check.summary', FALSE)) {
html = compare_Rcheck(); if (isTRUE(grepl('[.]html$', html))) browseURL(html)
}
res = Filter(function(x) !is.null(x), res)
if (length(res)) res
}
# remove the OK lines in the check log
clean_log = function() {
if (!file.exists(l <- '00check.log')) return()
x = grep('^[*].+OK$', read_utf8(l), invert = TRUE, value = TRUE)
# don't want diffs in random tempdir/tempfile paths when comparing check logs
x[grep(dirname(tempdir()), x, fixed = TRUE)] = 'RANDOM TEMPDIR/TEMPFILE PATH DELETED'
writeLines(tail(x, -2), l) # remove the first 2 lines (log dir name and R version)
}
# are the check logs identical under a series of *.Rcheck directories?
identical_logs = function(dirs) {
if (length(dirs) < 2) return(FALSE)
if (!all(file.exists(logs <- file.path(dirs, '00check.log')))) return(FALSE)
x = read_utf8(logs[1])
for (i in 2:length(dirs)) if (!identical(x, read_utf8(logs[i]))) return(FALSE)
TRUE
}
# add a new library path to R_LIBS_USER
tweak_r_libs = function(new) {
x = read_first(c('.Renviron', '~/.Renviron'))
x = grep('^\\s*#', x, invert = TRUE, value = TRUE)
x = gsub('^\\s+|\\s+$', '', x)
x = x[x != '']
i = grep('^R_LIBS_USER=.+', x)
if (length(i)) {
x[i[1]] = sub('(="?)', paste0('\\1', new, .Platform$path.sep), x[i[1]])
x
} else c(paste0('R_LIBS_USER=', new), x)
}
# read the first file that exists
read_first = function(files) {
for (f in files) if (file.exists(f)) return(read_utf8(f))
}
# a shorthand of tools::package_dependencies()
pkg_dep = function(x, ...) {
if (length(x)) unique(unlist(tools::package_dependencies(x, ...)))
}
# calculate the packages required to check a package
check_deps = function(x, db = available.packages(), which = 'all') {
if (identical(which, 'hard')) which = c('Depends', 'Imports', 'LinkingTo')
# packages that reverse depend on me
x1 = pkg_dep(x, db, which, reverse = TRUE)
# only check a sample of soft reverse dependencies (useful if there are too many)
if (identical(which, 'all') && (n <- getOption('xfun.rev_check.sample', 100)) >= 0) {
x2 = pkg_dep(x, db, c('Suggests', 'Enhances'), reverse = TRUE)
if (n < length(x2)) x1 = c(setdiff(x1, x2), sample(x2, n))
}
# to R CMD check x1, I have to install all their dependencies
x2 = pkg_dep(x1, db, 'all')
# and for those dependencies, I have to install the default dependencies
x3 = pkg_dep(x2, db, recursive = TRUE)
list(check = x1, install = unique(c(x1, x2, x3)))
}
# mclapply() with a different default for mc.cores and disable prescheduling
plapply = function(X, FUN, ...) {
parallel::mclapply(
X, FUN, ..., mc.cores = getOption('mc.cores', parallel::detectCores()),
mc.preschedule = FALSE
)
}
# download the source package from CRAN
download_tarball = function(p, db = available.packages(type = 'source'), dir = '.', retry = 3) {
if (!dir.exists(dir)) dir.create(dir, recursive = TRUE)
z = file.path(dir, sprintf('%s_%s.tar.gz', p, db[p, 'Version']))
# remove other versions of the package tarball
unlink(setdiff(list.files(dir, sprintf('^%s_.+.tar.gz', p), full.names = TRUE), z))
for (i in seq_len(retry)) {
if (file.exists(z)) break
try(download.file(paste(db[p, 'Repository'], basename(z), sep = '/'), z, mode = 'wb'))
}
z
}
# allow users to specify a custom install.packages() function via the global
# option xfun.install.packages
pkg_install = function(pkgs, ...) {
if (length(pkgs) == 0) return()
install = getOption('xfun.install.packages', install.packages)
if (length(pkgs) > 1)
message('Installing ', length(pkgs), ' packages: ', paste(pkgs, collapse = ' '))
install(pkgs, ...)
}
# clean up *.Rcheck if there are no warnings, errors, or notes in the log
clean_Rcheck = function(dir, log = read_utf8(file.path(dir, '00check.log'))) {
if (length(grep('(WARNING|ERROR|NOTE)$', log)) == 0) unlink(dir, recursive = TRUE)
!dir.exists(dir)
}
#' @rdname rev_check
#' @param status_only If \code{TRUE}, only compare the final statuses of the
#' checks (the last line of \file{00check.log}), and delete \file{*.Rcheck}
#' and \file{*.Rcheck2} if the statuses are identical, otherwise write out the
#' full diffs of the logs. If \code{FALSE}, compare the full logs under
#' \file{*.Rcheck} and \file{*.Rcheck2}.
#' @param output The output Markdown file to which the diffs in check logs will
#' be written. If the \pkg{markdown} package is available, the Markdown file
#' will be converted to HTML, so you can see the diffs more clearly.
#' @export
compare_Rcheck = function(status_only = FALSE, output = '00check_diffs.md') {
if (length(dirs <- list.files('.', '.+[.]Rcheck2$')) == 0) return()
d2 = function(d) c(sub('2$', '', d), d)
logs = function(d) file.path(d2(d), '00check.log')
dirs = dirs[rowSums(matrix(file.exists(logs(dirs)), ncol = 2)) == 2]
res = NULL
if (!status_only && Sys.which('diff') == '')
warning("The command 'diff' is not available; will not calculate exact diffs in logs.")
for (d in dirs) {
f = logs(d)
if (status_only) {
status_line = function(file) {
x = tail(read_utf8(file), 1)
if (!grepl('^Status: ', x)) stop('The last line of ', file, ' is not the status.')
x
}
if (status_line(f[1]) == status_line(f[2])) {
unlink(d2(d), recursive = TRUE); next
}
}
res = c(
res, paste('##', p <- sans_ext(d)), '',
sprintf('[CRAN version](https://cran.rstudio.com/package=%s) (-) vs current version (+):\n', p),
'```diff', file_diff(f), '```', ''
)
if (length(res2 <- cran_check_page(p, NULL))) res = c(
res, 'CRAN check logs:\n\n```', unique(unlist(strsplit(res2, '\n'))), '```\n'
)
}
writeLines(res, output)
if (!loadable('markdown')) return(output)
markdown::markdownToHTML(
text = gsub('>', '+', gsub('^<', '-', res)),
output = html_file <- with_ext(output, 'html'),
header = c(
"",
"",
""
), encoding = 'UTF-8'
)
unlink(output)
html_file
}
# compute the diffs of two files; if diffs too large, dedup them
file_diff = function(files, len = 200, use_diff = Sys.which('diff') != '') {
d = if (use_diff) {
suppressWarnings(system2('diff', shQuote(files), stdout = TRUE))
} else {
c(paste('<', read_utf8(files[1])), '---', paste('>', read_utf8(files[2])))
}
if (length(d) >= len) unique(d) else d
}
# specify a list of package names to be ignored when installing all dependencies
ignore_deps = function() {
if (file.exists('00ignore_deps')) scan('00ignore_deps', 'character')
}
# download a check summary of a package from CRAN
cran_check_page = function(pkg, con = '00check-cran.log') {
u = sprintf('https://cran.rstudio.com/web/checks/check_results_%s.html', pkg)
x = read_utf8(u)
if (length(i <- grep('Check Details', x, ignore.case = TRUE)) == 0) return()
x = x[i[1]:length(x)]
x = gsub('<[^>]+>', '', x)
x = gsub(' ', ' ', x)
x = gsub('>', '>', x)
x = gsub('<', '<', x)
x = gsub('\\s+', ' ', x)
x = paste(trimws(x), collapse = '\n')
x = gsub('\n\n+', '\n\n', x)
if (length(con) == 1) writeLines(x, con) else x
}
# download CRAN check summaries of all failed packages
cran_check_pages = function() {
dirs = list.files('.', '[.]Rcheck$')
for (d in dirs) {
if (dir.exists(d)) in_dir(d, cran_check_page(gsub('[.]Rcheck$', '', d)))
}
}
# kill a R CMD check process if it has been running for more then 60 minutes
kill_long_processes = function(etime = 30) {
while (TRUE) {
if (length(pids <- list_long_processes(etime))) {
message('Killing processes: ', paste(pids, collapse = ' '))
system2('kill', pids)
}
Sys.sleep(300)
}
}
list_long_processes = function(etime = 15) {
x = system('ps -ax -o pid,etime,command | grep "Rcmd check --no-manual"', intern = TRUE)
x = grep('_[0-9.-]+[.]tar[.]gz$', trimws(x), value = TRUE)
pids = unlist(lapply(strsplit(x, '\\s+'), function(z) {
pid = z[1]; time = as.numeric(unlist(strsplit(z[2], '-|:')))
time = sum(tail(c(rep(0, 4), time), 4) * c(24 * 3600, 3600, 60, 1))
name = gsub('.*/', '', tail(z, 1))
if (time > etime * 60) setNames(pid, name)
}))
pids
}
# parse the check log for missing LaTeX packages and install them
install_missing_latex = function() {
dirs = list.files('.', '[.]Rcheck$')
pkgs = NULL
for (d in dirs) {
if (dir.exists(d)) pkgs = c(pkgs, in_dir(
d, tinytex::parse_packages('00check.log', quiet = c(TRUE, FALSE, FALSE))
))
}
tinytex::tlmgr_install(unique(pkgs))
}
# return packages that haven't been updated for X days, and can be updated on CRAN
cran_updatable = function(days = 90, maintainer = 'Yihui Xie') {
info = tools::CRAN_package_db()
pkgs = info[grep(maintainer, info$Maintainer), 'Package']
info = setNames(vector('list', length(pkgs)), pkgs)
for (p in pkgs) {
message('Processing ', p)
x = readLines(u <- sprintf('https://cran.rstudio.com/web/packages/%s/', p))
i = which(x == '
Published:
')
if (length(i) == 0) stop('Cannot find the publishing date from ', u)
d = as.Date(gsub('?td>', '', x[i[1] + 1]))
x = try(readLines(u <- sprintf('https://cran.r-project.org/src/contrib/Archive/%s/', p)))
if (inherits(x, 'try-error')) {
info[[p]] = d; next
}
r = '.+
After writing about 20 R packages, I found I had accumulated several utility functions that I used across different packages, so I decided to extract them into a separate package. Previously I had been using the evil triple-colon ::: to access these internal utility functions. Now with xfun, these functions have been exported, and more importantly, documented. It should be better to use them under the sun instead of in the dark.
This page shows examples of a subset of functions in this package. For a full list of functions, see the help page help(package = 'xfun'). The source package is available on Github: https://github.com/yihui/xfun.
No more partial matching for lists!
I have been bitten many times by partial matching in lists, e.g., when I want x$a but the element a does not exist in the list x, it returns the value x$abc if abc exists in x. This is very annoying to me which is why I created strict lists. A strict list is a list for which the partial matching of the $ operator is disabled. The functions xfun::strict_list() and xfun::as_strict_list() are the equivalents to base::list() and base::as.list() respectively which always return as strict list, e.g.,
library(xfun)(z =strict_list(aaa ="I am aaa", b =1:5))
## $aaa
## [1] "I am aaa"
##
## $b
## [1] 1 2 3 4 5
z$a # NULL (strict matching)
## NULL
z$aaa # I am aaa
## [1] "I am aaa"
z$b
## [1] 1 2 3 4 5
z$c = "you can create a new element"z2 =unclass(z) # a normal listz2$a # partial matching
## [1] "I am aaa"
z3 =as_strict_list(z2) # a strict list againz3$a # NULL (strict matching) again!
## NULL
Similarly, the default partial matching in attr() can be annoying, too. The function xfun::attr() is simply a shorthand of attr(..., exact = TRUE).
I want it, or I do not want. There is no “I probably want”.
Output character vectors for human eyes
When R prints a character vector, your eyes may be distracted by the indices like [1], double quotes, and escape sequences. To see a character vector in its “raw” form, you can use cat(..., sep = '\n'). The function raw_string() marks a character vector as “raw”, and the corresponding printing function will call cat(sep = '\n') to print the character vector to the console.
library(xfun)raw_string(head(LETTERS))
A
B
C
D
E
F
(x =c("a \"b\"", "hello\tworld!"))
[1] "a \"b\"" "hello\tworld!"
raw_string(x) # this is more likely to be what you want to see
a "b"
hello world!
Print the content of a text file
I have used paste(readLines('foo'), collapse = '\n') many times before I decided to write a simple wrapper function xfun::file_string(). This function also makes use of raw_string(), so you can see the content of a file in the console as a side-effect, e.g.,
f =system.file("LICENSE", package ="xfun")xfun::file_string(f)
YEAR: 2018
COPYRIGHT HOLDER: Yihui Xie
as.character(xfun::file_string(f)) # essentially a character string
[1] "YEAR: 2018\nCOPYRIGHT HOLDER: Yihui Xie"
Search and replace strings in files
I can never remember how to properly use grep or sed to search and replace strings in multiple files. My favorite IDE, RStudio, has not provided this feature yet (you can only search and replace in the currently opened file). Therefore I did a quick and dirty implementation in R, including functions gsub_files(), gsub_dir(), and gsub_ext(), to search and replace strings in multiple files under a directory. Note that the files are assumed to be encoded in UTF-8. If you do not use UTF-8, we cannot be friends. Seriously.
All functions are based on gsub_file(), which performs searching and replacing in a single file, e.g.,
The function gsub_dir() is very flexible: you can limit the list of files by MIME types, or extensions. For example, if you want to do substitution in text files, you may use gsub_dir(..., mimetype = '^text/').
WARNING: Before using these functions, make sure that you have backed up your files, or version control your files. The files will be modified in-place. If you do not back up or use version control, there is no chance to regret.
Manipulate filename extensions
Functions file_ext() and sans_ext() are based on functions in tools. The function with_ext() adds or replaces extensions of filenames, and it is vectorized.
The series of functions is_linux(), is_macos(), is_unix(), and is_windows() test the types of the OS, using the information from .Platform and Sys.info(), e.g.,
xfun::is_macos()
## [1] TRUE
xfun::is_unix()
## [1] TRUE
xfun::is_linux()
## [1] FALSE
xfun::is_windows()
## [1] FALSE
Loading and attaching packages
Oftentimes I see users attach a series of packages in the beginning of their scripts by repeating library() multiple times. This could be easily vectorized, and the function xfun::pkg_attach() does this job. For example,
I also see scripts that contain code to install a package if it is not available, e.g.,
if (!requireNamespace("tinytex")) install.packages("tinytex")library(tinytex)
This could be done via
xfun::pkg_attach2("tinytex")
The function pkg_attach2() is a shorthand of pkg_attach(..., install = TRUE), which means if a package is not available, install it. This function can also deal with multiple packages.
The function loadable() tests if a package is loadable.
Read/write files in UTF-8
Functions read_utf8() and write_utf8() can be used to read/write files in UTF-8. They are simple wrappers of readLines() and writeLines().
Convert numbers to English words
The function numbers_to_words() (or n2w() for short) converts numbers to English words.
## [1] "one hundred billion, twelve million, three hundred forty-five thousand, six hundred seventy-eight"
n2w(-987654321)
## [1] "minus nine hundred eighty-seven million, six hundred fifty-four thousand, three hundred twenty-one"
n2w(1e+15-1)
## [1] "nine hundred ninety-nine trillion, nine hundred ninety-nine billion, nine hundred ninety-nine million, nine hundred ninety-nine thousand, nine hundred ninety-nine"
Check reverse dependencies of a package
Running R CMD check on the reverse dependencies of knitr and rmarkdown is my least favorite thing in developing R packages, because the numbers of their reverse dependencies are huge. The function rev_check() reflects some of my past experience in this process. I think I have automated it as much as possible, and made it as easy as possible to discover possible new problems introduced by the current version of the package (compared to the CRAN version). Finally I can just sit back and let it run.
Input a character vector into the RStudio source editor
The function rstudio_type() inputs characters in the RStudio source editor as if they were typed by a human. I came up with the idea when preparing my talk for rstudio::conf 2018 (see this post for more details).
Print session information
Since I have never been fully satisfied by the output of sessionInfo(), I tweaked it to make it more useful in my use cases. For example, it is rarely useful to print out the names of base R packages, or information about the matrix products / BLAS / LAPACK. Oftentimes I want additional information in the session information, such as the Pandoc version when rmarkdown is used. The function session_info() tweaks the output of sessionInfo(), and makes it possible for other packages to append information in the output of session_info().
You can choose to print out the versions of only the packages you specify, e.g.,