Doing stuff when files change
There’s a common pattern in development tools to help with rapid feedback: you run a long-lived process that does a certain task. At the same time, it watches the filesystem and restarts or re-runs that task whenever a file that you’re monitoring changes.
This is an extremely useful tool for rapid feedback (which is why we’ve integrated nodemon into our Conductance app server), but is not very flexible - most tools are integrated into a web framework or other environment, and can’t easily be used outside of it. There are a few generic tools to do this kind of thing - I personally use watchdog a lot, but it’s sucky in various ways:
- Configuring it to watch the right file types is hard
- Configuring it to ignore “junk” files is hard, leading to infinite feedback loops if you get it wrong
- It sees about 6 events from a single “save file” action, and then insists on running my build script 6 times in a row
- It takes a bash string, rather than a list of arguments - so you have to deal with double-escaping all your special characters
And yet for all of those issues, I haven’t found a better tool that does what I need.
My build workflow
Lately, I’ve been making heavy use of my relatively-new build system, gup. It’s a bit like make
, but way better in my humble-and-totally-biased-opinion. But going to a terminal window and typing up, enter
(or the wrist-saving alternative crtl-p, ctrl-m
) to rebuild things is tedious. But there’s no way I’m going to implement yet another watch-the-filesystem-and-then-re-run-something gup-specific tool, at least not until the lazy alternatives have been exhausted.
Obviously, my workflow isn’t just up, enter
. It’s (frequently):
- save file in vim
- go to terminal
- press
up, enter
- go to browser
- refresh
And you know what? Monitoring every file is kind of dumb for this workflow. I don’t have gremlins running around changing files in my working tree at random (I hope), I almost always want to reload in response to me changing a file (with vim, of course). So why not just cooperate?
The simple fix
So I’ve written a stupid-dumb vim plugin, and a stupid-dumb python script. The vim plugin touches a file in $XDG_USER_DIR
whenever vim saves a file. And then the script monitors just this file, and does whatever you told it to do each time the file is modified. The script automatically communicates with vim to enable / disable the plugin as needed, so it has no overhead when you’re not using it.
It’s called vim-watch, and I just gave you the link.
Addendum: restarting servers
While writing this post, I was a little disappointed that it still doesn’t quite replace tools that automatically restart a server when something changes, because it expects to run a build-style command that exits when it’s done - but servers run forever. Some unix daemons (like apache) restart themselves when you send them a HUP
signal, but that’s not so common in app servers. So now huppy exists, too.
It’s a tiny utility that’ll run whatever long-lived process you tell it to, and when it receives a HUP
signal it’ll kill that process (with SIGINT
) if it’s still running, then restart it. It seems surprising that this didn’t exist before (maybe my google-fu is failing me) but on the other hand it’s less than 60 lines of code - hardly an expensive wheel to reinvent.
You can use it like:
$ # step 1: start your server
$ huppy run-my-server
$ # step 2: use vim-watch to reload the server on changes
$ vim-watch killall -HUP huppy
$ # Or, if you need to rebuild stuff before restarting the server,
$ vim-watch bash -c 'gup && killall -HUP huppy'