jiu 0.1.5

A minimal command runner.
Documentation
# jiu

[![GitHub License](https://img.shields.io/github/license/PRO-2684/jiu?logo=opensourceinitiative)](https://github.com/PRO-2684/jiu/blob/main/LICENSE)
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/PRO-2684/jiu/release.yml?logo=githubactions)](https://github.com/PRO-2684/jiu/blob/main/.github/workflows/release.yml)
[![GitHub Release](https://img.shields.io/github/v/release/PRO-2684/jiu?logo=githubactions)](https://github.com/PRO-2684/jiu/releases)
[![GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/PRO-2684/jiu/total?logo=github)](https://github.com/PRO-2684/jiu/releases)
[![Crates.io Version](https://img.shields.io/crates/v/jiu?logo=rust)](https://crates.io/crates/jiu)
[![Crates.io Total Downloads](https://img.shields.io/crates/d/jiu?logo=rust)](https://crates.io/crates/jiu)
[![docs.rs](https://img.shields.io/docsrs/jiu?logo=rust)](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>

[![asciicast](https://asciinema.org/a/bnuQc8QP9IcgUoAY2gytD7h5T.svg)](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