# IF Filesystem-event Then (IFFT) [![Latest Version]][crates.io] [![Build Status]][travis]
[Build Status]: https://api.travis-ci.com/braincore/ifft.svg?branch=master
[travis]: https://travis-ci.com/braincore/ifft
[Latest Version]: https://img.shields.io/crates/v/ifft.svg
[crates.io]: https://crates.io/crates/ifft
IF a filesystem event (create, write, remove, chmod) occurs in a watched folder
that is not filtered out by an exclusion rule THEN execute a shell command.
Use this to watch for code changes to trigger: process restart; code
compilation; or test run.
## Installation
If you have [rust installed](https://www.rust-lang.org/tools/install) on your
machine:
```
cargo install ifft
```
Otherwise, check [releases](https://github.com/braincore/ifft/releases) for
downloads.
## Usage
### Hello, world.
Create a config file (`ifft.toml`) in a directory (let's say `~/ifft-test`):
```toml
[[ifft]]
# Matches everything including sub-folders
if = "**/*"
then = "echo hello, world."
```
Run `ifft` with the directory containing your config as the argument:
`ifft ~/ifft-test`.
You'll see the following output, which indicates that `ifft` found your config
file:
```
Found config: "~/ifft-test/ifft.toml"
```
In later examples, we'll see that multiple config files can be embedded
throughout the directory tree.
Now let's create a file that will trigger `ifft`: `touch ~/ifft-test/test1`
You'll see the following output:
```
[2019-05-12 14:55:57Z] Event: Create("~/ifft-test/test1")
Match from config in: "~/ifft-test"
Matched if-cond: "**/*"
[2019-05-12 14:55:57Z] Execute: "echo hello, world." from "~/ifft-test"
Exit code: 0
Stdout:
hello, world.
Stderr:
```
As you can see, triggers report the match condition and the exit code, stdout,
and stderr of the triggered command.
That's it. `ifft` simply listens for file changes and takes action.
### Advanced
Here's a more complex `ifft` config that would be in a folder such as `~/src`
with sub-folders `my-c-prog` and `my-rust-prog`:
```toml
# Never trigger on a backup or swap file. (VIM specific)
not = [
"*~",
"*.swp",
]
[[ifft]]
# If any .c or .h files change, recompile.
if = "my-c-prog/**/*.{c,h}"
then = "make"
working_dir = "my-c-prog"
[[ifft]]
if = "my-rust-prog/**/*.{rs,toml}"
# Ignore changes in the target folder to avoid recursive triggering.
not = ["my-rust-prog/target/*"]
then = "cargo build"
working_dir = "my-rust-prog"
# Contrived example to demonstrate other features.
[[ifft]]
if = "*"
# {{}} is substituted with the absolute path to the triggering file.
then = "cp -R {{}} ."
# working_dir can be an absolute path. If omitted, the working_dir is set to
# root.
working_dir = "/tmp"
```
The second `ifft` condition could be moved into a new `ifft.toml` in the
`my-rust-prog` folder. For equivalent functionality, the contents would be:
```
[[ifft]]
if = "**/*.{rs,toml}"
not = ["target/*"]
then = "cargo build"
```
This allows you to distribute config files all over, which has the advantage
of keeping them small and relevant to the folder they're in.
### On start
If you want to automatically trigger iffts on start without any file event,
use the `-r` flag. The argument will trigger any iffts with matching names. For
example, running `ifft ~/ifft-test -r build` will match:
```toml
[[ifft]]
name = "build"
if = "**/*.{rs,toml}"
not = ["target/*"]
then = "cargo build"
```
This is useful to ensure that projects are built on boot without having to wait
for a file event.
You can also use the `-q` flag to quit after the `-r` flag triggers have
completed. This can be used to initiate a one-time build or clean without
listening for changes afterwards.
Another strategy is to omit the `if` condition which means it can only be
triggered on start with the `-r` flag.
## Features
* Configure with a `toml` file.
* Config files can be distributed throughout a directory tree.
* Use glob patterns for `if` and `not` conditions.
* Global `not` filtering and per-trigger `not` filtering.
* Multiple events that trigger the same `if` are buffered and only trigger one
`then` execution.
* On start, iffts with a matching name can be triggered without any file event.
* Events on paths with symlink components will also have their absolute-path
equivalent tested against triggers.
## Platforms
Tested on Linux and OS X. Untested elsewhere.
## Usage with VirtualBox Shared Folders
On the guest OS, VirtualBox Shared Folders do not generate filesystem event
notifications. You'll need to use a separate filesystem event forwarder such as
[notify-forwarder](https://github.com/mhallin/notify-forwarder).
## Alternatives
* [bazel](https://bazel.build/) for a serious incremental build system.
* [watchexec](https://github.com/watchexec/watchexec) is a more full-featured
program.
* [entr](http://eradman.com/entrproject/) has a clean Unixy interface.
## Todo
* [] Add `.gitignore` parsing support.
* [] Flag to ignore hidden files.
* [] Flag to control verbosity of prints.
* [] Group events in quick succession together and trigger only once.
* [] Allow customization of type of FS events that trigger.
* [] Low priority: Compute the optimal path prefix for watching.
* [] Performance: Do not compile glob before each use. Current hack to make it
easy to access the glob pattern string if an error occurs.