elves / elvish

Powerful scripting language & versatile interactive shell

Home Page:https://elv.sh/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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 and timeout will be provided as milliseconds
  • timestamp 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 like watch ... | 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.