Skip to content

Commit cc5131a

Browse files
authored
Merge pull request #59 from r-lib/f-abs
- 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 mixing root-relative and absolute paths in `here::here()` (#59).
2 parents 4999f9a + 54a8b09 commit cc5131a

File tree

9 files changed

+101
-20
lines changed

9 files changed

+101
-20
lines changed

R/absolute.R

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# adapted from fs
2+
is_absolute_path <- function(x) {
3+
grepl("^[/\\\\~]|^[a-zA-Z]:[/\\\\]", x)
4+
}

R/criterion.R

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@ make_fix_root_file <- function(criterion, path, subdir = NULL) {
1111
root <- file.path(root, subdir)
1212
}
1313
eval(bquote(function(...) {
14-
file.path(.(root), ...)
14+
if (!missing(..1) && is_absolute_path(..1)) {
15+
file.path(...)
16+
} else {
17+
file.path(.(root), ...)
18+
}
1519
}))
1620
}
1721

@@ -73,17 +77,21 @@ root_criterion <- function(testfun, desc, subdir = NULL) {
7377
class = "root_criterion"
7478
)
7579

76-
#' \item{`find_file`}{A function with `...` argument that returns
77-
#' for a path relative to the root specified by this criterion.
80+
#' \item{`find_file`}{A function with `...` and `path` arguments
81+
#' that returns a path relative to the root,
82+
#' as specified by this criterion.
7883
#' The optional `path` argument specifies the starting directory,
7984
#' which defaults to `"."`.
85+
#' The function forwards to [find_root_file()],
86+
#' which passes `...` directly to `file.path()`
87+
#' if the first argument is an absolute path.
8088
#' }
8189
criterion$find_file <- make_find_root_file(criterion)
8290
#' \item{`make_fix_file`}{A function with a `path` argument that
8391
#' returns a function that finds paths relative to the root. For a
8492
#' criterion `cr`, the result of `cr$make_fix_file(".")(...)`
8593
#' is identical to `cr$find_file(...)`. The function created by
86-
#' `make_fix_file` can be saved to a variable to be more independent
94+
#' `make_fix_file()` can be saved to a variable to be more independent
8795
#' of the current working directory.
8896
#' }
8997
#' }

R/file.R

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
#' File paths relative to the root of a directory hierarchy
22
#'
3-
#' Append an arbitrary number of path components to the root using
4-
#' [base::file.path()].
5-
#'
6-
#' The `find_root_file()` function is a simple wrapper around
7-
#' [find_root()] that
3+
#' `find_root_file()` is a wrapper around [find_root()] that
84
#' appends an arbitrary number of path components to the root using
95
#' [base::file.path()].
106
#'
7+
#' This function operates on the notion of relative paths.
8+
#' The `...` argument is expected to contain a path relative to the root.
9+
#' If the first path component passed to `...` is already an absolute path,
10+
#' the `criterion` and `path` arguments are ignored,
11+
#' and `...` is forwarded to [file.path()].
12+
#'
1113
#' @param criterion A criterion, one of the predefined [criteria]
1214
#' or created by [root_criterion()].
1315
#' Will be coerced using [as_root_criterion()].
@@ -28,6 +30,10 @@
2830
#'
2931
#' @export
3032
find_root_file <- function(..., criterion, path = ".") {
33+
if (!missing(..1) && is_absolute_path(..1)) {
34+
return(file.path(...))
35+
}
36+
3137
root <- find_root(criterion = criterion, path = path)
3238
file.path(root, ...)
3339
}

README.Rmd

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,16 @@ is_r_package$find_file()
6060
is_r_package$find_file("tests", "testthat")
6161
```
6262

63+
There is one exception: if the first component passed to `find_file()` is already an absolute path.
64+
This allows safely applying this function to paths that may be absolute or relative:
65+
66+
```{r}
67+
setwd(file.path(pkg, "R"))
68+
path <- is_r_package$find_file()
69+
is_r_package$find_file(path, "tests", "testthat")
70+
```
71+
72+
6373
As long as you are sure that your working directory is somewhere inside your project, you can retrieve the project root.
6474

6575

README.md

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
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.
1313

1414
<pre class='chroma'>
15-
<span class='kr'><a href='https://rdrr.io/r/base/library.html'>library</a></span><span class='o'>(</span><span class='nv'><a href='https://github.com/r-lib/rprojroot'>rprojroot</a></span><span class='o'>)</span></pre>
15+
<span class='kr'><a href='https://rdrr.io/r/base/library.html'>library</a></span><span class='o'>(</span><span class='nv'><a href='https://rprojroot.r-lib.org/'>rprojroot</a></span><span class='o'>)</span></pre>
1616

1717
## Example
1818

@@ -21,11 +21,11 @@ The rprojroot package works best when you have a “project”: all related file
2121
<pre class='chroma'>
2222
<span class='nv'>dir</span> <span class='o'>&lt;-</span> <span class='nf'><a href='https://rdrr.io/r/base/tempfile.html'>tempfile</a></span><span class='o'>(</span><span class='o'>)</span>
2323
<span class='nv'>pkg</span> <span class='o'>&lt;-</span> <span class='nf'>usethis</span><span class='nf'>::</span><span class='nf'><a href='https://usethis.r-lib.org/reference/create_package.html'>create_package</a></span><span class='o'>(</span><span class='nv'>dir</span><span class='o'>)</span>
24-
<span class='c'>#&gt; <span style='color: #00BB00;'>✓</span><span> Creating </span><span style='color: #0000BB;'>'/tmp/RtmpG04Wy1/file2d4962962cd9/'</span></span>
25-
<span class='c'>#&gt; <span style='color: #00BB00;'>✓</span><span> Setting active project to </span><span style='color: #0000BB;'>'/tmp/RtmpG04Wy1/file2d4962962cd9'</span></span>
24+
<span class='c'>#&gt; <span style='color: #00BB00;'>✓</span><span> Creating </span><span style='color: #0000BB;'>'/tmp/RtmpBLE08t/file294c3c8acca7/'</span></span>
25+
<span class='c'>#&gt; <span style='color: #00BB00;'>✓</span><span> Setting active project to </span><span style='color: #0000BB;'>'/tmp/RtmpBLE08t/file294c3c8acca7'</span></span>
2626
<span class='c'>#&gt; <span style='color: #00BB00;'>✓</span><span> Creating </span><span style='color: #0000BB;'>'R/'</span></span>
2727
<span class='c'>#&gt; <span style='color: #00BB00;'>✓</span><span> Writing </span><span style='color: #0000BB;'>'DESCRIPTION'</span></span>
28-
<span class='c'>#&gt; <span style='color: #0000BB;'>Package</span><span>: file2d4962962cd9</span></span>
28+
<span class='c'>#&gt; <span style='color: #0000BB;'>Package</span><span>: file294c3c8acca7</span></span>
2929
<span class='c'>#&gt; <span style='color: #0000BB;'>Title</span><span>: What the Package Does (One Line, Title Case)</span></span>
3030
<span class='c'>#&gt; <span style='color: #0000BB;'>Version</span><span>: 0.0.0.9000</span></span>
3131
<span class='c'>#&gt; <span style='color: #0000BB;'>Date</span><span>: 2020-11-08</span></span>
@@ -50,18 +50,26 @@ R packages satisfy the `is_r_package` criterion. A criterion is an object that c
5050
<span class='nv'>is_r_package</span>
5151
<span class='c'>#&gt; Root criterion: contains a file `DESCRIPTION` with contents matching `^Package: `</span>
5252
<span class='nv'>is_r_package</span><span class='o'>$</span><span class='nf'>find_file</span><span class='o'>(</span><span class='o'>)</span>
53-
<span class='c'>#&gt; [1] "/tmp/RtmpG04Wy1/file2d4962962cd9"</span>
53+
<span class='c'>#&gt; [1] "/tmp/RtmpBLE08t/file294c3c8acca7"</span>
5454
<span class='nv'>is_r_package</span><span class='o'>$</span><span class='nf'>find_file</span><span class='o'>(</span><span class='s'>"tests"</span>, <span class='s'>"testthat"</span><span class='o'>)</span>
55-
<span class='c'>#&gt; [1] "/tmp/RtmpG04Wy1/file2d4962962cd9/tests/testthat"</span></pre>
55+
<span class='c'>#&gt; [1] "/tmp/RtmpBLE08t/file294c3c8acca7/tests/testthat"</span></pre>
5656

5757
This works identically when starting from a subdirectory:
5858

5959
<pre class='chroma'>
6060
<span class='nf'><a href='https://rdrr.io/r/base/getwd.html'>setwd</a></span><span class='o'>(</span><span class='nf'><a href='https://rdrr.io/r/base/file.path.html'>file.path</a></span><span class='o'>(</span><span class='nv'>pkg</span>, <span class='s'>"R"</span><span class='o'>)</span><span class='o'>)</span>
6161
<span class='nv'>is_r_package</span><span class='o'>$</span><span class='nf'>find_file</span><span class='o'>(</span><span class='o'>)</span>
62-
<span class='c'>#&gt; [1] "/tmp/RtmpG04Wy1/file2d4962962cd9"</span>
62+
<span class='c'>#&gt; [1] "/tmp/RtmpBLE08t/file294c3c8acca7"</span>
6363
<span class='nv'>is_r_package</span><span class='o'>$</span><span class='nf'>find_file</span><span class='o'>(</span><span class='s'>"tests"</span>, <span class='s'>"testthat"</span><span class='o'>)</span>
64-
<span class='c'>#&gt; [1] "/tmp/RtmpG04Wy1/file2d4962962cd9/tests/testthat"</span></pre>
64+
<span class='c'>#&gt; [1] "/tmp/RtmpBLE08t/file294c3c8acca7/tests/testthat"</span></pre>
65+
66+
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:
67+
68+
<pre class='chroma'>
69+
<span class='nf'><a href='https://rdrr.io/r/base/getwd.html'>setwd</a></span><span class='o'>(</span><span class='nf'><a href='https://rdrr.io/r/base/file.path.html'>file.path</a></span><span class='o'>(</span><span class='nv'>pkg</span>, <span class='s'>"R"</span><span class='o'>)</span><span class='o'>)</span>
70+
<span class='nv'>path</span> <span class='o'>&lt;-</span> <span class='nv'>is_r_package</span><span class='o'>$</span><span class='nf'>find_file</span><span class='o'>(</span><span class='o'>)</span>
71+
<span class='nv'>is_r_package</span><span class='o'>$</span><span class='nf'>find_file</span><span class='o'>(</span><span class='nv'>path</span>, <span class='s'>"tests"</span>, <span class='s'>"testthat"</span><span class='o'>)</span>
72+
<span class='c'>#&gt; [1] "/tmp/RtmpBLE08t/file294c3c8acca7/tests/testthat"</span></pre>
6573

6674
As long as you are sure that your working directory is somewhere inside your project, you can retrieve the project root.
6775

tests/testthat/test-absolute.R

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# From fs
2+
describe("is_absolute_path", {
3+
it("detects windows absolute paths", {
4+
expect_true(is_absolute_path("c:\\"))
5+
expect_true(is_absolute_path("c:/"))
6+
expect_true(is_absolute_path("P:/"))
7+
expect_true(is_absolute_path("P:\\"))
8+
expect_true(is_absolute_path("\\\\server\\mountpoint\\"))
9+
expect_true(is_absolute_path("\\foo"))
10+
expect_true(is_absolute_path("\\foo\\bar"))
11+
})
12+
it("detects posix absolute paths", {
13+
expect_false(is_absolute_path(""))
14+
expect_false(is_absolute_path("foo/bar"))
15+
expect_false(is_absolute_path("./foo/bar"))
16+
expect_false(is_absolute_path("../foo/bar"))
17+
18+
expect_true(is_absolute_path("/"))
19+
expect_true(is_absolute_path("/foo"))
20+
expect_true(is_absolute_path("/foo/bar"))
21+
expect_true(is_absolute_path("~/foo/bar"))
22+
})
23+
})
24+

tests/testthat/test-make.R

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,17 @@ test_that("Shortcuts", {
77
)
88

99
R <- make_fix_root_file("testthat.R", getwd())
10-
oldwd <- setwd("~")
11-
on.exit(setwd(oldwd))
10+
11+
oldwd <- withr::local_dir("~")
1212

1313
expect_equal(
1414
normalizePath(R("testthat"), mustWork = TRUE),
1515
normalizePath(oldwd, mustWork = TRUE)
1616
)
17+
18+
path <- R()
19+
expect_equal(
20+
normalizePath(R(path, "testthat"), mustWork = TRUE),
21+
normalizePath(oldwd, mustWork = TRUE)
22+
)
1723
})

tests/testthat/test-root.R

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,11 @@ test_that("has_dir", {
102102
find_root_file("c", criterion = has_dir("b"), path = path),
103103
file.path(hierarchy(2L), "c")
104104
),
105+
# Absolute paths are stripped
106+
expect_equal(
107+
find_root_file(hierarchy(3L), "c", criterion = has_dir("b"), path = path),
108+
hierarchy(4L)
109+
),
105110
expect_equal(find_root(has_dir("c"), path = path), hierarchy(3L)),
106111
expect_error(
107112
find_root(has_dir("e"), path = path),

vignettes/rprojroot.Rmd

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,22 @@ root <- rprojroot::is_r_package
6060
The `root` object contains a function that helps locating files below the root
6161
of your package, regardless of your current working directory.
6262
If you are sure that your working directory is somewhere below your project's root,
63-
use the `root$find_file()` function:
63+
use the `root$find_file()` function.
64+
In this example here, we're starting in the `vignettes` subdirectory and find the original `DESCRIPTION` file:
6465

6566
```{r}
67+
basename(getwd())
6668
readLines(root$find_file("DESCRIPTION"), 3)
6769
```
6870

71+
There is one exception: if the first component passed to `find_file()` is already an absolute path.
72+
This allows safely applying this function to paths that may be absolute or relative:
73+
74+
```{r}
75+
path <- root$find_file()
76+
readLines(root$find_file(path, "DESCRIPTION"), 3)
77+
```
78+
6979
You can also
7080
construct an accessor to your root using the `root$make_fix_file()` function:
7181

0 commit comments

Comments
 (0)