std/watch
watch(path, options, callback) watches a file or a directory and gives
each change to callback as a FileEvent while the loop of
std/loop runs. An open watcher keeps the loop running until it
is closed:
import "std/loop"
import "std/watch"
WatchOptions options = WatchOptions()
options.debounce_ms = 100
options.ignore = [".git", "build", "*.tmp"]
Watcher sources = watch("src", options, func(FileEvent e) do
match e.change do
when RENAMED then
print("moved", e.old_path, "to", e.path)
when OVERFLOW then
print("too much changed; reading", e.path, "again")
else
print(e.change, e.path)
end
end)?
run_loop()A FileEvent has the change, the path, which is the watched path
joined with the path below it with / between names, the old_path of a
rename, and the message of a failure:
Change |
Meaning |
|---|---|
CREATED |
the path appeared, made there or moved in from outside what is watched |
CHANGED |
a file's contents or times changed |
REMOVED |
the path went away, removed or moved outside what is watched |
RENAMED |
it moved from old_path to path, both inside what is watched, also from one folder to another |
OVERFLOW |
more changed than the system could keep track of, below path, which must be read again |
FAILED |
watching failed, as message says: for one directory, while the rest goes on, or for all of it |
WatchOptions sets how it watches:
| Field | Meaning |
|---|---|
recursive |
true (the default) takes in every directory below; false only the directory's own entries |
debounce_ms |
above 0, changes wait until that many milliseconds pass without another, and those to one path become one: made and then changed is made, made and then removed is nothing, removed and made again is changed |
ignore |
patterns of paths to leave out, with everything inside a directory that matches |
Patterns are matched against the path below the watched directory: *
stands for any characters but /, ? for one, and ** for any characters,
/ too, and a pattern without / is matched against each name of the
path, so "build" leaves out every directory named build and all that is in
it.
| Function | Result |
|---|---|
watch(path, options, callback) |
a Result[Watcher]; a path that is not there is an ERROR of kind "not_found" |
close_watcher(w) |
stops watching; changes that wait for a debounce are dropped too. with closes a watcher as well |
is_watching(w) |
whether it still watches: it stops when it is closed, and when the watched path goes away, which comes as REMOVED for that path |
next_change(path, options) |
a Task[FileEvent] that settles with the first change, and stops watching |
A watched file is followed through its directory, so a file that an editor
replaces when it saves comes as CHANGED, and one removed and made again
as REMOVED and then CREATED. A directory's own times are not changes:
what happens to its entries is told for them. Changes to one path come in
the order they happened, but the systems differ in the details, so a
program should take a change as a reason to look at the path, not as an
exact record.
Each system has its own way to watch:
- Linux uses inotify, with a watch for each directory. A directory
that appears is watched at once, and what was made in it before then is
told as
CREATED. inotify has a limit of watched directories for each user; reaching it is an error, or aFAILEDchange for a directory that appears later, that namesfs.inotify.max_user_watches. - macOS uses FSEvents, which tells changes a moment after they happen.
- Windows uses
ReadDirectoryChangesExW, which also pairs a move from one folder to another; on file systems without it, such as some network shares, such a move comes asREMOVEDandCREATED. - The BSDs use kqueue, which needs a descriptor for every file and
directory, so a large tree can reach the limit of open files (
ulimit -n). SettingCUARZO_WATCH_KQUEUEmakes macOS watch this way too.