@@ -14,12 +14,13 @@ use perseus_cli::{
14
14
} ;
15
15
use perseus_cli:: {
16
16
check, create_dist, delete_dist, errors:: * , export_error_page, order_reload, run_reload_server,
17
- snoop_build, snoop_server, snoop_wasm_build, Tools ,
17
+ snoop_build, snoop_server, snoop_wasm_build, Tools , WATCH_EXCLUSIONS ,
18
18
} ;
19
19
use std:: env;
20
20
use std:: path:: { Path , PathBuf } ;
21
21
use std:: process:: { Command , ExitCode } ;
22
22
use std:: sync:: mpsc:: channel;
23
+ use walkdir:: WalkDir ;
23
24
24
25
// All this does is run the program and terminate with the acquired exit code
25
26
#[ tokio:: main]
@@ -154,6 +155,33 @@ async fn core(dir: PathBuf) -> Result<i32, Error> {
154
155
custom_watch,
155
156
..
156
157
} ) if * watch && watch_allowed => {
158
+ // Parse the custom watching information into includes and excludes (these are
159
+ // all canonicalized to allow easier comparison)
160
+ let mut watch_includes: Vec < PathBuf > = Vec :: new ( ) ;
161
+ let mut watch_excludes: Vec < PathBuf > = Vec :: new ( ) ;
162
+ for custom in custom_watch {
163
+ if custom. starts_with ( '!' ) {
164
+ let custom = custom. strip_prefix ( '!' ) . unwrap ( ) ;
165
+ let path = Path :: new ( custom) ;
166
+ let full_path =
167
+ path. canonicalize ( )
168
+ . map_err ( |err| WatchError :: WatchFileNotResolved {
169
+ filename : custom. to_string ( ) ,
170
+ source : err,
171
+ } ) ?;
172
+ watch_excludes. push ( full_path) ;
173
+ } else {
174
+ let path = Path :: new ( custom) ;
175
+ let full_path =
176
+ path. canonicalize ( )
177
+ . map_err ( |err| WatchError :: WatchFileNotResolved {
178
+ filename : custom. to_string ( ) ,
179
+ source : err,
180
+ } ) ?;
181
+ watch_includes. push ( full_path) ;
182
+ }
183
+ }
184
+
157
185
let ( tx_term, rx) = channel ( ) ;
158
186
let tx_fs = tx_term. clone ( ) ;
159
187
// Set the handler for termination events (more than just SIGINT) on all
@@ -201,37 +229,125 @@ async fn core(dir: PathBuf) -> Result<i32, Error> {
201
229
tx_fs. send ( Event :: Reload ) . unwrap ( ) ;
202
230
} )
203
231
. map_err ( |err| WatchError :: WatcherSetupFailed { source : err } ) ?;
204
- // Watch the current directory
232
+
233
+ // Resolve all the exclusions to a list of files
234
+ let mut file_watch_excludes = Vec :: new ( ) ;
235
+ for entry in watch_excludes. iter ( ) {
236
+ // To allow exclusions to work sanely, we have to manually resolve the file tree
237
+ if entry. is_dir ( ) {
238
+ // The `notify` crate internally follows symlinks, so we do here too
239
+ for entry in WalkDir :: new ( & entry) . follow_links ( true ) {
240
+ let entry = entry
241
+ . map_err ( |err| WatchError :: ReadCustomDirEntryFailed { source : err } ) ?;
242
+ let entry = entry. path ( ) ;
243
+
244
+ file_watch_excludes. push ( entry. to_path_buf ( ) ) ;
245
+ }
246
+ } else {
247
+ file_watch_excludes. push ( entry. to_path_buf ( ) ) ;
248
+ }
249
+ }
250
+
251
+ // Watch the current directory, accounting for exclusions at the top-level
252
+ // simply to avoid even starting to watch `target` etc., although
253
+ // user exclusions are generally handled separately (and have to be,
254
+ // since otherwise we would be trying to later remove watches that were never
255
+ // added)
205
256
for entry in std:: fs:: read_dir ( "." )
206
257
. map_err ( |err| WatchError :: ReadCurrentDirFailed { source : err } ) ?
207
258
{
208
- // We want to exclude `target/` and `dist`, otherwise we should watch everything
209
259
let entry = entry. map_err ( |err| WatchError :: ReadDirEntryFailed { source : err } ) ?;
210
- let name = entry. file_name ( ) ;
211
- if name != "target"
212
- && name != "dist"
213
- && name != ".git"
214
- && name != "target_engine"
215
- && name != "target_wasm"
216
- {
217
- watcher
218
- . watch ( & entry. path ( ) , RecursiveMode :: Recursive )
219
- . map_err ( |err| WatchError :: WatchFileFailed {
220
- filename : entry. path ( ) . to_str ( ) . unwrap ( ) . to_string ( ) ,
221
- source : err,
260
+ let entry_name = entry. file_name ( ) . to_string_lossy ( ) . to_string ( ) ;
261
+
262
+ // The base watch exclusions operate at the very top level
263
+ if WATCH_EXCLUSIONS . contains ( & entry_name. as_str ( ) ) {
264
+ continue ;
265
+ }
266
+
267
+ let entry = entry. path ( ) . canonicalize ( ) . map_err ( |err| {
268
+ WatchError :: WatchFileNotResolved {
269
+ filename : entry. path ( ) . to_string_lossy ( ) . to_string ( ) ,
270
+ source : err,
271
+ }
272
+ } ) ?;
273
+ // To allow exclusions to work sanely, we have to manually resolve the file tree
274
+ if entry. is_dir ( ) {
275
+ // The `notify` crate internally follows symlinks, so we do here too
276
+ for entry in WalkDir :: new ( & entry) . follow_links ( true ) {
277
+ let entry = entry
278
+ . map_err ( |err| WatchError :: ReadCustomDirEntryFailed { source : err } ) ?;
279
+ if entry. path ( ) . is_dir ( ) {
280
+ continue ;
281
+ }
282
+ let entry = entry. path ( ) . canonicalize ( ) . map_err ( |err| {
283
+ WatchError :: WatchFileNotResolved {
284
+ filename : entry. path ( ) . to_string_lossy ( ) . to_string ( ) ,
285
+ source : err,
286
+ }
222
287
} ) ?;
288
+
289
+ if !file_watch_excludes. contains ( & entry) {
290
+ watcher
291
+ // The recursivity flag here will be irrelevant in all cases
292
+ . watch ( & entry, RecursiveMode :: Recursive )
293
+ . map_err ( |err| WatchError :: WatchFileFailed {
294
+ filename : entry. to_string_lossy ( ) . to_string ( ) ,
295
+ source : err,
296
+ } ) ?;
297
+ }
298
+ }
299
+ } else {
300
+ if !file_watch_excludes. contains ( & entry) {
301
+ watcher
302
+ // The recursivity flag here will be irrelevant in all cases
303
+ . watch ( & entry, RecursiveMode :: Recursive )
304
+ . map_err ( |err| WatchError :: WatchFileFailed {
305
+ filename : entry. to_string_lossy ( ) . to_string ( ) ,
306
+ source : err,
307
+ } ) ?;
308
+ }
223
309
}
224
310
}
225
- // Watch any other files/directories the user has nominated
226
- for entry in custom_watch. iter ( ) {
227
- watcher
228
- // If it's a directory, we'll watch it recursively
229
- // If it's a file, the second parameter here is usefully ignored
230
- . watch ( Path :: new ( entry) , RecursiveMode :: Recursive )
231
- . map_err ( |err| WatchError :: WatchFileFailed {
232
- filename : entry. to_string ( ) ,
233
- source : err,
234
- } ) ?;
311
+ // Watch any other files/directories the user has nominated (pre-canonicalized
312
+ // at the top-level)
313
+ for entry in watch_includes. iter ( ) {
314
+ // To allow exclusions to work sanely, we have to manually resolve the file tree
315
+ if entry. is_dir ( ) {
316
+ // The `notify` crate internally follows symlinks, so we do here too
317
+ for entry in WalkDir :: new ( & entry) . follow_links ( true ) {
318
+ let entry = entry
319
+ . map_err ( |err| WatchError :: ReadCustomDirEntryFailed { source : err } ) ?;
320
+ if entry. path ( ) . is_dir ( ) {
321
+ continue ;
322
+ }
323
+ let entry = entry. path ( ) . canonicalize ( ) . map_err ( |err| {
324
+ WatchError :: WatchFileNotResolved {
325
+ filename : entry. path ( ) . to_string_lossy ( ) . to_string ( ) ,
326
+ source : err,
327
+ }
328
+ } ) ?;
329
+
330
+ if !file_watch_excludes. contains ( & entry) {
331
+ watcher
332
+ // The recursivity flag here will be irrelevant in all cases
333
+ . watch ( & entry, RecursiveMode :: Recursive )
334
+ . map_err ( |err| WatchError :: WatchFileFailed {
335
+ filename : entry. to_string_lossy ( ) . to_string ( ) ,
336
+ source : err,
337
+ } ) ?;
338
+ }
339
+ }
340
+ } else {
341
+ if !file_watch_excludes. contains ( & entry) {
342
+ watcher
343
+ // The recursivity flag here will be irrelevant in all cases
344
+ . watch ( & entry, RecursiveMode :: Recursive )
345
+ . map_err ( |err| WatchError :: WatchFileFailed {
346
+ filename : entry. to_string_lossy ( ) . to_string ( ) ,
347
+ source : err,
348
+ } ) ?;
349
+ }
350
+ }
235
351
}
236
352
237
353
// This will store the handle to the child process
0 commit comments