# jiu
[](https://github.com/PRO-2684/jiu/blob/main/LICENSE)
[](https://github.com/PRO-2684/jiu/blob/main/.github/workflows/release.yml)
[](https://github.com/PRO-2684/jiu/releases)
[](https://github.com/PRO-2684/jiu/releases)
[](https://crates.io/crates/jiu)
[](https://crates.io/crates/jiu)
[](https://docs.rs/jiu)
A minimal command runner.
## 🤔 Comparison
This tool is heavily inspired by [`just`](https://github.com/casey/just/), but is fundamentally different. To summarize:
- Pro: It handles arguments correctly, and without any ambiguity
- `just` could cause argument splitting issues
- Although [there are workarounds](https://just.systems/man/en/avoiding-argument-splitting.html), corner cases still exist
- Pro: Is independent of shell
- Pro: Provides shell completion and delegation support (WIP)
- Con: But at the cost of much less customization and features
## 📥 Installation
### Using [`binstall`](https://github.com/cargo-bins/cargo-binstall)
```shell
cargo binstall jiu
```
### Downloading from Releases
Navigate to the [Releases page](https://github.com/PRO-2684/jiu/releases) and download respective binary for your platform. Make sure to give it execute permissions.
### Compiling from Source
```shell
cargo install jiu
```
## 💡 Examples
<details><summary>Demo asciicast</summary>
[](https://asciinema.org/a/bnuQc8QP9IcgUoAY2gytD7h5T)
</details>
See [`.jiu.toml`](./.jiu.toml) for a simple example used in this repository, ~~or the [`tests`](./tests) directory for more complex examples~~. Here's an example involving complex arguments:
```shell
jiu dummy 1 "2" '"3"' " 4" "" "5 6"
```
Which will invoke [`dummy.sh`](./scripts/dummy.sh), printing arguments it received:
```shell
TERM = xterm-256color
Arguments:
1
2
"3"
4
5 6
```
Note that the arguments are all handled correctly.
## 📖 Usage
### Configuration
The config file is a simple TOML file named `.jiu.toml`. The format is as follows:
```toml
description = "`jiu`: A minimal command runner." # Description of the configuration (Optional)
default = "run" # Default recipe to run when invoked without any arguments (Optional)
[[recipes]]
names = ["run", "r"] # Names of the recipe (Required)
description = "Compile and run" # Description of the recipe (Optional)
arguments = ["*rest"] # Arguments to the recipe (Optional)
command = ["cargo", "run", "--", ["*rest"]] # Command to run (Required)
# ...More recipes
```
#### Description
The `description` field is a string that describes the recipe or the entire configuration. It is optional, but it is a good practice to include it. The description will be displayed when listing recipes. To add some colors to the dull description, use ANSI escape codes like:
```toml
description = "\u001b[1;36mjiu\u001b[22;39m: A minimal command runner."
```
#### Default
The `default` field is a string that specifies the default recipe to run when no arguments are provided. It is optional, defaulting to empty string.
- If it is empty, `jiu` will list all recipes.
- The default recipe must be able to accept no arguments.
- If the default recipe is not found, an error will be returned.
#### Names
The `names` field is a list of names that the recipe can be called with. It should contain at least one name, otherwise the recipe will never be matched. Each name:
- Should be unique across all recipes, otherwise only the first one will be matched.
- Should not contain spaces.
- Should not start with special characters, especially `-`, which would be interpreted as an option.
- Should not be empty.
Where "should" means that it is a good practice to follow, but not explicitly enforced. For example, you can have a recipe with the name `my recipe`, but to call it you would have to escape the space or use quotes, which would be inconvenient.
#### Arguments
The `arguments` field is a list of arguments that the recipe accepts. It should be a list of strings, where each string represents an argument. An argument is made up of an optional leading symbol and a name.
##### Types
The type of the argument is determined by the leading symbol, which can be one of the following:
- `*`: A variadic argument. This means that the argument can accept zero or more values.
- `+`: A required variadic argument. This means that the argument must accept one or more values.
- `?`: An optional argument. This means that the argument can accept zero or one value.
If the leading symbol is omitted, the argument is treated as a required argument.
##### Greedy Matching
> [!NOTE]
> This behavior may be changed in the future.
The `*` and `+` arguments are greedy, meaning that they will consume all remaining arguments. For example, if you have a recipe with the following arguments:
```toml
arguments = ["*arg0", "*arg1"]
```
Then `*arg1` will always be empty, since `*arg0` will consume all remaining arguments. Also consider:
```toml
arguments = ["*arg0", "arg1"]
```
In this case, `*arg0` will consume all remaining arguments, leaving required argument `arg1` empty. So `jiu` will return an error, although the arguments can be interpreted without ambiguity.
Also be careful when working with optional arguments, since they share the same greedy behavior. For example:
```toml
arguments = ["?arg0", "arg1"]
```
When a single argument is passed, `?arg0` will consume it, leaving `arg1` empty. So this will also cause an error.
#### Command
The `command` field is a list representing the command to run, and is made up of strings and arrays of length 1. Each string is treated as a literal, while each array is treated as a placeholder.
The placeholders are interpolated with concrete values when the recipe is run. After interpolation, the command is executed in the directory of the config file.
A placeholder can be one of the following:
- `$VAR`: An environment variable. This will be replaced with the value of the environment variable `VAR`.
- If the variable is not set, an error will be returned.
- If the variable is empty, it will still be passed as an empty argument.
- Others: An argument. This will be replaced with the value of the argument. If the argument is variadic, it will be replaced with all values of the argument.
### Shell Completion
> [!NOTE]
> This is a WIP.
```bash
mkdir -p ~/.local/share/bash-completion/completions # Create the directory if it doesn't exist
echo 'source <(COMPLETE=bash jiu)' > ~/.local/share/bash-completion/completions/jiu
```
### Running
```shell
$ jiu -h
Usage: jiu [OPTION_OR_RECIPE] [ARGS]...
jiu: A minimal command runner.
Options:
-h, --help Show this help message
-v, --version Show version information
-l, --list List all available recipes
```
If no option or recipe is specified, `jiu` will run the default recipe, listing all recipes if not specified.
### Debugging
Run with environment variable `JIU_DEBUG` set to enable debug mode. In bash, you can do this with:
```shell
JIU_DEBUG=1 jiu [OPTION_OR_RECIPE] [ARGS]...
```
Which would provide additional information for debugging purposes.
## ✅ TODO
- `env` field on recipes and global
- Completion delegation
- Migrate to `clap` for parsing arguments and completion
- Completion blocked on [clap#5424](https://github.com/clap-rs/clap/issues/5424) and [clap#3166](https://github.com/clap-rs/clap/issues/3166)
- Set working directories
- Where the config file is located (default)
- Where the command is invoked
- Custom working directory, relative to the config file
## 🎉 Credits
- [`just`](https://github.com/casey/just/) - where the inspiration came from