///|
pub struct Watcher {
priv state : WatchState
priv emitter : Emitter
}
///|
fn Watcher::new(options : Options) -> Watcher {
{ state: WatchState::new(options), emitter: Emitter::new() }
}
///|
fn ascii_lowercase(s : String) -> String {
let mut result = ""
for ch in s {
let c = ch.to_int()
let lower = if c >= 65 && c <= 90 { Int::unsafe_to_char(c + 32) } else { ch }
result = result + lower.to_string()
}
result
}
///|
fn parse_int_option(s : String) -> Int? {
if s == "" {
return None
}
let mut result = 0
for ch in s {
let d = ch.to_int() - '0'.to_int()
if d < 0 || d > 9 {
return None
}
result = result * 10 + d
}
Some(result)
}
///|
fn apply_env_overrides(options : Options) -> Options {
let mut opts = options
match chokidar_getenv("CHOKIDAR_USEPOLLING") {
Some(val) => {
let lower = ascii_lowercase(val)
if lower == "false" || lower == "0" {
opts = { ..opts, use_polling: false }
} else {
opts = { ..opts, use_polling: lower != "" }
}
}
None => ()
}
match chokidar_getenv("CHOKIDAR_INTERVAL") {
Some(val) =>
match parse_int_option(val) {
Some(n) => opts = { ..opts, interval: n }
None => ()
}
None => ()
}
opts
}
///|
pub fn watch(
paths : WatchPaths,
options? : Options = Options::default(),
) -> Watcher raise ChokidarError {
let watcher = Watcher::new(apply_env_overrides(options))
watcher.add(paths)
}
///|
pub fn Watcher::on(
self : Watcher,
event : EventName,
callback : (Event) -> Unit,
) -> Watcher {
self.emitter.on(event, callback)
self
}
///|
priv struct PlannedAdd {
root : String
handle : BackendHandle
entries : Array[SnapshotEntry]
events : Array[Event]
}
///|
fn initial_events_from_entries(
entries : Array[SnapshotEntry],
options : Options,
) -> Array[Event] {
if options.ignore_initial {
[]
} else {
entries.map(entry => event_from_entry(event_name_for_add(entry.kind), entry))
}
}
///|
fn close_planned_adds(plans : Array[PlannedAdd]) -> Unit {
for plan in plans {
backend_close(plan.handle)
}
}
///|
pub fn Watcher::add(
self : Watcher,
paths : WatchPaths,
) -> Watcher raise ChokidarError {
let values = match paths {
One(path) => [path]
Many(items) => items
}
for path in values {
guard path != "" else {
raise ChokidarError("watch path must not be empty")
}
}
let plans : Array[PlannedAdd] = []
for path in values {
let root = normalize_path(path)
let handle = backend_watch(root, self.state.options) catch {
ChokidarError(message) => {
close_planned_adds(plans)
raise ChokidarError(message)
}
}
let entries = scan_snapshot(root, self.state.options) catch {
ChokidarError(message) => {
backend_close(handle)
close_planned_adds(plans)
raise ChokidarError(message)
}
}
plans.push({
root,
handle,
entries,
events: initial_events_from_entries(entries, self.state.options),
})
}
for plan in plans {
self.state.add_child(path_dirname(plan.root), path_basename(plan.root))
self.state.remember_root(plan.root)
self.state.handles.push(plan.handle)
self.state.set_snapshot(plan.root, plan.entries)
for event in plan.events {
self.queue_scanned_event(plan.root, event)
}
}
self
}
///|
pub fn Watcher::unwatch(self : Watcher, paths : WatchPaths) -> Watcher {
let values = match paths {
One(path) => [path]
Many(items) => items
}
for path in values {
self.state.remove_child(path_dirname(path), path_basename(path))
}
self
}
///|
pub fn Watcher::close(self : Watcher) -> Unit {
self.state.closed = true
for handle in self.state.handles {
backend_close(handle)
}
self.state.clear()
self.emitter.remove_all_listeners()
}
///|
pub fn Watcher::off(
self : Watcher,
event : EventName,
callback : (Event) -> Unit,
) -> Watcher {
self.emitter.off(event, callback)
self
}
///|
pub fn Watcher::remove_all_listeners(self : Watcher) -> Watcher {
self.emitter.remove_all_listeners()
self
}
///|
pub fn Watcher::get_watched(self : Watcher) -> Map[String, Array[String]] {
self.state.get_watched()
}
///|
fn Watcher::dispatch_ready_event(self : Watcher, event : Event) -> Unit {
let normalized = self.state.normalize_atomic(event)
for item in normalized {
if self.state.allow_event(item.name, item.path) {
self.emitter.emit(item)
}
}
}
///|
fn Watcher::emit_event(self : Watcher, event : Event) -> Unit {
if self.state.closed ||
is_ignored(event.path, stats=event.stats, self.state.options.ignored) {
return
}
let event = if self.state.options.always_stat &&
event.stats is None &&
(event.name == Add || event.name == AddDir || event.name == Change) {
let full = absolute_path_for(event.path, self.state.options)
match stat_for_path(full, self.state.options.follow_symlinks) {
None => return
Some(s) => { ..event, stats: Some(s) }
}
} else {
event
}
let ready_events = self.state.normalize_await_write(event)
for ready in ready_events {
self.dispatch_ready_event(ready)
}
}
///|
fn Watcher::record_event_in_watched(self : Watcher, event : Event) -> Unit {
match event.name {
Add | AddDir =>
self.state.add_child(path_dirname(event.path), path_basename(event.path))
Unlink | UnlinkDir =>
self.state.remove_child(
path_dirname(event.path),
path_basename(event.path),
)
Change | Ready | Raw | Error | All => ()
}
}
///|
fn Watcher::emit_scanned_event(
self : Watcher,
root : String,
event : Event,
) -> Unit {
let path = event_path_for(join_path(root, event.path), self.state.options)
let event = Event::{ ..event, path, }
self.record_event_in_watched(event)
self.emit_event(event)
}
///|
fn Watcher::queue_scanned_event(
self : Watcher,
root : String,
event : Event,
) -> Unit {
let path = event_path_for(join_path(root, event.path), self.state.options)
let event = Event::{ ..event, path, }
self.record_event_in_watched(event)
self.state.pending_events.push(event)
}
///|
fn Watcher::scan_root(
self : Watcher,
root : String,
initial~ : Bool,
) -> Unit raise ChokidarError {
let old_entries = self.state.snapshot_for(root)
let new_entries = scan_snapshot(root, self.state.options)
self.state.set_snapshot(root, new_entries)
let events = if initial {
if self.state.options.ignore_initial {
[]
} else {
new_entries.map(entry => {
event_from_entry(event_name_for_add(entry.kind), entry)
})
}
} else {
diff_snapshots(old_entries, new_entries)
}
for event in events {
if initial {
self.queue_scanned_event(root, event)
} else {
self.emit_scanned_event(root, event)
}
}
}
///|
fn raw_event_affects_root(raw : RawEvent, root : String) -> Bool {
if raw.watched_path == "" {
true
} else if raw.watched_path == root {
true
} else {
match raw.event_path {
Some(path) =>
recursive_path_matches(root, join_path(raw.watched_path, path))
None => false
}
}
}
///|
fn path_for_raw_event(raw : RawEvent, options : Options) -> String {
match raw.event_path {
Some(path) => event_path_for(join_path(raw.watched_path, path), options)
None => event_path_for(raw.watched_path, options)
}
}
///|
fn Watcher::process_raw_events(
self : Watcher,
raw_events : Array[RawEvent],
) -> Unit raise ChokidarError {
for event in self.state.pending_events {
self.emit_event(event)
}
self.state.pending_events.clear()
if !self.state.ready {
self.state.ready = true
self.emit_event({
name: Ready,
path: "",
stats: None,
raw: None,
error: None,
})
}
for raw in raw_events {
self.emit_event({
name: Raw,
path: path_for_raw_event(raw, self.state.options),
stats: None,
raw: Some(raw),
error: None,
})
}
if self.state.options.use_polling {
for root in self.state.root_paths {
self.scan_root(root, initial=false)
}
} else {
for raw in raw_events {
for root in self.state.root_paths {
if raw_event_affects_root(raw, root) {
self.scan_root(root, initial=false)
}
}
}
}
}
///|
fn Watcher::finish_process_cycle(self : Watcher) -> Unit {
for ready in self.state.flush_stable_writes() {
self.dispatch_ready_event(ready)
}
for pending in self.state.flush_expired_pending_unlinks() {
if self.state.allow_event(pending.name, pending.path) {
self.emitter.emit(pending)
}
}
self.state.clear_throttles()
}
///|
pub fn Watcher::process(self : Watcher) -> Unit raise ChokidarError {
if self.state.closed {
return
}
let raw_events = backend_drain_events()
let error = try {
self.process_raw_events(raw_events)
None
} catch {
ChokidarError(message) => Some(message)
}
self.finish_process_cycle()
match error {
Some(message) => {
self.emitter.emit({
name: Error,
path: "",
stats: None,
raw: None,
error: Some(ChokidarError(message)),
})
raise ChokidarError(message)
}
None => ()
}
}