Skip to content

Commit 96857ca

Browse files
committed
watch args
1 parent 43b9f36 commit 96857ca

18 files changed

Lines changed: 351 additions & 100 deletions

File tree

crates/bsnext_fs/examples/watch_cmd.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use actix::{Actor, WrapFuture};
1+
use actix::Actor;
22
use bsnext_fs::actor::FsWatcher;
33
use bsnext_fs::watch_path_handler::RequestWatchPath;
44
use bsnext_fs::{FsEvent, FsEventContext};

crates/bsnext_fs/src/watcher.rs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -260,13 +260,22 @@ fn is_ignored_path_type<P: AsRef<Path>>(subject: &P) -> bool {
260260
// Starts at 4913 and increments by 123 if it already exists.
261261
// We only check for the first 10 increments as it's highly unlikely
262262
// that Vim will cycle through more than that in practice.
263-
match path_ref.file_name().map(|name| name.as_encoded_bytes()) {
263+
let bytes = path_ref.file_name().map(|name| name.as_encoded_bytes());
264+
matches!(
265+
bytes,
264266
Some(
265-
b"4913" | b"5036" | b"5159" | b"5282" | b"5405" | b"5528" | b"5651" | b"5774" | b"5897"
266-
| b"6020",
267-
) => true,
268-
_ => false,
269-
}
267+
b"4913"
268+
| b"5036"
269+
| b"5159"
270+
| b"5282"
271+
| b"5405"
272+
| b"5528"
273+
| b"5651"
274+
| b"5774"
275+
| b"5897"
276+
| b"6020",
277+
)
278+
)
270279
}
271280

272281
// todo: If a folder is explicitly watched, these rules should be ignored

crates/bsnext_input/src/input_test/mod.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::route::{
2-
CompressionOpts, CorsOpts, DebounceDuration, DelayKind, DelayOpts, FilterKind, MultiWatch,
2+
CompressionOpts, CorsOpts, DebounceDuration, DelayKind, DelayOpts, MultiWatch, PathPattern,
33
Route, Spec, WatcherDirs,
44
};
55
use crate::watch_opts::WatchOpts;
@@ -215,7 +215,7 @@ fn test_deserialize_watch() {
215215
c.opts.watch,
216216
WatchOpts::Spec(Spec {
217217
debounce: Some(DebounceDuration::Ms(2000)),
218-
filter: None,
218+
only: None,
219219
ignore: None,
220220
run: None,
221221
before: None,
@@ -233,7 +233,7 @@ servers:
233233
- dirs: ./other
234234
debounce:
235235
ms: 2000
236-
filter:
236+
only:
237237
ext: "**/*.css"
238238
"#;
239239
let c: Input = serde_yaml::from_str(input).unwrap();
@@ -242,13 +242,13 @@ servers:
242242
vec![
243243
MultiWatch {
244244
dirs: WatcherDirs::Single("./".to_string()),
245-
opts: Some(Spec::default())
245+
spec: Some(Spec::default())
246246
},
247247
MultiWatch {
248248
dirs: WatcherDirs::Single("./other".to_string()),
249-
opts: Some(Spec {
249+
spec: Some(Spec {
250250
debounce: Some(DebounceDuration::Ms(2000)),
251-
filter: Some(FilterKind::Extension {
251+
only: Some(PathPattern::Extension {
252252
ext: "**/*.css".to_string()
253253
}),
254254
..Default::default()
@@ -267,7 +267,7 @@ servers:
267267
// - dirs: ./other
268268
// debounce:
269269
// ms: 2000
270-
// filter:
270+
// only:
271271
// ext: "**/*.css"
272272
// run:
273273
// - sh: echo lol

crates/bsnext_input/src/lib.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::route::{BeforeRunOptItem, MultiWatch, RunOptItem};
1+
use crate::route::{BeforeRunOptItem, DebounceDuration, MultiWatch, PathPattern, RunOptItem};
22
use crate::server_config::{ServerConfig, ServerIdentity};
33
use crate::startup::StartupContext;
44
use crate::yml::YamlError;
@@ -52,7 +52,7 @@ impl Input {
5252
let root_tasks = self
5353
.watchers
5454
.iter()
55-
.flat_map(|watcher| watcher.opts.as_ref().and_then(|spec| spec.before.clone()))
55+
.flat_map(|watcher| watcher.spec.as_ref().and_then(|spec| spec.before.clone()))
5656
.flatten()
5757
.map(BeforeRunOptItem::into_run_opt);
5858

@@ -64,7 +64,7 @@ impl Input {
6464
.watchers
6565
.iter()
6666
.filter_map(|watcher| {
67-
watcher.opts.as_ref().and_then(|spec| spec.before.clone())
67+
watcher.spec.as_ref().and_then(|spec| spec.before.clone())
6868
})
6969
.flatten()
7070
})
@@ -123,6 +123,9 @@ impl FromStr for Input {
123123
#[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)]
124124
pub struct InputConfig {
125125
pub infer_watchers: InferWatchers,
126+
pub global_fs_ignore: Option<PathPattern>,
127+
pub global_fs_only: Option<PathPattern>,
128+
pub global_fs_debounce: Option<DebounceDuration>,
126129
}
127130

128131
#[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)]

crates/bsnext_input/src/route.rs

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ pub enum DelayKind {
292292
}
293293

294294
#[derive(
295-
Debug, Ord, PartialOrd, PartialEq, Eq, Hash, Clone, serde::Deserialize, serde::Serialize,
295+
Debug, Ord, PartialOrd, PartialEq, Eq, Hash, Clone, Copy, serde::Deserialize, serde::Serialize,
296296
)]
297297
pub enum DebounceDuration {
298298
#[serde(rename = "ms")]
@@ -303,12 +303,31 @@ pub enum DebounceDuration {
303303
Debug, Ord, PartialOrd, PartialEq, Eq, Hash, Clone, serde::Deserialize, serde::Serialize,
304304
)]
305305
#[serde(untagged)]
306-
pub enum FilterKind {
306+
pub enum PathPattern {
307+
/// Use this when you have only an unknown string to process.
307308
StringDefault(String),
308-
Extension { ext: String },
309-
Glob { glob: String },
310-
Any { any: String },
311-
List(Vec<FilterKind>),
309+
Extension {
310+
ext: String,
311+
},
312+
Glob {
313+
glob: String,
314+
},
315+
Any {
316+
any: String,
317+
},
318+
List(Vec<PathPattern>),
319+
}
320+
321+
impl FromStr for PathPattern {
322+
type Err = anyhow::Error;
323+
324+
fn from_str(s: &str) -> Result<Self, Self::Err> {
325+
let next = s.trim();
326+
if next.is_empty() {
327+
return Err(anyhow::anyhow!("path patterns cannot be empty"));
328+
}
329+
Ok(Self::StringDefault(next.to_owned()))
330+
}
312331
}
313332

314333
#[derive(
@@ -325,8 +344,8 @@ pub enum FilterKind {
325344
)]
326345
pub struct Spec {
327346
pub debounce: Option<DebounceDuration>,
328-
pub filter: Option<FilterKind>,
329-
pub ignore: Option<FilterKind>,
347+
pub only: Option<PathPattern>,
348+
pub ignore: Option<PathPattern>,
330349
pub run: Option<Vec<RunOptItem>>,
331350
pub before: Option<Vec<BeforeRunOptItem>>,
332351
}
@@ -521,7 +540,7 @@ impl Default for PrefixOpt {
521540
pub struct MultiWatch {
522541
pub dirs: WatcherDirs,
523542
#[serde(flatten)]
524-
pub opts: Option<Spec>,
543+
pub spec: Option<Spec>,
525544
}
526545

527546
#[derive(

crates/bsnext_input/src/watch_opt_test/mod.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::route::{
2-
DebounceDuration, FilterKind, RunAll, RunAllOpts, RunOptItem, RunSeq, SeqOpts, ShRunOptItem,
2+
DebounceDuration, PathPattern, RunAll, RunAllOpts, RunOptItem, RunSeq, SeqOpts, ShRunOptItem,
33
Spec,
44
};
55
use crate::watch_opts::WatchOpts;
@@ -9,11 +9,11 @@ fn test_watch_opts_debounce() {
99
let input = r#"
1010
debounce:
1111
ms: 200
12-
filter: "**/*.css"
12+
only: "**/*.css"
1313
"#;
1414
let expected = WatchOpts::Spec(Spec {
1515
debounce: Some(DebounceDuration::Ms(200)),
16-
filter: Some(FilterKind::StringDefault("**/*.css".into())),
16+
only: Some(PathPattern::StringDefault("**/*.css".into())),
1717
ignore: None,
1818
run: None,
1919
before: None,
@@ -25,11 +25,11 @@ fn test_watch_opts_debounce() {
2525
#[test]
2626
fn test_watch_opts_inline_filter() {
2727
let input = r#"
28-
filter: "**/*.css"
28+
only: "**/*.css"
2929
"#;
3030
let expected = WatchOpts::Spec(Spec {
3131
debounce: None,
32-
filter: Some(FilterKind::StringDefault("**/*.css".into())),
32+
only: Some(PathPattern::StringDefault("**/*.css".into())),
3333
ignore: None,
3434
run: None,
3535
before: None,
@@ -41,12 +41,12 @@ fn test_watch_opts_inline_filter() {
4141
#[test]
4242
fn test_watch_opts_explicit_filter_ext() {
4343
let input = r#"
44-
filter:
44+
only:
4545
ext: "css"
4646
"#;
4747
let expected = WatchOpts::Spec(Spec {
4848
debounce: None,
49-
filter: Some(FilterKind::Extension {
49+
only: Some(PathPattern::Extension {
5050
ext: "css".to_string(),
5151
}),
5252
ignore: None,
@@ -59,12 +59,12 @@ fn test_watch_opts_explicit_filter_ext() {
5959
#[test]
6060
fn test_watch_opts_explicit_filter_glob() {
6161
let input = r#"
62-
filter:
62+
only:
6363
glob: "**/*.css"
6464
"#;
6565
let expected = WatchOpts::Spec(Spec {
6666
debounce: None,
67-
filter: Some(FilterKind::Glob {
67+
only: Some(PathPattern::Glob {
6868
glob: "**/*.css".into(),
6969
}),
7070
ignore: None,

crates/bsnext_system/src/start/start_kind/snapshots/bsnext_system__start__start_kind__start_from_paths__test__test-2.snap

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,6 @@ watchers: []
2424
run: {}
2525
config:
2626
infer_watchers: RoutesAndServers
27+
global_fs_ignore: ~
28+
global_fs_only: ~
29+
global_fs_debounce: ~

crates/bsnext_system/src/start/start_kind/snapshots/bsnext_system__start__start_kind__start_from_paths__test__test.snap

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,8 @@ Input {
4848
run: {},
4949
config: InputConfig {
5050
infer_watchers: RoutesAndServers,
51+
global_fs_ignore: None,
52+
global_fs_only: None,
53+
global_fs_debounce: None,
5154
},
5255
}

crates/bsnext_system/src/start/start_kind/start_from_paths.rs

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ pub struct StartFromPaths {
1818

1919
impl SystemStart for StartFromPaths {
2020
fn resolve_input(&self, ctx: &StartupContext) -> Result<SystemStartArgs, Box<InputError>> {
21-
let span = tracing::debug_span!("StartFromTrailingArgs::resolve_input");
21+
let span = tracing::debug_span!("StartFromPaths::resolve_input");
2222
let _g = span.entered();
2323

2424
let port = self.port;
@@ -40,32 +40,59 @@ impl SystemStart for StartFromPaths {
4040
Ok(input)
4141
};
4242

43-
let mut input_stub = Input::default();
43+
let explicit_watch_count =
44+
self.watch_sub_opts.paths.len() + self.watch_sub_opts.before.len();
4445

45-
'watch_overrides: {
46-
// if there is some explicit --watch.paths given, we want to ONLY support that
47-
let total = self.watch_sub_opts.paths.len() + self.watch_sub_opts.before.len();
48-
if total == 0 {
49-
tracing::debug!("not appending watchers since paths + before tasks were empty");
50-
break 'watch_overrides;
51-
}
52-
53-
let span = tracing::debug_span!(parent: None, "StartFromTrailingArgs watch_sub_opts");
54-
let _g = span.entered();
55-
tracing::debug!("{} paths to watch", self.watch_sub_opts.paths.len());
56-
tracing::debug!("{} sh_commands to run", self.watch_sub_opts.run.len());
57-
let multi = MultiWatch::from(self.watch_sub_opts.clone());
58-
input_stub.watchers = vec![multi];
59-
input_stub.config.infer_watchers = InferWatchers::None;
60-
}
46+
let input = if explicit_watch_count > 0 {
47+
with_explicit_paths(&self.watch_sub_opts)
48+
} else {
49+
with_inferred_watchers(&self.watch_sub_opts)
50+
};
6151

6252
Ok(SystemStartArgs::InputOnlyDeferred {
63-
input: input_stub,
53+
input,
6454
create: Lazy::new(Box::new(lazy)),
6555
})
6656
}
6757
}
6858

59+
/// when we have given explicit paths on the command line, we are opting out of inferred watchers,
60+
/// for example, the following command would normally create a watcher for the path 'public'
61+
/// `bslive public`
62+
/// however, if we want to watch something else, we need a way to opt-out of the inferred ones, like:
63+
/// `bslive public --watch.paths src`
64+
/// ^ in this case, we only want to watch 'src' and prevent inferred things later.
65+
fn with_explicit_paths(opts: &WatchSubOpts) -> Input {
66+
let mut input = Input::default();
67+
let span = tracing::debug_span!(parent: None, "StartFromPaths watch_overrides");
68+
let _g = span.entered();
69+
tracing::debug!("{} paths to watch", opts.paths.len());
70+
tracing::debug!("{} sh_commands to run", opts.run.len());
71+
let multi = MultiWatch::from(opts.clone());
72+
input.watchers = vec![multi];
73+
input.config.infer_watchers = InferWatchers::None;
74+
input
75+
}
76+
77+
/// in this second scenario, we're probably starting a server without explicit watch paths given as CLI args.
78+
/// eg: `bslive .`
79+
/// this means we'll watch the directory, but it might accidentally end up watching a noisy build folder or similar
80+
/// in that case, we still want to forward --ignore and --only args, but nothing else to allow the user to opt-out
81+
/// eg: `bslive . --only dist/*.html`
82+
fn with_inferred_watchers(opts: &WatchSubOpts) -> Input {
83+
let mut input = Input::default();
84+
let span = tracing::debug_span!(parent: None, "StartFromPaths global_overrides");
85+
let _g = span.entered();
86+
tracing::debug!("{} ignore add", opts.ignore.len());
87+
let multi = MultiWatch::from(opts.clone());
88+
if let Some(spec_from_cli) = multi.spec {
89+
input.config.global_fs_ignore = spec_from_cli.ignore;
90+
input.config.global_fs_only = spec_from_cli.only;
91+
input.config.global_fs_debounce = spec_from_cli.debounce;
92+
}
93+
input
94+
}
95+
6996
fn from_dir_paths<T: AsRef<str>>(
7097
cwd: &Path,
7198
paths: &[T],

crates/bsnext_system/src/watch/mod.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::watch::watch_sub_opts::WatchSubOpts;
22
use bsnext_core::shared_args::LoggingOpts;
3-
use bsnext_input::route::MultiWatch;
3+
use bsnext_input::route::{MultiWatch, PathPattern};
44
use bsnext_tracing::OutputFormat;
55
use watch_runner::WatchRunnerStr;
66

@@ -20,6 +20,15 @@ pub struct WatchCommand {
2020
/// if true, listed commands will execute once before watching starts
2121
#[arg(long)]
2222
pub initial: bool,
23+
/// how long to buffer changes for
24+
#[arg(long)]
25+
pub debounce: Option<usize>,
26+
/// paths to ignore
27+
#[arg(long, num_args(0..))]
28+
pub ignore: Vec<PathPattern>,
29+
/// patterns to allow - when given, paths MUST match one of these
30+
#[arg(long, num_args(0..))]
31+
pub only: Vec<PathPattern>,
2332
/// provide this flag to disable command prefixes
2433
#[arg(long = "no-prefix", default_value = "false")]
2534
pub no_prefix: bool,
@@ -37,7 +46,10 @@ impl From<WatchCommand> for MultiWatch {
3746
paths: value.paths,
3847
run: value.run,
3948
before: value.before,
49+
ignore: value.ignore,
50+
only: value.only,
4051
initial: value.initial,
52+
debounce: value.debounce,
4153
};
4254
MultiWatch::from(sub_opts)
4355
}

0 commit comments

Comments
 (0)