Skip to content

Instantly share code, notes, and snippets.

@cespare
Last active August 29, 2015 14:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cespare/ad6c41aa28583cac8b2c to your computer and use it in GitHub Desktop.
Save cespare/ad6c41aa28583cac8b2c to your computer and use it in GitHub Desktop.
rendered markdown exmaple
<h1>Reflex</h1>
<p>Reflex is a small tool to watch a directory and rerun a command when certain files change. It's great for
automatically running compile/lint/test tasks and for reloading your application when the code changes.</p>
<h2>A simple example</h2>
<div class="highlight"><pre># Rerun make whenever a .c file changes
reflex -r &#39;\.c$&#39; make
</pre></div>
<h2>Installation</h2>
<div class="highlight"><pre>$ go get github.com/cespare/reflex
</pre></div>
<p>Note that this has some dependencies outside of the Go standard library. You'll need git installed, and <code>go
get</code> will automatically fetch them for you.</p>
<p>Reflex probably only works on Linux and Mac OS.</p>
<p>TODO: provide compiled downloads for linux/darwin amd64.</p>
<h2>Usage</h2>
<p>The following is given by running <code>reflex -h</code>:</p>
<div class="highlight"><pre>Usage: reflex [OPTIONS] [COMMAND]
COMMAND is any command you&#39;d like to run. Any instance of {} will be replaced
with the filename of the changed file. (The symbol may be changed with the
--substitute flag.)
OPTIONS are given below:
--all=false:
Include normally ignored files (VCS and editor special files).
-c, --config=&quot;&quot;:
A configuration file that describes how to run reflex
(or &#39;-&#39; to read the configuration from stdin).
-d, --decoration=&quot;plain&quot;:
How to decorate command output. Choices: none, plain, fancy.
-g, --glob=[]:
A shell glob expression to match filenames. (May be repeated.)
-G, --inverse-glob=[]:
A shell glob expression to exclude matching filenames.
(May be repeated.)
-R, --inverse-regex=[]:
A regular expression to exclude matching filenames.
(May be repeated.)
--only-dirs=false:
Only match directories (not files).
--only-files=false:
Only match files (not directories).
-r, --regex=[]:
A regular expression to match filenames. (May be repeated.)
-e, --sequential=false:
Don&#39;t run multiple commands at the same time.
-s, --start-service=false:
Indicates that the command is a long-running process to be
restarted on matching changes.
--substitute=&quot;{}&quot;:
The substitution symbol that is replaced with the filename
in a command.
-v, --verbose=false:
Verbose mode: print out more information about what reflex is doing.
Examples:
# Print each .txt file if it changes
$ reflex -r &#39;\.txt$&#39; echo {}
# Run &#39;make&#39; if any of the .c files in this directory change:
$ reflex -g &#39;*.c&#39; make
# Build and run a server; rebuild and restart when .java files change:
$ reflex -r &#39;\.java$&#39; -s -- sh -c &#39;make &amp;&amp; java bin/Server&#39;
</pre></div>
<h3>Overview</h3>
<p>Reflex watches file changes in the current working directory and re-runs the command that you specify. The
flags change what changes cause the command to be rerun and other behavior.</p>
<h3>Patterns</h3>
<p>You can specify files to match using either shell glob patterns (<code>-g</code>) or regular expressions (<code>-r</code>). If you
don't specify either, reflex will run your command after any file changes. (Reflex ignores some common editor
and version control files; see Ignored files, below.)</p>
<p>You can specify inverse matches by using the <code>--inverse-glob</code> (<code>-G</code>) and <code>--inverse-regex</code> (<code>-R</code>) flags.</p>
<p>If you specify multiple globs/regexes (e.g. <code>-r foo -r bar -R baz -G x/*/y</code>), only files that match all
patterns and none of the inverse patterns are selected.</p>
<p>The shell glob syntax is described <a href="http://golang.org/pkg/path/filepath/#Match">here</a>, while the regular
expression syntax is described <a href="https://code.google.com/p/re2/wiki/Syntax">here</a>.</p>
<p>The path that is matched against the glob or regular expression does not have a leading <code>./</code>. For example, if
there is a file <code>./foobar.txt</code> that changes, then it will be matched by the regular expression <code>^foobar</code>. If
the path is a directory, it has a trailing <code>/</code>.</p>
<h3>--start-service</h3>
<p>The <code>--start-service</code> flag (short version: <code>-s</code>) inverts the behavior of command running: it runs the command
when reflex starts and kills/restarts it each time files change. This is expected to be used with an
indefinitely-running command, such as a server. You can use this flag to relaunch the server when the code is
changed.</p>
<h3>Substitution</h3>
<p>Reflex provides a way for you to determine, inside your command, what file changed. This is via a substitution
symbol. The default is <code>{}</code>. Every instance of the substitution symbol inside your command is replaced by the
filename.</p>
<p>As a simple example, suppose you're writing Coffeescript and you wish to compile the CS files to Javascript
when they change. You can do this with:</p>
<div class="highlight"><pre>$ reflex -r &#39;\.coffee$&#39; -- coffee -c {}
</pre></div>
<p>In case you need to use <code>{}</code> for something else in your command, you can change the substitution symbol with
the <code>--substitute</code> flag.</p>
<h3>Configuration file</h3>
<p>What if you want to run many watches at once? For example, when writing web applications I often want to
rebuild/rerun the server when my code changes, but also build SCSS and Coffeescript when those change as well.
Instead of running multiple reflex instances, which is cumbersome (and inefficient), you can give reflex a
configuration file.</p>
<p>The configuration file syntax is simple: each line is a command, and each command is composed of flags and
arguments -- just like calling reflex but without the initial <code>reflex</code>. Lines that start with <code>#</code> are ignored.
Here's an example:</p>
<div class="highlight"><pre># Rebuild SCSS when it changes
-r &#39;\.scss$&#39; -- sh -c &#39;sass {} `basename {} .scss`.css&#39;
# Restart server when ruby code changes
-sr &#39;\.rb$&#39; -- ./bin/run_server.sh
</pre></div>
<p>If you want to change the configuration file and have reflex reload it on the fly, you can run reflex inside
reflex:</p>
<div class="highlight"><pre>reflex -s -g reflex.conf -- reflex -c reflex.conf
</pre></div>
<p>This tells reflex to run another reflex process as a service that's restarted whenever <code>reflex.conf</code> changes.</p>
<h3>--sequential</h3>
<p>When using a config file to run multiple simultaneous commands, reflex will run them at the same time (if
appropriate). That is, a particular command can only be run once a previous run of that command finishes, but
two different commands may run at the same time. This is usually what you want (for speed).</p>
<p>As a concrete example, consider this config file:</p>
<div class="highlight"><pre>-- sh -c &#39;for i in `seq 1 5`; do sleep 0.1; echo first; done&#39;
-- sh -c &#39;for i in `seq 1 5`; do sleep 0.1; echo second; done&#39;
</pre></div>
<p>When this runs, you'll see something like this:</p>
<div class="highlight"><pre>[01] second
[00] first
[01] second
[00] first
[00] first
[01] second
[01] second
[00] first
[01] second
[00] first
</pre></div>
<p>Note that the output is interleaved. (Reflex does ensure that each line of output is not interleaved with a
different line.) If, for some reason, you need to ensure that your commands don't run at the same time, you
can do this with the <code>--sequential</code> (<code>-e</code>) flag. Then the output would look like (for example):</p>
<div class="highlight"><pre>[01] second
[01] second
[01] second
[01] second
[01] second
[00] first
[00] first
[00] first
[00] first
[00] first
</pre></div>
<h3>Decoration</h3>
<p>By default, each line of output from your command is prefixed with something like <code>[00]</code>, which is simply an
id that reflex assigns to each command. You can use <code>--decoration</code> (<code>-d</code>) to change this output:
<code>--decoration=none</code> will print the output as is; <code>--decoration=fancy</code> will color each line differently
depending on which command it is, making it easier to distinguish the output.</p>
<h3>Ignored files</h3>
<p>Reflex ignores a variety of version control and editor metadata files by default. If you wish for these to be
included, you can provide reflex with the <code>--all</code> flag.</p>
<p>You can see a list of regular expressions that match the files that reflex ignores by default
<a href="https://github.com/cespare/reflex/blob/master/defaultexclude.go#L5">here</a>.</p>
<h2>Notes and Tips</h2>
<p>If you don't use <code>-r</code> or <code>-g</code>, reflex will match every file.</p>
<p>Reflex only considers file creation and modification changes. It does not report attribute changes nor
deletions.</p>
<p>For ignoring directories, it's easiest to use a regular expression: <code>-R '^dir/'</code>.</p>
<p>Many regex characters are interpreted specially by various shells. You'll generally want to minimize this
effect by putting the regex in single quotes.</p>
<p>If your command has options, you'll probably need to use <code>--</code> to separate the reflex flags from your command
flags. For example: <code>reflex -r '.*\.txt' -- ls -l</code>.</p>
<p>If you're going to use shell things, you need to invoke a shell as a parent process:</p>
<div class="highlight"><pre>reflex -- sh -c &#39;sleep 1 &amp;&amp; echo {}&#39;
</pre></div>
<p>If your command is running with sudo, you'll need a passwordless sudo, because you cannot enter your password
in through reflex.</p>
<p>It's not difficult to accidentally make an infinite loop with certain commands. For example, consider this
command: <code>reflex -r '\.txt' cp {} {}.bak</code>. If <code>foo.txt</code> changes, then this will create <code>foo.txt.bak</code>,
<code>foo.txt.bak.bak</code>, and so forth, because the regex <code>\.txt</code> matches each file. Reflex doesn't have any kind of
infinite loop detection, so be careful with commands like <code>cp</code>.</p>
<p>The restart behavior works as follows: if your program is still running, reflex sends it SIGINT; after 1
second if it's still alive, it gets SIGKILL. The new process won't be started up until the old process is
dead.</p>
<h3>Batching</h3>
<p>Part of what reflex does is apply some heuristics to batch together file changes. There are many reasons that
files change on disk, and these changes frequently come in large bursts. For instance, when you save a file in
your editor, it probably makes a tempfile and then copies it over the target, leading to several different
changes. Reflex hides this from you by batching some changes together.</p>
<p>One thing to note, though, is that the the batching is a little different depending on whether or not you have
a substitution symbol in your command. If you do not, then updates for different files that all match your
pattern can be batched together in a single update that only causes your command to be run once.</p>
<p>If you are using a substitution symbol, however, each unique matching file will be batched separately.</p>
<h3>Argument list splitting</h3>
<p>When you give reflex a command from the commandline (i.e., not in a config file), that command is split into
pieces by whatever shell you happen to be using. When reflex parses the config file, however, it must do that
splitting itself. For this purpose, it uses <a href="https://github.com/kballard/go-shellquote">this library</a> which
attempts to match <code>sh</code>'s argument splitting rules.</p>
<p>This difference can lead to slightly different behavior when running commands from a config file. If you're
confused, it can help to use <code>--verbose</code> (<code>-v</code>) which will print out each command as interpreted by reflex.</p>
<h3>Open file limits</h3>
<p>Reflex currently must hold an open file descriptor for every directory it's watching, recursively. If you run
reflex at the top of a big directory tree, you can easily run into file descriptor limits. You might see an
error like this:</p>
<div class="highlight"><pre>open some/path: too many open files
</pre></div>
<p>There are several things you can do to get around this problem.</p>
<ol>
<li>Run reflex in the most specific directory possible. Don't run <code>reflex -g path/to/project/*.c ...</code> from
<code>$HOME</code>; instead run reflex in <code>path/to/project</code>.</li>
<li>Ignore large subdirectories. Reflex already ignores, for instance, <code>.git/</code>. If you have other large
subdirectories, you can ignore those yourself: <code>reflex -R '^third_party/' ...</code> ignores everything under
<code>third_party/</code> in your project directory.</li>
<li>Raise the fd limit using <code>ulimit</code> or some other tool. On some systems, this might default to a
restrictively small value like 256.</li>
</ol>
<p>See <a href="https://github.com/cespare/reflex/issues/6">issue #6</a> for some more background on this issue.</p>
<h2>The competition</h2>
<ul>
<li><a href="https://github.com/guard/guard">https://github.com/guard/guard</a></li>
<li><a href="https://github.com/alexch/rerun">https://github.com/alexch/rerun</a></li>
<li><a href="https://github.com/mynyml/watchr">https://github.com/mynyml/watchr</a></li>
<li><a href="https://github.com/eaburns/Watch">https://github.com/eaburns/Watch</a></li>
<li><a href="https://github.com/alloy/kicker">https://github.com/alloy/kicker</a></li>
</ul>
<h3>Why you should use reflex instead</h3>
<ul>
<li>Reflex has no dependencies. No need to install Ruby or anything like that.</li>
<li>Reflex uses an appropriate file watching mechanism to watch for changes efficiently on your platform.</li>
<li>Reflex gives your command the name of the file that changed.</li>
<li>No DSL to learn -- just give it a shell command.</li>
<li>No plugins.</li>
<li>Not tied to any language, framework, workflow, or editor.</li>
</ul>
<h2>Authors</h2>
<ul>
<li>Caleb Spare (<a href="https://github.com/cespare">cespare</a>)</li>
<li>Rich Liebling (<a href="https://github.com/rliebling">rliebling</a>)</li>
<li>Seth W. Klein (<a href="https://github.com/sethwklein">sethwklein</a>)</li>
<li>Vincent Vanackere (<a href="https://github.com/vanackere">vanackere</a>)</li>
</ul>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment