Let somebody else run shell commands for you.
shell_grunt2 watches a directory tree and runs shell commands whenever a file
changes. Which commands run for which files is described in a Lua file, so the
rules can be as simple or as clever as you like.
$ shell_grunt2 # watches '.' with tasks from ./watcher.lua
$ shell_grunt2 -f build/tasks.lua # a different task file
$ shell_grunt2 -d src # watch only a subdirectory
$ shell_grunt2 --check # validate the task file and print the tasks
$ shell_grunt2 --run-at-startup # run everything once, without waiting for a changeThe task file is watched too: save it and the tasks are reloaded in place. If it does not compile, the error is printed and the watcher keeps running, so the next save picks it up.
Only one watcher runs per task file. The lock lives in the temp directory and records the owning pid, so a watcher that was killed does not block the next one.
The file must return a list of tasks. A minimal one:
return {
{
command = "cargo test",
},
}A task with everything spelled out:
return {
{
-- Shown in messages. Optional; defaults to "task #1".
name = "rust",
-- Optional. Called with the path that changed, relative to the watched
-- tree; the task runs when it returns anything but false or nil. Without
-- it, every change runs the task.
should_run = function(path)
if path:starts_with("target/") then
return false
end
return path:ext() == "rs" or path:ext() == "toml"
end,
-- Wait this many milliseconds after the last change before starting, so a
-- "save all" results in one run. Optional, defaults to 50.
start_delay = 50,
-- Clear the screen before running. Optional, defaults to true.
clear_screen = true,
-- Send output to these files, with colors removed. Optional.
redirect_stdout = "/tmp/cargo.out",
redirect_stderr = "/tmp/cargo.err",
-- Keep output off the terminal. Optional, both default to false.
suppress_stdout = false,
suppress_stderr = false,
-- Extra environment for the commands. Optional.
environment = { RUST_BACKTRACE = "1" },
-- Run in order, stop at the first failure.
commands = {
{ name = "Check", command = "cargo check --color always" },
{ name = "Test", command = "cargo test --color always" },
},
},
}commands accepts several shapes, pick whichever is shortest for the job:
command = "cargo check" -- one command
commands = { "cargo check", "cargo test" } -- a chain
commands = { name = "Check", command = "cargo check" } -- one, with a name
commands = { -- a named chain
{ name = "Check", command = "cargo check" },
{ name = "Test", command = "cargo test" },
}A command table understands:
| Key | Meaning |
|---|---|
command |
The command line. Required. Quoting works as in a shell: git commit -m 'a message'. |
name |
Shown while running. Defaults to the command line. |
work_directory |
Run the command here instead of in the current directory. |
shell |
Run through $SHELL -c, so pipes, &&, globs and redirections work. Defaults to false. |
Commands run in the order given and the chain stops at the first failure. A command that cannot be started at all - a typo in the binary name, for instance - counts as a failure but does not stop the watcher.
should_run is called with the path relative to the watched tree, so a change to
/home/me/project/src/main.rs under shell_grunt2 -d /home/me/project arrives as
src/main.rs. Anchored matches like path:starts_with("target/") therefore do what
they look like they do. Files outside the watched tree - the task file, if you keep
it elsewhere - keep their absolute path.
These are added to Lua's string table, so they work as methods on any string:
| Helper | "src/bin/main.rs" gives |
|---|---|
path:ext() |
"rs" (nil if there is no extension) |
path:basename() |
"main.rs" |
path:dirname() |
"src/bin" |
path:stem() |
"main" |
path:starts_with("src/") |
true |
path:ends_with(".rs") |
true |
Everything else Lua offers works too, for example path:match("^src/") or
path:find("test", 1, true).
$ cargo build --release
$ cargo testLua is compiled in, there is nothing to install alongside it. Unix only - the tool
signals child processes and shells out through $SHELL.