I wrote a Python program to do exactly this, called
UPDATE: This answer is not remotely as good as my other answer, search this page for 'rerun2'.
pip install rerun
and usage is very simple:
The command is expected as a single arg, not a sequence of space-separated args. Hence quote it as shown, which reduces any extra escaping you'd have to add. Just type the command as you would have typed it at the command line, but surrounded by quotes.
By default it watches all files in or under the current directory, skipping things like known source control dirs, .git, .svn, etc.
Optional flags include '-i NAME' which ignores changes to named files or directories. This can be given multiple times.
Since it's a Python script, it needs to run the command as a sub-process, and we use a new instance of the user's current shell to interpret 'COMMAND' and decide what process to actually run. However, if your command contains shell aliases and the like which are defined in .bashrc, these will not be loaded by the subshell. To fix this, you can give rerun a '-I' flag, to use interactive (aka 'login') subshells. This is slower and more error-prone than starting a regular shell, because it has to source your .bashrc.
I use it with Python 3, but last I checked rerun still worked with Python 2.
Double-edged sword is that it uses polling instead of inotify. On the upside, this means it works on every OS. Plus, it's better than some other solutions shown here in terms of only running the given command once for a bunch of filesystem changes, not once per modified file, while at the same time it does run the command a second time if any files change again while command is running.
On the downside, polling means that there is a 0.0 to 1.0 second latency, and of course it's slow to monitor extremely large directories. Having said that, I've never encountered a project large enough that this is even noticeable so long as you use '-i' to ignore big things like your virtualenv and node_modules.
rerun has been indispensible to me for years - I basically use it eight hours every day for running tests, rebuilding dot files as I edit them, etc. But now I come to type this up here, it's clear that I need to switch to a solution that uses inotify (I no longer use Windows or OSX.) and is written in Bash (so it works with aliases without any extra fiddling.)