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
- go to browser
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'