Feature Request: Inotify module
fennewald opened this issue · comments
This is a feature request for an inotify
module to be added to the elvish stdlib. If accepted, I would develop this feature. I am primarily looking for @xiaq 's blessing to bloat add to the stdlib.
This module would provide two commands, watch
and wait
. They would function identically, with the only different being wait
would exit after a single event was caught, and watch
would continue indefinitely.
Function signatures
fn watch {|
&events=[create delete modify]
&debounce=250
&timeout=0
&recursive=$true
&rich-output=$false
@files
| ... }
events
would be a list of events to be caught. By default, it would consist of create
, delete
, and modify
events, but the full list of valid events would be(stolen directly from inotifywatch):
access file or directory contents were read
modify file or directory contents were written
attrib file or directory attributes changed
close_write file or directory closed, after being opened in
writable mode
close_nowrite file or directory closed, after being opened in
read-only mode
close file or directory closed, regardless of read/write mode
open file or directory opened
moved_to file or directory moved to watched directory
moved_from file or directory moved from watched directory
move file or directory moved to or from watched directory
move_self A watched file or directory was moved.
create file or directory created within watched directory
delete file or directory deleted within watched directory
delete_self file or directory was deleted
unmount file system containing file or directory unmounted
debounce
would be a duration, in milliseconds, to debounce events by. If two events are registered within debounce
time of each other, they are combined and sent as one event.
timeout
is a timeout of the maximum duration without edits. If timeout
passes without any events being received, the command will exit (without error). If set to 0, no timeout is used.
recursive
is a flag to determine if directories should be recursively watched.
rich-output
is a flag to determine if the rich output format should be used. When false
, each event sends only the modified filename on the value channel. For example:
~> watch &rich-output=$false .
▶ filename1.txt
▶ filename2.txt
▶ filename3.txt
If true, each event sends a key-value object, with the filename, list of captured events, and timestamp of the events. For combined, debounced events, the latest timestamp is sent:
~> watch &rich-output=$true .
▶ [&path=filename1.txt &time=(num 1681497234) &events=[access modify]]
@files
is a list of files to apply watches to.
Example usage
Running a lambda each time a file was modified
~> watch . | each {|f|
echo $f modified
}
filename1.txt modified
filename2.txt modified
Decision points
There are a few decisions made that are arbitrary, and may not be the best choice. Open to suggestions
debounce
andtimeout
will be provided as millisecondstimestamp
for caught events will be provided as a unix timestamp- timeouts will not produce errors
Caveats
- inotify is only supported on linux
- There is already an inotifywatch command on linux that does most of the above behavior
Alternatives
One solution would be to wrap the already existing inotify-tools, available on most linux package managers. I don't think this is the best approach. These tools lack critical features, like debouncing and output parsing. These additional features are complex enough that implementing them in an elvish wrapper would be non-trivial.
However, developing the behavior above as a separate command, that implements all the logic defined above, and simply outputs json, could be wrapped relatively easily by elvish, and might make more sense than an addition to the stdlib.
looking into the golang file watching ecosystem, it seems the list of events above is likely more verbose than needed.
I plan on using fsnotify, which means this will be cross-platform, but will only support Create, Write, Rename, Remove, Chmod
events
Regarding the design itself:
-
There's precedent in accepting duration strings (as accepted by time.ParseDuration) in
sleep
. -
There's no precedent in outputting timestamps though. It should probably be another basic data type, like numbers.
-
debounce
can be a general-purpose command, so you'd do something likewatch ... | debounce 1s
.
But...
However, developing the behavior above as a separate command, that implements all the logic defined above, and simply outputs json, could be wrapped relatively easily by elvish, and might make more sense than an addition to the stdlib.
I agree with this :) Developing a command that is based on fsnotify and outputs JSON makes a lot of sense.
I do like the idea of having this well integrated into Elvish - the output is structured, so it's quite clumsy in traditional shells but very easy in Elvish. If you'd like to build an Elvish module that wraps it, that's something I'm thinking about in #1607 (case 3).
Makes sense :). I have a few thoughts about #1607 I'll leave there.
Note that there is no reason this needs to be limited to working on Linux. The fswatch command shows that it is possible to support Linux, the various BSDs (including macOS), and Windows. In fact, if you're going to write a module that wraps an external command to make using the external command easier to use with Elvish I would argue that fswatch
is a better choice given that it also works on all the platforms supported by Elvish.