Readme
Rhask – Rhai-based Task Runner
Rhask is a lightweight task runner written in Rust. Tasks and groups are authored in Rhai , can be nested arbitrarily, and are invoked through fully qualified names such as group. task (or by short names when they are unique).
Quick Start (Install / Setup)
# Install from crates.io
cargo install rhask
# List tasks at the project root
rhask list
# Run a task
rhask run <task>
Example rhaskfile. rhai :
task ( " hello" , | | {
actions ( | | {
print ( " Hello from Rhask!" ) ;
} ) ;
} ) ;
Place rhaskfile. rhai at the repository root (copy rhaskfile_demo. rhai or rhaskfile_sample. rhai to get started).
Use - f/ - - file to point Rhask at any Rhai script, e.g. rhask - f . / rhaskfile_demo. rhai list .
rhask run < task> and rhask < task> behave the same; ambiguous short names print the candidates.
Declare default_task ( " group.task" ) so rhask (with no arguments) runs that entry, otherwise it falls back to listing tasks.
Features
1. File loading & project root discovery
The configuration file is rhaskfile. rhai .
Rhask searches the current directory and walks up the parents, loading only the first file it finds .
Override the search path with - f / --file to load any other Rhai script.
2. Declaring and managing tasks/groups
Use task ( " name" , | | { ... } ) / group ( " name" , | | { ... } ) to build nested hierarchies.
Nest as deeply as you like; there is no limit on groups or tasks.
Execute entries via fully qualified names such as group. subgroup. task .
When a short name is unique you can omit the prefix; conflicts print a candidate list and require a fully qualified retry.
3. Listing tasks (tree vs. flat)
You can scope the listing to a subtree (e.g. rhask list deploy - F ).
Tree view
rhask list
Shows the hierarchy as an indented tree.
description ( ) text is aligned on the right-hand side.
Flat view
rhask list - - flat / rhask list - F
Prints each task as full. path description on a single line (colorized on TTYs).
4. Execution rules (run / default tasks / ambiguity)
Execute tasks via rhask run < task> or the shorthand rhask < task> .
Running bare rhask behaves as follows:
Execute the task registered via default_task ( " ..." ) when present.
Otherwise fall back to rhask list .
Ambiguous < task > names print candidates and exit (Rhask will not guess). Re-run with the full path.
args ( # { key: default, ... } ) declares CLI parameters; ( ) marks them as required.
CLI values may be passed as positional arguments, key= value , --key=value , or --key value , and you can mix the styles.
5. Working directories with dir ( )
Call dir ( " path" ) once per task to pin its working directory.
Relative paths are resolved from the directory that hosts rhaskfile. rhai ; absolute paths are left unchanged.
Paths are validated at load time—nonexistent or non-directory paths raise an error.
When a task trigger ( ) s another task, the callee’s own dir ( ) always wins; parent settings are never inherited.
Without dir ( ) the task runs in the shell directory where you launched rhask .
6. Shell-free external commands (cmd / pipe / exec )
1. cmd ( )
cmd ( [ " git" , " status" ] )
Describes a command as an array, avoiding shell quoting issues.
2. pipe ( )
cmd ( [ " git" , " branch" , " -vv" ] )
. pipe ( cmd ( [ " grep" , " gone" ] ) )
Chains any number of stages; each becomes a native process.
Lets you express git | grep | awk -style flows without shell syntax.
3. build ( )
Finalizes the pipeline before execution.
Tweak behavior via . timeout ( ms) , . env ( # { } ) , . allow_exit_codes ( [ 0 , 1 ] ) , and similar helpers.
4. exec ( )
Mirrors stdout/stderr to the console and returns # { success, status, stdout, stderr, duration_ms } .
Throws when the exit code is not allowed.
5. exec_stream ( )
Streaming-friendly variant that lets you process stdout/stderr via callbacks (omit them to stream directly to the terminal).
7. Other utilities
rhask completions < shell> generates Bash/Zsh/Fish completion scripts (task names included).
The repo bundles scripts/coverage.sh as a helper around cargo llvm-cov .
Rhai import statements work as in upstream Rhai, so you can split large task files as needed.
Usage
CLI Commands
Command
Description
rhask list [ group]
Display registered tasks/groups as a tree. Passing a fully qualified name limits the output to that subtree.
rhask list - - flat / rhask list - F
Print each task as full. path plus an aligned description (colorized on TTYs, works with group filters and tools like fzf ).
rhask run < task> [ args…]
Execute a task. Ambiguous leaves print the candidates and ask you to re-run with a full path. The shorthand rhask < task> behaves the same.
rhask - f < file> …
Explicitly load a Rhai script. Place - f/ - - file before the subcommand or task name (e.g. rhask - f . / demo. rhai list ).
rhask (no arguments)
Run the configured default_task ( ) or fall back to rhask list when unset.
rhask completions < shell>
Emit shell completion scripts (see below).
Passing Arguments
Declaring args ( # { target: ( ) , profile: " debug" } ) assigns positional arguments in lexicographic key order (profile → target in this example). ( ) marks required parameters. Use whichever CLI style you prefer:
Positional: rhask run build release x86_64- unknown- linux- gnu
key= value : rhask run build profile= release target= x86_64- unknown- linux- gnu
--key=value : rhask run build - - target= x86_64- apple- darwin
--key value : rhask run build - - target wasm32- unknown- unknown
Mix and match as needed
Unknown keys raise an error, and missing required values trigger a descriptive failure.
Task Definition Example
task ( " build" , | | {
description ( " Build the project" ) ;
args ( # {
target: ( ) ,
profile: " debug"
} ) ;
actions ( | profile , target | {
print ( " build => profile:" + profile + " , target:" + target) ;
} ) ;
} ) ;
group ( " release_flow" , | | {
description ( " Release tasks" ) ;
task ( " package" , | | {
actions ( | | {
exec ( cmd ( [ " cargo" , " package" ] ) . build ( ) ) ;
} ) ;
} ) ;
} ) ;
Helpers Available from Rhai
Function
Description
task ( name, | | { ... } )
Declare a task; call description /actions /args inside it.
group ( name, | | { ... } )
Declare a group; nest additional groups or tasks.
description ( text)
Usable inside task ( ) /group ( ) ; sets the label shown in listings (call once per task).
actions ( | | { ... } )
Usable inside task ( ) ; registers the executable closure (call once). Invoke trigger ( ) or exec ( ... ) from here.
args ( # { key1: default1, key2: ( ) , ... } )
Usable inside task ( ) ; declares CLI parameters. ( ) = no default = required. Call once per task.
dir ( path)
Usable inside task ( ) ; pins the working directory (call once). Relative paths resolve from the rhaskfile directory; absolute paths stay as-is. Invalid paths error at load time.
default_task ( " full.path" )
Declare once at the top level (imports included) to define the fallback when rhask is run without arguments.
trigger ( name, positional? , named? )
Usable inside actions ( ) ; runs another task. Accepts positional arrays and/or named maps. The callee’s dir ( ) takes precedence over the caller’s.
cmd ( [ cmd, arg, ... ] )
Build external commands inside actions ( ) . Chain . env ( ) / . pipe ( ) and finish with . build ( ) before running exec ( ... ) or exec_stream ( ... ) .
exec ( pipeline) / exec_stream ( pipeline, stdout_cb? , stderr_cb? )
Usable inside actions ( ) ; execute pipelines and receive # { success, status, stdout, stderr, duration_ms } . exec_stream lets you process output live.
Pinning the working directory with dir ( )
dir ( path) is allowed once per task ( ) . Absolute paths remain untouched; relative paths are resolved against the directory that hosts the loaded rhaskfile (the one Rhask found or the file passed via - f/ - - file).
Nonexistent paths or non-directories abort loading with errors such as dir ( ) : ' ... ' is not a directory.
When dir ( ) is present, Rhask chdir s into that location before executing actions ( ) . Nested exec ( ) /trigger ( ) calls always honor the callee’s dir ( ) rather than inheriting from parents.
Tasks without dir ( ) run in the shell directory from which you launched rhask . Set dir ( " ." ) or dir ( " scripts" ) explicitly if you need predictability.
The resolution root is always the directory of the initially loaded rhaskfile. If you run a child script directly via rhask - f child/ file. rhai , relative paths will resolve from that child file instead, so plan accordingly.
task ( " coverage" , | | {
description ( " Run coverage helper script from scripts/" ) ;
dir ( " scripts" ) ;
actions ( | | {
exec ( cmd ( [ " ./coverage.sh" , " --mode" , " unit" ] ) . build ( ) ) ;
} ) ;
} ) ;
Running external commands (cmd / exec / exec_stream )
Describe the pipeline
Build it with cmd ( [ program, arg, ... ] ) , chain . pipe ( cmd ( [ ... ] ) ) , override the environment via . env ( # { KEY : " VALUE" } ) , and call . build ( ) . After build ( ) you can still attach . timeout ( ms) or . allow_exit_codes ( [ 0 , 1 ] ) .
Execute it
exec ( cmd ( ... ) . pipe ( ... ) . build ( ) ) returns # { success, status, stdout, stderr, duration_ms } , mirrors stdout/stderr to the terminal, and throws on disallowed exit codes.
exec_stream ( cmd ( ... ) . build ( ) , stdout_cb? , stderr_cb? ) suits streaming workloads. Omit the callbacks to stream directly to the console.
Run inside actions ( )
Pipelines can be assembled anywhere, but actually executing them is restricted to actions ( ) so dir ( ) semantics and nested trigger ( ) calls stay consistent.
actions ( | | {
let pipeline = cmd ( [ " git" , " branch" , " -vv" ] )
. pipe ( cmd ( [ " grep" , " gone]" ] ) )
. pipe ( cmd ( [ " awk" , " {print $1}" ] ) )
. build ( ) ;
let result = exec ( pipeline) ;
print ( " deleted branches:\n " + result. stdout) ;
} ) ;
Shell Completions
rhask completions bash > ~ /.local/share/bash-completion/rhask
echo " source ~/.local/share/bash-completion/rhask" >> ~ /.bashrc
Drop the generated file into the appropriate completion directory (Bash/Zsh/Fish) and source it. Task/group names defined in Rhai are part of the completion results. When you pass - f/ - - file, the completion function forwards that value so suggestions always match the referenced rhaskfile.
Logs & Output
User-facing information goes to stdout ; warnings and errors go to stderr .
Enable tracing with RUST_LOG = debug rhask run … (or trace ) thanks to the env_logger backend.
Calls such as trigger ( ) or exec ( cmd ( [ ... ] ) . build ( ) ) must happen inside actions ( ) ; doing so elsewhere raises errors.
Color output is enabled automatically on TTYs and disabled for redirected/CI environments.
Coverage
scripts/coverage.sh wraps cargo-llvm-cov / llvm-tools-preview , installing the tools on demand (requires rustup ).
./scripts/coverage.sh -- mode all (default):
run unit + integration tests and write target/coverage/html/index.html .
./scripts/coverage.sh -- mode unit:
run unit tests only and write target/coverage-unit/html/index.html .
./scripts/coverage.sh -- mode integration:
run tests/*.rs only and write target/coverage-integration/html/index.html .
Arguments after - - are forwarded directly to cargo llvm-cov . Use the same script from CI as well.
License
Dual-licensed under MIT OR Apache-2.0.