16
16
# ' interactive or if the user chooses not to install the packages,
17
17
# ' the current evaluation is aborted.
18
18
# '
19
+ # ' You can disable the prompt by setting the
20
+ # ' `rlib_restart_package_not_found` global option to `FALSE`. In that
21
+ # ' case, missing packages always cause an error.
22
+ # '
19
23
# ' @param pkg The package names.
20
24
# ' @param reason Optional string indicating why is `pkg` needed.
21
25
# ' Appears in error messages (if non-interactive) and user prompts
27
31
# ' provided in `pkg` are installed, `FALSE`
28
32
# ' otherwise. `check_installed()` either doesn't return or returns
29
33
# ' `NULL`.
34
+ # '
35
+ # ' @section Handling package not found errors:
36
+ # ' `check_installed()` signals error conditions of class
37
+ # ' `rlib_error_package_not_found`. The error includes `pkg` and
38
+ # ' `version` fields. They are vectorised and may include several
39
+ # ' packages.
40
+ # '
41
+ # ' The error is signalled with a `rlib_restart_package_not_found`
42
+ # ' restart on the stack to allow handlers to install the required
43
+ # ' packages. To do so, add a [calling handler][withCallingHandlers]
44
+ # ' for `rlib_error_package_not_found`, install the required packages,
45
+ # ' and invoke the restart without arguments. This restarts the check
46
+ # ' from scratch.
47
+ # '
48
+ # ' The condition is not signalled in non-interactive sessions, in the
49
+ # ' restarting case, or if the `rlib_restart_package_not_found` user
50
+ # ' option is set to `FALSE`.
51
+ # '
30
52
# ' @export
31
53
# ' @examples
32
54
# ' is_installed("utils")
35
57
is_installed <- function (pkg , ... , version = NULL ) {
36
58
check_dots_empty0(... )
37
59
60
+ # Internal mechanism for unit tests
61
+ hook <- peek_option(" rlang:::is_installed_hook" )
62
+ if (is_function(hook )) {
63
+ return (all(hook(pkg , version )))
64
+ }
65
+
38
66
if (! all(map_lgl(pkg , function (x ) is_true(requireNamespace(x , quietly = TRUE ))))) {
39
67
return (FALSE )
40
68
}
@@ -71,50 +99,48 @@ check_installed <- function(pkg,
71
99
needs_install <- ! map2_lgl(pkg , version , function (p , v ) is_installed(p , version = v ))
72
100
}
73
101
74
- if (! any(needs_install )) {
102
+ missing_pkgs <- pkg [needs_install ]
103
+ missing_vers <- version [needs_install ]
104
+
105
+ if (! length(missing_pkgs )) {
75
106
return (invisible (NULL ))
76
107
}
77
108
78
- missing_pkgs <- pkg [needs_install ]
79
- missing_pkgs <- chr_quoted(missing_pkgs )
109
+ cnd <- new_error_package_not_found(
110
+ missing_pkgs ,
111
+ missing_vers ,
112
+ reason = reason
113
+ )
80
114
81
- if (! is_null(version )) {
82
- missing_vers <- version [needs_install ]
83
- missing_pkgs <- map2_chr(missing_pkgs , missing_vers , function (p , v ) {
84
- if (is_na(v )) {
85
- p
86
- } else {
87
- paste0(p , " (>= " , v , " )" )
88
- }
89
- })
115
+ restart <- peek_option(" rlib_restart_package_not_found" ) %|| % TRUE
116
+ if (! is_bool(restart )) {
117
+ abort(" `rlib_restart_package_not_found` must be a logical value." )
90
118
}
91
119
92
- missing_pkgs_enum <- chr_enumerate(missing_pkgs , final = " and" )
120
+ if (! is_interactive() || ! restart ) {
121
+ abort(cnd_header(cnd ))
122
+ }
93
123
94
- n <- length(missing_pkgs )
95
- info <- pluralise(
96
- n ,
97
- paste0(" The package " , missing_pkgs_enum , " is required" ),
98
- paste0(" The packages " , missing_pkgs_enum , " are required" )
99
- )
100
- if (is_null(reason )) {
101
- info <- paste0(info , " ." )
102
- } else {
103
- info <- paste(info , reason )
124
+ if (signal_package_not_found(cnd )) {
125
+ # A calling handler asked for a restart. Disable restarts and try
126
+ # again.
127
+ return (with_options(
128
+ " rlib_restart_package_not_found" = FALSE ,
129
+ check_installed(pkg , reason , version = version )
130
+ ))
104
131
}
105
132
133
+ header <- cnd_header(cnd )
134
+
135
+ n <- length(missing_pkgs )
106
136
question <- pluralise(
107
137
n ,
108
138
" Would you like to install it?" ,
109
139
" Would you like to install them?"
110
140
)
111
141
112
- if (! is_interactive()) {
113
- abort(info )
114
- }
115
-
116
142
cat(paste_line(
117
- paste0(info(), " " , info ),
143
+ paste0(info(), " " , header ),
118
144
paste0(cross(), " " , question ),
119
145
.trailing = TRUE
120
146
))
@@ -128,3 +154,71 @@ check_installed <- function(pkg,
128
154
utils :: install.packages(missing_pkgs )
129
155
}
130
156
}
157
+
158
+ new_error_package_not_found <- function (pkg ,
159
+ version = NULL ,
160
+ ... ,
161
+ reason = NULL ,
162
+ class = NULL ) {
163
+ if (! is_character(pkg )) {
164
+ abort(" `pkg` must be character vector." )
165
+ }
166
+ if (! length(pkg )) {
167
+ abort(" `pkg` must contain at least one package." )
168
+ }
169
+ if (! is_null(version ) && ! is_character(version , n = length(pkg ))) {
170
+ abort(" `version` must be a character vector as long as `pkg`." )
171
+ }
172
+
173
+ error_cnd(
174
+ class = c(class , " rlib_error_package_not_found" ),
175
+ pkg = pkg ,
176
+ version = version ,
177
+ reason = reason ,
178
+ ...
179
+ )
180
+ }
181
+
182
+ # ' @export
183
+ cnd_header.rlib_error_package_not_found <- function (cnd , ... ) {
184
+ pkg <- cnd $ pkg
185
+ version <- cnd $ version
186
+ reason <- cnd $ reason
187
+ n <- length(pkg )
188
+
189
+ pkg_enum <- chr_quoted(cnd $ pkg )
190
+
191
+ if (! is_null(version )) {
192
+ pkg_enum <- map2_chr(pkg_enum , version , function (p , v ) {
193
+ if (is_na(v )) {
194
+ p
195
+ } else {
196
+ paste0(p , " (>= " , v , " )" )
197
+ }
198
+ })
199
+ }
200
+
201
+ pkg_enum <- chr_enumerate(pkg_enum , final = " and" )
202
+
203
+ info <- pluralise(
204
+ n ,
205
+ paste0(" The package " , pkg_enum , " is required" ),
206
+ paste0(" The packages " , pkg_enum , " are required" )
207
+ )
208
+
209
+ if (is_null(reason )) {
210
+ paste0(info , " ." )
211
+ } else {
212
+ paste(info , reason )
213
+ }
214
+ }
215
+
216
+ signal_package_not_found <- function (cnd ) {
217
+ withRestarts({
218
+ signalCondition(cnd )
219
+ FALSE
220
+ },
221
+ rlib_restart_package_not_found = function () {
222
+ TRUE
223
+ })
224
+ }
0 commit comments