rprojroot/0000755000176200001440000000000014521666122012317 5ustar liggesusersrprojroot/NAMESPACE0000644000176200001440000000217314521632656013546 0ustar liggesusers# Generated by roxygen2: do not edit by hand S3method("|",root_criterion) S3method(as_root_criterion,character) S3method(as_root_criterion,default) S3method(as_root_criterion,root_criterion) S3method(format,root_criterion) S3method(print,root_criterion) S3method(str,root_criteria) export(as.root_criterion) export(as_root_criterion) export(criteria) export(find_package_root_file) export(find_remake_root_file) export(find_root) export(find_root_file) export(find_rstudio_root_file) export(find_testthat_root_file) export(from_wd) export(get_root_desc) export(has_basename) export(has_dir) export(has_file) export(has_file_pattern) export(is.root_criterion) export(is_drake_project) export(is_git_root) export(is_pkgdown_project) export(is_projectile_project) export(is_quarto_project) export(is_r_package) export(is_remake_project) export(is_renv_project) export(is_root_criterion) export(is_rstudio_project) export(is_svn_root) export(is_testthat) export(is_vcs_root) export(root_criterion) export(thisfile) export(thisfile_knit) export(thisfile_r) export(thisfile_rscript) export(thisfile_source) importFrom(utils,str) importFrom(utils,tail) rprojroot/LICENSE0000644000176200001440000000005714136674000013322 0ustar liggesusersYEAR: 2020 COPYRIGHT HOLDER: rprojroot authors rprojroot/README.md0000644000176200001440000002003214473542244013577 0ustar liggesusers # [rprojroot](https://rprojroot.r-lib.org/) [![Lifecycle: stable](https://img.shields.io/badge/lifecycle-stable-brightgreen.svg)](https://lifecycle.r-lib.org/articles/stages.html) [![rcc](https://github.com/r-lib/rprojroot/workflows/rcc/badge.svg)](https://github.com/r-lib/rprojroot/actions) [![CRAN\_Status\_Badge](https://www.r-pkg.org/badges/version/rprojroot)](https://cran.r-project.org/package=rprojroot) [![Codecov test coverage](https://codecov.io/gh/r-lib/rprojroot/branch/main/graph/badge.svg)](https://app.codecov.io/gh/r-lib/rprojroot?branch=main) This package helps accessing files relative to a *project root* to [stop the working directory insanity](https://gist.github.com/jennybc/362f52446fe1ebc4c49f). It is a low-level helper package for the [here](https://here.r-lib.org/) package.
library(rprojroot)
## Example The rprojroot package works best when you have a “project”: all related files contained in a subdirectory that can be categorized using a strict criterion. Let’s create a package for demonstration.
dir <- tempfile()
pkg <- usethis::create_package(dir)
#>  Creating '/tmp/RtmpBLE08t/file294c3c8acca7/'
#>  Setting active project to '/tmp/RtmpBLE08t/file294c3c8acca7'
#>  Creating 'R/'
#>  Writing 'DESCRIPTION'
#> Package: file294c3c8acca7
#> Title: What the Package Does (One Line, Title Case)
#> Version: 0.0.0.9000
#> Date: 2020-11-08
#> Authors@R (parsed):
#>     * Kirill Müller <krlmlr+r@mailbox.org> [aut, cre] (<https://orcid.org/0000-0002-1416-3412>)
#> Description: What the package does (one paragraph).
#> License: GPL-3
#> URL: https://github.com/krlmlr/rprojroot,
#>     https://krlmlr.github.io/rprojroot
#> BugReports: https://github.com/krlmlr/rprojroot/issues
#> Encoding: UTF-8
#> LazyData: true
#> Roxygen: list(markdown = TRUE)
#> RoxygenNote: 7.1.1.9000
#>  Writing 'NAMESPACE'
#>  Setting active project to '<no active project>'
R packages satisfy the `is_r_package` criterion. A criterion is an object that contains a `find_file()` function. With `pkg` as working directory, the function works like [`file.path()`](https://rdrr.io/r/base/file.path.html), rooted at the working directory:
setwd(pkg)
is_r_package
#> Root criterion: contains a file `DESCRIPTION` with contents matching `^Package: `
is_r_package$find_file()
#> [1] "/tmp/RtmpBLE08t/file294c3c8acca7"
is_r_package$find_file("tests", "testthat")
#> [1] "/tmp/RtmpBLE08t/file294c3c8acca7/tests/testthat"
This works identically when starting from a subdirectory:
setwd(file.path(pkg, "R"))
is_r_package$find_file()
#> [1] "/tmp/RtmpBLE08t/file294c3c8acca7"
is_r_package$find_file("tests", "testthat")
#> [1] "/tmp/RtmpBLE08t/file294c3c8acca7/tests/testthat"
There is one exception: if the first component passed to `find_file()` is already an absolute path. This allows safely applying this function to paths that may be absolute or relative:
setwd(file.path(pkg, "R"))
path <- is_r_package$find_file()
is_r_package$find_file(path, "tests", "testthat")
#> [1] "/tmp/RtmpBLE08t/file294c3c8acca7/tests/testthat"
As long as you are sure that your working directory is somewhere inside your project, you can retrieve the project root. ## Installation and further reading Install the package from CRAN:
install.package("rprojroot")
See the [documentation](https://rprojroot.r-lib.org/articles/rprojroot.html) for more detail. ------------------------------------------------------------------------ ## Code of Conduct Please note that the rprojroot project is released with a [Contributor Code of Conduct](https://rprojroot.r-lib.org/CODE_OF_CONDUCT.html). By contributing to this project, you agree to abide by its terms. rprojroot/man/0000755000176200001440000000000014521632656013077 5ustar liggesusersrprojroot/man/criteria.Rd0000644000176200001440000000336114521632656015173 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/root.R \docType{data} \name{criteria} \alias{criteria} \alias{is_rstudio_project} \alias{is_r_package} \alias{is_remake_project} \alias{is_drake_project} \alias{is_pkgdown_project} \alias{is_renv_project} \alias{is_projectile_project} \alias{is_quarto_project} \alias{is_git_root} \alias{is_svn_root} \alias{is_vcs_root} \alias{is_testthat} \alias{from_wd} \title{Prespecified criteria} \usage{ criteria is_rstudio_project is_r_package is_remake_project is_drake_project is_pkgdown_project is_renv_project is_projectile_project is_quarto_project is_git_root is_svn_root is_vcs_root is_testthat from_wd } \description{ This is a collection of commonly used root criteria. } \details{ \code{is_rstudio_project} looks for a file with extension \code{.Rproj}. \code{is_r_package} looks for a \code{DESCRIPTION} file. \code{is_remake_project} looks for a \code{remake.yml} file. \code{is_drake_project} looks for a \code{.drake} directory. \code{is_pkgdown_project} looks for a \verb{_pkgdown.yml}, \verb{_pkgdown.yaml}, \verb{pkgdown/_pkgdown.yml} and/or \verb{inst/_pkgdown.yml} file. \code{is_renv_project} looks for an \code{renv.lock} file. \code{is_projectile_project} looks for a \code{.projectile} file. \code{is_quarto_project} looks for a \verb{_quarto.yml} file. \code{is_git_root} looks for a \code{.git} directory. \code{is_svn_root} looks for a \code{.svn} directory. \code{is_vcs_root} looks for the root of a version control system, currently only Git and SVN are supported. \code{is_testthat} looks for the \code{testthat} directory, works when developing, testing, and checking a package. \code{from_wd} uses the current working directory. } \keyword{datasets} rprojroot/man/rprojroot-package.Rd0000644000176200001440000000212214473542516017015 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/rprojroot-package.R \docType{package} \name{rprojroot-package} \alias{rprojroot} \alias{rprojroot-package} \title{rprojroot: Finding Files in Project Subdirectories} \description{ Robust, reliable and flexible paths to files below a project root. The 'root' of a project is defined as a directory that matches a certain criterion, e.g., it contains a certain regular file. } \details{ See the "Value" section in \code{\link[=root_criterion]{root_criterion()}} for documentation of root criterion objects, and \link{criteria} for useful predefined root criteria. } \examples{ criteria \dontrun{ is_r_package$find_file("NAMESPACE") root_fun <- is_r_package$make_fix_file() root_fun("NAMESPACE") } } \seealso{ Useful links: \itemize{ \item \url{https://rprojroot.r-lib.org/} \item \url{https://github.com/r-lib/rprojroot} \item Report bugs at \url{https://github.com/r-lib/rprojroot/issues} } } \author{ \strong{Maintainer}: Kirill Müller \email{kirill@cynkra.com} (\href{https://orcid.org/0000-0002-1416-3412}{ORCID}) } rprojroot/man/find_root_file.Rd0000644000176200001440000000403114136674000016335 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/file.R, R/shortcut.R \name{find_root_file} \alias{find_root_file} \alias{find_rstudio_root_file} \alias{find_package_root_file} \alias{find_remake_root_file} \alias{find_testthat_root_file} \title{File paths relative to the root of a directory hierarchy} \usage{ find_root_file(..., criterion, path = ".") find_rstudio_root_file(..., path = ".") find_package_root_file(..., path = ".") find_remake_root_file(..., path = ".") find_testthat_root_file(..., path = ".") } \arguments{ \item{...}{\verb{[character]}\cr Further path components passed to \code{\link[=file.path]{file.path()}}. All arguments must be the same length or length one.} \item{criterion}{\verb{[root_criterion]}\cr A criterion, one of the predefined \link{criteria} or created by \code{\link[=root_criterion]{root_criterion()}}. Will be coerced using \code{\link[=as_root_criterion]{as_root_criterion()}}.} \item{path}{\verb{[character(1)]}\cr The start directory.} } \value{ The normalized path of the root as specified by the search criteria, with the additional path components appended. Throws an error if no root is found. } \description{ \code{find_root_file()} is a wrapper around \code{\link[=find_root]{find_root()}} that appends an arbitrary number of path components to the root using \code{\link[base:file.path]{base::file.path()}}. } \details{ This function operates on the notion of relative paths. The \code{...} argument is expected to contain a path relative to the root. If the first path component passed to \code{...} is already an absolute path, the \code{criterion} and \code{path} arguments are ignored, and \code{...} is forwarded to \code{\link[=file.path]{file.path()}}. } \examples{ \dontrun{ find_package_root_file("tests", "testthat.R") has_file("DESCRIPTION", "^Package: ")$find_file has_file("DESCRIPTION", "^Package: ")$make_fix_file(".") } } \seealso{ \code{\link[=find_root]{find_root()}} \code{\link[utils:glob2rx]{utils::glob2rx()}} \code{\link[base:file.path]{base::file.path()}} } rprojroot/man/figures/0000755000176200001440000000000014136674000014532 5ustar liggesusersrprojroot/man/figures/lifecycle-defunct.svg0000644000176200001440000000170414136674000020642 0ustar liggesuserslifecyclelifecycledefunctdefunct rprojroot/man/figures/lifecycle-maturing.svg0000644000176200001440000000170614136674000021042 0ustar liggesuserslifecyclelifecyclematuringmaturing rprojroot/man/figures/lifecycle-archived.svg0000644000176200001440000000170714136674000021002 0ustar liggesusers lifecyclelifecyclearchivedarchived rprojroot/man/figures/lifecycle-soft-deprecated.svg0000644000176200001440000000172614136674000022267 0ustar liggesuserslifecyclelifecyclesoft-deprecatedsoft-deprecated rprojroot/man/figures/lifecycle-questioning.svg0000644000176200001440000000171414136674000021560 0ustar liggesuserslifecyclelifecyclequestioningquestioning rprojroot/man/figures/lifecycle-superseded.svg0000644000176200001440000000171314136674000021355 0ustar liggesusers lifecyclelifecyclesupersededsuperseded rprojroot/man/figures/lifecycle-stable.svg0000644000176200001440000000167414136674000020472 0ustar liggesuserslifecyclelifecyclestablestable rprojroot/man/figures/lifecycle-experimental.svg0000644000176200001440000000171614136674000021712 0ustar liggesuserslifecyclelifecycleexperimentalexperimental rprojroot/man/figures/lifecycle-deprecated.svg0000644000176200001440000000171214136674000021311 0ustar liggesuserslifecyclelifecycledeprecateddeprecated rprojroot/man/root_criterion.Rd0000644000176200001440000001066314473542244016434 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/criterion.R, R/root.R \name{root_criterion} \alias{root_criterion} \alias{is_root_criterion} \alias{as_root_criterion} \alias{as_root_criterion.character} \alias{as_root_criterion.root_criterion} \alias{|.root_criterion} \alias{has_file} \alias{has_dir} \alias{has_file_pattern} \alias{has_basename} \title{Is a directory the project root?} \usage{ root_criterion(testfun, desc, subdir = NULL) is_root_criterion(x) as_root_criterion(x) \method{as_root_criterion}{character}(x) \method{as_root_criterion}{root_criterion}(x) \method{|}{root_criterion}(x, y) has_file(filepath, contents = NULL, n = -1L, fixed = FALSE) has_dir(filepath) has_file_pattern(pattern, contents = NULL, n = -1L, fixed = FALSE) has_basename(basename, subdir = NULL) } \arguments{ \item{testfun}{\verb{[function|list(function)]}\cr A function with one parameter that returns \code{TRUE} if the directory specified by this parameter is the project root, and \code{FALSE} otherwise. Can also be a list of such functions.} \item{desc}{\verb{[character]}\cr A textual description of the test criterion, of the same length as \code{testfun}.} \item{subdir}{\verb{[character]}\cr Subdirectories to start the search in, if found} \item{x}{\verb{[object]}\cr An object.} \item{y}{\verb{[object]}\cr An object.} \item{filepath}{\verb{[character(1)]}\cr File path (can contain directories).} \item{contents, fixed}{\verb{[character(1)]}\cr If \code{contents} is \code{NULL} (the default), file contents are not checked. Otherwise, \code{contents} is a regular expression (if \code{fixed} is \code{FALSE}) or a search string (if \code{fixed} is \code{TRUE}), and file contents are checked matching lines.} \item{n}{\verb{[integerish(1)]}\cr Maximum number of lines to read to check file contents.} \item{pattern}{\verb{[character(1)]}\cr Regular expression to match the file name against.} \item{basename}{\verb{[character(1)]}\cr The required name of the root directory.} } \value{ An S3 object of class \code{root_criterion} wit the following members: \describe{ \item{\code{testfun}}{The \code{testfun} argument} \item{\code{desc}}{The \code{desc} argument} \item{\code{subdir}}{The \code{subdir} argument} \item{\code{find_file}}{A function with \code{...} and \code{path} arguments that returns a path relative to the root, as specified by this criterion. The optional \code{path} argument specifies the starting directory, which defaults to \code{"."}. The function forwards to \code{\link[=find_root_file]{find_root_file()}}, which passes \code{...} directly to \code{file.path()} if the first argument is an absolute path. } \item{\code{make_fix_file}}{A function with a \code{path} argument that returns a function that finds paths relative to the root. For a criterion \code{cr}, the result of \code{cr$make_fix_file(".")(...)} is identical to \code{cr$find_file(...)}. The function created by \code{make_fix_file()} can be saved to a variable to be more independent of the current working directory. } } } \description{ Objects of the \code{root_criterion} class decide if a given directory is a project root. } \details{ Construct criteria using \code{root_criterion} in a very general fashion by specifying a function with a \code{path} argument, and a description. The \code{as_root_criterion()} function accepts objects of class \code{root_criterion}, and character values; the latter will be converted to criteria using \code{has_file}. Root criteria can be combined with the \code{|} operator. The result is a composite root criterion that requires either of the original criteria to match. The \code{has_file()} function constructs a criterion that checks for the existence of a specific file (which itself can be in a subdirectory of the root) with specific contents. The \code{has_dir()} function constructs a criterion that checks for the existence of a specific directory. The \code{has_file_pattern()} function constructs a criterion that checks for the existence of a file that matches a pattern, with specific contents. The \code{has_basename()} function constructs a criterion that checks if the \code{\link[base:basename]{base::basename()}} of the root directory has a specific name, with support for case-insensitive file systems. } \examples{ root_criterion(function(path) file.exists(file.path(path, "somefile")), "has somefile") has_file("DESCRIPTION") is_r_package \dontrun{ is_r_package$find_file is_r_package$make_fix_file(".") } } rprojroot/man/thisfile.Rd0000644000176200001440000000336514473542244015203 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/thisfile.R \name{thisfile} \alias{thisfile} \alias{thisfile_source} \alias{thisfile_r} \alias{thisfile_rscript} \alias{thisfile_knit} \title{Determines the path of the currently running script} \usage{ thisfile() thisfile_source() thisfile_r() thisfile_rscript() thisfile_knit() } \value{ The path of the currently running script, NULL if it cannot be determined. } \description{ \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#soft-deprecated}{\figure{lifecycle-soft-deprecated.svg}{options: alt='[Soft-deprecated]'}}}{\strong{[Soft-deprecated]}} \R does not store nor export the path of the currently running script. This is an attempt to circumvent this limitation by applying heuristics (such as call stack and argument inspection) that work in many cases. \strong{CAVEAT}: Use this function only if your workflow does not permit other solution: if a script needs to know its location, it should be set outside the context of the script if possible. } \details{ This functions currently work only if the script was \code{source}d, processed with \code{knitr}, or run with \code{Rscript} or using the \code{--file} parameter to the \code{R} executable. For code run with \code{Rscript}, the exact value of the parameter passed to \code{Rscript} is returned. } \section{Life cycle}{ These functions are now available in the \pkg{whereami} package. } \examples{ \dontrun{ thisfile() } } \references{ \url{https://stackoverflow.com/q/1815606/946850} } \seealso{ \code{\link[base:source]{base::source()}}, \code{\link[utils:Rscript]{utils::Rscript()}}, \code{\link[base:getwd]{base::getwd()}} } \author{ Kirill Müller, Hadley Wickham, Michael R. Head } \keyword{internal} rprojroot/man/find_root.Rd0000644000176200001440000000314214521631672015346 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/root.R \name{find_root} \alias{find_root} \alias{get_root_desc} \title{Find the root of a directory hierarchy} \usage{ find_root(criterion, path = ".") get_root_desc(criterion, path) } \arguments{ \item{criterion}{\verb{[root_criterion]}\cr A criterion, one of the predefined \link{criteria} or created by \code{\link[=root_criterion]{root_criterion()}}. Will be coerced using \code{\link[=as_root_criterion]{as_root_criterion()}}.} \item{path}{\verb{[character(1)]}\cr The start directory.} } \value{ The normalized path of the root as specified by the search criterion. Throws an error if no root is found } \description{ A \emph{root} is defined as a directory that contains a regular file whose name matches a given pattern and which optionally contains a given text. The search for a root starts at a given directory (the working directory by default), and proceeds up the directory hierarchy. \code{get_root_desc()} returns the description of the criterion for a root path. This is especially useful for composite root criteria created with \code{\link[=|.root_criterion]{|.root_criterion()}}. } \details{ Starting from the working directory, the \code{find_root()} function searches for the root. If a root is found, the \code{...} arguments are used to construct a path; thus, if no extra arguments are given, the root is returned. If no root is found, an error is thrown. } \examples{ \dontrun{ find_root(glob2rx("DESCRIPTION"), "^Package: ") } } \seealso{ \code{\link[utils:glob2rx]{utils::glob2rx()}} \code{\link[=file.path]{file.path()}} } rprojroot/man/deprecated.Rd0000644000176200001440000000066014136674000015457 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/deprecated.R \name{deprecated} \alias{deprecated} \alias{as.root_criterion} \alias{is.root_criterion} \title{Deprecated functions} \usage{ as.root_criterion(...) is.root_criterion(...) } \description{ Use \code{\link[=as_root_criterion]{as_root_criterion()}} and \code{\link[=is_root_criterion]{is_root_criterion()}}, respectively. } \keyword{internal} rprojroot/DESCRIPTION0000644000176200001440000000211314521666122014022 0ustar liggesusersPackage: rprojroot Title: Finding Files in Project Subdirectories Version: 2.0.4 Authors@R: person(given = "Kirill", family = "M\u00fcller", role = c("aut", "cre"), email = "kirill@cynkra.com", comment = c(ORCID = "0000-0002-1416-3412")) Description: Robust, reliable and flexible paths to files below a project root. The 'root' of a project is defined as a directory that matches a certain criterion, e.g., it contains a certain regular file. License: MIT + file LICENSE URL: https://rprojroot.r-lib.org/, https://github.com/r-lib/rprojroot BugReports: https://github.com/r-lib/rprojroot/issues Depends: R (>= 3.0.0) Suggests: covr, knitr, lifecycle, mockr, rlang, rmarkdown, testthat (>= 3.0.0), withr VignetteBuilder: knitr Config/testthat/edition: 3 Encoding: UTF-8 RoxygenNote: 7.2.3 NeedsCompilation: no Packaged: 2023-11-05 06:47:23 UTC; kirill Author: Kirill Müller [aut, cre] () Maintainer: Kirill Müller Repository: CRAN Date/Publication: 2023-11-05 10:20:02 UTC rprojroot/build/0000755000176200001440000000000014521635173013420 5ustar liggesusersrprojroot/build/vignette.rds0000644000176200001440000000034114521635173015755 0ustar liggesusersmQ0 ?y)/xl ldo>XMu2mȴ |s lRBqA6b0~i- Wa2!4\a?]@lst_|(Bfa4sL}/>Ԉ&G;RAkӅyRB]$$۽_'rprojroot/tests/0000755000176200001440000000000014136674000013455 5ustar liggesusersrprojroot/tests/testthat/0000755000176200001440000000000014521666122015321 5ustar liggesusersrprojroot/tests/testthat/test-testthat.R0000644000176200001440000000074114136674000020257 0ustar liggesuserstest_that("is_testthat", { expect_snapshot(is_testthat) testthat_path <- normalizePath("package/tests/testthat", winslash = "/") expect_equal(is_testthat$find_file(path = "package"), testthat_path) expect_equal(is_testthat$find_file(path = "package/tests"), testthat_path) expect_equal(is_testthat$find_file(path = "package/tests/testthat"), testthat_path) }) test_that("dogfood", { expect_true(file.exists(is_testthat$find_file("hierarchy", "a", "b", "c", "d"))) }) rprojroot/tests/testthat/test-root.R0000644000176200001440000002343414521631672017414 0ustar liggesuserstest_that("is_root_criterion", { expect_true(is_root_criterion(has_file("DESCRIPTION"))) expect_false(is_root_criterion("DESCRIPTION")) expect_true(is_root_criterion(as_root_criterion("DESCRIPTION"))) }) test_that("as_root_criterion", { reset_env <- function(x) { if (is.function(x)) { environment(x) <- .GlobalEnv } else if (is.list(x)) { x <- lapply(x, reset_env) } x } expect_equal( lapply(as_root_criterion("x"), reset_env), lapply(has_file("x"), reset_env) ) expect_error(as_root_criterion(5), "Cannot coerce") }) test_that("Formatting", { expect_snapshot(format(is_r_package)) expect_snapshot(is_r_package) expect_snapshot(is_vcs_root) expect_snapshot(has_file("a", contents = "foo", fixed = TRUE)) expect_snapshot(has_file_pattern("a.*b", contents = "foo", fixed = TRUE)) expect_snapshot(criteria) expect_snapshot(str(criteria)) }) test_that("Combining criteria", { comb_crit <- is_r_package | is_rstudio_project expect_true(is_root_criterion(comb_crit)) expect_snapshot(comb_crit) expect_equal( find_root(comb_crit, "hierarchy"), find_root(is_rstudio_project, "hierarchy/a") ) }) test_that("has_file", { wd <- normalizePath(getwd(), winslash = "/") hierarchy <- function(n = 0L) { do.call(file.path, list(wd, "hierarchy", "a", "b", "c")[seq_len(n + 1L)]) } stop_path <- hierarchy(1L) path <- hierarchy(4L) mockr::with_mock( is_root = function(x) x == stop_path, { expect_equal(find_root("a", path = path), hierarchy(3L)) expect_equal(find_root("b", path = path), hierarchy(3L)) expect_equal(find_root("b/a", path = path), hierarchy(2L)) expect_equal(find_root("c", path = path), hierarchy(1L)) expect_equal(find_root("d", path = path), hierarchy(4L)) expect_equal(find_root(has_file("DESCRIPTION", "^Package: ", 1), path = path), hierarchy(1L)) expect_equal(find_root(has_file("DESCRIPTION", "^Package: "), path = path), hierarchy(1L)) expect_equal(find_root(has_file("DESCRIPTION", "package* does", fixed = TRUE), path = path), hierarchy(1L)) expect_error( find_root("test-root.R", path = path), "No root directory found" ) expect_error( find_root("rprojroot.Rproj", path = path), "No root directory found" ) expect_error( find_root(has_file("e", "f"), path = path), "No root directory found" ) expect_error( find_root(has_file("e", "f", 1), path = path), "No root directory found" ) expect_error(has_file("/a"), "absolute") TRUE } ) }) test_that("has_file_pattern", { wd <- normalizePath(getwd(), winslash = "/") hierarchy <- function(n = 0L) { do.call(file.path, list(wd, "hierarchy", "a", "b", "c")[seq_len(n + 1L)]) } stop_path <- hierarchy(1L) path <- hierarchy(4L) mockr::with_mock( is_root = function(x) x == stop_path, { expect_equal(find_root(has_file_pattern(glob2rx("a")), path = path), hierarchy(3L)) expect_equal(find_root(has_file_pattern(glob2rx("b")), path = path), hierarchy(3L)) expect_equal( find_root(has_file_pattern("[ab]", "File b"), path = path), hierarchy(3L) ) expect_equal( find_root(has_file_pattern("[ab]", "File b in root"), path = path), hierarchy(1L) ) expect_equal(find_root(has_file_pattern(glob2rx("c")), path = path), hierarchy(1L)) expect_equal(find_root(has_file_pattern(glob2rx("d")), path = path), hierarchy(4L)) expect_equal(find_root(has_file_pattern(glob2rx("DES*ION"), "^Package: ", 1), path = path), hierarchy(1L)) expect_equal(find_root(has_file_pattern(glob2rx("DESCRI?TION"), "^Package: "), path = path), hierarchy(1L)) expect_equal(find_root(has_file_pattern(glob2rx("D?SCRIPTI?N"), "package* does", fixed = TRUE), path = path), hierarchy(1L)) expect_error( find_root(has_file_pattern(glob2rx("test-root.R")), path = path), "No root directory found" ) expect_error( find_root(has_file_pattern(glob2rx("rprojroot.Rproj")), path = path), "No root directory found" ) expect_error( find_root(has_file_pattern(glob2rx("e"), "f"), path = path), "No root directory found" ) expect_error( find_root(has_file_pattern(glob2rx("e"), "f", 1), path = path), "No root directory found" ) } ) }) test_that("has_dir", { wd <- normalizePath(getwd(), winslash = "/") hierarchy <- function(n = 0L) { do.call(file.path, list(wd, "hierarchy", "a", "b", "c")[seq_len(n + 1L)]) } stop_path <- hierarchy(1L) path <- hierarchy(4L) mockr::with_mock( is_root = function(x) x == stop_path, { expect_equal(find_root(has_dir("a"), path = path), hierarchy(1L)) expect_equal(find_root(has_dir("b"), path = path), hierarchy(2L)) expect_equal(find_root(has_dir("c"), path = path), hierarchy(3L)) expect_error( find_root(has_dir("e"), path = path), "No root directory found" ) expect_error( find_root(has_dir("rprojroot.Rproj"), path = path), "No root directory found" ) expect_error(has_dir("/a"), "absolute") } ) }) test_that("has_basename", { wd <- normalizePath(getwd(), winslash = "/") hierarchy <- function(n = 0L) { do.call(file.path, list(wd, "hierarchy", "a", "b", "c")[seq_len(n + 1L)]) } stop_path <- hierarchy(1L) path <- hierarchy(4L) mockr::with_mock( is_root = function(x) x == stop_path, { expect_equal(find_root(has_basename("a"), path = path), hierarchy(2L)) expect_equal(find_root(has_basename("b"), path = path), hierarchy(3L)) expect_equal(find_root(has_basename("c"), path = path), hierarchy(4L)) expect_error( find_root(has_basename("d"), path = path), "No root directory found" ) expect_error( find_root(has_basename("rprojroot.Rproj"), path = path), "No root directory found" ) } ) }) test_that("concrete criteria", { wd <- normalizePath(getwd(), winslash = "/") hierarchy <- function(n = 0L) { do.call(file.path, list(wd, "hierarchy", "a", "b", "c")[seq_len(n + 1L)]) } # HACK writeLines(character(), file.path(hierarchy(3L), ".projectile")) stop_path <- hierarchy(0L) path <- hierarchy(4L) mockr::with_mock( is_root = function(x) x == stop_path, { expect_equal(find_root(is_rstudio_project, path = path), hierarchy(1L)) expect_equal(find_root(is_remake_project, path = path), hierarchy(2L)) expect_equal(find_root(is_projectile_project, path = path), hierarchy(3L)) } ) }) test_that("is_svn_root", { temp_dir <- tempfile("svn") unzip("vcs/svn.zip", exdir = temp_dir) wd <- normalizePath(temp_dir, winslash = "/") hierarchy <- function(n = 0L) { do.call(file.path, list(wd, "svn", "a", "b", "c")[seq_len(n + 1L)]) } stop_path <- normalizePath(tempdir(), winslash = "/") path <- hierarchy(4L) mockr::with_mock( is_root = function(x) x == stop_path, { expect_equal(find_root(is_svn_root, path = path), hierarchy(1L)) expect_equal(find_root(is_vcs_root, path = path), hierarchy(1L)) expect_error( find_root(is_svn_root, path = hierarchy(0L)), "No root directory found" ) expect_error( find_root(is_vcs_root, path = hierarchy(0L)), "No root directory found" ) } ) }) setup_git_root <- function(separate_git_dir = FALSE) { temp_dir <- tempfile("git") unzip("vcs/git.zip", exdir = temp_dir) wd <- normalizePath(temp_dir, winslash = "/") hierarchy <- function(n = 0L) { do.call(file.path, list(wd, "git", "a", "b", "c")[seq_len(n + 1L)]) } if (separate_git_dir) { # Copy .git dir to a separate location, then make a .git file. # (other_git_folder becomes a bare git repo) old_git_location <- file.path(wd, "git", ".git") new_git_location <- file.path(wd, "other_git_folder") file.rename(old_git_location, new_git_location) writeLines(paste("gitdir:", new_git_location), old_git_location) } return(hierarchy) } test_that("is_git_root", { hierarchy <- setup_git_root(separate_git_dir = FALSE) path <- hierarchy(4L) stop_path <- normalizePath(tempdir(), winslash = "/") mockr::with_mock( is_root = function(x) x == stop_path, { expect_equal(find_root(is_git_root, path = path), hierarchy(1L)) expect_equal(find_root(is_vcs_root, path = path), hierarchy(1L)) expect_error( find_root(is_git_root, path = hierarchy(0L)), "No root directory found" ) expect_error( find_root(is_vcs_root, path = hierarchy(0L)), "No root directory found" ) } ) }) test_that("is_git_root for separated git directory", { hierarchy <- setup_git_root(separate_git_dir = TRUE) path <- hierarchy(4L) stop_path <- normalizePath(tempdir(), winslash = "/") mockr::with_mock( is_root = function(x) x == stop_path, { expect_equal(find_root(is_git_root, path = path), hierarchy(1L)) expect_equal(find_root(is_vcs_root, path = path), hierarchy(1L)) expect_error( find_root(is_git_root, path = hierarchy(0L)), "No root directory found" ) expect_error( find_root(is_vcs_root, path = hierarchy(0L)), "No root directory found" ) } ) }) test_that("finds root", { skip_on_cran() # Checks that search for root actually terminates expect_error( find_root("9259cfa7884bf51eb9dd80b52c26dcdf9cd28e82"), "No root directory found" ) }) test_that("stops if depth reached", { find_root_mocked <- find_root mock_env <- new.env() mock_env$dirname <- identity environment(find_root_mocked) <- mock_env # Checks that search for root terminates for very deep hierarchies expect_error(find_root_mocked(""), "Maximum search of [0-9]+ exceeded") }) rprojroot/tests/testthat/startup.Rs0000644000176200001440000000000014136674000017313 0ustar liggesusersrprojroot/tests/testthat/setup.R0000644000176200001440000000061414521631734016606 0ustar liggesusersif (requireNamespace("rlang", quietly = TRUE)) { colon_colon <- `::` ensym <- rlang::ensym inject <- rlang::inject as_string <- rlang::as_string `::` <- function(x, y) { x_sym <- ensym(x) y_sym <- ensym(y) tryCatch( inject(colon_colon(!!x_sym, !!y_sym)), packageNotFoundError = function(e) { skip_if_not_installed(as_string(x_sym)) } ) } } rprojroot/tests/testthat/test-absolute.R0000644000176200001440000000145314473542515020247 0ustar liggesusers# From fs describe("is_absolute_path", { it("detects windows absolute paths", { expect_true(is_absolute_path("c:\\")) expect_true(is_absolute_path("c:/")) expect_true(is_absolute_path("P:/")) expect_true(is_absolute_path("P:\\")) expect_true(is_absolute_path("\\\\server\\mountpoint\\")) expect_true(is_absolute_path("\\foo")) expect_true(is_absolute_path("\\foo\\bar")) }) it("detects posix absolute paths", { expect_false(is_absolute_path("")) expect_false(is_absolute_path("foo/bar")) expect_false(is_absolute_path("./foo/bar")) expect_false(is_absolute_path("../foo/bar")) expect_true(is_absolute_path("/")) expect_true(is_absolute_path("/foo")) expect_true(is_absolute_path("/foo/bar")) expect_true(is_absolute_path("~/foo/bar")) }) }) rprojroot/tests/testthat/test-path.R0000644000176200001440000000263114136674000017353 0ustar liggesusers# Adapted from fs describe("path", { it("returns paths UTF-8 encoded", { skip_on_os("solaris") expect_equal(Encoding(path("föö")), "UTF-8") }) it("returns paths UTF-8 encoded 2", { skip_on_os("solaris") skip_on_os("windows") expect_equal(Encoding(path("\U4F60\U597D.R")), "UTF-8") }) it("returns empty strings for empty inputs", { expect_equal(path(""), "") expect_equal(path(character()), character()) expect_equal(path("foo", character(), "bar"), character()) }) it("propagates NA strings", { expect_equal(path(NA_character_), NA_character_) expect_equal(path("foo", NA_character_), NA_character_) expect_equal(path(c("foo", "bar"), c("baz", NA_character_)), c("foo/baz", NA_character_)) }) it("does not double paths", { expect_equal(path("", "foo"), "/foo") # This could be a UNC path, so we keep the doubled path. expect_equal(path("//foo", "bar"), "//foo/bar") }) it("errors on paths which are too long", { expect_error(path(paste(rep("a", 100000), collapse = ""))) }) it("follows recycling rules", { expect_equal(path("foo", character()), character()) expect_equal(path("foo", "bar"), "foo/bar") expect_equal(path("foo", c("bar", "baz")), c("foo/bar", "foo/baz")) expect_equal(path(c("foo", "qux"), c("bar", "baz")), c("foo/bar", "qux/baz")) expect_error(path(c("foo", "qux", "foo2"), c("bar", "baz"))) }) }) rprojroot/tests/testthat/hierarchy/0000755000176200001440000000000014473542152017301 5ustar liggesusersrprojroot/tests/testthat/hierarchy/a/0000755000176200001440000000000014136674000017513 5ustar liggesusersrprojroot/tests/testthat/hierarchy/a/b/0000755000176200001440000000000014521635173017742 5ustar liggesusersrprojroot/tests/testthat/hierarchy/a/b/a0000644000176200001440000000000014136674000020065 0ustar liggesusersrprojroot/tests/testthat/hierarchy/a/b/b0000644000176200001440000000000714136674000020075 0ustar liggesusersFile b rprojroot/tests/testthat/hierarchy/a/b/d/0000755000176200001440000000000014136674000020157 5ustar liggesusersrprojroot/tests/testthat/hierarchy/a/b/d/e0000644000176200001440000000000014136674000020314 0ustar liggesusersrprojroot/tests/testthat/hierarchy/a/b/c/0000755000176200001440000000000014136674000020156 5ustar liggesusersrprojroot/tests/testthat/hierarchy/a/b/c/d0000644000176200001440000000000014136674000020312 0ustar liggesusersrprojroot/tests/testthat/hierarchy/a/remake.yml0000644000176200001440000000000014136674000021470 0ustar liggesusersrprojroot/tests/testthat/hierarchy/hierarchy.Rproj0000644000176200001440000000060114136674000022264 0ustar liggesusersVersion: 1.0 RestoreWorkspace: No SaveWorkspace: No AlwaysSaveHistory: Default EnableCodeIndexing: Yes UseSpacesForTab: Yes NumSpacesForTab: 2 Encoding: UTF-8 RnwWeave: knitr LaTeX: pdfLaTeX AutoAppendNewline: Yes StripTrailingWhitespace: Yes BuildType: Package PackageUseDevtools: Yes PackageInstallArgs: --no-multiarch --with-keep.source PackageRoxygenize: rd,collate,namespace rprojroot/tests/testthat/hierarchy/DESCRIPTION0000644000176200001440000000041014473542152021002 0ustar liggesusersPackage: hierarchy Title: What the Package Does (one line, title case) Version: 0.0-0 Authors@R: as.person("Kirill Müller [aut, cre]") Description: What the package* does. Depends: R (>= 3.2.0) License: GPL-3 LazyData: true Encoding: UTF-8 rprojroot/tests/testthat/hierarchy/b0000644000176200001440000000001714136674000017435 0ustar liggesusersFile b in root rprojroot/tests/testthat/hierarchy/c0000644000176200001440000000000014136674000017426 0ustar liggesusersrprojroot/tests/testthat/test-file.R0000644000176200001440000000226514473542516017353 0ustar liggesuserstest_that("has_file", { wd <- normalizePath(getwd(), winslash = "/") hierarchy <- function(n = 0L) { do.call(file.path, list(wd, "hierarchy", "a", "b", "c")[seq_len(n + 1L)]) } stop_path <- hierarchy(1L) path <- hierarchy(4L) mockr::with_mock( is_root = function(x) x == stop_path, { expect_equal( find_root_file("c", criterion = "b/a", path = path), file.path(hierarchy(2L), "c") ) # Absolute paths are stripped expect_equal( find_root_file("/x", "y", criterion = "b/a", path = path), file.path("/x", "y") ) expect_identical( find_root_file("c", NA, criterion = "b/a", path = path), NA_character_ ) expect_identical( find_root_file("c", character(), criterion = "b/a", path = path), character() ) expect_error( find_root_file(letters[1:2], letters[1:3], criterion = "a", path = path) ) expect_error( find_root_file(letters[1:2], character(), criterion = "a", path = path) ) expect_error( find_root_file(c("b", "/x"), "c", criterion = "a", path = path), "absolute and relative" ) } ) }) rprojroot/tests/testthat/_snaps/0000755000176200001440000000000014521631760016604 5ustar liggesusersrprojroot/tests/testthat/_snaps/root.md0000644000176200001440000001024614521631754020117 0ustar liggesusers# Formatting Code format(is_r_package) Output [1] "Root criterion: contains a file \"DESCRIPTION\" with contents matching \"^Package: \"" --- Code is_r_package Output Root criterion: contains a file "DESCRIPTION" with contents matching "^Package: " --- Code is_vcs_root Output Root criterion: one of - contains a directory ".git" - contains a file ".git" with contents matching "^gitdir: " - contains a directory ".svn" --- Code has_file("a", contents = "foo", fixed = TRUE) Output Root criterion: contains a file "a" with contents "foo" --- Code has_file_pattern("a.*b", contents = "foo", fixed = TRUE) Output Root criterion: contains a file matching "a.*b" with contents "foo" --- Code criteria Output $is_rstudio_project Root criterion: contains a file matching "[.]Rproj$" with contents matching "^Version: " in the first line $is_r_package Root criterion: contains a file "DESCRIPTION" with contents matching "^Package: " $is_remake_project Root criterion: contains a file "remake.yml" $is_pkgdown_project Root criterion: one of - contains a file "_pkgdown.yml" - contains a file "_pkgdown.yaml" - contains a file "pkgdown/_pkgdown.yml" - contains a file "inst/_pkgdown.yml" $is_renv_project Root criterion: contains a file "renv.lock" with contents matching ""Packages":\s*\{" $is_projectile_project Root criterion: contains a file ".projectile" $is_quarto_project Root criterion: contains a file "_quarto.yml" $is_git_root Root criterion: one of - contains a directory ".git" - contains a file ".git" with contents matching "^gitdir: " $is_svn_root Root criterion: contains a directory ".svn" $is_vcs_root Root criterion: one of - contains a directory ".git" - contains a file ".git" with contents matching "^gitdir: " - contains a directory ".svn" $is_testthat Root criterion: directory name is "testthat" (also look in subdirectories: `tests/testthat`, `testthat`) $from_wd Root criterion: from current working directory attr(,"class") [1] "root_criteria" --- Code str(criteria) Output List of 12 $ is_rstudio_project : chr "Root criterion: contains a file matching \"[.]Rproj$\" with contents matching \"^Version: \" in the first line" $ is_r_package : chr "Root criterion: contains a file \"DESCRIPTION\" with contents matching \"^Package: \"" $ is_remake_project : chr "Root criterion: contains a file \"remake.yml\"" $ is_pkgdown_project : chr [1:5] "Root criterion: one of" "- contains a file \"_pkgdown.yml\"" "- contains a file \"_pkgdown.yaml\"" "- contains a file \"pkgdown/_pkgdown.yml\"" ... $ is_renv_project : chr "Root criterion: contains a file \"renv.lock\" with contents matching \"\"Packages\":\\s*\\{\"" $ is_projectile_project: chr "Root criterion: contains a file \".projectile\"" $ is_quarto_project : chr "Root criterion: contains a file \"_quarto.yml\"" $ is_git_root : chr [1:3] "Root criterion: one of" "- contains a directory \".git\"" "- contains a file \".git\" with contents matching \"^gitdir: \"" $ is_svn_root : chr "Root criterion: contains a directory \".svn\"" $ is_vcs_root : chr [1:4] "Root criterion: one of" "- contains a directory \".git\"" "- contains a file \".git\" with contents matching \"^gitdir: \"" "- contains a directory \".svn\"" $ is_testthat : chr "Root criterion: directory name is \"testthat\" (also look in subdirectories: `tests/testthat`, `testthat`)" $ from_wd : chr "Root criterion: from current working directory" # Combining criteria Code comb_crit Output Root criterion: one of - contains a file "DESCRIPTION" with contents matching "^Package: " - contains a file matching "[.]Rproj$" with contents matching "^Version: " in the first line rprojroot/tests/testthat/_snaps/testthat.md0000644000176200001440000000024514521631754020772 0ustar liggesusers# is_testthat Code is_testthat Output Root criterion: directory name is "testthat" (also look in subdirectories: `tests/testthat`, `testthat`) rprojroot/tests/testthat/vcs/0000755000176200001440000000000014136674052016117 5ustar liggesusersrprojroot/tests/testthat/vcs/git.zip0000644000176200001440000005321314136674052017432 0ustar liggesusersPK .Jgit/UT @zX=zXux PK.Jgit/DESCRIPTIONUT @zX@zXux =J1yKW3 HT0j⚹LB$dl|1*ݞ?ӡ>@ %F$v6I̐ A&P"xg=!s1Q)^ ^A-1iUI =Zsp{`wt|ū#Z>da 89hY)Jm̿O_"2ќ+|lXB^˺tkh`FGCoyw_݈PK .Jgit/a/UT @zX@zXux PK .Jgit/a/b/UT @zX@zXux PK .J git/a/b/aUT @zX@zXux PK .J %J git/a/b/bUT @zX@zXux File b PK .J git/a/b/c/UT @zX@zXux PK .J git/a/b/c/dUT @zX@zXux PK Y.J git/.git/UT ZAzX=zXux PK `.Jgit/.git/hooks/UT =zX=zXux PK`.JO $git/.git/hooks/applypatch-msg.sampleUT =zX=zXux U]N1 s *HHp.nS-l[^o^ܭ7>ɩZ{B!"0([J8˽V&ZfTA=;H9/-Ov`&F3j.[K?@#rvDO BYq+7Sy:Qfm(}DJ 9CSQ ^&4VB09eٝh}6ЃV` Br)}??M PK`.JL $git/.git/hooks/pre-applypatch.sampleUT =zX=zXux U[N1 EK:@VVB=DL&Q>nq,o#S!pWcHƞjO8/ 霷.Eh_p*^L|huV#iت7 Q>ޮ"\'yvFi[R^GgpP|XCMh4`KLZ)0x,W㲵ol6DkȢ;xdi #FQnapB7\>cZŗ6z7"<ϑV\>%2O0 o9J[F 8UVmK(OPRpvs'+aε{@zhngPK`.J git/.git/hooks/commit-msg.sampleUT =zX=zXux }_o0şOqH+VMJ" XD&7l|w&E=Re M]C͍F^S~WrݶҡZVTɷi;OЊ Lշ"0h l'>Oպo ޒ\i ap(Hk{*uFwF G'$d OYo:ye 皒MRϗ >!-7)q]ͳ`:T|G"mm/#U1EY)*R])H6ڛ*?A _H~ uPN6N p8Waj{wypjGXOw|-/]2c\+ }FLj'#(9^j=^k.P~Y8cA1PG8jG䈐d}7+Yg4 _{]EPK`.JI !git/.git/hooks/pre-receive.sampleUT =zX=zXux uPN0<_1}@[ʑ*HV"!UnM;B;vʣqzg3:ZZ; :*Ҙ lVbCZQm2<+MʷWP& Y:Q;앓hJd텄^ի%i$㒟jBk=hq/BK/E3UM2R;TG֡I/y:M4[,R +~EkâveKL q(h_uԌMkS6g9o5^q{~㱯a~"M%qpQ,kbbPK`.J!D%ugit/.git/hooks/update.sampleUT =zX=zXux WQo"7~f܂`IҷHuM*Um:EfnM]]8HH1g曙ofLd&DA7GI+[2NR\ ELŏLJes2l)Ւ4<rA-2 Pc.xǐ6¤z }D%'~!ƁWfbRDI=SWs@ #'vd"X +#S8 zfJeIhڤܤ E-$_%'-\29LH52 {a֗a2Jpt)П4FaOw~˵E$S͝GKY쮤-# P^5m4#zBxJΘ9}[7wfP \jni}J\kWG:IqL{mk).Z| =mn_mF>0.pJp{q`GI*pݓ !X/u;Կcv?15EVY"eʇ!H}nfs}p-;Xqds|(Fu}Բ+&'@(s5 ϟ =wbmU,LA:fW5 :aNpC;]qc@bS*7USݝpt]+!&_󚓝ZBѓVGkXټ9Ww]gDoeg ƚح,b @vJtՎP#n>=>n%-|@Ux|` :m7Pz9YqnU4k˛3k@jFyFG2LU mmFP%;p$끕^ ؛2/[/vM gaCŴ'QJ۩A!Qޭ5' +G6 $R _>)!JÉl\k,)ޮy?PK`.J(git/.git/hooks/prepare-commit-msg.sampleUT =zX=zXux uS]s0|&3M?,[;L) ig[T\Iɏ$9! =e˥ԥ&$ Sz {a|PJzP:'Z,{)ZMb$}QZLύTJx #cQ<%ƀ1Foh#hVX̻4!H& U*G!K3F_h 8/rAbiǺ^Ω8(,Fb%`DWe$0lp u2qlupށh)4Q8Av#{J,+V!bl<-#4Gl>%6fܙO6mc=BV͠ӳ0X)Ω3i46vpq!mfQVcMp*%u 𸕕DTŕX!%B_Kqg"l!jX%H7ͧ92dt~@'rp6>.xNf-3(sCY (oP@Sz38= ;=҄‡>xMHlpk%*2'٫41NTA~!,|]|zw9i㨙,Aq8-_dt㲟Zz%)Qx:zDv~˓PK`.Jz" git/.git/hooks/pre-rebase.sampleUT =zX=zXux XkoF,[E>۸AndıGPbMr3(jsg"-+b﹏ѓofU$zBl4G4?<~NL9, Q*G{ԯhFi-et]ﵱ Zf"cKpZI Ţ$(KKcU<ժ`%xh!)|,̦R?V,f1(y.G*՚%TBBZ358ԕZ3ih- x&Ma0,(|fۄJ,*J1wZqԘ {(ZWjmĚj㢪,<0ɮ% &(^B5xJ4(.ǰ7aBZy"S疲A U" z`Uz3섵F,+>QDO'429wNZfd3̍l0z̦X<~~yvqCwwQ@t % $H+H+b94c(G*@SK#{H7@% w6U{R)@A6/KOSq\e乫 .` 俭ww+EϿS5JfSE,|ݳ&u5h<물>[H( ͉*%!'ۚt<|  뙸sZe>r[oBclvkj+H-QfKv0;HOJFFmXw2\3Npg9=A7:vpưHwn5ҫz2 ۛĪI"$%[F(ר'0v/ ` ެ6J&؄rA,goqx^%GiCQƳStg~3c Qqzw$ۈfLbr<ݓg8wt^a|(sYp+rGpt4{Wj4r7 tN#Jr^J˷o]L#>5 BmwM?7Moί_Pv;\B'f<^ 4D cMߦcɔX(RF0.xzp?"zJoXN^AW,_EeI"|(3. J2-c6 Hdes=Kz}7F W1@ +V&S,Sң~Qg(ve@f+_ gu%q+ 6D0YBQ73'/`KVg.1Bo\ /$:RVn2Ç7=7,. 0xx%%FyXc+]zz@Y~r:UVFmY7 T/Dc&r?Roy"'e-k-@?^F^]^_z;o`+o~ņc O[`Tk{x4tJh;HE`?(%lغʒ-|:Vhwq.A_WI% ˗ݢM;f `9 S\$HyJ*F $Cql;H'Nh8ZT]@wmm8|Ra]/`L/zxgq{ @f;>$k.օ53Wco{!nZ;!aW ʹyt 1W2_!w8^yVӆ2r zQ/c6k]^γ=4!m)goyKp&? t3ș6͜1\ ?w紇Mķ3W/ )>ɇBo=p'K,Z,eKEUu3H>h_SD*{Lq{ u?6&~%~c&V5ߟPKT.J7"Fgit/.git/configUT PAzXPAzXux =A 0ur{I$wsL@j;fY ʝp ('w#&Tn3οP3cN$ں4)v8uO_Y ~2PK`.J7?Igit/.git/descriptionUT =zX=zXux  KMMQ(J-/,/VHM,Q(,VHIUPOI-N.,(SW(Wʧ"iPK W.Jgit/.git/logs/UT UAzXUAzXux PKY.JzxBgit/.git/logs/HEADUT YAzXUAzXux ;0kr-A:u!z! HV .L .\ }C2XA g+QApL5 3PawC1)龝y4chQ TJaniN&oFfJ*kX:4B2sڰ&o8.)TSѼPK W.Jgit/.git/logs/refs/UT UAzXUAzXux PK W.Jgit/.git/logs/refs/heads/UT UAzXUAzXux PKY.JzxBgit/.git/logs/refs/heads/masterUT YAzXUAzXux ;0kr-A:u!z! HV .L .\ }C2XA g+QApL5 3PawC1)龝y4chQ TJaniN&oFfJ*kX:4B2sڰ&o8.)TSѼPK `.J+is git/.git/HEADUT =zX=zXux ref: refs/heads/master PKY.Jڅ{git/.git/indexUT YAzXYAzXux s rf```b*M|whV{c000`F1AKb=po盽,qn` πO?jCT}4{kݚʣnz<5Q?I?9aPs6v9)̱fR>,\ -?`½s IGd\1K$2&T\z8OWmj'ԢĢJ,=!A@*\kgWg {xs\W;j` ɻrs1OiNi~"`  OLf0T0Rl:í+^.Mm~q'+eE*k_V*PK `.Jgit/.git/branches/UT =zX=zXux PK `.Jgit/.git/refs/UT =zX=zXux PK `.Jgit/.git/refs/tags/UT =zX=zXux PK Y.Jgit/.git/refs/heads/UT YAzX=zXux PK Y.JY))git/.git/refs/heads/masterUT YAzXYAzXux 68636ba53c0af48b81340540deb025822c8a5843 PK Y.J"Cgit/.git/COMMIT_EDITMSGUT YAzXUAzXux add hierarchy PK `.Jgit/.git/info/UT =zX=zXux PK`.Jw=!git/.git/info/excludeUT =zX=zXux -A 0Dbv/\yq۟6؍g7Ǽ hc)iX& kOo@.miuk6p72)AȄgKlK s7Qq=ZƏȜ< uY^1+ >bN8Z#Eù깆PK W.Jgit/.git/rr-cache/UT UAzXUAzXux PK Y.Jgit/.git/objects/UT YAzX=zXux PK X.Jgit/.git/objects/11/UT WAzXWAzXux PK X.JP4:git/.git/objects/11/0eb5a45dc2e0464f870e9ba3fe02e034896a17UT WAzXWAzXux x=J@]S\J ]X-.PJՅ\2C3Ý X͝/Jַ0Y\Q' #+}[(kL4jO2q4tʁHx#; UYW 0egZxnwlyGc[UzCb䢦؄}! gh.D{ƠO@6-`^*QxisƄ$qcGJ_PK X.Jgit/.git/objects/a5/UT WAzXWAzXux PK X.Jc'X:git/.git/objects/a5/1ca34cde8238dd99ac22001b1f9f3090dece5fUT WAzXWAzXux xKOR04epIUHRS(/S PK X.Jgit/.git/objects/e6/UT WAzXWAzXux PK X.J:git/.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391UT WAzXWAzXux xKOR0` PK `.Jgit/.git/objects/pack/UT =zX=zXux PK .Jgit/.git/objects/4b/UT @zX@zXux PK .J: :git/.git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904UT @zX@zXux x+)JMU0` ,PK X.Jgit/.git/objects/30/UT WAzXWAzXux PK X.JWtu:git/.git/objects/30/e02be1a85ca418689078d43ae19e2d26b7ab42UT WAzXWAzXux x]N0ǽSGLй3偞ACi2OoDzTTZ'In}'0) H_$c3TLZE:_a6Y}}E:h:VKos#NcI)1HqPR)0V܂ .qYidOT"M~r[<{"fTbHSԅ%ت A&jQo_]AV*R <4tsyPK Y.Jgit/.git/objects/2a/UT YAzXYAzXux PK Y.J[..:git/.git/objects/2a/26db49a6962700da5bd4084ae0e5a22d6583eeUT YAzXYAzXux x+)JMU0d040031QHax6M9{wk+qIOD&PK Y.Jgit/.git/objects/6e/UT YAzXYAzXux PK Y.J++:git/.git/objects/6e/d4c082894affb09de26ff4679e444e1c376483UT YAzXYAzXux x+)JMU0`01$:O$l1a1cB PK Y.Jgit/.git/objects/93/UT YAzXYAzXux PK Y.JTp+:git/.git/objects/93/f3e6d4c33c7d336913484b08d11d7f26c5382cUT YAzXYAzXux x+)JMU04`040031Qpq v cۺ$7vً1=071D+:o(K<?fI Kek;s| C%}4{kݚʣnz<*ZXQTP`@ኘ%*X=}EPK W.Jgit/.git/objects/cf/UT UAzXUAzXux PK W.J8:git/.git/objects/cf/3d5641dfc957f7efd3eb04931cae565ef61917UT UAzXUAzXux x 1@Qϩb Îw$;Cfc%XxK~3$FK˚8BEN,K7&:eҟۮ8Lv|:kMmǷ0|YWY=FPK `.Jgit/.git/objects/info/UT =zX=zXux PK Y.Jgit/.git/objects/68/UT YAzXWAzXux PK Y.J~?i:git/.git/objects/68/636ba53c0af48b81340540deb025822c8a5843UT YAzXYAzXux xMJ1 ]?fҟ)IRy#͝h߯c&/pGr3fg$O0؋gR /6a`l+8.!%PmǔɎ5UwVGޠO:]_C9hwvPS:= ڨjrAVS # l`LijR֧5x٦vԱ:D4x붧ԠiW+I@ĕ~~PK .JyEgit/bUT @zX@zXux File b in root PK .Jgit/cUT @zX@zXux PK .JAgit/UT@zXux PK.J>git/DESCRIPTIONUT@zXux PK .JAcgit/a/UT@zXux PK .JAgit/a/b/UT@zXux PK .J git/a/b/aUT@zXux PK .J %J (git/a/b/bUT@zXux PK .J Argit/a/b/c/UT@zXux PK .J git/a/b/c/dUT@zXux PK Y.J Agit/.git/UTZAzXux PK `.JA>git/.git/hooks/UT=zXux PK`.JO $git/.git/hooks/applypatch-msg.sampleUT=zXux PK`.JL $git/.git/hooks/pre-applypatch.sampleUT=zXux PK`.J !cgit/.git/hooks/post-update.sampleUT=zXux PK`.J؏DHgit/.git/hooks/pre-push.sampleUT=zXux PK`.J%0\j = git/.git/hooks/pre-commit.sampleUT=zXux PK`.J :git/.git/hooks/commit-msg.sampleUT=zXux PK`.JI !git/.git/hooks/pre-receive.sampleUT=zXux PK`.J!D%u/git/.git/hooks/update.sampleUT=zXux PK`.J(git/.git/hooks/prepare-commit-msg.sampleUT=zXux PK`.Jz" git/.git/hooks/pre-rebase.sampleUT=zXux PKT.J7"F,"git/.git/configUTPAzXux PK`.J7?I"git/.git/descriptionUT=zXux PK W.JA#git/.git/logs/UTUAzXux PKY.JzxB#git/.git/logs/HEADUTYAzXux PK W.JA$git/.git/logs/refs/UTUAzXux PK W.JA %git/.git/logs/refs/heads/UTUAzXux PKY.JzxB\%git/.git/logs/refs/heads/masterUTYAzXux PK `.J+is \&git/.git/HEADUT=zXux PKY.Jڅ{&git/.git/indexUTYAzXux PK `.JA}(git/.git/branches/UT=zXux PK `.JA(git/.git/refs/UT=zXux PK `.JA)git/.git/refs/tags/UT=zXux PK Y.JA^)git/.git/refs/heads/UTYAzXux PK Y.JY)))git/.git/refs/heads/masterUTYAzXux PK Y.J"C)*git/.git/COMMIT_EDITMSGUTYAzXux PK `.JA*git/.git/info/UT=zXux PK`.Jw=!*git/.git/info/excludeUT=zXux PK W.JA+git/.git/rr-cache/UTUAzXux PK Y.JA,git/.git/objects/UTYAzXux PK X.JAc,git/.git/objects/11/UTWAzXux PK X.JP4:$,git/.git/objects/11/0eb5a45dc2e0464f870e9ba3fe02e034896a17UTWAzXux PK X.JA.git/.git/objects/a5/UTWAzXux PK X.Jc'X:$].git/.git/objects/a5/1ca34cde8238dd99ac22001b1f9f3090dece5fUTWAzXux PK X.JA.git/.git/objects/e6/UTWAzXux PK X.J:$>/git/.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391UTWAzXux PK `.JA/git/.git/objects/pack/UT=zXux PK .JA0git/.git/objects/4b/UT@zXux PK .J: :$_0git/.git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904UT@zXux PK X.JA0git/.git/objects/30/UTWAzXux PK X.JWtu:$01git/.git/objects/30/e02be1a85ca418689078d43ae19e2d26b7ab42UTWAzXux PK Y.JA2git/.git/objects/2a/UTYAzXux PK Y.J[..:$3git/.git/objects/2a/26db49a6962700da5bd4084ae0e5a22d6583eeUTYAzXux PK Y.JA3git/.git/objects/6e/UTYAzXux PK Y.J++:$3git/.git/objects/6e/d4c082894affb09de26ff4679e444e1c376483UTYAzXux PK Y.JA4git/.git/objects/93/UTYAzXux PK Y.JTp+:$4git/.git/objects/93/f3e6d4c33c7d336913484b08d11d7f26c5382cUTYAzXux PK W.JA6git/.git/objects/cf/UTUAzXux PK W.J8:$P6git/.git/objects/cf/3d5641dfc957f7efd3eb04931cae565ef61917UTUAzXux PK `.JAJ7git/.git/objects/info/UT=zXux PK Y.JA7git/.git/objects/68/UTYAzXux PK Y.J~?i:$7git/.git/objects/68/636ba53c0af48b81340540deb025822c8a5843UTYAzXux PK X.JE: :$9git/.git/objects/68/21c186de959771d06bbd41d5fe9d4271ee675dUTWAzXux PK Y.JA9git/.git/objects/7e/UTYAzXux PK Y.Jcc:$9git/.git/objects/7e/29db8b3b97c863c3b6533890a3015231cddf61UTYAzXux PK Y.J:git/.git/MERGE_RRUTYAzXux PK.Jas@ %F$v6I̐ A&P"xg=!s1Q)^ ^A-1iUI =Zsp{`wt|ū#Z>da 89hY)Jm̿O_"2ќ+|lXB^˺tkh`FGCoyw_݈PK 6}/J svn/.svn/UT ({X'=zXux PK 6}/JMsvn/.svn/formatUT ({X({Xux 12 PK6}/J 'Rsvn/.svn/wc.dbUT ({X({Xux [ly:WkFS%pxo]YeѦ oApX)RKRAJ%}I4A^CmzyiH7-y-$M (ǶgI39s\T,W60GeEa^k6 x1fw1Jz=g1ֳ3>2^mV.ַ|ycnQV6+;r6Cץ4EN c:3i9өΓxROzR7Az;. #3˹kktp糙jsj&{~/Ib^~Cz~px8ܬL_ót^fyɧLISFRVH\R%T#QM3U"$ʧSF\գ>=%?vKd~=w)' =Kuj|p#ɝ1n ̱'+0cJgՓ5a*SB, z3}Op -v{~"dDW%;'cA-{}g_;7;5~q8c<<>ݨAd'iwhEzCMʪlɨiČj"z9H&OzBz"O,y^G;~_ ZNOE)vf5'W/٧#?~ ;d;F|ȞGB>n46ưC>Nh=8w>?h &clw*ÿ6>) /t>~P ]\oG9|1~wךoֳXzu岶NZۙ=]3g؛5 ^%nRÎS%m"ۯa|t=se[ngߵ*ץ*~xvj$'=!KW Cš_N4Q`Ǟ/J<ˢ_'[dk]EkTM/-Z"ձg z{ב0F1rV#ۑ!";c؃5yau{s:魲Fy.\W կ?îkw⳸n:Xg^@_j̣ytm i7&:5r^ÞGQ1MCP$H%{0*N˻Ʊϼd:fsDnϽikpU\cGýnK!F>36h??1/;[ -_g82.߽%Kh* zc}Rs+y&k|O@h{_뚸#YwL5_hމ Gݿӱ+LNֿM)$<9"ӮD]J`y;^_FބPK 6}/JMsvn/.svn/entriesUT ({X({Xux 12 PK .J svn/.svn/tmp/UT '=zX'=zXux PK 6}/Jsvn/.svn/pristine/UT ({X({Xux PK 6}/Jsvn/.svn/pristine/f0/UT ({X({Xux PK6}/Jash߯c&/pGr3fg$O0؋gR /6a`l+8.!%PmǔɎ5UwVGޠO:]_C9hwvPS:= ڨjrAVS # l`LijR֧5x٦vԱ:D4x붧ԠiW+I@ĕ~~PK 6}/Jsvn/.svn/pristine/f3/UT ({X({Xux PK 6}/JyEFsvn/.svn/pristine/f3/f30dae958ef83fc13091258906c050936e39b8f9.svn-baseUT ({X({Xux File b in root PK 6}/Jsvn/.svn/pristine/da/UT ({X({Xux PK 6}/JFsvn/.svn/pristine/da/da39a3ee5e6b4b0d3255bfef95601890afd80709.svn-baseUT ({X({Xux PK 6}/Jsvn/.svn/pristine/9e/UT ({X({Xux PK 6}/J %JFsvn/.svn/pristine/9e/9ed97191f4672ff9ef5d9659438d776ea57fb6ba.svn-baseUT ({X({Xux File b PK 6}/Jsvn/.svn/pristine/1f/UT ({X({Xux PK6}/JFsvn/.svn/pristine/1f/1fff0ad79f00c81b19245903e27229dff1771fd4.svn-baseUT ({X({Xux =J1yKW3 HT0j⚹LB$dl|1*ݞ?ӡ>@ %F$v6I̐ A&P"xg=!s1Q)^ ^A-1iUI =Zsp{`wt|ū#Z>da 89hY)Jm̿O_"2ќ+|lXB^˺tkh`FGCoyw_݈PK 6}/Jsvn/.svn/wc.db-journalUT ({X({Xux PK 6}/Jsvn/a/UT ({X({Xux PK 6}/Jsvn/a/b/UT ({X({Xux PK 6}/J svn/a/b/aUT ({X({Xux PK 6}/J %J svn/a/b/bUT ({X({Xux File b PK 6}/J svn/a/b/c/UT ({X({Xux PK 6}/J svn/a/b/c/dUT ({X({Xux PK6}/Jash߯c&/pGr3fg$O0؋gR /6a`l+8.!%PmǔɎ5UwVGޠO:]_C9hwvPS:= ڨjrAVS # l`LijR֧5x٦vԱ:D4x붧ԠiW+I@ĕ~~PK 6}/JyEsvn/bUT ({X({Xux File b in root PK 6}/Jsvn/cUT ({X({Xux PK 6}/JAsvn/UT({Xux PK6}/J>svn/DESCRIPTIONUT({Xux PK 6}/J Acsvn/.svn/UT({Xux PK 6}/JMsvn/.svn/formatUT({Xux PK6}/J 'Rsvn/.svn/wc.dbUT({Xux PK 6}/JMsvn/.svn/entriesUT({Xux PK .J Asvn/.svn/tmp/UT'=zXux PK 6}/JA svn/.svn/pristine/UT({Xux PK 6}/JAlsvn/.svn/pristine/f0/UT({Xux PK6}/Jas %\VignetteIndexEntry{Finding files in project subdirectories} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- The *rprojroot* package solves a seemingly trivial but annoying problem that occurs sooner or later in any largish project: How to find files in subdirectories? Ideally, file paths are relative to the *project root*. Unfortunately, we cannot always be sure about the current working directory: For instance, in RStudio it's sometimes: - the project root (when running R scripts), - a subdirectory (when building vignettes), - again the project root (when executing chunks of a vignette). ```{r} basename(getwd()) ``` In some cases, it's even outside the project root. This vignette starts with a very brief summary that helps you get started, followed by a longer description of the features. ## TL;DR What is your project: An R package? ```{r} rprojroot::is_r_package ``` Or an RStudio project? ```{r} rprojroot::is_rstudio_project ``` Or something else? ```{r} rprojroot::has_file(".git/index") ``` For now, we assume it's an R package: ```{r} root <- rprojroot::is_r_package ``` The `root` object contains a function that helps locating files below the root of your package, regardless of your current working directory. If you are sure that your working directory is somewhere below your project's root, use the `root$find_file()` function. In this example here, we're starting in the `vignettes` subdirectory and find the original `DESCRIPTION` file: ```{r} basename(getwd()) readLines(root$find_file("DESCRIPTION"), 3) ``` There is one exception: if the first component passed to `find_file()` is already an absolute path. This allows safely applying this function to paths that may be absolute or relative: ```{r} path <- root$find_file() readLines(root$find_file(path, "DESCRIPTION"), 3) ``` You can also construct an accessor to your root using the `root$make_fix_file()` function: ```{r} root_file <- root$make_fix_file() ``` Note that `root_file()` is a *function* that works just like `$find_file()` but will find the files even if the current working directory is outside your project: ```{r} withr::with_dir( "../..", readLines(root_file("DESCRIPTION"), 3) ) ``` If you know the absolute path of some directory below your project, but cannot be sure of your current working directory, pass that absolute path to `root$make_fix_file()`: ```r root_file <- root$make_fix_file("C:\\Users\\User Name\\...") ``` As a last resort, you can get the path of standalone R scripts or vignettes using the `thisfile()` function: ```r root_file <- root$make_fix_file(dirname(thisfile())) ``` The remainder of this vignette describes implementation details and advanced features. ## Project root We assume a self-contained project where all files and directories are located below a common *root* directory. Also, there should be a way to unambiguously identify this root directory. (Often, the root contains a regular file whose name matches a given pattern, and/or whose contents match another pattern.) In this case, the following method reliably finds our project root: - Start the search in any subdirectory of our project - Proceed up the directory hierarchy until the root directory has been identified The Git version control system (and probably many other tools) use a similar approach: A Git command can be executed from within any subdirectory of a repository. ### A simple example The `find_root()` function implements the core functionality. It returns the path to the first directory that matches the filtering criteria, or throws an error if there is no such directory. Filtering criteria are constructed in a generic fashion using the `root_criterion()` function, the `has_file()` function constructs a criterion that checks for the presence of a file with a specific name and specific contents. ```{r} library(rprojroot) # List all files and directories below the root dir(find_root(has_file("DESCRIPTION"))) ``` #### Relative paths to a stable root Here we illustrate the power of *rprojroot* by demonstrating how to access the same file from two different working directories. Let your project be a package called `pkgname` and consider the desired file `rrmake.R` at `pkgname/R/rrmake.R`. First, we show how to access from the `vignettes` directory, and then from the `tests/testthat` directory. ##### Example A: From `vignettes` When your working directory is `pkgname/vignettes`, you can access the `rrmake.R` file by: 1. Supplying a pathname relative to your working directory. Here's two ways to do that: ```{r, eval = FALSE} rel_path_from_vignettes <- "../R/rrmake.R" rel_path_from_vignettes <- file.path("..", "R", "rrmake.R") ##identical ``` 2. Supplying a pathname to the file relative from the root of the package, e.g., ```{r, eval = FALSE} rel_path_from_root <- "R/rrmake.R" rel_path_from_root <- file.path("R", "rrmake.R") ##identical ``` This second method requires finding the root of the package, which can be done with the `has_file()` function: ```{r} has_file("DESCRIPTION") ``` So, using *rprojroot* you can specify the path relative from root in the following manner: ```{r} # Specify a path/to/file relative to the root rel_path_from_root <- find_root_file("R", "rrmake.R", criterion = has_file("DESCRIPTION")) ``` ##### Example B: From `tests/testthat` When your working directory is `pkgname/tests/testthat`, you can access the `rrmake.R` file by: 1. Supplying a pathname relative to your working directory. ```{r, eval = FALSE} rel_path_from_testthat <- "../../R/rrmake.R" ``` Note that this is different than in the previous example! However, the second method is the same... 2. Supplying a pathname to the file relative from the root of the package. With *rprojroot*, this is the exact same as in the previous example. ```{r} # Specify a path/to/file relative to the root rel_path_from_root <- find_root_file("R", "rrmake.R", criterion = has_file("DESCRIPTION")) ``` ##### Summary of Examples A and B Since Examples A and B used different working directories, `rel_path_from_vignettes` and `rel_path_from_testthat` were different. This is an issue when trying to re-use the same code. This issue is solved by using *rprojroot*: the function `find_root_file()` finds a file relative from the root, where the root is determined from checking the criterion with `has_file()`. Note that the follow code produces identical results when building the vignette *and* when sourcing the chunk in RStudio, provided that the current working directory is the project root or anywhere below. So, we can check to make sure that *rprojroot* has successfully determined the correct path: ```{r} # Specify a path/to/file relative to the root rel_path_from_root <- find_root_file("R", "rrmake.R", criterion = has_file("DESCRIPTION")) # Find a file relative to the root file.exists(rel_path_from_root) ``` ### Criteria The `has_file()` function (and the more general `root_criterion()`) both return an S3 object of class `root_criterion`: ```{r} has_file("DESCRIPTION") ``` In addition, character values are coerced to `has_file` criteria by default, this coercion is applied automatically by `find_root()`. (This feature is used by the introductory example.) ```{r} as_root_criterion("DESCRIPTION") ``` The return value of these functions can be stored and reused; in fact, the package provides `r length(criteria)` such criteria: ```{r} criteria ``` Defining new criteria is easy: ```{r} has_license <- has_file("LICENSE") has_license is_projecttemplate_project <- has_file("config/global.dcf", "^version: ") is_projecttemplate_project ``` You can also combine criteria via the `|` operator: ```{r} is_r_package | is_rstudio_project ``` ### Shortcuts To avoid specifying the search criteria for the project root every time, shortcut functions can be created. The `find_package_root_file()` is a shortcut for `find_root_file(..., criterion = is_r_package)`: ```{r} # Print first lines of the source for this document head(readLines(find_package_root_file("vignettes", "rprojroot.Rmd"))) ``` To save typing effort, define a shorter alias: ```{r} P <- find_package_root_file # Use a shorter alias file.exists(P("vignettes", "rprojroot.Rmd")) ``` Each criterion actually contains a function that allows finding a file below the root specified by this criterion. As our project does not have a file named `LICENSE`, querying the root results in an error: ```{r error = TRUE} # Use the has_license criterion to find the root R <- has_license$find_file R # Our package does not have a LICENSE file, trying to find the root results in an error R() ``` ### Fixed root We can also create a function that computes a path relative to the root *at creation time*. ```{r eval = (Sys.getenv("IN_PKGDOWN") != "")} # Define a function that computes file paths below the current root F <- is_r_package$make_fix_file() F # Show contents of the NAMESPACE file in our project readLines(F("NAMESPACE")) ``` This is a more robust alternative to `$find_file()`, because it *fixes* the project directory when `$make_fix_file()` is called, instead of searching for it every time. (For that reason it is also slightly faster, but I doubt this matters in practice.) This function can be used even if we later change the working directory to somewhere outside the project: ```{r eval = (Sys.getenv("IN_PKGDOWN") != "")} # Print the size of the namespace file, working directory outside the project withr::with_dir( "../..", file.size(F("NAMESPACE")) ) ``` The `make_fix_file()` member function also accepts an optional `path` argument, in case you know your project's root but the current working directory is somewhere outside. The path to the current script or `knitr` document can be obtained using the `thisfile()` function, but it's much easier and much more robust to just run your scripts with the working directory somewhere below your project root. ## `testthat` files Tests run with [`testthat`](https://cran.r-project.org/package=testthat) commonly use files that live below the `tests/testthat` directory. Ideally, this should work in the following situation: - During package development (working directory: package root) - When testing with `devtools::test()` (working directory: `tests/testthat`) - When running `R CMD check` (working directory: a renamed recursive copy of `tests`) The `is_testthat` criterion allows robust lookup of test files. ```{r} is_testthat ``` The example code below lists all files in the [hierarchy](https://github.com/r-lib/rprojroot/tree/main/tests/testthat/hierarchy) test directory. It uses two project root lookups in total, so that it also works when rendering the vignette (*sigh*): ```{r} dir(is_testthat$find_file("hierarchy", path = is_r_package$find_file())) ``` ### Another example: custom testing utilities The hassle of using saved data files for testing is made even easier by using *rprojroot* in a utility function. For example, suppose you have a testing file at `tests/testthat/test_my_fun.R` which tests the `my_fun()` function: ```{r, eval = FALSE} my_fun_run <- do.call(my_fun, my_args) testthat::test_that( "my_fun() returns expected output", testthat::expect_equal( my_fun_run, expected_output ) ) ``` There are two pieces of information that you'll need every time `test_my_fun.R` is run: `my_args` and `expected_output`. Typically, these objects are saved to `.Rdata` files and saved to the same subdirectory. For example, you could save them to `my_args.Rdata` and `expected_output.Rdata` under the `tests/testthat/testing_data` subdirectory. And, to find them easily in any contexts, you can use *rprojroot*! Since all of the data files live in the same subdirectory, you can create a utility function `get_my_path()` that will always look in that directory for these types of files. And, since the *testthat* package will look for and source the `tests/testthat/helper.R` file before running any tests, you can place a `get_my_path()` in this file and use it throughout your tests: ```{r, eval = FALSE} ## saved to tests/testthat/helper.R get_my_path <- function(file_name) { rprojroot::find_testthat_root_file( "testing_data", filename ) } ``` Now you can ask `get_my_path()` to find your important data files by using the function within your test scripts! ```{r, eval = FALSE} ## Find the correct path with your custom rprojroot helper function path_to_my_args_file <- get_my_path("my_args.Rdata") ## Load the input arguments load(file = path_to_my_args_file) ## Run the function with those arguments my_fun_run <- do.call(my_fun,my_args) ## Load the historical expectation with the helper load(file = get_my_path("expected_output.Rdata")) ## Pass all tests and achieve nirvana testthat::test_that( "my_fun() returns expected output", testthat::expect_equal( my_fun_run, expected_output ) ) ``` For an example in the wild, see the [`test_sheet()` function](https://github.com/tidyverse/readxl/blob/0d9ad4f570f6580ff716e0e9ba5048447048e9f0/tests/testthat/helper.R#L1-L3) in the *readxl* package. ## Summary The *rprojroot* package allows easy access to files below a project root if the project root can be identified easily, e.g. if it is the only directory in the whole hierarchy that contains a specific file. This is a robust solution for finding files in largish projects with a subdirectory hierarchy if the current working directory cannot be assumed fixed. (However, at least initially, the current working directory must be somewhere below the project root.) ## Acknowledgement This package was inspired by the gist ["Stop the working directory insanity"](https://gist.github.com/jennybc/362f52446fe1ebc4c49f) by Jennifer Bryan, and by the way Git knows where its files are. rprojroot/R/0000755000176200001440000000000014521632656012525 5ustar liggesusersrprojroot/R/utils.R0000644000176200001440000000054614136674000014004 0ustar liggesuserslist_files <- function(path, filename) { files <- dir(path = path, pattern = filename, all.files = TRUE, full.names = TRUE) dirs <- dir.exists(files) files <- files[!dirs] files } match_contents <- function(f, contents, n, fixed) { if (is.null(contents)) { return(TRUE) } fc <- readLines(f, n) any(grepl(contents, fc, fixed = fixed)) } rprojroot/R/thisfile.R0000644000176200001440000000753314136674000014456 0ustar liggesusers# nocov start #' Determines the path of the currently running script #' #' @description #' `r lifecycle::badge("soft-deprecated")` #' #' \R does not store nor export the path of the currently running #' script. This is an attempt to circumvent this limitation by applying #' heuristics (such as call stack and argument inspection) that work in many #' cases. #' **CAVEAT**: Use this function only if your workflow does not permit other #' solution: if a script needs to know its location, it should be set outside #' the context of the script if possible. #' #' @details This functions currently work only if the script was `source`d, #' processed with `knitr`, #' or run with `Rscript` or using the `--file` parameter to the #' `R` executable. For code run with `Rscript`, the exact value #' of the parameter passed to `Rscript` is returned. #' #' @section Life cycle: #' #' These functions are now available in the \pkg{whereami} package. #' #' @return The path of the currently running script, NULL if it cannot be #' determined. #' @seealso [base::source()], [utils::Rscript()], [base::getwd()] #' @references [https://stackoverflow.com/q/1815606/946850]() #' @author Kirill Müller, Hadley Wickham, Michael R. Head #' @keywords internal #' @examples #' \dontrun{ #' thisfile() #' } #' @export thisfile <- function() { lifecycle::deprecate_soft( "2.0.0", "rprojroot::thisfile()", "whereami::thisfile()" ) if (!is.null(res <- thisfile_source())) { res } else if (!is.null(res <- thisfile_r())) { res } else if (!is.null(res <- thisfile_rscript())) { res } else if (!is.null(res <- thisfile_knit())) { res } else { NULL } } #' @rdname thisfile #' @export thisfile_source <- function() { lifecycle::deprecate_soft( "2.0.0", "rprojroot::thisfile_source()", "whereami::thisfile_source()" ) for (i in -(1:sys.nframe())) { if (identical(args(sys.function(i)), args(base::source))) { return(normalizePath(sys.frame(i)$ofile)) } } NULL } #' @rdname thisfile #' @importFrom utils tail #' @export thisfile_r <- function() { lifecycle::deprecate_soft( "2.0.0", "rprojroot::thisfile_r()", "whereami::thisfile_r()" ) cmd_args <- commandArgs(trailingOnly = FALSE) if (!grepl("^R(?:|term)(?:|[.]exe)$", basename(cmd_args[[1L]]), ignore.case = TRUE)) { return(NULL) } cmd_args_trailing <- commandArgs(trailingOnly = TRUE) leading_idx <- seq.int(from = 1, length.out = length(cmd_args) - length(cmd_args_trailing)) cmd_args <- cmd_args[leading_idx] file_idx <- c(which(cmd_args == "-f") + 1, which(grepl("^--file=", cmd_args))) res <- gsub("^(?:|--file=)(.*)$", "\\1", cmd_args[file_idx]) # If multiple --file arguments are given, R uses the last one res <- tail(res[res != ""], 1) if (length(res) > 0) { return(res) } NULL } #' @rdname thisfile #' @importFrom utils tail #' @export thisfile_rscript <- function() { lifecycle::deprecate_soft( "2.0.0", "rprojroot::thisfile_rscript()", "whereami::thisfile_rscript()" ) cmd_args <- commandArgs(trailingOnly = FALSE) if (!grepl("^R(?:term|script)(?:|[.]exe)$", basename(cmd_args[[1L]]), ignore.case = TRUE)) { return(NULL) } cmd_args_trailing <- commandArgs(trailingOnly = TRUE) leading_idx <- seq.int(from = 1, length.out = length(cmd_args) - length(cmd_args_trailing)) cmd_args <- cmd_args[leading_idx] res <- gsub("^(?:--file=(.*)|.*)$", "\\1", cmd_args) # If multiple --file arguments are given, R uses the last one res <- tail(res[res != ""], 1) if (length(res) > 0) { return(res) } NULL } #' @rdname thisfile #' @export thisfile_knit <- function() { lifecycle::deprecate_soft( "2.0.0", "rprojroot::thisfile_knit()", "whereami::thisfile_knit()" ) if (requireNamespace("knitr")) { return(knitr::current_input(dir = TRUE)) } NULL } # nocov end rprojroot/R/deprecated.R0000644000176200001440000000060314136674000014736 0ustar liggesusers#' Deprecated functions #' #' Use [as_root_criterion()] and [is_root_criterion()], respectively. #' #' @inheritParams as_root_criterion #' @inheritParams is_root_criterion #' @keywords internal #' @name deprecated #' @export as.root_criterion <- function(...) { as_root_criterion(...) } #' @export #' @rdname deprecated is.root_criterion <- function(...) { is_root_criterion(...) } rprojroot/R/rprojroot-package.R0000644000176200001440000000051214136674000016266 0ustar liggesusers#' @details #' See the "Value" section in [root_criterion()] for documentation #' of root criterion objects, and [criteria] for useful predefined #' root criteria. #' #' @examples #' criteria #' \dontrun{ #' is_r_package$find_file("NAMESPACE") #' root_fun <- is_r_package$make_fix_file() #' root_fun("NAMESPACE") #' } "_PACKAGE" rprojroot/R/root.R0000644000176200001440000002705614521632656013645 0ustar liggesusers#' @rdname root_criterion #' @param x `[object]`\cr #' An object. #' @export is_root_criterion <- function(x) { inherits(x, "root_criterion") } #' @rdname root_criterion #' @export as_root_criterion <- function(x) UseMethod("as_root_criterion", x) #' @details #' The `as_root_criterion()` function accepts objects of class #' `root_criterion`, and character values; the latter will be #' converted to criteria using `has_file`. #' #' @rdname root_criterion #' @export as_root_criterion.character <- function(x) { has_file(x) } #' @rdname root_criterion #' @export as_root_criterion.root_criterion <- identity #' @export as_root_criterion.default <- function(x) { stop("Cannot coerce ", x, " to type root_criterion.", call. = FALSE) } #' @export format.root_criterion <- function(x, ...) { if (length(x$desc) > 1) { c("Root criterion: one of", paste0("- ", x$desc)) } else { paste0("Root criterion: ", x$desc) } } #' @export print.root_criterion <- function(x, ...) { cat(format(x), sep = "\n") invisible(x) } #' @export #' @rdname root_criterion #' @details Root criteria can be combined with the `|` operator. The result is a #' composite root criterion that requires either of the original criteria to #' match. #' @param y `[object]`\cr #' An object. `|.root_criterion` <- function(x, y) { stopifnot(is_root_criterion(y)) root_criterion( c(x$testfun, y$testfun), c(x$desc, y$desc) ) } #' Find the root of a directory hierarchy #' #' A \emph{root} is defined as a directory that contains a regular file #' whose name matches a given pattern and which optionally contains a given text. #' The search for a root starts at a given directory (the working directory #' by default), and proceeds up the directory hierarchy. #' #' Starting from the working directory, the `find_root()` function searches #' for the root. #' If a root is found, the `...` arguments are used to construct a path; #' thus, if no extra arguments are given, the root is returned. #' If no root is found, an error is thrown. #' #' @inheritParams find_root_file #' @return The normalized path of the root as specified by the search criterion. #' Throws an error if no root is found #' #' @examples #' \dontrun{ #' find_root(glob2rx("DESCRIPTION"), "^Package: ") #' } #' #' @seealso [utils::glob2rx()] [file.path()] #' #' @export find_root <- function(criterion, path = ".") { criterion <- as_root_criterion(criterion) start_path <- get_start_path(path, criterion$subdir) path <- start_path for (i in seq_len(.MAX_DEPTH)) { for (f in criterion$testfun) { if (f(path)) { return(path) } } if (is_root(path)) { stop("No root directory found in ", start_path, " or its parent directories. ", paste(format(criterion), collapse = "\n"), call. = FALSE ) } path <- dirname(path) } stop("Maximum search of ", .MAX_DEPTH, " exceeded. Last path: ", path, call. = FALSE) } .MAX_DEPTH <- 100L get_start_path <- function(path, subdirs) { path <- normalizePath(path, winslash = "/", mustWork = TRUE) for (subdir in subdirs) { subdir_path <- file.path(path, subdir) if (dir.exists(subdir_path)) { return(subdir_path) } } path } # Borrowed from devtools is_root <- function(path) { identical( normalizePath(path, winslash = "/"), normalizePath(dirname(path), winslash = "/") ) } #' @rdname find_root #' @description `get_root_desc()` returns the description of the criterion #' for a root path. This is especially useful for composite root criteria #' created with [|.root_criterion()]. #' @export get_root_desc <- function(criterion, path) { for (i in seq_along(criterion$testfun)) { if (criterion$testfun[[i]](path)) { return(criterion$desc[[i]]) } } stop("path is not a root. ", paste(format(criterion), collapse = "\n"), call. = FALSE ) } format_lines <- function(n) { if (n == 1) "line" else paste0(n, " lines") } #' @details #' The `has_file()` function constructs a criterion that checks for the #' existence of a specific file (which itself can be in a subdirectory of the #' root) with specific contents. #' #' @rdname root_criterion #' @param filepath `[character(1)]`\cr #' File path (can contain directories). #' @param contents,fixed `[character(1)]`\cr #' If `contents` is `NULL` (the default), file contents are not checked. #' Otherwise, `contents` is a regular expression #' (if `fixed` is `FALSE`) or a search string (if `fixed` is `TRUE`), and #' file contents are checked matching lines. #' @param n `[integerish(1)]`\cr #' Maximum number of lines to read to check file contents. #' @export has_file <- function(filepath, contents = NULL, n = -1L, fixed = FALSE) { force(filepath) stopifnot(is.character(filepath), length(filepath) == 1) force(contents) if (!is.null(contents)) { stopifnot(is.character(contents), length(contents) == 1) } force(n) stopifnot(length(n) == 1) check_relative(filepath) testfun <- eval(bquote(function(path) { testfile <- file.path(path, .(filepath)) if (!file.exists(testfile)) { return(FALSE) } if (dir.exists(testfile)) { return(FALSE) } match_contents(testfile, .(contents), .(n), .(fixed)) })) desc <- paste0( 'contains a file "', filepath, '"', if (!is.null(contents)) { paste0( " with contents ", if (!fixed) "matching ", '"', contents, '"', if (n >= 0L) paste0(" in the first ", format_lines(n)) ) } ) root_criterion(testfun, desc) } #' @details #' The `has_dir()` function constructs a criterion that checks for the #' existence of a specific directory. #' #' @rdname root_criterion #' @export has_dir <- function(filepath) { force(filepath) stopifnot(is.character(filepath), length(filepath) == 1) check_relative(filepath) testfun <- eval(bquote(function(path) { testfile <- file.path(path, .(filepath)) dir.exists(testfile) })) desc <- paste0('contains a directory "', filepath, '"') root_criterion(testfun, desc) } check_relative <- function(filepath) { if (is_absolute_path(filepath)) { stop("filepath must be a file or a relative path, not an absolute path.", call. = FALSE) } } #' @details #' The `has_file_pattern()` function constructs a criterion that checks for the #' existence of a file that matches a pattern, with specific contents. #' #' @rdname root_criterion #' @param pattern `[character(1)]`\cr #' Regular expression to match the file name against. #' @export has_file_pattern <- function(pattern, contents = NULL, n = -1L, fixed = FALSE) { force(pattern) stopifnot(is.character(pattern), length(pattern) == 1) force(contents) if (!is.null(contents)) { stopifnot(is.character(contents), length(contents) == 1) } force(n) stopifnot(length(n) == 1) testfun <- eval(bquote(function(path) { files <- list_files(path, .(pattern)) for (f in files) { if (!match_contents(f, .(contents), .(n), .(fixed))) { next } return(TRUE) } return(FALSE) })) desc <- paste0( 'contains a file matching "', pattern, '"', if (!is.null(contents)) { paste0( " with contents ", if (!fixed) "matching ", '"', contents, '"', if (n >= 0L) paste0(" in the first ", format_lines(n)) ) } ) root_criterion(testfun, desc) } #' @details #' The `has_basename()` function constructs a criterion that checks if the #' [base::basename()] of the root directory has a specific name, #' with support for case-insensitive file systems. #' #' @rdname root_criterion #' @param basename `[character(1)]`\cr #' The required name of the root directory. #' @export has_basename <- function(basename, subdir = NULL) { force(basename) testfun <- eval(bquote(function(path) { # Support case insensitive file systems. tolower(basename(path)) == tolower(.(basename)) && dir.exists(file.path(dirname(path), .(basename))) })) desc <- paste0('directory name is "', basename, '"') root_criterion(testfun, desc, subdir = subdir) } #' @export is_rstudio_project <- has_file_pattern("[.]Rproj$", contents = "^Version: ", n = 1L) #' @export is_r_package <- has_file("DESCRIPTION", contents = "^Package: ") #' @export is_remake_project <- has_file("remake.yml") #' @export is_drake_project <- has_dir(".drake") #' @export is_pkgdown_project <- has_file("_pkgdown.yml") | has_file("_pkgdown.yaml") | has_file("pkgdown/_pkgdown.yml") | has_file("inst/_pkgdown.yml") #' @export is_renv_project <- has_file("renv.lock", contents = '"Packages":\\s*\\{') #' @export is_projectile_project <- has_file(".projectile") #' @export is_quarto_project <- has_file("_quarto.yml") #' @export is_git_root <- has_dir(".git") | has_file(".git", contents = "^gitdir: ") #' @export is_svn_root <- has_dir(".svn") #' @export is_vcs_root <- is_git_root | is_svn_root #' @export is_testthat <- has_basename("testthat", c("tests/testthat", "testthat")) #' @export from_wd <- root_criterion(function(path) TRUE, "from current working directory") #' Prespecified criteria #' #' This is a collection of commonly used root criteria. #' #' @format NULL #' #' @export criteria <- structure( list( is_rstudio_project = is_rstudio_project, is_r_package = is_r_package, is_remake_project = is_remake_project, is_pkgdown_project = is_pkgdown_project, is_renv_project = is_renv_project, is_projectile_project = is_projectile_project, is_quarto_project = is_quarto_project, is_git_root = is_git_root, is_svn_root = is_svn_root, is_vcs_root = is_vcs_root, is_testthat = is_testthat, from_wd = from_wd ), class = "root_criteria" ) #' @export #' @importFrom utils str str.root_criteria <- function(object, ...) { str(lapply(object, format)) } #' @details #' `is_rstudio_project` looks for a file with extension `.Rproj`. #' #' @format NULL #' @rdname criteria #' @export "is_rstudio_project" #' @details #' `is_r_package` looks for a `DESCRIPTION` file. #' #' @format NULL #' @rdname criteria #' @export "is_r_package" #' @details #' `is_remake_project` looks for a `remake.yml` file. #' #' @format NULL #' @rdname criteria #' @export "is_remake_project" #' @details #' `is_drake_project` looks for a `.drake` directory. #' #' @format NULL #' @rdname criteria #' @export "is_drake_project" #' @details #' `is_pkgdown_project` looks for a `_pkgdown.yml`, `_pkgdown.yaml`, `pkgdown/_pkgdown.yml` and/or `inst/_pkgdown.yml` file. #' #' @format NULL #' @rdname criteria #' @export "is_pkgdown_project" #' @details #' `is_renv_project` looks for an `renv.lock` file. #' #' @format NULL #' @rdname criteria #' @export "is_renv_project" #' @details #' `is_projectile_project` looks for a `.projectile` file. #' #' @format NULL #' @rdname criteria #' @export "is_projectile_project" #' @details #' `is_quarto_project` looks for a `_quarto.yml` file. #' #' @format NULL #' @rdname criteria #' @export "is_quarto_project" #' @details #' `is_git_root` looks for a `.git` directory. #' #' @format NULL #' @rdname criteria #' @export "is_git_root" #' @details #' `is_svn_root` looks for a `.svn` directory. #' #' @format NULL #' @rdname criteria #' @export "is_svn_root" #' @details #' `is_vcs_root` looks for the root of a version control #' system, currently only Git and SVN are supported. #' #' @format NULL #' @rdname criteria #' @export "is_vcs_root" #' @details #' `is_testthat` looks for the `testthat` directory, works when #' developing, testing, and checking a package. #' #' @format NULL #' @rdname criteria #' @export "is_testthat" #' @details #' `from_wd` uses the current working directory. #' #' @format NULL #' @rdname criteria #' @export "from_wd" rprojroot/R/absolute.R0000644000176200001440000000014014136674000014450 0ustar liggesusers# adapted from fs is_absolute_path <- function(x) { grepl("^[/\\\\~]|^[a-zA-Z]:[/\\\\]", x) } rprojroot/R/path.R0000644000176200001440000000126114136674000013573 0ustar liggesusers# Modeled after fs::path() path <- function(...) { dots <- list(...) if (!is.null(names(dots)) && any(names(dots) != "")) { warning("Arguments must be unnamed", call. = FALSE) } # Different recycling rules for zero-length vectors lengths <- lengths(dots) if (any(lengths == 0) && all(lengths %in% 0:1)) { return(character()) } # Side effect: check recycling rules component_df <- as.data.frame(dots, stringsAsFactors = FALSE) missing <- apply(is.na(component_df), 1, any) components <- lapply(component_df, function(x) enc2utf8(as.character(x))) out <- do.call(file.path, components) out[missing] <- NA_character_ Encoding(out) <- "UTF-8" out } rprojroot/R/shortcut.R0000644000176200001440000000054514136674000014516 0ustar liggesusers#' @rdname find_root_file #' @export find_rstudio_root_file <- is_rstudio_project$find_file #' @rdname find_root_file #' @export find_package_root_file <- is_r_package$find_file #' @rdname find_root_file #' @export find_remake_root_file <- is_remake_project$find_file #' @rdname find_root_file #' @export find_testthat_root_file <- is_testthat$find_file rprojroot/R/criterion.R0000644000176200001440000000724614473542244014656 0ustar liggesusersmake_find_root_file <- function(criterion) { force(criterion) eval(bquote(function(..., path = ".") { find_root_file(..., criterion = criterion, path = path) })) } make_fix_root_file <- function(criterion, path, subdir = NULL) { root <- find_root(criterion = criterion, path = path) if (!is.null(subdir)) { root <- file.path(root, subdir) } eval(bquote(function(...) { if (!missing(..1)) { abs <- is_absolute_path(..1) if (all(abs)) { return(path(...)) } if (any(abs)) { stop("Combination of absolute and relative paths not supported.", call. = FALSE) } } path(.(root), ...) })) } #' Is a directory the project root? #' #' Objects of the `root_criterion` class decide if a #' given directory is a project root. #' #' Construct criteria using `root_criterion` in a very general fashion #' by specifying a function with a `path` argument, and a description. #' #' @param testfun `[function|list(function)]`\cr #' A function with one parameter that returns `TRUE` #' if the directory specified by this parameter is the project root, #' and `FALSE` otherwise. Can also be a list of such functions. #' @param desc `[character]`\cr #' A textual description of the test criterion, of the same length #' as `testfun`. #' @param subdir `[character]`\cr #' Subdirectories to start the search in, if found #' #' @return #' An S3 object of class `root_criterion` wit the following members: #' #' @export #' #' @examples #' root_criterion(function(path) file.exists(file.path(path, "somefile")), "has somefile") #' has_file("DESCRIPTION") #' is_r_package #' \dontrun{ #' is_r_package$find_file #' is_r_package$make_fix_file(".") #' } root_criterion <- function(testfun, desc, subdir = NULL) { testfun <- check_testfun(testfun) stopifnot(length(desc) == length(testfun)) full_desc <- paste0( desc, if (!is.null(subdir)) { paste0( " (also look in subdirectories: ", paste0("`", subdir, "`", collapse = ", "), ")" ) } ) criterion <- structure( list( #' @return #' \describe{ #' \item{`testfun`}{The `testfun` argument} testfun = testfun, #' \item{`desc`}{The `desc` argument} desc = full_desc, #' \item{`subdir`}{The `subdir` argument} subdir = subdir ), class = "root_criterion" ) #' \item{`find_file`}{A function with `...` and `path` arguments #' that returns a path relative to the root, #' as specified by this criterion. #' The optional `path` argument specifies the starting directory, #' which defaults to `"."`. #' The function forwards to [find_root_file()], #' which passes `...` directly to `file.path()` #' if the first argument is an absolute path. #' } criterion$find_file <- make_find_root_file(criterion) #' \item{`make_fix_file`}{A function with a `path` argument that #' returns a function that finds paths relative to the root. For a #' criterion `cr`, the result of `cr$make_fix_file(".")(...)` #' is identical to `cr$find_file(...)`. The function created by #' `make_fix_file()` can be saved to a variable to be more independent #' of the current working directory. #' } #' } criterion$make_fix_file <- function(path = getwd(), subdir = NULL) { make_fix_root_file(criterion, path, subdir) } criterion } check_testfun <- function(testfun) { if (is.function(testfun)) { testfun <- list(testfun) } for (f in testfun) { if (!isTRUE(all.equal(names(formals(f)), "path"))) { stop("All functions in testfun must have exactly one argument 'path'", call. = FALSE) } } testfun } rprojroot/R/file.R0000644000176200001440000000327014136674000013560 0ustar liggesusers#' File paths relative to the root of a directory hierarchy #' #' `find_root_file()` is a wrapper around [find_root()] that #' appends an arbitrary number of path components to the root using #' [base::file.path()]. #' #' This function operates on the notion of relative paths. #' The `...` argument is expected to contain a path relative to the root. #' If the first path component passed to `...` is already an absolute path, #' the `criterion` and `path` arguments are ignored, #' and `...` is forwarded to [file.path()]. #' #' @param criterion `[root_criterion]`\cr #' A criterion, one of the predefined [criteria] #' or created by [root_criterion()]. #' Will be coerced using [as_root_criterion()]. #' @param path `[character(1)]`\cr #' The start directory. #' @param ... `[character]`\cr #' Further path components passed to [file.path()]. #' All arguments must be the same length or length one. #' @return The normalized path of the root as specified by the search criteria, #' with the additional path components appended. #' Throws an error if no root is found. #' #' @examples #' \dontrun{ #' find_package_root_file("tests", "testthat.R") #' has_file("DESCRIPTION", "^Package: ")$find_file #' has_file("DESCRIPTION", "^Package: ")$make_fix_file(".") #' } #' #' @seealso [find_root()] [utils::glob2rx()] [base::file.path()] #' #' @export find_root_file <- function(..., criterion, path = ".") { if (!missing(..1)) { abs <- is_absolute_path(..1) if (all(abs)) { return(path(...)) } if (any(abs)) { stop("Combination of absolute and relative paths not supported.", call. = FALSE) } } root <- find_root(criterion = criterion, path = path) path(root, ...) } rprojroot/NEWS.md0000644000176200001440000001140714521635135013420 0ustar liggesusers # rprojroot 2.0.4 (2023-11-05) ## Features - Add `is_renv_project` criterion looking for an `renv.lock` file (@gadenbuie, #86). - Add `is_quarto_project` criterion looking for a Quarto project (@olivroy, #91, #92). ## Chore - Update maintainer e-mail address. ## Testing - Wrap `::` to skip if not installed in tests (#94). # rprojroot 2.0.3 (2022-03-25) - Add `is_pkgdown_project` root criterion looking for a `_pkgdown.yml`, `_pkgdown.yaml`, `pkgdown/_pkgdown.yml` and/or `inst/_pkgdown.yml` file (#79, @salim-b). - Avoid `LazyData` in `DESCRIPTION`. # rprojroot 2.0.2 (2020-11-15) ## Features - In `find_root_file()`, if the first path component is already an absolute path, the path is returned unchanged without referring to the root. This allows using both root-relative and absolute paths in `here::here()`. Mixing root-relative and absolute paths in the same call returns an error (#59). - `find_root_file()` propagates `NA` values in path components. Using tidyverse recycling rules for path components of length different from one (#66). - `has_file()` and `has_file_pattern()` gain `fixed` argument (#75). - New `is_drake_project` criterion (#34). - Add `subdir` argument to `make_fix_file()` (#33, @BarkleyBG). - Update documentation for version control criteria (#35, @uribo). ## Breaking changes - `has_file()` and `has_dir()` now throw an error if the `filepath` argument is an absolute path (#74). - `has_basename()` replaces `has_dirname()` to avoid confusion (#63). - `as_root_criterion()` and `is_root_criterion()` replace `as.` and `is.`, respectively. The latter are soft-deprecated. - `thisfile()` and related functions are soft-deprecated, now available in the whereami package (#43). ## Bug fixes - The `is_dirname()` criterion no longer considers sibling directories (#44). ## Internal - Use testthat 3e (#70). - The backports package is no longer imported (#68). - Re-license as MIT (#50). - Move checks to GitHub Actions (#52). - Availability of suggested packages knitr and rmarkdown, and pandoc, is now checked before running the corresponding tests. # rprojroot 1.3-2 (2017-12-22) - Availability of suggested packages knitr and rmarkdown, and pandoc, is now checked before running the corresponding tests. # rprojroot 1.3-1 (2017-12-18) - Adapt to testthat 2.0.0. - New `thisfile()`, moved from kimisc (#8). - Add more examples to vignette (#26, @BarkleyBG). - Detect `.git` directories created with `git clone --separate-git-dir=...` (#24, @karldw). # rprojroot 1.2 (2017-01-15) - New root criteria - `is_projectile_project` recognize projectile projects (#21). - `has_dir()` constructs root criteria that check for existence of a directory. - `is_git_root`, `is_svn_root` and `is_vcs_root` look for a version control system root (#19). - New function - `get_root_desc()` returns the description of the criterion that applies to a given root, useful for composite criteria created with `|`. - Minor enhancements - Improve formatting of alternative criteria (#18). - If root cannot be found, the start path is shown in the error message. - Internal - The `$testfun` member of the `rprojroot` S3 class is now a list of functions instead of a function. # rprojroot 1.1 (2016-10-29) - Compatibility - Compatible with R >= 3.0.0 with the help of the `backports` package. - New root criteria - `is_remake_project` and `find_remake_root_file()` look for [remake](https://github.com/richfitz/remake) project (#17). - `is_testthat` and `find_testthat_root_file()` that looks for `tests/testthat` root (#14). - `from_wd`, useful for creating accessors to a known path (#11). - Minor enhancement - Criteria can be combined with the `|` operator (#15). - Documentation - Add package documentation with a few examples (#13). - Clarify difference between `find_file()` and `make_fix_file()` in vignette (#9). - Remove unexported functions from documentation and examples (#10). - Use `pkgdown` to create website. - Testing - Use Travis instead of wercker. Travis tests three R versions, and OS X. - Improve AppVeyor testing. # rprojroot 1.0-2 (2016-03-28) - Fix test that fails on Windows only on CRAN. # rprojroot 1.0 (2016-03-26) Initial CRAN release. - S3 class `root_criterion`: - Member functions: `find_file()` and `make_fix_file()` - `root_criterion()` - `as.root_criterion()` - `is.root_criterion()` - `has_file()` - `has_file_pattern()` - Built-in criteria: - `is_r_package` - `is_rstudio_project` - Getting started: - `find_package_root_file()` - `find_rstudio_root_file()` - Use a custom notion of a project root: - `find_root()` - `find_root_file()` - Vignette rprojroot/MD50000644000176200001440000000743714521666122012642 0ustar liggesusers4c606b03e2f01a44588cd730e9ecc6be *DESCRIPTION 1a988b93209b9f325be60b4fc5c842a8 *LICENSE 425701abbd48c9e7fe3869df498fd4d3 *NAMESPACE cd671f97b0aa17ae3e74aff23129ced6 *NEWS.md fc5d6eebf8e6e87affaf596023359ddc *R/absolute.R 4c8fa3abb9748090275a467784cd9a61 *R/criterion.R 13136f1a572062f4a708ecd1fe172654 *R/deprecated.R 68d44a7082260a2a7af0141dfbd03147 *R/file.R ad1cf8f1c772a98b195cdd0dfd881653 *R/path.R ad1c81fabff174970174aa72034cef57 *R/root.R 624e9a6ec6b06a74b0e7267a9d68f609 *R/rprojroot-package.R 4de9a9bbf48372e1505dd1550bee0418 *R/shortcut.R 37d1f0a7b1f5a243713a33edeca4e95d *R/thisfile.R b3d903bcf82196ec51defeb5a0a56507 *R/utils.R 9cfccc7b09193cf33a1d2ec3cc324868 *README.md 37bc2c7ed56eeffcfac26861c5659494 *build/vignette.rds 0c660195baf64ac65aa74e64cbfd726b *inst/WORDLIST 4ce9da832cf6a889869cb19c3c5f8e69 *inst/doc/rprojroot.R 66e0c47a97adfb513cdecea04389533d *inst/doc/rprojroot.Rmd 8326d11a9994f20ed4aafe79e78543b7 *inst/doc/rprojroot.html aa692eb61155c34c553d44bba58f0443 *man/criteria.Rd a27e4391bab0a071072998710c235039 *man/deprecated.Rd cb1e46f469cfbbbde29c8b5113e1d789 *man/figures/lifecycle-archived.svg c0d2e5a54f1fa4ff02bf9533079dd1f7 *man/figures/lifecycle-defunct.svg a1b8c987c676c16af790f563f96cbb1f *man/figures/lifecycle-deprecated.svg c3978703d8f40f2679795335715e98f4 *man/figures/lifecycle-experimental.svg 952b59dc07b171b97d5d982924244f61 *man/figures/lifecycle-maturing.svg 27b879bf3677ea76e3991d56ab324081 *man/figures/lifecycle-questioning.svg 6902bbfaf963fbc4ed98b86bda80caa2 *man/figures/lifecycle-soft-deprecated.svg 53b3f893324260b737b3c46ed2a0e643 *man/figures/lifecycle-stable.svg 1c1fe7a759b86dc6dbcbe7797ab8246c *man/figures/lifecycle-superseded.svg ef0e63b7a15c468ccc5c065129c4b847 *man/find_root.Rd 606641411841f90fcfd3d516514934e6 *man/find_root_file.Rd 3faaf464273c1a73a5f2e4d9a7cbcea1 *man/root_criterion.Rd d9c548af9c08a2285deb6a237789df55 *man/rprojroot-package.Rd a170d87ee82034f16b259be704b18b26 *man/thisfile.Rd 1d802d92f687ffbb11ee26ae68396d94 *tests/testthat.R 6917e5535d9e596995a4057fd24c9d97 *tests/testthat/_snaps/root.md a9e236c40b363793546be3d943716cf5 *tests/testthat/_snaps/testthat.md f0cfca3e229adfcf416e2caa79d440f4 *tests/testthat/hierarchy/DESCRIPTION d41d8cd98f00b204e9800998ecf8427e *tests/testthat/hierarchy/a/b/a b16de7308a1f8d6f4f98b1a558ea957d *tests/testthat/hierarchy/a/b/b d41d8cd98f00b204e9800998ecf8427e *tests/testthat/hierarchy/a/b/c/d d41d8cd98f00b204e9800998ecf8427e *tests/testthat/hierarchy/a/b/d/e d41d8cd98f00b204e9800998ecf8427e *tests/testthat/hierarchy/a/remake.yml 55e116ea754236d78e4e6342adbe3661 *tests/testthat/hierarchy/b d41d8cd98f00b204e9800998ecf8427e *tests/testthat/hierarchy/c 3936ed002c1dd1fa5dc7a13d99d5df86 *tests/testthat/hierarchy/hierarchy.Rproj d41d8cd98f00b204e9800998ecf8427e *tests/testthat/package/DESCRIPTION 68b329da9893e34099c7d8ad5cb9c940 *tests/testthat/package/tests/testthat.R 68b329da9893e34099c7d8ad5cb9c940 *tests/testthat/package/tests/testthat/test-something.R 4450e46cec462306555081ab955c7ae8 *tests/testthat/scripts/thisfile-cat.R 1842225c060e51ad4fd7a44cf6dc2c68 *tests/testthat/scripts/thisfile.R b4eea69f30e5e903f792b50357b04e40 *tests/testthat/scripts/thisfile.Rmd 881b9487e8371e1e14c36a393f74c514 *tests/testthat/setup.R d41d8cd98f00b204e9800998ecf8427e *tests/testthat/startup.Rs a1354ce540e9280483a5e40d0c80a845 *tests/testthat/test-absolute.R 32495223ad8e12abe422d088a250af2f *tests/testthat/test-criterion.R b4c7e0c983b29a587bc3f3a89f44fddb *tests/testthat/test-file.R fabb5ac8fc1e28588f08f1753bc4733a *tests/testthat/test-path.R c41a3f6b2265da89f040b4597785c023 *tests/testthat/test-root.R a4ed88f8a0edd04bdb2d2ab2e30fb9f2 *tests/testthat/test-testthat.R d8879d25ee21b0f84992020d95500aaf *tests/testthat/vcs/git.zip 28e9555632b66c264d026cd065dda0db *tests/testthat/vcs/svn.zip 66e0c47a97adfb513cdecea04389533d *vignettes/rprojroot.Rmd rprojroot/inst/0000755000176200001440000000000014521635173013276 5ustar liggesusersrprojroot/inst/doc/0000755000176200001440000000000014521635173014043 5ustar liggesusersrprojroot/inst/doc/rprojroot.html0000644000176200001440000012647514521635173017010 0ustar liggesusers Finding files in project subdirectories

Finding files in project subdirectories

Kirill Müller

2023-11-05

The rprojroot package solves a seemingly trivial but annoying problem that occurs sooner or later in any largish project: How to find files in subdirectories? Ideally, file paths are relative to the project root.

Unfortunately, we cannot always be sure about the current working directory: For instance, in RStudio it’s sometimes:

  • the project root (when running R scripts),
  • a subdirectory (when building vignettes),
  • again the project root (when executing chunks of a vignette).
basename(getwd())
## [1] "vignettes"

In some cases, it’s even outside the project root.

This vignette starts with a very brief summary that helps you get started, followed by a longer description of the features.

TL;DR

What is your project: An R package?

rprojroot::is_r_package
## Root criterion: contains a file "DESCRIPTION" with contents matching "^Package: "

Or an RStudio project?

rprojroot::is_rstudio_project
## Root criterion: contains a file matching "[.]Rproj$" with contents matching "^Version: " in the first line

Or something else?

rprojroot::has_file(".git/index")
## Root criterion: contains a file ".git/index"

For now, we assume it’s an R package:

root <- rprojroot::is_r_package

The root object contains a function that helps locating files below the root of your package, regardless of your current working directory. If you are sure that your working directory is somewhere below your project’s root, use the root$find_file() function. In this example here, we’re starting in the vignettes subdirectory and find the original DESCRIPTION file:

basename(getwd())
## [1] "vignettes"
readLines(root$find_file("DESCRIPTION"), 3)
## [1] "Package: rprojroot"                            
## [2] "Title: Finding Files in Project Subdirectories"
## [3] "Version: 2.0.4"

There is one exception: if the first component passed to find_file() is already an absolute path. This allows safely applying this function to paths that may be absolute or relative:

path <- root$find_file()
readLines(root$find_file(path, "DESCRIPTION"), 3)
## [1] "Package: rprojroot"                            
## [2] "Title: Finding Files in Project Subdirectories"
## [3] "Version: 2.0.4"

You can also construct an accessor to your root using the root$make_fix_file() function:

root_file <- root$make_fix_file()

Note that root_file() is a function that works just like $find_file() but will find the files even if the current working directory is outside your project:

withr::with_dir(
  "../..",
  readLines(root_file("DESCRIPTION"), 3)
)
## [1] "Package: rprojroot"                            
## [2] "Title: Finding Files in Project Subdirectories"
## [3] "Version: 2.0.4"

If you know the absolute path of some directory below your project, but cannot be sure of your current working directory, pass that absolute path to root$make_fix_file():

root_file <- root$make_fix_file("C:\\Users\\User Name\\...")

As a last resort, you can get the path of standalone R scripts or vignettes using the thisfile() function:

root_file <- root$make_fix_file(dirname(thisfile()))

The remainder of this vignette describes implementation details and advanced features.

Project root

We assume a self-contained project where all files and directories are located below a common root directory. Also, there should be a way to unambiguously identify this root directory. (Often, the root contains a regular file whose name matches a given pattern, and/or whose contents match another pattern.) In this case, the following method reliably finds our project root:

  • Start the search in any subdirectory of our project
  • Proceed up the directory hierarchy until the root directory has been identified

The Git version control system (and probably many other tools) use a similar approach: A Git command can be executed from within any subdirectory of a repository.

A simple example

The find_root() function implements the core functionality. It returns the path to the first directory that matches the filtering criteria, or throws an error if there is no such directory. Filtering criteria are constructed in a generic fashion using the root_criterion() function, the has_file() function constructs a criterion that checks for the presence of a file with a specific name and specific contents.

library(rprojroot)

# List all files and directories below the root
dir(find_root(has_file("DESCRIPTION")))
##  [1] "DESCRIPTION"      "LICENSE"          "LICENSE.md"       "NAMESPACE"       
##  [5] "NEWS.md"          "R"                "README.Rmd"       "README.md"       
##  [9] "TODO.md"          "_pkgdown.yml"     "codecov.yml"      "cran-comments.md"
## [13] "inst"             "man"              "revdep"           "rprojroot.Rcheck"
## [17] "rprojroot.Rproj"  "tests"            "vignettes"

Relative paths to a stable root

Here we illustrate the power of rprojroot by demonstrating how to access the same file from two different working directories. Let your project be a package called pkgname and consider the desired file rrmake.R at pkgname/R/rrmake.R. First, we show how to access from the vignettes directory, and then from the tests/testthat directory.

Example A: From vignettes

When your working directory is pkgname/vignettes, you can access the rrmake.R file by:

  1. Supplying a pathname relative to your working directory. Here’s two ways to do that:
rel_path_from_vignettes <- "../R/rrmake.R"
rel_path_from_vignettes <- file.path("..", "R", "rrmake.R") ##identical
  1. Supplying a pathname to the file relative from the root of the package, e.g.,
rel_path_from_root <- "R/rrmake.R"
rel_path_from_root <- file.path("R", "rrmake.R") ##identical

This second method requires finding the root of the package, which can be done with the has_file() function:

has_file("DESCRIPTION")
## Root criterion: contains a file "DESCRIPTION"

So, using rprojroot you can specify the path relative from root in the following manner:

# Specify a path/to/file relative to the root
rel_path_from_root <- find_root_file("R", "rrmake.R", criterion = has_file("DESCRIPTION"))
Example B: From tests/testthat

When your working directory is pkgname/tests/testthat, you can access the rrmake.R file by:

  1. Supplying a pathname relative to your working directory.
rel_path_from_testthat <- "../../R/rrmake.R"

Note that this is different than in the previous example! However, the second method is the same…

  1. Supplying a pathname to the file relative from the root of the package. With rprojroot, this is the exact same as in the previous example.
# Specify a path/to/file relative to the root
rel_path_from_root <- find_root_file("R", "rrmake.R", criterion = has_file("DESCRIPTION"))
Summary of Examples A and B

Since Examples A and B used different working directories, rel_path_from_vignettes and rel_path_from_testthat were different. This is an issue when trying to re-use the same code. This issue is solved by using rprojroot: the function find_root_file() finds a file relative from the root, where the root is determined from checking the criterion with has_file().

Note that the follow code produces identical results when building the vignette and when sourcing the chunk in RStudio, provided that the current working directory is the project root or anywhere below. So, we can check to make sure that rprojroot has successfully determined the correct path:

# Specify a path/to/file relative to the root
rel_path_from_root <- find_root_file("R", "rrmake.R", criterion = has_file("DESCRIPTION"))

# Find a file relative to the root
file.exists(rel_path_from_root)
## [1] FALSE

Criteria

The has_file() function (and the more general root_criterion()) both return an S3 object of class root_criterion:

has_file("DESCRIPTION")
## Root criterion: contains a file "DESCRIPTION"

In addition, character values are coerced to has_file criteria by default, this coercion is applied automatically by find_root(). (This feature is used by the introductory example.)

as_root_criterion("DESCRIPTION")
## Root criterion: contains a file "DESCRIPTION"

The return value of these functions can be stored and reused; in fact, the package provides 12 such criteria:

criteria
## $is_rstudio_project
## Root criterion: contains a file matching "[.]Rproj$" with contents matching "^Version: " in the first line
## 
## $is_r_package
## Root criterion: contains a file "DESCRIPTION" with contents matching "^Package: "
## 
## $is_remake_project
## Root criterion: contains a file "remake.yml"
## 
## $is_pkgdown_project
## Root criterion: one of
## - contains a file "_pkgdown.yml"
## - contains a file "_pkgdown.yaml"
## - contains a file "pkgdown/_pkgdown.yml"
## - contains a file "inst/_pkgdown.yml"
## 
## $is_renv_project
## Root criterion: contains a file "renv.lock" with contents matching ""Packages":\s*\{"
## 
## $is_projectile_project
## Root criterion: contains a file ".projectile"
## 
## $is_quarto_project
## Root criterion: contains a file "_quarto.yml"
## 
## $is_git_root
## Root criterion: one of
## - contains a directory ".git"
## - contains a file ".git" with contents matching "^gitdir: "
## 
## $is_svn_root
## Root criterion: contains a directory ".svn"
## 
## $is_vcs_root
## Root criterion: one of
## - contains a directory ".git"
## - contains a file ".git" with contents matching "^gitdir: "
## - contains a directory ".svn"
## 
## $is_testthat
## Root criterion: directory name is "testthat" (also look in subdirectories: `tests/testthat`, `testthat`)
## 
## $from_wd
## Root criterion: from current working directory
## 
## attr(,"class")
## [1] "root_criteria"

Defining new criteria is easy:

has_license <- has_file("LICENSE")
has_license
## Root criterion: contains a file "LICENSE"
is_projecttemplate_project <- has_file("config/global.dcf", "^version: ")
is_projecttemplate_project
## Root criterion: contains a file "config/global.dcf" with contents matching "^version: "

You can also combine criteria via the | operator:

is_r_package | is_rstudio_project
## Root criterion: one of
## - contains a file "DESCRIPTION" with contents matching "^Package: "
## - contains a file matching "[.]Rproj$" with contents matching "^Version: " in the first line

Shortcuts

To avoid specifying the search criteria for the project root every time, shortcut functions can be created. The find_package_root_file() is a shortcut for find_root_file(..., criterion = is_r_package):

# Print first lines of the source for this document
head(readLines(find_package_root_file("vignettes", "rprojroot.Rmd")))
## [1] "---"                                               
## [2] "title: \"Finding files in project subdirectories\""
## [3] "author: \"Kirill Müller\""                         
## [4] "date: \"`r Sys.Date()`\""                          
## [5] "output: rmarkdown::html_vignette"                  
## [6] "vignette: >"

To save typing effort, define a shorter alias:

P <- find_package_root_file

# Use a shorter alias
file.exists(P("vignettes", "rprojroot.Rmd"))
## [1] TRUE

Each criterion actually contains a function that allows finding a file below the root specified by this criterion. As our project does not have a file named LICENSE, querying the root results in an error:

# Use the has_license criterion to find the root
R <- has_license$find_file
R
## function (..., path = ".") 
## {
##     find_root_file(..., criterion = criterion, path = path)
## }
## <environment: 0x135adac48>
# Our package does not have a LICENSE file, trying to find the root results in an error
R()
## [1] "/private/var/folders/dj/yhk9rkx97wn_ykqtnmk18xvc0000gn/T/RtmpDwUyx7/Rbuildf23e3bfc9008/rprojroot"

Fixed root

We can also create a function that computes a path relative to the root at creation time.

# Define a function that computes file paths below the current root
F <- is_r_package$make_fix_file()
F

# Show contents of the NAMESPACE file in our project
readLines(F("NAMESPACE"))

This is a more robust alternative to $find_file(), because it fixes the project directory when $make_fix_file() is called, instead of searching for it every time. (For that reason it is also slightly faster, but I doubt this matters in practice.)

This function can be used even if we later change the working directory to somewhere outside the project:

# Print the size of the namespace file, working directory outside the project
withr::with_dir(
  "../..",
  file.size(F("NAMESPACE"))
)

The make_fix_file() member function also accepts an optional path argument, in case you know your project’s root but the current working directory is somewhere outside. The path to the current script or knitr document can be obtained using the thisfile() function, but it’s much easier and much more robust to just run your scripts with the working directory somewhere below your project root.

testthat files

Tests run with testthat commonly use files that live below the tests/testthat directory. Ideally, this should work in the following situation:

  • During package development (working directory: package root)
  • When testing with devtools::test() (working directory: tests/testthat)
  • When running R CMD check (working directory: a renamed recursive copy of tests)

The is_testthat criterion allows robust lookup of test files.

is_testthat
## Root criterion: directory name is "testthat" (also look in subdirectories: `tests/testthat`, `testthat`)

The example code below lists all files in the hierarchy test directory. It uses two project root lookups in total, so that it also works when rendering the vignette (sigh):

dir(is_testthat$find_file("hierarchy", path = is_r_package$find_file()))
## [1] "DESCRIPTION"     "a"               "b"               "c"              
## [5] "hierarchy.Rproj"

Another example: custom testing utilities

The hassle of using saved data files for testing is made even easier by using rprojroot in a utility function. For example, suppose you have a testing file at tests/testthat/test_my_fun.R which tests the my_fun() function:

my_fun_run <- do.call(my_fun, my_args)

testthat::test_that(
  "my_fun() returns expected output",
  testthat::expect_equal(
    my_fun_run,
    expected_output
  )
)

There are two pieces of information that you’ll need every time test_my_fun.R is run: my_args and expected_output. Typically, these objects are saved to .Rdata files and saved to the same subdirectory. For example, you could save them to my_args.Rdata and expected_output.Rdata under the tests/testthat/testing_data subdirectory. And, to find them easily in any contexts, you can use rprojroot!

Since all of the data files live in the same subdirectory, you can create a utility function get_my_path() that will always look in that directory for these types of files. And, since the testthat package will look for and source the tests/testthat/helper.R file before running any tests, you can place a get_my_path() in this file and use it throughout your tests:

## saved to tests/testthat/helper.R
get_my_path <- function(file_name) {
  rprojroot::find_testthat_root_file(
    "testing_data", filename
  )
}

Now you can ask get_my_path() to find your important data files by using the function within your test scripts!

## Find the correct path with your custom rprojroot helper function
path_to_my_args_file <- get_my_path("my_args.Rdata")

## Load the input arguments
load(file = path_to_my_args_file)

## Run the function with those arguments
my_fun_run <- do.call(my_fun,my_args)

## Load the historical expectation with the helper
load(file = get_my_path("expected_output.Rdata"))

## Pass all tests and achieve nirvana
testthat::test_that(
  "my_fun() returns expected output",
  testthat::expect_equal(
    my_fun_run,
    expected_output
  )
)

For an example in the wild, see the test_sheet() function in the readxl package.

Summary

The rprojroot package allows easy access to files below a project root if the project root can be identified easily, e.g. if it is the only directory in the whole hierarchy that contains a specific file. This is a robust solution for finding files in largish projects with a subdirectory hierarchy if the current working directory cannot be assumed fixed. (However, at least initially, the current working directory must be somewhere below the project root.)

Acknowledgement

This package was inspired by the gist “Stop the working directory insanity” by Jennifer Bryan, and by the way Git knows where its files are.

rprojroot/inst/doc/rprojroot.R0000644000176200001440000001343514521635172016233 0ustar liggesusers## ----------------------------------------------------------------------------- basename(getwd()) ## ----------------------------------------------------------------------------- rprojroot::is_r_package ## ----------------------------------------------------------------------------- rprojroot::is_rstudio_project ## ----------------------------------------------------------------------------- rprojroot::has_file(".git/index") ## ----------------------------------------------------------------------------- root <- rprojroot::is_r_package ## ----------------------------------------------------------------------------- basename(getwd()) readLines(root$find_file("DESCRIPTION"), 3) ## ----------------------------------------------------------------------------- path <- root$find_file() readLines(root$find_file(path, "DESCRIPTION"), 3) ## ----------------------------------------------------------------------------- root_file <- root$make_fix_file() ## ----------------------------------------------------------------------------- withr::with_dir( "../..", readLines(root_file("DESCRIPTION"), 3) ) ## ----------------------------------------------------------------------------- library(rprojroot) # List all files and directories below the root dir(find_root(has_file("DESCRIPTION"))) ## ----eval = FALSE------------------------------------------------------------- # rel_path_from_vignettes <- "../R/rrmake.R" # rel_path_from_vignettes <- file.path("..", "R", "rrmake.R") ##identical # ## ----eval = FALSE------------------------------------------------------------- # rel_path_from_root <- "R/rrmake.R" # rel_path_from_root <- file.path("R", "rrmake.R") ##identical ## ----------------------------------------------------------------------------- has_file("DESCRIPTION") ## ----------------------------------------------------------------------------- # Specify a path/to/file relative to the root rel_path_from_root <- find_root_file("R", "rrmake.R", criterion = has_file("DESCRIPTION")) ## ----eval = FALSE------------------------------------------------------------- # rel_path_from_testthat <- "../../R/rrmake.R" ## ----------------------------------------------------------------------------- # Specify a path/to/file relative to the root rel_path_from_root <- find_root_file("R", "rrmake.R", criterion = has_file("DESCRIPTION")) ## ----------------------------------------------------------------------------- # Specify a path/to/file relative to the root rel_path_from_root <- find_root_file("R", "rrmake.R", criterion = has_file("DESCRIPTION")) # Find a file relative to the root file.exists(rel_path_from_root) ## ----------------------------------------------------------------------------- has_file("DESCRIPTION") ## ----------------------------------------------------------------------------- as_root_criterion("DESCRIPTION") ## ----------------------------------------------------------------------------- criteria ## ----------------------------------------------------------------------------- has_license <- has_file("LICENSE") has_license is_projecttemplate_project <- has_file("config/global.dcf", "^version: ") is_projecttemplate_project ## ----------------------------------------------------------------------------- is_r_package | is_rstudio_project ## ----------------------------------------------------------------------------- # Print first lines of the source for this document head(readLines(find_package_root_file("vignettes", "rprojroot.Rmd"))) ## ----------------------------------------------------------------------------- P <- find_package_root_file # Use a shorter alias file.exists(P("vignettes", "rprojroot.Rmd")) ## ----error = TRUE------------------------------------------------------------- # Use the has_license criterion to find the root R <- has_license$find_file R # Our package does not have a LICENSE file, trying to find the root results in an error R() ## ----eval = (Sys.getenv("IN_PKGDOWN") != "")---------------------------------- # # Define a function that computes file paths below the current root # F <- is_r_package$make_fix_file() # F # # # Show contents of the NAMESPACE file in our project # readLines(F("NAMESPACE")) ## ----eval = (Sys.getenv("IN_PKGDOWN") != "")---------------------------------- # # Print the size of the namespace file, working directory outside the project # withr::with_dir( # "../..", # file.size(F("NAMESPACE")) # ) ## ----------------------------------------------------------------------------- is_testthat ## ----------------------------------------------------------------------------- dir(is_testthat$find_file("hierarchy", path = is_r_package$find_file())) ## ----eval = FALSE------------------------------------------------------------- # my_fun_run <- do.call(my_fun, my_args) # # testthat::test_that( # "my_fun() returns expected output", # testthat::expect_equal( # my_fun_run, # expected_output # ) # ) ## ----eval = FALSE------------------------------------------------------------- # ## saved to tests/testthat/helper.R # get_my_path <- function(file_name) { # rprojroot::find_testthat_root_file( # "testing_data", filename # ) # } ## ----eval = FALSE------------------------------------------------------------- # ## Find the correct path with your custom rprojroot helper function # path_to_my_args_file <- get_my_path("my_args.Rdata") # # ## Load the input arguments # load(file = path_to_my_args_file) # # ## Run the function with those arguments # my_fun_run <- do.call(my_fun,my_args) # # ## Load the historical expectation with the helper # load(file = get_my_path("expected_output.Rdata")) # # ## Pass all tests and achieve nirvana # testthat::test_that( # "my_fun() returns expected output", # testthat::expect_equal( # my_fun_run, # expected_output # ) # ) rprojroot/inst/doc/rprojroot.Rmd0000644000176200001440000003327314473542515016562 0ustar liggesusers--- title: "Finding files in project subdirectories" author: "Kirill Müller" date: "`r Sys.Date()`" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Finding files in project subdirectories} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- The *rprojroot* package solves a seemingly trivial but annoying problem that occurs sooner or later in any largish project: How to find files in subdirectories? Ideally, file paths are relative to the *project root*. Unfortunately, we cannot always be sure about the current working directory: For instance, in RStudio it's sometimes: - the project root (when running R scripts), - a subdirectory (when building vignettes), - again the project root (when executing chunks of a vignette). ```{r} basename(getwd()) ``` In some cases, it's even outside the project root. This vignette starts with a very brief summary that helps you get started, followed by a longer description of the features. ## TL;DR What is your project: An R package? ```{r} rprojroot::is_r_package ``` Or an RStudio project? ```{r} rprojroot::is_rstudio_project ``` Or something else? ```{r} rprojroot::has_file(".git/index") ``` For now, we assume it's an R package: ```{r} root <- rprojroot::is_r_package ``` The `root` object contains a function that helps locating files below the root of your package, regardless of your current working directory. If you are sure that your working directory is somewhere below your project's root, use the `root$find_file()` function. In this example here, we're starting in the `vignettes` subdirectory and find the original `DESCRIPTION` file: ```{r} basename(getwd()) readLines(root$find_file("DESCRIPTION"), 3) ``` There is one exception: if the first component passed to `find_file()` is already an absolute path. This allows safely applying this function to paths that may be absolute or relative: ```{r} path <- root$find_file() readLines(root$find_file(path, "DESCRIPTION"), 3) ``` You can also construct an accessor to your root using the `root$make_fix_file()` function: ```{r} root_file <- root$make_fix_file() ``` Note that `root_file()` is a *function* that works just like `$find_file()` but will find the files even if the current working directory is outside your project: ```{r} withr::with_dir( "../..", readLines(root_file("DESCRIPTION"), 3) ) ``` If you know the absolute path of some directory below your project, but cannot be sure of your current working directory, pass that absolute path to `root$make_fix_file()`: ```r root_file <- root$make_fix_file("C:\\Users\\User Name\\...") ``` As a last resort, you can get the path of standalone R scripts or vignettes using the `thisfile()` function: ```r root_file <- root$make_fix_file(dirname(thisfile())) ``` The remainder of this vignette describes implementation details and advanced features. ## Project root We assume a self-contained project where all files and directories are located below a common *root* directory. Also, there should be a way to unambiguously identify this root directory. (Often, the root contains a regular file whose name matches a given pattern, and/or whose contents match another pattern.) In this case, the following method reliably finds our project root: - Start the search in any subdirectory of our project - Proceed up the directory hierarchy until the root directory has been identified The Git version control system (and probably many other tools) use a similar approach: A Git command can be executed from within any subdirectory of a repository. ### A simple example The `find_root()` function implements the core functionality. It returns the path to the first directory that matches the filtering criteria, or throws an error if there is no such directory. Filtering criteria are constructed in a generic fashion using the `root_criterion()` function, the `has_file()` function constructs a criterion that checks for the presence of a file with a specific name and specific contents. ```{r} library(rprojroot) # List all files and directories below the root dir(find_root(has_file("DESCRIPTION"))) ``` #### Relative paths to a stable root Here we illustrate the power of *rprojroot* by demonstrating how to access the same file from two different working directories. Let your project be a package called `pkgname` and consider the desired file `rrmake.R` at `pkgname/R/rrmake.R`. First, we show how to access from the `vignettes` directory, and then from the `tests/testthat` directory. ##### Example A: From `vignettes` When your working directory is `pkgname/vignettes`, you can access the `rrmake.R` file by: 1. Supplying a pathname relative to your working directory. Here's two ways to do that: ```{r, eval = FALSE} rel_path_from_vignettes <- "../R/rrmake.R" rel_path_from_vignettes <- file.path("..", "R", "rrmake.R") ##identical ``` 2. Supplying a pathname to the file relative from the root of the package, e.g., ```{r, eval = FALSE} rel_path_from_root <- "R/rrmake.R" rel_path_from_root <- file.path("R", "rrmake.R") ##identical ``` This second method requires finding the root of the package, which can be done with the `has_file()` function: ```{r} has_file("DESCRIPTION") ``` So, using *rprojroot* you can specify the path relative from root in the following manner: ```{r} # Specify a path/to/file relative to the root rel_path_from_root <- find_root_file("R", "rrmake.R", criterion = has_file("DESCRIPTION")) ``` ##### Example B: From `tests/testthat` When your working directory is `pkgname/tests/testthat`, you can access the `rrmake.R` file by: 1. Supplying a pathname relative to your working directory. ```{r, eval = FALSE} rel_path_from_testthat <- "../../R/rrmake.R" ``` Note that this is different than in the previous example! However, the second method is the same... 2. Supplying a pathname to the file relative from the root of the package. With *rprojroot*, this is the exact same as in the previous example. ```{r} # Specify a path/to/file relative to the root rel_path_from_root <- find_root_file("R", "rrmake.R", criterion = has_file("DESCRIPTION")) ``` ##### Summary of Examples A and B Since Examples A and B used different working directories, `rel_path_from_vignettes` and `rel_path_from_testthat` were different. This is an issue when trying to re-use the same code. This issue is solved by using *rprojroot*: the function `find_root_file()` finds a file relative from the root, where the root is determined from checking the criterion with `has_file()`. Note that the follow code produces identical results when building the vignette *and* when sourcing the chunk in RStudio, provided that the current working directory is the project root or anywhere below. So, we can check to make sure that *rprojroot* has successfully determined the correct path: ```{r} # Specify a path/to/file relative to the root rel_path_from_root <- find_root_file("R", "rrmake.R", criterion = has_file("DESCRIPTION")) # Find a file relative to the root file.exists(rel_path_from_root) ``` ### Criteria The `has_file()` function (and the more general `root_criterion()`) both return an S3 object of class `root_criterion`: ```{r} has_file("DESCRIPTION") ``` In addition, character values are coerced to `has_file` criteria by default, this coercion is applied automatically by `find_root()`. (This feature is used by the introductory example.) ```{r} as_root_criterion("DESCRIPTION") ``` The return value of these functions can be stored and reused; in fact, the package provides `r length(criteria)` such criteria: ```{r} criteria ``` Defining new criteria is easy: ```{r} has_license <- has_file("LICENSE") has_license is_projecttemplate_project <- has_file("config/global.dcf", "^version: ") is_projecttemplate_project ``` You can also combine criteria via the `|` operator: ```{r} is_r_package | is_rstudio_project ``` ### Shortcuts To avoid specifying the search criteria for the project root every time, shortcut functions can be created. The `find_package_root_file()` is a shortcut for `find_root_file(..., criterion = is_r_package)`: ```{r} # Print first lines of the source for this document head(readLines(find_package_root_file("vignettes", "rprojroot.Rmd"))) ``` To save typing effort, define a shorter alias: ```{r} P <- find_package_root_file # Use a shorter alias file.exists(P("vignettes", "rprojroot.Rmd")) ``` Each criterion actually contains a function that allows finding a file below the root specified by this criterion. As our project does not have a file named `LICENSE`, querying the root results in an error: ```{r error = TRUE} # Use the has_license criterion to find the root R <- has_license$find_file R # Our package does not have a LICENSE file, trying to find the root results in an error R() ``` ### Fixed root We can also create a function that computes a path relative to the root *at creation time*. ```{r eval = (Sys.getenv("IN_PKGDOWN") != "")} # Define a function that computes file paths below the current root F <- is_r_package$make_fix_file() F # Show contents of the NAMESPACE file in our project readLines(F("NAMESPACE")) ``` This is a more robust alternative to `$find_file()`, because it *fixes* the project directory when `$make_fix_file()` is called, instead of searching for it every time. (For that reason it is also slightly faster, but I doubt this matters in practice.) This function can be used even if we later change the working directory to somewhere outside the project: ```{r eval = (Sys.getenv("IN_PKGDOWN") != "")} # Print the size of the namespace file, working directory outside the project withr::with_dir( "../..", file.size(F("NAMESPACE")) ) ``` The `make_fix_file()` member function also accepts an optional `path` argument, in case you know your project's root but the current working directory is somewhere outside. The path to the current script or `knitr` document can be obtained using the `thisfile()` function, but it's much easier and much more robust to just run your scripts with the working directory somewhere below your project root. ## `testthat` files Tests run with [`testthat`](https://cran.r-project.org/package=testthat) commonly use files that live below the `tests/testthat` directory. Ideally, this should work in the following situation: - During package development (working directory: package root) - When testing with `devtools::test()` (working directory: `tests/testthat`) - When running `R CMD check` (working directory: a renamed recursive copy of `tests`) The `is_testthat` criterion allows robust lookup of test files. ```{r} is_testthat ``` The example code below lists all files in the [hierarchy](https://github.com/r-lib/rprojroot/tree/main/tests/testthat/hierarchy) test directory. It uses two project root lookups in total, so that it also works when rendering the vignette (*sigh*): ```{r} dir(is_testthat$find_file("hierarchy", path = is_r_package$find_file())) ``` ### Another example: custom testing utilities The hassle of using saved data files for testing is made even easier by using *rprojroot* in a utility function. For example, suppose you have a testing file at `tests/testthat/test_my_fun.R` which tests the `my_fun()` function: ```{r, eval = FALSE} my_fun_run <- do.call(my_fun, my_args) testthat::test_that( "my_fun() returns expected output", testthat::expect_equal( my_fun_run, expected_output ) ) ``` There are two pieces of information that you'll need every time `test_my_fun.R` is run: `my_args` and `expected_output`. Typically, these objects are saved to `.Rdata` files and saved to the same subdirectory. For example, you could save them to `my_args.Rdata` and `expected_output.Rdata` under the `tests/testthat/testing_data` subdirectory. And, to find them easily in any contexts, you can use *rprojroot*! Since all of the data files live in the same subdirectory, you can create a utility function `get_my_path()` that will always look in that directory for these types of files. And, since the *testthat* package will look for and source the `tests/testthat/helper.R` file before running any tests, you can place a `get_my_path()` in this file and use it throughout your tests: ```{r, eval = FALSE} ## saved to tests/testthat/helper.R get_my_path <- function(file_name) { rprojroot::find_testthat_root_file( "testing_data", filename ) } ``` Now you can ask `get_my_path()` to find your important data files by using the function within your test scripts! ```{r, eval = FALSE} ## Find the correct path with your custom rprojroot helper function path_to_my_args_file <- get_my_path("my_args.Rdata") ## Load the input arguments load(file = path_to_my_args_file) ## Run the function with those arguments my_fun_run <- do.call(my_fun,my_args) ## Load the historical expectation with the helper load(file = get_my_path("expected_output.Rdata")) ## Pass all tests and achieve nirvana testthat::test_that( "my_fun() returns expected output", testthat::expect_equal( my_fun_run, expected_output ) ) ``` For an example in the wild, see the [`test_sheet()` function](https://github.com/tidyverse/readxl/blob/0d9ad4f570f6580ff716e0e9ba5048447048e9f0/tests/testthat/helper.R#L1-L3) in the *readxl* package. ## Summary The *rprojroot* package allows easy access to files below a project root if the project root can be identified easily, e.g. if it is the only directory in the whole hierarchy that contains a specific file. This is a robust solution for finding files in largish projects with a subdirectory hierarchy if the current working directory cannot be assumed fixed. (However, at least initially, the current working directory must be somewhere below the project root.) ## Acknowledgement This package was inspired by the gist ["Stop the working directory insanity"](https://gist.github.com/jennybc/362f52446fe1ebc4c49f) by Jennifer Bryan, and by the way Git knows where its files are. rprojroot/inst/WORDLIST0000644000176200001440000000023714136674000014464 0ustar liggesusersAcknowledgement AppVeyor Codecov Hadley Lifecycle ORCID RStudio Wickham backports kimisc knitr pandoc rcc readxl rmarkdown testthat tidyverse wercker whereami