Caution
Pre-alpha
Voy is a lightweight, embeddable filesystem event pipeline and multi-route orchestrator. Similar to tools like Watchman, Watchexec, and entr, Voy watches files for events and optionally executes commands in response to those events. Where Voy shines is:
- Small footprint
- Fully embeddable
- Foreground or background (daemon) command execution
- Multi-route orchestrator for more complex needs
- Process group isolation
| Feature | Voy | Watchman | Watchexec | entr |
|---|---|---|---|---|
| Embeddable | Yes | No | Yes | No |
| Foreground Execution | Yes | No | Yes | Yes |
| Background Execution1 | Yes | Yes | No | No |
| Multi-Route Orchestration | Yes | Yes | No | No |
| Built-in Globbing | Yes | Yes | Yes | No |
| Environment Variable Injection | Yes | Yes | Yes | No |
| Recursive Traversal | Yes | Yes | Yes | No |
| Dynamic Directory Watching | Yes | Yes | Yes | No |
| Multi-OS Support | No | Yes | Yes | Yes |
| Naive Polling (Fallback) | No | Yes | Yes | Yes |
Voy requires a C++23 compliant compiler (e.g., GCC 14+ or Clang 18+). To build and install:
git clone https://github.com/mharrisb1/voy.git
cd voy
make installTo use Voy from the command line, create a .voy.json (or .yaml) configuration file in the root of your project. This file defines the routes and commands you want to execute when files change.
{
"debounce_ms": 150,
"routes": [
{
"name": "compile_project",
"watch": ["src/**/*.cpp", "include/**/*.hpp"],
"ignore": ["build/**"],
"events": ["modify", "create", "delete"],
"action": {
"command": ["make", "build"],
"workdir": ".",
"grace_period_ms": 2000,
"env": {
"BUILD_ENV": "development"
}
}
}
]
}Note on Globs: Voy uses a lightweight, custom string matching engine that follows
.gitignoresemantics.Supported:
**: Recursive directory matching (e.g.,src/**/*.cpp)*: Any sequence of characters within a single directory (e.g.,src/*.cpp)?: Any single character (e.g.,test_?.cpp)[...]: Bracket character classes, including ranges and negation (e.g.,[a-z]*.cpp,[!0-9]*)- Leading
/: Anchor the match to the root of the project (e.g.,/build/)- Trailing
/: Match directories only (e.g.,build/)Not Supported (Escaped as literals):
- Brace expansion (e.g.,
*.{cpp,hpp}) - you must specify these as separate watch rules.- Extended regex syntax (e.g.,
+,(),|).
Start the file watcher in the foreground. By default, it looks for a .voy.json file in the current directory:
voy watchYou can also specify a custom configuration file path using the --config flag:
voy -c configs/voy.json watchHelp (voy -h):
Usage: voy [OPTIONS] <COMMAND>
Naughty little file watcher
Commands:
watch Start the event loop in the foreground
Options:
-h, --help Print this help and exit
-c, --config <val> Path to the config file (default: .voy.json)
-f, --format <val> Config format (json, yaml) (default: json)
-r, --rootdir <val> Root directory for watcher (default: .)
--no-vcs-ignore Don't load .gitignore
--no-project-ignore Don't load .ignore
The core of Voy is libvoy which is a zero-dependency embeddable filesystem event pipeline and multi-route orchestrator. With libvoy you can create your own file watching systems by leveraging all of the components that make Voy work.
Using libvoy over other options like chokidar or watchdog allows you to use a file watcher utility where interpreted languages, larger memory overhead, and garbage collection are prohibitively expensive. It also shines when integrated in existing C++ pipelines.
#include <chrono>
#include <vector>
#include <voy/voy.h>
namespace Renderer {
void compile_and_inject_shader(const std::string& shader_path) {...}
}
int main() {
auto engine = voy::Engine::builder()
.with_debounce_window(std::chrono::milliseconds(10))
.on_stdout([](std::string_view chunk) {
std::cout << chunk;
})
.on_stderr([](std::string_view chunk) {
std::cerr << "\033[31m[voy error]\033[0m " << chunk;
})
.add_route(voy::Route::builder("shader_hot_reload")
.watch("assets/shaders/**/*.frag")
.watch("assets/shaders/**/*.vert")
.on_events(voy::EventType::Modify)
.with_callback([](const std::vector<voy::Event>& events) {
for (const auto& evt : events) {
Renderer::compile_and_inject_shader(evt.path.string());
}
})
.build())
.build();
if (engine) {
engine->run();
}
}Warning
Volatile before stable release
For each process created by Voy, the following environment variables are passed in:
| Name | Description |
|---|---|
VOY_ROUTE_NAME |
Optional name of route that handled the event |
VOY_BATCH_SIZE |
Number of unique events collected in debounce window |
VOY_EVENT_TYPE |
Type of event |
VOY_EVENT_TIME |
Original timestamp of the event (ISO) |
VOY_EVENT_PATH |
Absolute path of file or subdirectory |
If more than one event is observed and handled within the debounce window then VOY_EVENT_PATH will be a delimited list of paths (the system's path separator, :, will be used). The VOY_BATCH_SIZE will let you know how many events were in the batch. The event types will also be OR'd so that if one path was associated with a modify event and another was associated with created then the reported VOY_EVENT_TYPE will be modify|created.