-
Notifications
You must be signed in to change notification settings - Fork 23
Expand file tree
/
Copy pathcriterion.R
More file actions
132 lines (121 loc) · 4 KB
/
criterion.R
File metadata and controls
132 lines (121 loc) · 4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
make_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
#' If given, the criterion will also be tested in the subdirectories
#' defined by this argument, in the order given.
#' The first existing directory will be used as a starting point.
#' This is used for the [is_testthat] criterion that needs to
#' *descend* into `tests/testthat` if starting at the package root,
#' but stay inside `tests/testthat` if called from a testthat test.
#'
#' @return
#' An S3 object of class `root_criterion` with 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
}