xfun/0000755000176200001440000000000013607036513011236 5ustar liggesusersxfun/NAMESPACE0000644000176200001440000000231513606702706012461 0ustar liggesusers# 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/LICENSE0000644000176200001440000000004713223200735012235 0ustar liggesusersYEAR: 2018 COPYRIGHT HOLDER: Yihui Xie xfun/README.md0000644000176200001440000000107613563361006012520 0ustar liggesusers# xfun [![Build Status](https://travis-ci.org/yihui/xfun.svg)](https://travis-ci.org/yihui/xfun) [![Coverage status](https://codecov.io/gh/yihui/xfun/branch/master/graph/badge.svg)](https://codecov.io/github/yihui/xfun?branch=master) [![Downloads from the RStudio CRAN mirror](https://cranlogs.r-pkg.org/badges/xfun)](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/0000755000176200001440000000000013606702660012013 5ustar liggesusersxfun/man/rename_seq.Rd0000644000176200001440000000332313606702707014424 0ustar liggesusers% 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.Rd0000644000176200001440000000105613606702707012727 0ustar liggesusers% 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.Rd0000644000176200001440000000063113606702707013764 0ustar liggesusers% 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.Rd0000644000176200001440000000166013606702707014170 0ustar liggesusers% 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.Rd0000644000176200001440000000146613606702706014616 0ustar liggesusers% 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.Rd0000644000176200001440000000062613606702707013476 0ustar liggesusers% 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.Rd0000644000176200001440000000222213606702707013616 0ustar liggesusers% 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.Rd0000644000176200001440000000133113606702706015311 0ustar liggesusers% 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.Rd0000644000176200001440000000341313606702707014650 0ustar liggesusers% 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.Rd0000644000176200001440000000325013606702706014242 0ustar liggesusers% 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.Rd0000644000176200001440000000077613606702706013267 0ustar liggesusers% 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.Rd0000644000176200001440000000113313606702707013730 0ustar liggesusers% 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.Rd0000644000176200001440000000100713606702706014606 0ustar liggesusers% 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.Rd0000644000176200001440000000155113606702707014625 0ustar liggesusers% 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.Rd0000644000176200001440000000170413606702706015113 0ustar liggesusers% 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.Rd0000644000176200001440000000454713606702706014370 0ustar liggesusers% 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.Rd0000644000176200001440000000265713606702707014453 0ustar liggesusers% 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.Rd0000644000176200001440000000525713606702707014422 0ustar liggesusers% 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.Rd0000644000176200001440000000337213606702707015007 0ustar liggesusers% 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.Rd0000644000176200001440000000147313606702707015114 0ustar liggesusers% 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.Rd0000644000176200001440000000074513606702707014465 0ustar liggesusers% 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.Rd0000644000176200001440000000212213606702707015033 0ustar liggesusers% 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.Rd0000644000176200001440000000105613606702707014071 0ustar liggesusers% 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.Rd0000644000176200001440000000324313606702707014777 0ustar liggesusers% 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.Rd0000644000176200001440000000132013606702707015627 0ustar liggesusers% 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.Rd0000644000176200001440000000060213606702707014476 0ustar liggesusers% 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.Rd0000644000176200001440000000100613606702707014242 0ustar liggesusers% 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.Rd0000644000176200001440000001401413606702707014235 0ustar liggesusers% 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.Rd0000644000176200001440000000155313606702706014106 0ustar liggesusers% 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.Rd0000644000176200001440000000070513606702707015322 0ustar liggesusers% 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.Rd0000644000176200001440000000236313606702707015703 0ustar liggesusers% 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.Rd0000644000176200001440000000141213606702707014627 0ustar liggesusers% 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.Rd0000644000176200001440000000074013606702706013550 0ustar liggesusers% 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.Rd0000644000176200001440000000174113606702707014466 0ustar liggesusers% 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/DESCRIPTION0000644000176200001440000000215413607036513012746 0ustar liggesusersPackage: 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/0000755000176200001440000000000013606770153012341 5ustar liggesusersxfun/build/vignette.rds0000644000176200001440000000032313606770153014676 0ustar liggesusersb```b`fcb`b2 1# 'H+ MAwS+)O)M.S(W)ES ֞Q&1a(DXT%iewI-HK î?/S+`zP԰Aհe ,s\ܠL t7`~΢r=xA$Gs=ʕXVr7-/xfun/tests/0000755000176200001440000000000013357673770012416 5ustar liggesusersxfun/tests/test-all.R0000644000176200001440000000004113326436067014251 0ustar liggesuserslibrary(testit) test_pkg('xfun') xfun/tests/testit/0000755000176200001440000000000013544666311013721 5ustar liggesusersxfun/tests/testit/test-markdown.R0000644000176200001440000000345313544667771016663 0ustar liggesuserslibrary(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.R0000644000176200001440000000141513377267011016137 0ustar liggesuserslibrary(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.R0000644000176200001440000000245313606703165016331 0ustar liggesuserslibrary(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.R0000644000176200001440000000213013377266446016614 0ustar liggesuserslibrary(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.R0000644000176200001440000000237413544660035017773 0ustar liggesuserslibrary(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.R0000644000176200001440000000052213377264552016603 0ustar liggesuserslibrary(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.R0000644000176200001440000000137213377266145016002 0ustar liggesuserslibrary(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.R0000644000176200001440000000254413377266511015437 0ustar liggesuserslibrary(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.R0000644000176200001440000000074413373640067016166 0ustar liggesuserslibrary(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/0000755000176200001440000000000013606770153013252 5ustar liggesusersxfun/vignettes/xfun.Rmd0000644000176200001440000002014713563361006014675 0ustar liggesusers--- 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/0000755000176200001440000000000013574507516011450 5ustar liggesusersxfun/R/revcheck.R0000644000176200001440000004704613606173027013370 0ustar liggesusers#' 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('', '', 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 = '.+(\\d{4,}-\\d{2}-\\d{2}) .+' d = c(d, as.Date(gsub(r, '\\1', grep(r, x, value = TRUE)))) info[[p]] = sort(d, decreasing = TRUE) } flag = unlist(lapply(info, function(d) { sum(d > Sys.Date() - 180) < 6 && d[1] < Sys.Date() - days })) names(which(flag)) } xfun/R/image.R0000644000176200001440000000062613530013170012635 0ustar liggesusers# add a border to an image via ImageMagick add_border = function(input, pixels = 1, color = 'black', output) { input = normalizePath(input) if (missing(output)) output = paste0(sans_ext(input), '-output.', file_ext(input)) system2('convert', shQuote(c( input, '-shave', paste(pixels, pixels, sep = 'x'), '-bordercolor', color, '-border', pixels, output) )) optipng(dirname(output)) } xfun/R/utils.R0000644000176200001440000001761013373637754012745 0ustar liggesusers#' Obtain an attribute of an object without partial matching #' #' An abbreviation of \code{base::\link[base]{attr}(exact = TRUE)}. #' @param ... Passed to \code{base::\link[base]{attr}()} (without the #' \code{exact} argument). #' @export #' @examples #' z = structure(list(a = 1), foo = 2) #' base::attr(z, 'f') # 2 #' xfun::attr(z, 'f') # NULL #' xfun::attr(z, 'foo') # 2 attr = function(...) base::attr(..., exact = TRUE) #' Set the global option \code{\link{options}(stringsAsFactors = FALSE)} inside #' a parent function and restore the option after the parent function exits #' #' 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()}. #' @export #' @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 stringsAsStrings = function() { opts = options(stringsAsFactors = FALSE) do.call( on.exit, list(substitute(options(x), list(x = opts)), add = TRUE), envir = parent.frame() ) } #' @rdname stringsAsStrings #' @export strings_please = stringsAsStrings #' Evaluate an expression under a specified working directory #' #' Change the working directory, evaluate the expression, and restore the #' working directory. #' @param dir Path to a directory. #' @param expr An R expression. #' @export #' @examples #' library(xfun) #' in_dir(tempdir(), {print(getwd()); list.files()}) in_dir = function(dir, expr) { owd = setwd(dir); on.exit(setwd(owd)) expr } #' Test if an object is identical to \code{FALSE} #' #' A simple abbreviation of \code{identical(x, FALSE)}. #' @param x An R object. #' @export #' @examples #' library(xfun) #' isFALSE(TRUE) # false #' isFALSE(FALSE) # true #' isFALSE(c(FALSE, FALSE)) # false isFALSE = function(x) identical(x, FALSE) #' Parse R code and do not keep the source #' #' An abbreviation of \code{parse(keep.source = FALSE)}. #' @param code A character vector of the R source code. #' @export #' @return R \code{\link{expression}}s. #' @examples library(xfun) #' parse_only('1+1'); parse_only(c('y~x', '1:5 # a comment')) #' parse_only(character(0)) parse_only = function(code) { if (length(code) == 0) return(expression()) parse(text = code, keep.source = FALSE) } #' Try to evaluate an expression silently #' #' An abbreviation of \code{try(silent = TRUE)}. #' @param expr An R expression. #' @export #' @examples library(xfun) #' z = try_silent(stop('Wrong!')) #' inherits(z, 'try-error') try_silent = function(expr) try(expr, silent = TRUE) #' An alternative to sessionInfo() to print session information #' #' 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. #' #' 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. #' @param 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. #' @param dependencies Whether to print out the versions of the recursive #' dependencies of packages. #' @return A character vector of the session information marked as #' \code{\link{raw_string}()}. #' @export #' @examples xfun::session_info() #' if (loadable('MASS')) xfun::session_info('MASS') session_info = function(packages = NULL, dependencies = TRUE) { res = sessionInfo() res$matprod = res$BLAS = res$LAPACK = NULL if (loadable('rstudioapi') && rstudioapi::isAvailable()) { res$running = paste0(res$running, ', RStudio ', rstudioapi::getVersion()) } tweak_info = function(obj, extra = NULL) { res = capture.output(print(obj)) i = grep('^(attached base packages|Matrix products):\\s*$', res, ignore.case = TRUE) if (length(i)) res = res[-c(i, i + 1)] res = gsubi('^\\s*locale:\\s*$', 'Locale:', res) res = gsub('^\\s*\\[[0-9]+]\\s*', ' ', res) # remove vector indices like [1] res = gsubi('^\\s*other attached packages:\\s*$', 'Package version:', res) # print the locale info on a single line if possible if (length(i <- which(res == 'Locale:')) == 1 && res[i + 2] == '') { res[i] = paste(res[i], gsub('\\s*/\\s*', ' / ', gsub('^\\s+', '', res[i + 1]))) res = res[-(i + 1)] } raw_string(c(res, extra)) } version_info = function(pkgs) { res = lapply(pkgs, function(p) { list(Version = as.character(packageVersion(p)), Package = p) }) as.list(setNames(res, pkgs)) } res$basePkgs = raw_string(list()) info = c(res$otherPkgs, res$loadedOnly) if (length(packages) > 0) { info = info[intersect(names(info), packages)] info = c(info, version_info(setdiff(packages, names(info)))) } res$loadedOnly = NULL if (dependencies) { deps = pkg_dep(names(info), installed.packages(), recursive = TRUE) deps = sort(setdiff(deps, names(info))) info = c(info, version_info(deps)) } if (length(packages) > 0 || dependencies) info = info[sort(names(info))] res$otherPkgs = info extra = unlist(lapply(packages, function(p) tryCatch( c('', getFromNamespace('xfun_session_info', p)()), error = function(e) NULL) )) tweak_info(res, extra) } gsubi = function(...) gsub(..., ignore.case = TRUE) #' Try various methods to download a file #' #' 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}. #' @param url The URL of the file. #' @param 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). #' @param ... Other arguments to be passed to \code{\link{download.file}()} #' (except \code{method}). #' @return The integer code \code{0} for success, or an error if none of the #' methods work. #' @export download_file = function(url, output = basename(url), ...) { if (missing(output)) output = gsub('[?#].*$', '', output) # remove query/hash download = function(method = 'auto') download.file(url, output, ..., method = method) for (method in c('libcurl', if (is_windows()) 'wininet', 'auto')) { if (!inherits(try_silent(res <- download(method = method)), 'try-error') && res == 0) return(res) } # check for libcurl/curl/wget/lynx, call download.file with appropriate method res = NA if (Sys.which('curl') != '') { # curl needs to add a -L option to follow redirects opts = if (is.null(getOption('download.file.extra'))) options(download.file.extra = '-L') res = download(method = 'curl'); options(opts) if (res == 0) return(res) } if (Sys.which('wget') != '') { if ((res <- download(method = 'wget')) == 0) return(res) } if (Sys.which('lynx') != '') { if ((res <- download(method = 'lynx')) == 0) return(res) } if (is.na(res)) stop('No download method works (auto/wininet/wget/curl/lynx)') res } xfun/R/rstudio.R0000644000176200001440000000425713234445363013266 0ustar liggesusers#' Type a character vector into the RStudio source editor #' #' Use the \pkg{rstudioapi} package to insert characters one by one into the #' RStudio source editor, as if they were typed by a human. #' @param x A character vector. #' @param pause A function to return a number in seconds to pause after typing #' each character. #' @param 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. #' @param 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. #' @export #' @import stats #' @examples library(xfun) #' if (loadable('rstudioapi') && rstudioapi::isAvailable()) { #' rstudio_type('Hello, RStudio! xfun::rstudio_type() looks pretty cool!', #' pause = function() runif(1, 0, .5), mistake = .1) #' } rstudio_type = function(x, pause = function() .1, mistake = 0, save = 0) { get_ctx = function() rstudioapi::getSourceEditorContext() ctx = get_ctx() if (is.null(id <- ctx$id)) { message('Please make sure an RStudio editor tab is open') return() } save_it = function(prob = 1) { if (ctx$path == '' || (rbinom(1, 1, prob) == 0)) return() ctx = get_ctx() # in case a new line is automatically added at the end when saving the doc on.exit(rstudioapi::setSelectionRanges(ctx$selection[[1]]$range, id), add = TRUE) rstudioapi::documentSave(id) } type_one = function(x) { rstudioapi::insertText(text = x, id = id) Sys.sleep(pause()) } type_mistake = function() { n = sample(1:10, 1) x = sample(ascii_chars, n, replace = TRUE) for (i in x) type_one(i) Sys.sleep(.5) ctx = rstudioapi::getSourceEditorContext() r = ctx$selection[[1]]$range r$start[2] = r$start[2] - n rstudioapi::modifyRange(r, '', id) Sys.sleep(.5) } x = paste(x, collapse = '\n') for (i in unlist(strsplit(x, ''))) { type_one(i); save_it(save) if (runif(1) < mistake) type_mistake() } save_it(as.integer(save > 0)) # if prob is non-zero, save it finally invisible() } ascii_chars = intToUtf8(32:126, TRUE) xfun/R/io.R0000644000176200001440000001136213564535055012203 0ustar liggesusers#' Read / write files encoded in UTF-8 #' #' 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)}. #' @param con A connection or a file path. #' @param error Whether to signal an error when non-UTF8 characters are detected #' (if \code{FALSE}, only a warning message is issued). #' @param text A character vector (will be converted to UTF-8 via #' \code{\link{enc2utf8}()}). #' @param ... Other arguments passed to \code{\link{writeLines}()} (except #' \code{useBytes}, which is \code{TRUE} in \code{write_utf8()}). #' @export read_utf8 = function(con, error = FALSE) { # users may have set options(encoding = 'UTF-8'), which usually won't help but # will bring more trouble than good, so we reset this option temporarily opts = options(encoding = 'native.enc'); on.exit(options(opts), add = TRUE) x = readLines(con, encoding = 'UTF-8', warn = FALSE) i = invalid_utf8(x) n = length(i) if (n > 0) (if (error) stop else warning)( if (is.character(con)) c('The file ', con, ' is not encoded in UTF-8. '), 'These lines contain invalid UTF-8 characters: ', paste(c(head(i), if (n > 6) '...'), collapse = ', ') ) x } #' @rdname read_utf8 #' @export write_utf8 = function(text, con, ...) { if (is.null(text)) text = character(0) if (identical(con, '')) { cat(text, sep = '\n', file = con) } else { # prevent re-encoding the text in the file() connection in writeLines() # https://kevinushey.github.io/blog/2018/02/21/string-encoding-and-r/ opts = options(encoding = 'native.enc'); on.exit(options(opts), add = TRUE) writeLines(enc2utf8(text), con, ..., useBytes = TRUE) } } # which lines are invalid UTF-8 invalid_utf8 = function(x) { which(!is.na(x) & is.na(iconv(x, 'UTF-8', 'UTF-8'))) } #' Read a text file and concatenate the lines by \code{'\n'} #' #' The source code of this function should be self-explanatory. #' @param file Path to a text file (should be encoded in UTF-8). #' @return A character string of text lines concatenated by \code{'\n'}. #' @export #' @examples #' xfun::file_string(system.file('DESCRIPTION', package = 'xfun')) file_string = function(file) { raw_string(paste(read_utf8(file), collapse = '\n')) } #' Search and replace strings in files #' #' These functions provide the "file" version of \code{\link{gsub}()}, i.e., #' they perform searching and replacement in files via \code{gsub()}. #' @param file Path of a single file. #' @param ... 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. #' @param 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). #' @param files A vector of file paths. #' @param dir Path to a directory (all files under this directory will be #' replaced). #' @param recursive Whether to find files recursively under a directory. #' @param ext A vector of filename extensions (without the leading periods). #' @param 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. #' @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! #' @export #' @examples library(xfun) #' f = tempfile() #' writeLines(c('hello', 'world'), f) #' gsub_file(f, 'world', 'woRld', fixed = TRUE) #' readLines(f) gsub_file = function(file, ..., rw_error = TRUE) { if (!(file.access(file, 2) == 0 && file.access(file, 4) == 0)) { (if (rw_error) stop else warning)('Unable to read or write to ', file) if (!rw_error) return(invisible()) } x1 = tryCatch(read_utf8(file, error = TRUE), error = function(e) if (rw_error) stop(e)) if (is.null(x1)) return(invisible()) x2 = gsub(x = x1, ...) if (!identical(x1, x2)) write_utf8(x2, file) } #' @rdname gsub_file #' @export gsub_files = function(files, ...) { for (f in files) gsub_file(f, ...) } #' @rdname gsub_file #' @export gsub_dir = function(..., dir = '.', recursive = TRUE, ext = NULL, mimetype = '.*') { files = list.files(dir, full.names = TRUE, recursive = recursive) if (length(ext)) files = files[file_ext(files) %in% ext] if (mimetype != '.*') files = files[grep(mimetype, mime::guess_type(files))] gsub_files(files, ...) } #' @rdname gsub_file #' @export gsub_ext = function(ext, ..., dir = '.', recursive = TRUE) { gsub_dir(..., dir = dir, recursive = recursive, ext = ext) } xfun/R/packages.R0000644000176200001440000001553313473561550013354 0ustar liggesusers#' Attach or load packages, and automatically install missing packages if #' requested #' #' \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)}. #' #' 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. #' @param ... Package names (character vectors, and must always be quoted). #' @param 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. #' @param message Whether to show the package startup messages (if any startup #' messages are provided in a package). #' @return \code{pkg_attach()} returns \code{NULL} invisibly. \code{pkg_load()} #' returns a logical vector, indicating whether the packages can be loaded. #' @import utils #' @export #' @examples library(xfun) #' pkg_attach('stats', 'graphics') #' # pkg_attach2('servr') # automatically install servr if it is not installed #' #' (pkg_load('stats', 'graphics')) pkg_attach = function( ..., install = FALSE, message = getOption('xfun.pkg_attach.message', TRUE) ) { if (!message) library = function(...) { suppressPackageStartupMessages(base::library(...)) } for (i in c(...)) { if (install && !loadable(i)) install.packages(i) library(i, character.only = TRUE) } } #' @param error Whether to signal an error when certain packages cannot be loaded. #' @rdname pkg_attach #' @export pkg_load = function(..., error = TRUE, install = FALSE) { n = length(pkg <- c(...)); res = logical(n) if (n == 0) return(invisible(res)) for (i in seq_len(n)) { res[i] = loadable(p <- pkg[i]) if (install && !res[i]) { install.packages(p); res[i] = loadable(p) } } if (error && any(!res)) stop('Package(s) not loadable: ', paste(pkg[!res], collapse = ' ')) invisible(res) } #' @param pkg A single package name. #' @param 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). #' @param 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}. #' @rdname pkg_attach #' @export loadable = function(pkg, strict = TRUE, new_session = FALSE) { if (length(pkg) != 1L) stop("'pkg' must be a character vector of length one") if (new_session) { Rscript(c('-e', shQuote(sprintf('library("%s")', pkg))), stdout = FALSE, stderr = FALSE) == 0 } else { if (strict) { suppressPackageStartupMessages(requireNamespace(pkg, quietly = TRUE)) } else pkg %in% .packages(TRUE) } } #' @rdname pkg_attach #' @export pkg_attach2 = function(...) pkg_attach(..., install = TRUE) #' @rdname pkg_attach #' @export pkg_load2 = function(...) pkg_load(..., install = TRUE) broken_packages = function(reinstall = TRUE) { pkgs = unlist(plapply(.packages(TRUE), function(p) if (!loadable(p)) p)) if (reinstall) { remove.packages(pkgs); pkg_install(pkgs) } else pkgs } #' Install a source package from a directory #' #' Run \command{R CMD build} to build a tarball from a source directory, and run #' \command{R CMD INSTALL} to install it. #' @param src The package source directory. #' @param 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). #' @param build_opts The options for \command{R CMD build}. #' @param install_opts The options for \command{R CMD INSTALL}. #' @export #' @return Invisible status from \command{R CMD INSTALL}. install_dir = function(src, build = TRUE, build_opts = NULL, install_opts = NULL) { desc = file.path(src, 'DESCRIPTION') pv = read.dcf(desc, fields = c('Package', 'Version')) # delete existing tarballs unlink(sprintf('%s_*.tar.gz', pv[1, 1])) pkg = if (build) { Rcmd(c('build', build_opts, shQuote(src))) sprintf('%s_%s.tar.gz', pv[1, 1], pv[1, 2]) } else src res = Rcmd(c('INSTALL', install_opts, pkg)) if (build) unlink(pkg) if (res != 0) stop('Failed to install the package ', pkg) invisible(res) } install_brew_deps = function(pkg = .packages(TRUE)) { con = url('https://macos.rbind.org/bin/macosx/sysreqsdb.rds') on.exit(close(con), add = TRUE) inst = installed.packages() pkg = intersect(pkg, pkg_needs_compilation(inst)) deps = readRDS(con) deps = deps[c(pkg, pkg_dep(pkg, inst, recursive = TRUE))] deps = paste(na.omit(unique(unlist(deps))), collapse = ' ') if (deps != '') system(paste('brew install', deps)) } pkg_needs_compilation = function(db = installed.packages()) { pkgs = unname(db[tolower(db[, 'NeedsCompilation']) == 'yes', 'Package']) pkgs[!is.na(pkgs)] } #' An alias of \code{remotes::install_github()} #' #' 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}. #' @param ... Arguments to be passed to #' \code{remotes::\link[remotes]{install_github}()}. #' @export install_github = function(...) remotes::install_github(...) # Remove packages not installed from CRAN reinstall_from_cran = function(dry_run = TRUE, skip_github = TRUE) { r = paste(c('Repository', if (skip_github) 'GithubRepo'), collapse = '|') r = paste0('^(', r, '): ') for (lib in .libPaths()) { pkgs = .packages(TRUE, lib) pkgs = setdiff(pkgs, c('xfun', 'rstudio', base_pkgs())) for (p in pkgs) { desc = read_utf8(system.file('DESCRIPTION', package = p, lib.loc = lib)) if (!any(grepl(r, desc))) { if (dry_run) message(p, ': ', lib) else install.packages(p, lib = lib) } } } } base_pkgs = function() rownames(installed.packages(priority = 'base')) xfun/R/paths.R0000644000176200001440000001012613525064336012704 0ustar liggesusers#' Manipulate filename extensions #' #' Functions to obtain (\code{file_ext()}), remove (\code{sans_ext()}), and #' change (\code{with_ext()}) extensions in filenames. #' #' \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}()}. #' @param x A character of file paths. #' @export #' @return A character vector of the same length as \code{x}. #' @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') file_ext = function(x) tools::file_ext(x) #' @rdname file_ext #' @export sans_ext = function(x) tools::file_path_sans_ext(x) #' @param ext A vector of new extensions. #' @rdname file_ext #' @export with_ext = function(x, ext) { if (anyNA(ext)) stop("NA is not allowed in 'ext'") n1 = length(x); n2 = length(ext); r = '([.][[:alnum:]]+)?$' if (n1 * n2 == 0) return(x) i = !grepl('^[.]', ext) & ext != '' ext[i] = paste0('.', ext[i]) if (all(ext == '')) ext = '' if (length(ext) == 1) return(sub(r, ext, x)) if (n1 > 1 && n1 != n2) stop("'ext' must be of the same length as 'x'") mapply(sub, r, ext, x, USE.NAMES = FALSE) } #' Normalize paths #' #' A wrapper function of \code{normalizePath()} with different defaults. #' @param path,winslash,must_work Arguments passed to #' \code{\link{normalizePath}()}. #' @export #' @examples library(xfun) #' normalize_path('~') normalize_path = function(path, winslash = '/', must_work = FALSE) { res = normalizePath(path, winslash = winslash, mustWork = must_work) if (is_windows()) res[is.na(path)] = NA res } #' Test if two paths are the same after they are normalized #' #' Compare two paths after normalizing them with the same separator (\code{/}). #' @param p1,p2 Two vectors of paths. #' @param ... Arguments to be passed to \code{\link{normalize_path}()}. #' @export #' @examples library(xfun) #' same_path('~/foo', file.path(Sys.getenv('HOME'), 'foo')) same_path = function(p1, p2, ...) { normalize_path(p1, ...) == normalize_path(p2, ...) } #' Rename files with a sequential numeric prefix #' #' 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}. #' @param 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$"}. #' @param 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}. #' @param replace Whether to remove existing numeric prefixes in filenames. #' @param start The starting number for the prefix (it can start from 0). #' @param 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. #' @return A named character vector. The names are original filenames, and the #' vector itself is the new filenames. #' @export #' @examples xfun::rename_seq() #' xfun::rename_seq('[.](jpeg|png)$', format = '%04d') rename_seq = function( pattern = '^[0-9]+-.+[.]Rmd$', format = 'auto', replace = TRUE, start = 1, dry_run = TRUE ) { n = length(files <- list.files('.', pattern)) if (n == 0) return(files) files2 = if (replace) sub('^[0-9]+-*', '', files) else files if (format == 'auto') format = paste0('%0', floor(log10(n)) + 1, 'd') files2 = paste(sprintf(format, seq_len(n) + start - 1), files2, sep = '-') if (!dry_run) file.rename(files, files2) setNames(files2, files) } xfun/R/command.R0000644000176200001440000000506313566001422013177 0ustar liggesusers#' Run OptiPNG on all PNG files under a directory #' #' Calls the command \command{optipng} to optimize all PNG files under a #' directory. #' @param dir Path to a directory. #' @references OptiPNG: \url{http://optipng.sourceforge.net}. #' @export optipng = function(dir = '.') { files = list.files(dir, '[.]png$', recursive = TRUE, full.names = TRUE) for (f in files) system2('optipng', shQuote(f)) } #' Run the commands \command{Rscript} and \command{R CMD} #' #' Wrapper functions to run the commands \command{Rscript} and \command{R CMD}. #' @param args A character vector of command-line arguments. #' @param ... Other arguments to be passed to \code{\link{system2}()}. #' @export #' @return A value returned by \code{system2()}. #' @examples library(xfun) #' Rscript(c('-e', '1+1')) #' Rcmd(c('build', '--help')) Rscript = function(args, ...) { system2(file.path(R.home('bin'), 'Rscript'), args, ...) } #' @rdname Rscript #' @export Rcmd = function(args, ...) { system2(file.path(R.home('bin'), 'R'), c('CMD', args), ...) } #' Upload to an FTP server via \command{curl} #' #' 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. #' #' 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. #' @param file Path to a local file. #' @param server The address of the FTP server. #' @param dir The remote directory to which the file should be uploaded. #' @param version The R version(s) on win-builder. #' @return Status code returned from \code{\link{system2}}. #' @export upload_ftp = function(file, server, dir = '') { if (dir != '') dir = gsub('/*$', '/', dir) system2('curl', shQuote(c('-T', file, paste0(server, dir)))) } #' @rdname upload_ftp #' @export upload_win_builder = function( file, version = c("R-devel", "R-release", "R-oldrelease", "R-devel_gcc8"), server = 'ftp://win-builder.r-project.org/' ) { res = unlist(lapply(version, upload_ftp, file = file, server = server)) setNames(res, version) } xfun/R/os.R0000644000176200001440000000123313311015222012164 0ustar liggesusers#' Test for types of operating systems #' #' Functions based on \code{.Platform$OS.type} and \code{Sys.info()} to test if #' the current operating system is Windows, macOS, Unix, or Linux. #' @rdname os #' @export #' @examples #' library(xfun) #' # only one of the following statements should be true #' is_windows() #' is_unix() && is_macos() #' is_linux() is_windows = function() .Platform$OS.type == 'windows' #' @rdname os #' @export is_unix = function() .Platform$OS.type == 'unix' #' @rdname os #' @export is_macos = function() unname(Sys.info()['sysname'] == 'Darwin') #' @rdname os #' @export is_linux = function() unname(Sys.info()['sysname'] == 'Linux') xfun/R/encoding.R0000644000176200001440000000256713341277771013372 0ustar liggesusers#' Try to use the system native encoding to represent a character vector #' #' 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. #' @param x A character vector. #' @param 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. #' @export #' @examples #' library(xfun) #' s = intToUtf8(c(20320, 22909)) #' Encoding(s) #' #' s2 = native_encode(s) #' Encoding(s2) native_encode = function(x, windows_only = is_windows()) { if (!windows_only) return(x) if (identical(enc2utf8(x2 <- enc2native(x)), x)) return(x2) warning('The character vector cannot be represented in the native encoding') x } #' Check if a character vector consists of entirely ASCII characters #' #' Converts the encoding of a character vector to \code{'ascii'}, and check if #' the result is \code{NA}. #' @param x A character vector. #' @return A logical vector indicating whether each element of the character #' vector is ASCII. #' @export #' @examples library(xfun) #' is_ascii(letters) # yes #' is_ascii(intToUtf8(8212)) # no is_ascii = function(x) { out = !is.na(iconv(x, to = 'ascii')) out[is.na(x)] = NA out } xfun/R/json.R0000644000176200001440000000411113317760435012535 0ustar liggesusers#' A simple JSON serializer #' #' 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. #' @param x An R object. #' @export #' @return A character string. #' @seealso The \pkg{jsonlite} package provides a full JSON serializer. #' @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;}')))) tojson = function(x) { if (is.null(x)) return('null') if (is.logical(x)) { if (length(x) != 1 || any(is.na(x))) stop('Logical values of length > 1 and NA are not supported') return(tolower(as.character(x))) } if (is.character(x) && inherits(x, 'JS_EVAL')) return(paste(x, collapse = '\n')) if (is.character(x) || is.numeric(x)) { return(json_vector(x, length(x) != 1 || inherits(x, 'AsIs'), is.character(x))) } if (is.list(x)) { if (length(x) == 0) return('{}') return(if (is.null(names(x))) { json_vector(unlist(lapply(x, tojson)), TRUE, quote = FALSE) } else { nms = paste0('"', names(x), '"') paste0('{\n', paste(nms, unlist(lapply(x, tojson)), sep = ': ', collapse = ',\n'), '\n}') }) } stop('The class of x is not supported: ', paste(class(x), collapse = ', ')) } #' @param to_array Whether to convert a vector to a JSON array (use \code{[]}). #' @param quote Whether to double quote the elements. #' @rdname tojson #' @export json_vector = function(x, to_array = FALSE, quote = TRUE) { if (quote) { x = gsub('(["\\])', "\\\\\\1", x) x = gsub('[[:space:]]', " ", x) if (length(x)) x = paste0('"', x, '"') } if (to_array) paste0('[', paste(x, collapse = ', '), ']') else x } xfun/R/data-structure.R0000644000176200001440000000560413544660035014540 0ustar liggesusers#' Strict lists #' #' A strict list is essentially a normal \code{\link{list}()} but it does not #' allow partial matching with \code{$}. #' #' 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. #' @param ... Objects (list elements), possibly named. Ignored in the #' \code{print()} method. #' @export #' @return 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. #' @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! strict_list = function(...) { as_strict_list(list(...)) } # https://twitter.com/xieyihui/status/782462926862954496 #' @param x For \code{as_strict_list()}, the object to be coerced to a strict #' list. #' #' For \code{print()}, a strict list. #' @rdname strict_list #' @export as_strict_list = function(x) { structure(as.list(x), class = 'xfun_strict_list') } #' @param name The name (a character string) of the list element. #' @rdname strict_list #' @export `$.xfun_strict_list` = function(x, name) x[[name]] #' @rdname strict_list #' @export print.xfun_strict_list = function(x, ...) { print(unclass(x)) } #' Print a character vector in its raw form #' #' 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). #' @param x For \code{raw_string()}, a character vector. For the print method, #' the \code{raw_string()} object. #' @export #' @examples library(xfun) #' raw_string(head(LETTERS)) #' raw_string(c('a "b"', 'hello\tworld!')) raw_string = function(x) { if (is.null(x)) x = as.character(x) class(x) = 'xfun_raw_string' x } #' @param ... Other arguments (currently ignored). #' @rdname raw_string #' @export print.xfun_raw_string = function(x, ...) { if (length(x)) cat(x, sep = '\n') invisible(x) } xfun/R/string.R0000644000176200001440000001104213606702705013070 0ustar liggesusers#' Convert numbers to English words #' #' 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()}. #' @param x A numeric vector. Values should be integers. The absolute values #' should be less than \code{1e15}. #' @param 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}. #' @param hyphen Whether to insert hyphen (-) when the number is between 21 and #' 99 (except 30, 40, etc.). #' @param 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}. #' @return A character vector. #' @author Daijiang Li #' @export #' @examples library(xfun) #' n2w(0, cap = TRUE) #' n2w(0:121, and = TRUE) #' n2w(1e6) #' n2w(1e11+12345678) #' n2w(-987654321) #' n2w(1e15-1) numbers_to_words = function(x, cap = FALSE, hyphen = TRUE, and = FALSE) { if (!is.numeric(x)) stop('The input is not numeric.') if (any(abs(x) >= 1e15)) stop('The absolute value must be less than 1e15.') opts = options(scipen = 15); on.exit(options(opts), add = TRUE) # avoid scientific notation if (any(x != floor(x))) stop('The numbers must be integer. ') zero_to_19 = c( 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven', 'twelve', paste0(c('thir', 'four', 'fif', 'six', 'seven', 'eigh', 'nine'), 'teen') ) names(zero_to_19) = as.character(0:19) tens = c('twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety') names(tens) = as.character(seq(20, 90, 10)) marks = c('', 'thousand,', 'million,', 'billion,', 'trillion,') convert_1 = function(x_c) zero_to_19[x_c] # 0 - 9 # 10 - 99 convert_2 = function(x_c) { x_cs = strsplit(x_c, split = '')[[1]] if (x_cs[1] == 1) return(zero_to_19[x_c]) # 10 - 19 if (x_cs[2] == 0) return(tens[x_c]) # 20, 30, 40, ... # 21, 22, etc. paste(tens[as.integer(x_cs[1]) - 1], convert_1(x_cs[2]), sep = if (hyphen) '-' else ' ') } # 100 - 999 convert_3 = function(x_c) { x_cs = strsplit(x_c, split = '')[[1]] n_hundreds = paste(convert_1(x_cs[1]), 'hundred', sep = ' ') out = if (x_cs[2] == '0') { if (x_cs[3] == '0') return(n_hundreds) # x00 convert_1(x_cs[3]) # x0x } else { convert_2(paste(x_cs[2:3], collapse = '')) # xxx } paste(n_hundreds, out, sep = if (and) ' and ' else ' ') } convert_le3 = function(x_c) { x_c = gsub('^0+', '', x_c) # avoid something like 000, 001, 010; but also remove 0 n = nchar(x_c) if (n == 0) return('') if (n == 1) return(convert_1(x_c)) if (n == 2) return(convert_2(x_c)) if (n == 3) return(convert_3(x_c)) } convert_one = function(x) { minus = if (x >= 0) '' else { x = abs(x); 'minus ' } if (x == 0) { out = 'zero' # because convert_le3 removed all 0s } else { x_marks = strsplit(format(x, big.mark = ','), split = ',')[[1]] # e.g. 123,456,789 out = vapply(x_marks, convert_le3, character(1)) # group by 3 digits x_marks2 = marks[length(x_marks):1] # units? x_marks2[which(out == '')] = '' # e.g. 4,000,123, 000, remove millions out = paste(out, x_marks2, sep = ' ', collapse = ' ') # zip together } out = paste0(minus, out) out = gsub('^ *|,? *$', '', out) # trim heading/trailing space out = gsub(' {2,}', ' ', out) # remove multiple spaces if (cap) out = sub('^([a-z])', '\\U\\1', out, perl = TRUE) out } if (length(x) > 1) vapply(x, convert_one, character(1)) else convert_one(x) } #' @export #' @rdname numbers_to_words n2w = numbers_to_words #' Split a character vector by line breaks #' #' 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')}. #' @param x A character vector. #' @return All elements of the character vector are split by \code{'\n'} into #' lines. #' @export #' @examples xfun::split_lines(c('a', 'b\nc')) split_lines = function(x) { if (length(grep('\n', x)) == 0L) return(x) x = gsub('\n$', '\n\n', x) x[x == ''] = '\n' unlist(strsplit(x, '\n')) } xfun/R/markdown.R0000644000176200001440000001606113544667437013427 0ustar liggesusers#' Find the indices of lines in Markdown that are prose (not code blocks) #' #' Filter out the indices of lines between code block fences such as \verb{```} #' (could be three or four or more backticks). #' @param x A character vector of text in Markdown. #' @param warn Whether to emit a warning when code fences are not balanced. #' @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. #' @return An integer vector of indices of lines that are prose in Markdown. #' @export #' @examples library(xfun) #' prose_index(c('a', '```', 'b', '```', 'c')) #' prose_index(c('a', '````', '```r', '1+1', '```', '````', 'c')) prose_index = function(x, warn = TRUE) { idx = NULL; r = '^(\\s*```+).*'; s = '' for (i in grep(r, x)) { if (s == '') { s = gsub(r, '\\1', x[i]); idx = c(idx, i); next } # look for the next line with the same amount of backticks (end of block) if (grepl(paste0('^', s), x[i])) { idx = c(idx, i); s = '' } } xi = seq_along(x); n = length(idx) if (n == 0) return(xi) if (n %% 2 != 0) { if (warn) warning('Code fences are not balanced') # treat all lines as prose return(xi) } idx2 = matrix(idx, nrow = 2) idx2 = unlist(mapply(seq, idx2[1, ], idx2[2, ], SIMPLIFY = FALSE)) xi[-idx2] } #' Protect math expressions in pairs of backticks in Markdown #' #' 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. #' #' 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. #' @param x A character vector of text in Markdown. #' @return A character vector with math expressions 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. #' @export #' @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}')) protect_math = function(x) { i = prose_index(x) if (length(i)) x[i] = escape_math(x[i]) x } escape_math = function(x) { # replace $x$ with `\(x\)` (protect inline math in ) m = gregexpr('(?<=^|[\\s])[$](?! )[^$]+?(?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. #' #' 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. #' @param path Path to the file(s) or directory. #' @param 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. #' @param text The text for the hyperlink. #' @param ... 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()}. #' @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}). #' @return An HTML tag \samp{} with the appropriate attributes. #' @export #' @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) embed_file = function(path, name = basename(path), text = paste('Download', name), ...) { h = paste0("data:", mime::guess_type(path), ";base64,", base64enc::base64encode(path)) htmltools::a(text, href = h, download = name, ...) } #' @rdname embed_file #' @export embed_dir = function(path, name = paste0(normalize_path(path), '.zip'), ...) { name = gsub('/', '', basename(name)) in_dir(path, { name = file.path(tempdir(), name); on.exit(file.remove(name), add = TRUE) zip(name, '.'); embed_file(name, ...) }) } #' @rdname embed_file #' @export embed_files = function(path, name = with_ext(basename(path[1]), '.zip'), ...) { name = file.path(tempdir(), basename(name)) on.exit(file.remove(name), add = TRUE) zip(name, path) embed_file(name, ...) } zip = function(name, ...) { if (utils::zip(name, ...) != 0) stop('Failed to create the zip archive ', name) invisible(0) } xfun/NEWS.md0000644000176200001440000001312013606702614012332 0ustar liggesusers# CHANGES IN xfun VERSION 0.12 - Added a new function `split_lines()`. # CHANGES IN xfun VERSION 0.11 ## BUG FIXES - `read_utf8()` will read the file with `options(encoding = 'native.enc')` and ignore user's setting such as `options(encoding = 'UTF-8')` (#21). # CHANGES IN xfun VERSION 0.10 ## NEW FEATURES - Added the function `as_strict_list()` to convert an existing object to a strict list without wrapping it in another list if the object already is of type list (in contrast to how `strict_list()` behaves) (thanks, @salim-b, #20). # CHANGES IN xfun VERSION 0.9 ## NEW FEATURES - Added a function `rename_seq()` to rename files to add an incremental numeric prefix to the filenames, e.g., rename `a.txt`, `b.txt`, `c.txt` to `1-a.txt`, `2-b.txt`, `3-c.txt`. # CHANGES IN xfun VERSION 0.8 ## MINOR CHANGES - `xfun::write_utf8(NULL)` is equivalent to `xfun::write_utf8(character(0))` now (thanks, @schloerke, yihui/knitr#1714). # CHANGES IN xfun VERSION 0.7 ## MINOR CHANGES - `loadable()` is quiet with R 3.6.0 (https://stat.ethz.ch/pipermail/r-devel/2019-May/077774.html). # CHANGES IN xfun VERSION 0.6 ## NEW FEATURES - Added the `...` argument to `same_path()` to pass additional arguments to `normalize_path()`. ## BUG FIXES - The `warn` argument in `prose_index()` failed to suppress warnings. # CHANGES IN xfun VERSION 0.5 ## NEW FEATURES - Added functions `upload_ftp()` and `upload_win_builder()` to upload files to FTP servers. - Added a function `stringsAsStrings()` (see its help page for details). - Added an argument `warn` to `prose_index()` to suppress the warning when code fences are not balanced. ## BUG FIXES - Fixed the bug that `prose_index()` recognizes double backticks as code fences (thanks, @shrektan, #14 #15). # CHANGES IN xfun VERSION 0.4 ## NEW FEATURES - Added functions `embed_file()`, `embed_dir()`, and `embed_files()` to embed files in an HTML output file (e.g., from R Markdown), so that the files can be directly downloaded from the web browser. One use case is to call one of these functions in an R code chunk of an Rmd document to embed the Rmd source document or data files in the HTML output, so readers can download them. - Added a new argument `message` to `pkg_attach()`, so you can suppress package startup messages via `xfun::pkg_attach(..., message = FALSE)` or set the global option `options(xfun.pkg_attach.message = FALSE)` (thanks, @wch, yihui/knitr#1583). ## MINOR CHANGES - The argument `rw_error` was moved from `gsub_dir()` to `gsub_file()` (`gsub_dir(rw_error = ...)` will still work). - `is_ascii()` now returns `NA` for `NA_character_` (thanks, @shrektan, #8 #9). # CHANGES IN xfun VERSION 0.3 ## NEW FEATURES - Added a new functions `download_file()` to try various methods to download a file. - Added a new function `is_ascii()` to test if a character vector only consists of ASCII characters. - Added a new function `numbers_to_words()` to convert numbers to English words (thanks, @daijiang, #3). # CHANGES IN xfun VERSION 0.2 ## NEW FEATURES - Added a `new_session` argument to `loadable()`. - Added new functions `gsub_file()`, `gsub_files()`, `gsub_dir()`, and `gsub_ext()` to replace strings in files. - Added new functions `Rscript` and `Rcmd` as wrappers of `system2('Rscript')` and `system2('R', 'CMD')`, respectively. - Added a new function `install_dir()` to install a source package from a directory. - Added a new function `file_string()` to read a text file (encoded in UTF-8) and return its content a single character string (lines concatenated by `\n`). - Added a new function `raw_string()` to print a character vector in its "raw" form using `cat(..., sep = '\n')` instead of `print()`, because the latter may introduce `[1]`, "extra" double quotes, and escape sequences, which are not very human-readable. - Added a new function `session_info()` as an alternative to `sessionInfo()`. - Added a new function `rev_check()` to run `R CMD check` on the reverse dependencies of a package, and a corresponding helper function `compare_Rcheck()` for showing the differences in logs with the CRAN version and the current version of the package, respectively. - Added new functions for dealing with Markdown text: `prose_index()` returns the line indices of text that is prose (not code blocks), and `protect_math()` protects math expressions in Markdown in backticks. - Added an `error` argument to `read_utf8()` to signal an error if the file is not encoded in UTF-8. # CHANGES IN xfun VERSION 0.1 ## NEW FEATURES - `attr()` as an abbreviation of `base::attr(exact = TRUE)`. - `file_ext()`, `sans_ext()`, and `with_ext()` to manipulate extensions in filenames. - `in_dir()` to evaluate an R expression in a directory. - `isFALSE()` as an abbreviation of `identical(x, FALSE)`. - `is_windows()`, `is_macos()`, `is_linux()`, and `is_unix()` to test operating systems. - `native_encode()` to try to encode a character vector in the native encoding. - `normalize_path()` as an abbreviation of `normalizePath(winslash = '/', mustWork = FALSE)`. - `optipng()` to run the command `optipng` to optimize all PNG files under a directory. - `parse_only()` parses R code without keeping the source references. - `pkg_attach()` and `pkg_load()` to attach and load a vector of packages, respectively (and optionally, install the missing packages). - `read_utf8()` and `write_utf8()` to read and write UTF-8 files, respectively. - `same_path()` to test if two paths are the same. - `strict_list()` is a version of `list()` that disables partial matching of the `$` operator. - `tojson()` is a simple JSON serializer. - `try_silent()` is an abbreviation of `try(silent = TRUE)`. xfun/MD50000644000176200001440000000663613607036513011561 0ustar liggesusers741e834cf5ce5ea00a3273490cd4c494 *DESCRIPTION 4da54e2d32ef92748cac3d16b89df90a *LICENSE e344168ff9a7629fb84929073f3a8be9 *NAMESPACE 79a8eb549ae7f7c5735176f35f3b1e14 *NEWS.md 4888f5ab319c768d12f87d0c89232783 *R/command.R 35e3a91a84d20cf056edfab66ca1885d *R/data-structure.R 1d1e25149949ee96afb2ffe984593108 *R/encoding.R f35c8127e2c4780a928f9fc7912b5774 *R/image.R f840aa2f6fd3ea02c247ca1a4a564973 *R/io.R 8a887ec21c43bbe327669b90edc6bfc1 *R/json.R 32f05975f6b9b2064f99110b1d8c508d *R/markdown.R f6d758bc0c85eb8183e219887611615b *R/os.R 144ea383d6f4bb19a807a77eaa076c5a *R/packages.R df61cf50ca44c45a512ccdf9eae0b662 *R/paths.R 167d106a12959e74b45a621dc1455394 *R/revcheck.R f1dd49b6d5a6050c1245f5235ea2f46f *R/rstudio.R 9052274402f58eb3ea73b1b44618b4b6 *R/string.R 60d7a8f755153b12bd6f30c12fbbeb39 *R/utils.R 286eaec562ed567b24ef247586f35ab4 *README.md 84f4164a793a7694772da11a767b99ae *build/vignette.rds 8e3727b96fea910c6226ad448b5b0a83 *inst/doc/xfun.R 1e4c7a20159934b58fd12a31d4e12e77 *inst/doc/xfun.Rmd 428b05dc21e219d82060961e0c5a3651 *inst/doc/xfun.html 4e4105a570be434f2780a252d213d2f0 *man/Rscript.Rd 145823073737fb317192a88f96b0b1e8 *man/attr.Rd dfb1f4b9734ff77a7aa1629904f4eca9 *man/download_file.Rd 271ce263fd5381e795dfe3331f70b5fa *man/embed_file.Rd 211bda4b44c10e11f8c7324a52e87298 *man/file_ext.Rd 4e06ec02507a27ecafdcc920ca11f034 *man/file_string.Rd 09ac3348e9b66291e9ef038b1388d8d8 *man/gsub_file.Rd 12dfedf428830334f6fb5a68e01a34c0 *man/in_dir.Rd c386c1d87c2cbec4ab3268246a6c3174 *man/install_dir.Rd 92eb34f64cc68efcdaa6b306b24d9e08 *man/install_github.Rd 6b29efad62d48a40ea8ae985e615f208 *man/isFALSE.Rd b27aa5dc7640437154c8c94236b1efbf *man/is_ascii.Rd dd4158aecdeaf9267d33102ac60be53d *man/native_encode.Rd 77303bb77ea468d691b933b5dc397b10 *man/normalize_path.Rd 736537ffe052881a429024524db0b22d *man/numbers_to_words.Rd b98967ab64e797693e0aee06240cfd92 *man/optipng.Rd 172f886a4e06c47f9cb05f06781d3072 *man/os.Rd 7cbfa9aa2787f1abe314fcd39fd320c4 *man/parse_only.Rd b59ad79669889f969cb6bc5d0464c434 *man/pkg_attach.Rd beb2cf47c519361c8e3dbe13ca0f3b9e *man/prose_index.Rd 30ea18271d700ba35e246dfc4683d190 *man/protect_math.Rd 1a5280c440d309598289acac4358d325 *man/raw_string.Rd afd2f09a974aa036d99d4594d3132ca1 *man/read_utf8.Rd 93e07573f831ea574bbecf858abb36ad *man/rename_seq.Rd 48149f7b1cf3d8322ff7928a55b6d3ad *man/rev_check.Rd 8d03fc84d7df7e0a771520c3239b7fb5 *man/rstudio_type.Rd ed767135afc65d66523e669608fae972 *man/same_path.Rd 1a592b13507ee4f02d7d91518a945de9 *man/session_info.Rd 15db5d87f4cc66894ea4b1e972ab22b7 *man/split_lines.Rd 6b826a293afba42edddf52412f11aeb6 *man/strict_list.Rd 8779a107140a0f5caeba6f5b08087733 *man/stringsAsStrings.Rd 4331e8a8ad2287858a3fb8654ea0a4f4 *man/tojson.Rd 1f21a2a55da87a2ba8ae5f053e133860 *man/try_silent.Rd 851477c71c032336b44265d86bf5002f *man/upload_ftp.Rd f42e361fb74d8b667622f351df8c6803 *tests/test-all.R 19e2b1fd05b00d7d575badd05ff2c474 *tests/testit/test-data-structure.R dc33bf3f00da4dd1053dd67980fc3fc7 *tests/testit/test-encoding.R 953e4a41774ae1d4b2cd0f56ef991d78 *tests/testit/test-io.R 587c9ff5681e022c39406dca8740b804 *tests/testit/test-json.R 7a5b84d56a70871fa9005ef2074ef0f0 *tests/testit/test-markdown.R 13faeceb9c0af45438e7c16aad74fd0b *tests/testit/test-packages.R b4e048eb33987713eec3b96110943cc2 *tests/testit/test-paths.R b9014b5c356ce4276f89238a49b4c27a *tests/testit/test-string.R 70e2184db79e0acd800d8cac64a5cd65 *tests/testit/test-utils.R 1e4c7a20159934b58fd12a31d4e12e77 *vignettes/xfun.Rmd xfun/inst/0000755000176200001440000000000013606770153012217 5ustar liggesusersxfun/inst/doc/0000755000176200001440000000000013606770153012764 5ustar liggesusersxfun/inst/doc/xfun.Rmd0000644000176200001440000002014713563361006014407 0ustar liggesusers--- 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/inst/doc/xfun.html0000644000176200001440000010027613606770153014640 0ustar liggesusers An Introduction to xfun

An Introduction to xfun

A Collection of Miscellaneous Functions

Yihui Xie

2020-01-12

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 list
z2$a  # partial matching
## [1] "I am aaa"
z3 = as_strict_list(z2)  # a strict list again
z3$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!

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.,

library(xfun)
f = tempfile()
writeLines(c("hello", "world"), f)
gsub_file(f, "world", "woRld", fixed = TRUE)
file_string(f)
hello
woRld

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.

library(xfun)
p = c("abc.doc", "def123.tex", "path/to/foo.Rmd")
file_ext(p)
## [1] "doc" "tex" "Rmd"
sans_ext(p)
## [1] "abc"         "def123"      "path/to/foo"
with_ext(p, ".txt")
## [1] "abc.txt"         "def123.txt"      "path/to/foo.txt"
with_ext(p, c(".ppt", ".sty", ".Rnw"))
## [1] "abc.ppt"         "def123.sty"      "path/to/foo.Rnw"
with_ext(p, "html")
## [1] "abc.html"         "def123.html"      "path/to/foo.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.,

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,

library(testit)
library(parallel)
library(tinytex)
library(mime)

is equivalent to

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.,

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.

n2w(0, cap = TRUE)
## [1] "Zero"
n2w(seq(0, 121, 11), and = TRUE)
##  [1] "zero"                       "eleven"                    
##  [3] "twenty-two"                 "thirty-three"              
##  [5] "forty-four"                 "fifty-five"                
##  [7] "sixty-six"                  "seventy-seven"             
##  [9] "eighty-eight"               "ninety-nine"               
## [11] "one hundred and ten"        "one hundred and twenty-one"
n2w(1e+06)
## [1] "one million"
n2w(1e+11 + 12345678)
## [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).

xfun/inst/doc/xfun.R0000644000176200001440000000464313606770153014076 0ustar liggesusers## ----setup, include=FALSE----------------------------------------------------- library(xfun) ## ----------------------------------------------------------------------------- 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! ## ----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 ## ----comment=''--------------------------------------------------------------- f = system.file("LICENSE", package = "xfun") xfun::file_string(f) as.character(xfun::file_string(f)) # essentially a character string ## ----comment=''--------------------------------------------------------------- library(xfun) f = tempfile() writeLines(c("hello", "world"), f) gsub_file(f, "world", "woRld", fixed = TRUE) file_string(f) ## ----------------------------------------------------------------------------- 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::is_macos() xfun::is_unix() xfun::is_linux() xfun::is_windows() ## ----eval=FALSE--------------------------------------------------------------- # library(testit) # library(parallel) # library(tinytex) # library(mime) ## ----eval=FALSE--------------------------------------------------------------- # xfun::pkg_attach(c('testit', 'parallel', 'tinytex', 'mime')) ## ----eval=FALSE--------------------------------------------------------------- # if (!requireNamespace('tinytex')) install.packages('tinytex') # library(tinytex) ## ----eval=FALSE--------------------------------------------------------------- # xfun::pkg_attach2('tinytex') ## ----------------------------------------------------------------------------- n2w(0, cap = TRUE) n2w(seq(0, 121, 11), and = TRUE) n2w(1e+06) n2w(1e+11 + 12345678) n2w(-987654321) n2w(1e+15 - 1) ## ----------------------------------------------------------------------------- xfun::session_info(c('xfun', 'rmarkdown', 'knitr', 'tinytex'), dependencies = FALSE)