Compare commits

..

No commits in common. "f5bb82dea3edde07b5aca4303e2d73a300f45d5e" and "2bacbddadb781600f3b7b7a1c202c66c3ae6df3c" have entirely different histories.

33 changed files with 299 additions and 977 deletions

View File

@ -32,7 +32,7 @@ jobs:
- name: Completion Scripts - name: Completion Scripts
run: | run: |
./bin/update-completions ./bin/generate-completions
git diff --no-ext-diff --quiet --exit-code git diff --no-ext-diff --quiet --exit-code
./tests/completions/just.bash ./tests/completions/just.bash

View File

@ -20,7 +20,6 @@ jobs:
- armv7-unknown-linux-musleabihf - armv7-unknown-linux-musleabihf
- x86_64-apple-darwin - x86_64-apple-darwin
- x86_64-pc-windows-msvc - x86_64-pc-windows-msvc
- aarch64-pc-windows-msvc
- x86_64-unknown-linux-musl - x86_64-unknown-linux-musl
include: include:
- target: aarch64-apple-darwin - target: aarch64-apple-darwin
@ -40,8 +39,6 @@ jobs:
target_rustflags: '' target_rustflags: ''
- target: x86_64-pc-windows-msvc - target: x86_64-pc-windows-msvc
os: windows-latest os: windows-latest
- target: aarch64-pc-windows-msvc
os: windows-latest
target_rustflags: '' target_rustflags: ''
- target: x86_64-unknown-linux-musl - target: x86_64-unknown-linux-musl
os: ubuntu-latest os: ubuntu-latest
@ -64,11 +61,6 @@ jobs:
sudo apt-get update sudo apt-get update
sudo apt-get install gcc-arm-linux-gnueabihf sudo apt-get install gcc-arm-linux-gnueabihf
- name: Install AArch64 Toolchain (Windows)
if: ${{ matrix.target == 'aarch64-pc-windows-msvc' }}
run: |
rustup target add aarch64-pc-windows-msvc
- name: Ref Type - name: Ref Type
id: ref-type id: ref-type
run: cargo run --package ref-type -- --reference ${{ github.ref }} >> $GITHUB_OUTPUT run: cargo run --package ref-type -- --reference ${{ github.ref }} >> $GITHUB_OUTPUT

158
Cargo.lock generated
View File

@ -11,21 +11,6 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "ansi_term" name = "ansi_term"
version = "0.12.1" version = "0.12.1"
@ -107,12 +92,6 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "autocfg"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.3.2" version = "1.3.2"
@ -160,12 +139,6 @@ dependencies = [
"regex-automata 0.1.10", "regex-automata 0.1.10",
] ]
[[package]]
name = "bumpalo"
version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]] [[package]]
name = "camino" name = "camino"
version = "1.1.7" version = "1.1.7"
@ -190,20 +163,6 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
[[package]]
name = "chrono"
version = "0.4.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"wasm-bindgen",
"windows-targets 0.52.5",
]
[[package]] [[package]]
name = "clap" name = "clap"
version = "2.34.0" version = "2.34.0"
@ -278,12 +237,6 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2"
[[package]]
name = "core-foundation-sys"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
[[package]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.12" version = "0.2.12"
@ -547,29 +500,6 @@ version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "iana-time-zone"
version = "0.1.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]] [[package]]
name = "is_terminal_polyfill" name = "is_terminal_polyfill"
version = "1.70.0" version = "1.70.0"
@ -582,15 +512,6 @@ version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "js-sys"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
dependencies = [
"wasm-bindgen",
]
[[package]] [[package]]
name = "just" name = "just"
version = "1.27.0" version = "1.27.0"
@ -598,7 +519,6 @@ dependencies = [
"ansi_term", "ansi_term",
"blake3", "blake3",
"camino", "camino",
"chrono",
"clap 4.5.4", "clap 4.5.4",
"clap_complete", "clap_complete",
"clap_mangen", "clap_mangen",
@ -703,15 +623,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]] [[package]]
name = "num_cpus" name = "num_cpus"
version = "1.16.0" version = "1.16.0"
@ -722,12 +633,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "once_cell"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]] [[package]]
name = "option-ext" name = "option-ext"
version = "0.2.0" version = "0.2.0"
@ -1268,60 +1173,6 @@ version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.66",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.66",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
[[package]] [[package]]
name = "which" name = "which"
version = "6.0.1" version = "6.0.1"
@ -1356,15 +1207,6 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-core"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [
"windows-targets 0.52.5",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.48.0" version = "0.48.0"

View File

@ -21,7 +21,6 @@ members = [".", "crates/*"]
ansi_term = "0.12.0" ansi_term = "0.12.0"
blake3 = { version = "1.5.0", features = ["rayon", "mmap"] } blake3 = { version = "1.5.0", features = ["rayon", "mmap"] }
camino = "1.0.4" camino = "1.0.4"
chrono = "0.4.38"
clap = { version = "4.0.0", features = ["env", "wrap_help"] } clap = { version = "4.0.0", features = ["env", "wrap_help"] }
clap_complete = "4.0.0" clap_complete = "4.0.0"
clap_mangen = "0.2.20" clap_mangen = "0.2.20"

145
README.md
View File

@ -656,24 +656,6 @@ Available recipes:
lint lint
``` ```
Recipes in submodules can be listed with `just --list PATH`, where `PATH` is a
space- or `::`-separated module path:
```
$ cat justfile
mod foo
$ cat foo.just
mod bar
$ cat bar.just
baz:
$ just --unstable foo bar
Available recipes:
baz
$ just --unstable foo::bar
Available recipes:
baz
```
`just --summary` is more concise: `just --summary` is more concise:
```sh ```sh
@ -811,7 +793,7 @@ foo:
| `allow-duplicate-variables` | boolean | `false` | Allow variables appearing later in a `justfile` to override earlier variables with the same name. | | `allow-duplicate-variables` | boolean | `false` | Allow variables appearing later in a `justfile` to override earlier variables with the same name. |
| `dotenv-filename` | string | - | Load a `.env` file with a custom name, if present. | | `dotenv-filename` | string | - | Load a `.env` file with a custom name, if present. |
| `dotenv-load` | boolean | `false` | Load a `.env` file, if present. | | `dotenv-load` | boolean | `false` | Load a `.env` file, if present. |
| `dotenv-path` | string | - | Load a `.env` file from a custom path and error if not present. Overrides `dotenv-filename`. | | `dotenv-path` | string | - | Load a `.env` file from a custom path, if present. Overrides `dotenv-filename`. |
| `export` | boolean | `false` | Export all variables as environment variables. | | `export` | boolean | `false` | Export all variables as environment variables. |
| `fallback` | boolean | `false` | Search `justfile` in parent directory if the first recipe on the command line is not found. | | `fallback` | boolean | `false` | Search `justfile` in parent directory if the first recipe on the command line is not found. |
| `ignore-comments` | boolean | `false` | Ignore recipe lines beginning with `#`. | | `ignore-comments` | boolean | `false` | Ignore recipe lines beginning with `#`. |
@ -880,9 +862,7 @@ bar
If `dotenv-load`, `dotenv-filename` or `dotenv-path` is set, `just` will load If `dotenv-load`, `dotenv-filename` or `dotenv-path` is set, `just` will load
environment variables from a file. environment variables from a file.
If `dotenv-path` is set, `just` will look for a file at the given path. It is If `dotenv-path` is set, `just` will look for a file at the given path.
an error if a dotenv file is not found at `dotenv-path`, but not an error if a
dotenv file is not found with `dotenv-filename`.
Otherwise, `just` looks for a file named `.env` by default, unless Otherwise, `just` looks for a file named `.env` by default, unless
`dotenv-filename` set, in which case the value of `dotenv-filename` is used. `dotenv-filename` set, in which case the value of `dotenv-filename` is used.
@ -2221,87 +2201,7 @@ foo $bar:
echo $bar echo $bar
``` ```
### Dependencies ### Running Recipes at the End of a Recipe
Dependencies run before recipes that depend on them:
```just
a: b
@echo A
b:
@echo B
```
```
$ just a
B
A
```
In a given invocation of `just`, a recipe with the same arguments will only run
once, regardless of how many times it appears in the command-line invocation,
or how many times it appears as a dependency:
```just
a:
@echo A
b: a
@echo B
c: a
@echo C
```
```
$ just a a a a a
A
$ just b c
A
B
C
```
Multiple recipes may depend on a recipe that performs some kind of setup, and
when those recipes run, that setup will only be performed once:
```just
build:
cc main.c
test-foo: build
./a.out --test foo
test-bar: build
./a.out --test bar
```
```
$ just test-foo test-bar
cc main.c
./a.out --test foo
./a.out --test bar
```
Recipes in a given run are only skipped when they receive the same arguments:
```just
build:
cc main.c
test TEST: build
./a.out --test {{TEST}}
```
```
$ just test foo test bar
cc main.c
./a.out --test foo
./a.out --test bar
```
#### Running Recipes at the End of a Recipe
Normal dependencies of a recipes always run before a recipe starts. That is to Normal dependencies of a recipes always run before a recipe starts. That is to
say, the dependee always runs before the depender. These dependencies are say, the dependee always runs before the depender. These dependencies are
@ -2338,7 +2238,7 @@ echo 'D!'
D! D!
``` ```
#### Running Recipes in the Middle of a Recipe ### Running Recipes in the Middle of a Recipe
`just` doesn't support running recipes in the middle of another recipe, but you `just` doesn't support running recipes in the middle of another recipe, but you
can call `just` recursively in the middle of a recipe. Given the following can call `just` recursively in the middle of a recipe. Given the following
@ -3372,43 +3272,6 @@ Since `set windows-shell` has higher precedence than `set shell`, you can use
`set windows-shell` to pick a shell on Windows, and `set shell` to pick a shell `set windows-shell` to pick a shell on Windows, and `set shell` to pick a shell
for all other platforms. for all other platforms.
### Timestamps
`just` can print timestamps before each recipe commands:
```just
recipe:
echo one
sleep 2
echo two
```
```
$ just --timestamp recipe
[07:28:46] echo one
one
[07:28:46] sleep 2
[07:28:48] echo two
two
```
By default, timestamps are formatted as `HH:MM:SS`. The format can be changed
with `--timestamp-format`:
```
$ just --timestamp recipe --timestamp-format '%H:%M:%S%.3f %Z'
[07:32:11:.349 UTC] echo one
one
[07:32:11:.350 UTC] sleep 2
[07:32:13:.352 UTC] echo two
two
```
The argument to `--timestamp-format` is a `strftime`-style format string, see
the
[`chrono` library docs](https://docs.rs/chrono/latest/chrono/format/strftime/index.html)
for details.
Changelog Changelog
--------- ---------

View File

@ -2,12 +2,10 @@
set -euxo pipefail set -euxo pipefail
cargo build
for script in completions/*; do for script in completions/*; do
shell=${script##*.} shell=${script##*.}
if [ $shell == nu ]; then if [ $shell == nu ]; then
continue continue
fi fi
./target/debug/just --completions $shell > $script cargo run -- --completions $shell > $script
done done

View File

@ -29,8 +29,8 @@ _just() {
done done
case "${cmd}" in case "${cmd}" in
just) "$1")
opts="-n -f -q -u -v -d -c -e -l -s -E -g -h -V --check --chooser --color --command-color --yes --dry-run --dump-format --highlight --list-heading --list-prefix --no-aliases --no-deps --no-dotenv --no-highlight --justfile --quiet --set --shell --shell-arg --shell-command --clear-shell-args --unsorted --unstable --verbose --working-directory --changelog --choose --command --completions --dump --edit --evaluate --fmt --init --list --groups --man --show --summary --variables --dotenv-filename --dotenv-path --global-justfile --timestamp --timestamp-format --help --version [ARGUMENTS]..." opts="-n -f -q -u -v -d -c -e -l -s -E -g -h -V --check --chooser --color --command-color --yes --dry-run --dump-format --highlight --list-heading --list-prefix --no-aliases --no-deps --no-dotenv --no-highlight --justfile --quiet --set --shell --shell-arg --shell-command --clear-shell-args --unsorted --unstable --verbose --working-directory --changelog --choose --command --completions --dump --edit --evaluate --fmt --init --list --groups --man --show --summary --variables --dotenv-filename --dotenv-path --global-justfile --help --version [ARGUMENTS]..."
if [[ ${cur} == -* ]] ; then if [[ ${cur} == -* ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0 return 0
@ -116,14 +116,6 @@ _just() {
COMPREPLY=($(compgen -W "bash elvish fish powershell zsh" -- "${cur}")) COMPREPLY=($(compgen -W "bash elvish fish powershell zsh" -- "${cur}"))
return 0 return 0
;; ;;
--list)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
-l)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--show) --show)
COMPREPLY=($(compgen -f "${cur}")) COMPREPLY=($(compgen -f "${cur}"))
return 0 return 0
@ -144,10 +136,6 @@ _just() {
COMPREPLY=($(compgen -f "${cur}")) COMPREPLY=($(compgen -f "${cur}"))
return 0 return 0
;; ;;
--timestamp-format)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
*) *)
COMPREPLY=() COMPREPLY=()
;; ;;

View File

@ -33,14 +33,11 @@ set edit:completion:arg-completer[just] = {|@words|
cand -c 'Run an arbitrary command with the working directory, `.env`, overrides, and exports set' cand -c 'Run an arbitrary command with the working directory, `.env`, overrides, and exports set'
cand --command 'Run an arbitrary command with the working directory, `.env`, overrides, and exports set' cand --command 'Run an arbitrary command with the working directory, `.env`, overrides, and exports set'
cand --completions 'Print shell completion script for <SHELL>' cand --completions 'Print shell completion script for <SHELL>'
cand -l 'List available recipes' cand -s 'Show information about <RECIPE>'
cand --list 'List available recipes' cand --show 'Show information about <RECIPE>'
cand -s 'Show recipe at <PATH>'
cand --show 'Show recipe at <PATH>'
cand --dotenv-filename 'Search for environment file named <DOTENV-FILENAME> instead of `.env`' cand --dotenv-filename 'Search for environment file named <DOTENV-FILENAME> instead of `.env`'
cand -E 'Load <DOTENV-PATH> as environment file instead of searching for one' cand -E 'Load <DOTENV-PATH> as environment file instead of searching for one'
cand --dotenv-path 'Load <DOTENV-PATH> as environment file instead of searching for one' cand --dotenv-path 'Load <DOTENV-PATH> as environment file instead of searching for one'
cand --timestamp-format 'Timestamp format string'
cand --check 'Run `--fmt` in ''check'' mode. Exits with 0 if justfile is formatted correctly. Exits with 1 and prints a diff if formatting is required.' cand --check 'Run `--fmt` in ''check'' mode. Exits with 0 if justfile is formatted correctly. Exits with 1 and prints a diff if formatting is required.'
cand --yes 'Automatically confirm all recipes.' cand --yes 'Automatically confirm all recipes.'
cand -n 'Print what just would do without doing it' cand -n 'Print what just would do without doing it'
@ -67,13 +64,14 @@ set edit:completion:arg-completer[just] = {|@words|
cand --evaluate 'Evaluate and print all variables. If a variable name is given as an argument, only print that variable''s value.' cand --evaluate 'Evaluate and print all variables. If a variable name is given as an argument, only print that variable''s value.'
cand --fmt 'Format and overwrite justfile' cand --fmt 'Format and overwrite justfile'
cand --init 'Initialize new justfile in project root' cand --init 'Initialize new justfile in project root'
cand -l 'List available recipes and their arguments'
cand --list 'List available recipes and their arguments'
cand --groups 'List recipe groups' cand --groups 'List recipe groups'
cand --man 'Print man page' cand --man 'Print man page'
cand --summary 'List names of available recipes' cand --summary 'List names of available recipes'
cand --variables 'List names of variables' cand --variables 'List names of variables'
cand -g 'Use global justfile' cand -g 'Use global justfile'
cand --global-justfile 'Use global justfile' cand --global-justfile 'Use global justfile'
cand --timestamp 'Print recipe command timestamps'
cand -h 'Print help' cand -h 'Print help'
cand --help 'Print help' cand --help 'Print help'
cand -V 'Print version' cand -V 'Print version'

View File

@ -48,11 +48,9 @@ complete -c just -l shell-arg -d 'Invoke shell with <SHELL-ARG> as an argument'
complete -c just -s d -l working-directory -d 'Use <WORKING-DIRECTORY> as working directory. --justfile must also be set' -r -F complete -c just -s d -l working-directory -d 'Use <WORKING-DIRECTORY> as working directory. --justfile must also be set' -r -F
complete -c just -s c -l command -d 'Run an arbitrary command with the working directory, `.env`, overrides, and exports set' -r complete -c just -s c -l command -d 'Run an arbitrary command with the working directory, `.env`, overrides, and exports set' -r
complete -c just -l completions -d 'Print shell completion script for <SHELL>' -r -f -a "{bash '',elvish '',fish '',powershell '',zsh ''}" complete -c just -l completions -d 'Print shell completion script for <SHELL>' -r -f -a "{bash '',elvish '',fish '',powershell '',zsh ''}"
complete -c just -s l -l list -d 'List available recipes' -r complete -c just -s s -l show -d 'Show information about <RECIPE>' -r
complete -c just -s s -l show -d 'Show recipe at <PATH>' -r
complete -c just -l dotenv-filename -d 'Search for environment file named <DOTENV-FILENAME> instead of `.env`' -r complete -c just -l dotenv-filename -d 'Search for environment file named <DOTENV-FILENAME> instead of `.env`' -r
complete -c just -s E -l dotenv-path -d 'Load <DOTENV-PATH> as environment file instead of searching for one' -r -F complete -c just -s E -l dotenv-path -d 'Load <DOTENV-PATH> as environment file instead of searching for one' -r -F
complete -c just -l timestamp-format -d 'Timestamp format string' -r
complete -c just -l check -d 'Run `--fmt` in \'check\' mode. Exits with 0 if justfile is formatted correctly. Exits with 1 and prints a diff if formatting is required.' complete -c just -l check -d 'Run `--fmt` in \'check\' mode. Exits with 0 if justfile is formatted correctly. Exits with 1 and prints a diff if formatting is required.'
complete -c just -l yes -d 'Automatically confirm all recipes.' complete -c just -l yes -d 'Automatically confirm all recipes.'
complete -c just -s n -l dry-run -d 'Print what just would do without doing it' complete -c just -s n -l dry-run -d 'Print what just would do without doing it'
@ -74,11 +72,11 @@ complete -c just -s e -l edit -d 'Edit justfile with editor given by $VISUAL or
complete -c just -l evaluate -d 'Evaluate and print all variables. If a variable name is given as an argument, only print that variable\'s value.' complete -c just -l evaluate -d 'Evaluate and print all variables. If a variable name is given as an argument, only print that variable\'s value.'
complete -c just -l fmt -d 'Format and overwrite justfile' complete -c just -l fmt -d 'Format and overwrite justfile'
complete -c just -l init -d 'Initialize new justfile in project root' complete -c just -l init -d 'Initialize new justfile in project root'
complete -c just -s l -l list -d 'List available recipes and their arguments'
complete -c just -l groups -d 'List recipe groups' complete -c just -l groups -d 'List recipe groups'
complete -c just -l man -d 'Print man page' complete -c just -l man -d 'Print man page'
complete -c just -l summary -d 'List names of available recipes' complete -c just -l summary -d 'List names of available recipes'
complete -c just -l variables -d 'List names of variables' complete -c just -l variables -d 'List names of variables'
complete -c just -s g -l global-justfile -d 'Use global justfile' complete -c just -s g -l global-justfile -d 'Use global justfile'
complete -c just -l timestamp -d 'Print recipe command timestamps'
complete -c just -s h -l help -d 'Print help' complete -c just -s h -l help -d 'Print help'
complete -c just -s V -l version -d 'Print version' complete -c just -s V -l version -d 'Print version'

View File

@ -36,14 +36,11 @@ Register-ArgumentCompleter -Native -CommandName 'just' -ScriptBlock {
[CompletionResult]::new('-c', 'c', [CompletionResultType]::ParameterName, 'Run an arbitrary command with the working directory, `.env`, overrides, and exports set') [CompletionResult]::new('-c', 'c', [CompletionResultType]::ParameterName, 'Run an arbitrary command with the working directory, `.env`, overrides, and exports set')
[CompletionResult]::new('--command', 'command', [CompletionResultType]::ParameterName, 'Run an arbitrary command with the working directory, `.env`, overrides, and exports set') [CompletionResult]::new('--command', 'command', [CompletionResultType]::ParameterName, 'Run an arbitrary command with the working directory, `.env`, overrides, and exports set')
[CompletionResult]::new('--completions', 'completions', [CompletionResultType]::ParameterName, 'Print shell completion script for <SHELL>') [CompletionResult]::new('--completions', 'completions', [CompletionResultType]::ParameterName, 'Print shell completion script for <SHELL>')
[CompletionResult]::new('-l', 'l', [CompletionResultType]::ParameterName, 'List available recipes') [CompletionResult]::new('-s', 's', [CompletionResultType]::ParameterName, 'Show information about <RECIPE>')
[CompletionResult]::new('--list', 'list', [CompletionResultType]::ParameterName, 'List available recipes') [CompletionResult]::new('--show', 'show', [CompletionResultType]::ParameterName, 'Show information about <RECIPE>')
[CompletionResult]::new('-s', 's', [CompletionResultType]::ParameterName, 'Show recipe at <PATH>')
[CompletionResult]::new('--show', 'show', [CompletionResultType]::ParameterName, 'Show recipe at <PATH>')
[CompletionResult]::new('--dotenv-filename', 'dotenv-filename', [CompletionResultType]::ParameterName, 'Search for environment file named <DOTENV-FILENAME> instead of `.env`') [CompletionResult]::new('--dotenv-filename', 'dotenv-filename', [CompletionResultType]::ParameterName, 'Search for environment file named <DOTENV-FILENAME> instead of `.env`')
[CompletionResult]::new('-E', 'E ', [CompletionResultType]::ParameterName, 'Load <DOTENV-PATH> as environment file instead of searching for one') [CompletionResult]::new('-E', 'E ', [CompletionResultType]::ParameterName, 'Load <DOTENV-PATH> as environment file instead of searching for one')
[CompletionResult]::new('--dotenv-path', 'dotenv-path', [CompletionResultType]::ParameterName, 'Load <DOTENV-PATH> as environment file instead of searching for one') [CompletionResult]::new('--dotenv-path', 'dotenv-path', [CompletionResultType]::ParameterName, 'Load <DOTENV-PATH> as environment file instead of searching for one')
[CompletionResult]::new('--timestamp-format', 'timestamp-format', [CompletionResultType]::ParameterName, 'Timestamp format string')
[CompletionResult]::new('--check', 'check', [CompletionResultType]::ParameterName, 'Run `--fmt` in ''check'' mode. Exits with 0 if justfile is formatted correctly. Exits with 1 and prints a diff if formatting is required.') [CompletionResult]::new('--check', 'check', [CompletionResultType]::ParameterName, 'Run `--fmt` in ''check'' mode. Exits with 0 if justfile is formatted correctly. Exits with 1 and prints a diff if formatting is required.')
[CompletionResult]::new('--yes', 'yes', [CompletionResultType]::ParameterName, 'Automatically confirm all recipes.') [CompletionResult]::new('--yes', 'yes', [CompletionResultType]::ParameterName, 'Automatically confirm all recipes.')
[CompletionResult]::new('-n', 'n', [CompletionResultType]::ParameterName, 'Print what just would do without doing it') [CompletionResult]::new('-n', 'n', [CompletionResultType]::ParameterName, 'Print what just would do without doing it')
@ -70,13 +67,14 @@ Register-ArgumentCompleter -Native -CommandName 'just' -ScriptBlock {
[CompletionResult]::new('--evaluate', 'evaluate', [CompletionResultType]::ParameterName, 'Evaluate and print all variables. If a variable name is given as an argument, only print that variable''s value.') [CompletionResult]::new('--evaluate', 'evaluate', [CompletionResultType]::ParameterName, 'Evaluate and print all variables. If a variable name is given as an argument, only print that variable''s value.')
[CompletionResult]::new('--fmt', 'fmt', [CompletionResultType]::ParameterName, 'Format and overwrite justfile') [CompletionResult]::new('--fmt', 'fmt', [CompletionResultType]::ParameterName, 'Format and overwrite justfile')
[CompletionResult]::new('--init', 'init', [CompletionResultType]::ParameterName, 'Initialize new justfile in project root') [CompletionResult]::new('--init', 'init', [CompletionResultType]::ParameterName, 'Initialize new justfile in project root')
[CompletionResult]::new('-l', 'l', [CompletionResultType]::ParameterName, 'List available recipes and their arguments')
[CompletionResult]::new('--list', 'list', [CompletionResultType]::ParameterName, 'List available recipes and their arguments')
[CompletionResult]::new('--groups', 'groups', [CompletionResultType]::ParameterName, 'List recipe groups') [CompletionResult]::new('--groups', 'groups', [CompletionResultType]::ParameterName, 'List recipe groups')
[CompletionResult]::new('--man', 'man', [CompletionResultType]::ParameterName, 'Print man page') [CompletionResult]::new('--man', 'man', [CompletionResultType]::ParameterName, 'Print man page')
[CompletionResult]::new('--summary', 'summary', [CompletionResultType]::ParameterName, 'List names of available recipes') [CompletionResult]::new('--summary', 'summary', [CompletionResultType]::ParameterName, 'List names of available recipes')
[CompletionResult]::new('--variables', 'variables', [CompletionResultType]::ParameterName, 'List names of variables') [CompletionResult]::new('--variables', 'variables', [CompletionResultType]::ParameterName, 'List names of variables')
[CompletionResult]::new('-g', 'g', [CompletionResultType]::ParameterName, 'Use global justfile') [CompletionResult]::new('-g', 'g', [CompletionResultType]::ParameterName, 'Use global justfile')
[CompletionResult]::new('--global-justfile', 'global-justfile', [CompletionResultType]::ParameterName, 'Use global justfile') [CompletionResult]::new('--global-justfile', 'global-justfile', [CompletionResultType]::ParameterName, 'Use global justfile')
[CompletionResult]::new('--timestamp', 'timestamp', [CompletionResultType]::ParameterName, 'Print recipe command timestamps')
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help') [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help') [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('-V', 'V ', [CompletionResultType]::ParameterName, 'Print version') [CompletionResult]::new('-V', 'V ', [CompletionResultType]::ParameterName, 'Print version')

View File

@ -31,14 +31,11 @@ _just() {
'*-c+[Run an arbitrary command with the working directory, \`.env\`, overrides, and exports set]: : ' \ '*-c+[Run an arbitrary command with the working directory, \`.env\`, overrides, and exports set]: : ' \
'*--command=[Run an arbitrary command with the working directory, \`.env\`, overrides, and exports set]: : ' \ '*--command=[Run an arbitrary command with the working directory, \`.env\`, overrides, and exports set]: : ' \
'*--completions=[Print shell completion script for <SHELL>]:SHELL:(bash elvish fish powershell zsh)' \ '*--completions=[Print shell completion script for <SHELL>]:SHELL:(bash elvish fish powershell zsh)' \
'()-l+[List available recipes]' \ '-s+[Show information about <RECIPE>]: :(_just_commands)' \
'()--list=[List available recipes]' \ '--show=[Show information about <RECIPE>]: :(_just_commands)' \
'-s+[Show recipe at <PATH>]: :(_just_commands)' \
'--show=[Show recipe at <PATH>]: :(_just_commands)' \
'(-E --dotenv-path)--dotenv-filename=[Search for environment file named <DOTENV-FILENAME> instead of \`.env\`]: : ' \ '(-E --dotenv-path)--dotenv-filename=[Search for environment file named <DOTENV-FILENAME> instead of \`.env\`]: : ' \
'-E+[Load <DOTENV-PATH> as environment file instead of searching for one]: :_files' \ '-E+[Load <DOTENV-PATH> as environment file instead of searching for one]: :_files' \
'--dotenv-path=[Load <DOTENV-PATH> as environment file instead of searching for one]: :_files' \ '--dotenv-path=[Load <DOTENV-PATH> as environment file instead of searching for one]: :_files' \
'--timestamp-format=[Timestamp format string]: : ' \
'--check[Run \`--fmt\` in '\''check'\'' mode. Exits with 0 if justfile is formatted correctly. Exits with 1 and prints a diff if formatting is required.]' \ '--check[Run \`--fmt\` in '\''check'\'' mode. Exits with 0 if justfile is formatted correctly. Exits with 1 and prints a diff if formatting is required.]' \
'--yes[Automatically confirm all recipes.]' \ '--yes[Automatically confirm all recipes.]' \
'(-q --quiet)-n[Print what just would do without doing it]' \ '(-q --quiet)-n[Print what just would do without doing it]' \
@ -65,13 +62,14 @@ _just() {
'--evaluate[Evaluate and print all variables. If a variable name is given as an argument, only print that variable'\''s value.]' \ '--evaluate[Evaluate and print all variables. If a variable name is given as an argument, only print that variable'\''s value.]' \
'--fmt[Format and overwrite justfile]' \ '--fmt[Format and overwrite justfile]' \
'--init[Initialize new justfile in project root]' \ '--init[Initialize new justfile in project root]' \
'-l[List available recipes and their arguments]' \
'--list[List available recipes and their arguments]' \
'--groups[List recipe groups]' \ '--groups[List recipe groups]' \
'--man[Print man page]' \ '--man[Print man page]' \
'--summary[List names of available recipes]' \ '--summary[List names of available recipes]' \
'--variables[List names of variables]' \ '--variables[List names of variables]' \
'(-f --justfile -d --working-directory)-g[Use global justfile]' \ '(-f --justfile -d --working-directory)-g[Use global justfile]' \
'(-f --justfile -d --working-directory)--global-justfile[Use global justfile]' \ '(-f --justfile -d --working-directory)--global-justfile[Use global justfile]' \
'--timestamp[Print recipe command timestamps]' \
'-h[Print help]' \ '-h[Print help]' \
'--help[Print help]' \ '--help[Print help]' \
'-V[Print version]' \ '-V[Print version]' \

View File

@ -164,8 +164,8 @@ watch-readme:
just render-readme just render-readme
fswatch -ro README.adoc | xargs -n1 -I{} just render-readme fswatch -ro README.adoc | xargs -n1 -I{} just render-readme
update-completions: generate-completions:
./bin/update-completions ./bin/generate-completions
test-completions: test-completions:
./tests/completions/just.bash ./tests/completions/just.bash

View File

@ -166,7 +166,6 @@ impl<'src> Analyzer<'src> {
name, name,
recipes, recipes,
settings, settings,
source: root.into(),
warnings, warnings,
}) })
} }

View File

@ -47,10 +47,10 @@ pub(crate) const ZSH_COMPLETION_REPLACEMENTS: &[(&str, &str)] = &[
r"'*--set=[Override <VARIABLE> with <VALUE>]: :(_just_variables)' \", r"'*--set=[Override <VARIABLE> with <VALUE>]: :(_just_variables)' \",
), ),
( (
r"'()-s+[Show recipe at <PATH>]:PATH: ' \ r"'()-s+[Show information about <RECIPE>]:RECIPE: ' \
'()--show=[Show recipe at <PATH>]:PATH: ' \", '()--show=[Show information about <RECIPE>]:RECIPE: ' \",
r"'-s+[Show recipe at <PATH>]: :(_just_commands)' \ r"'-s+[Show information about <RECIPE>]: :(_just_commands)' \
'--show=[Show recipe at <PATH>]: :(_just_commands)' \", '--show=[Show information about <RECIPE>]: :(_just_commands)' \",
), ),
( (
"'*::ARGUMENTS -- Overrides and recipe(s) to run, defaulting to the first recipe in the \ "'*::ARGUMENTS -- Overrides and recipe(s) to run, defaulting to the first recipe in the \
@ -202,6 +202,7 @@ pub(crate) const BASH_COMPLETION_REPLACEMENTS: &[(&str, &str)] = &[
fi fi
fi"#, fi"#,
), ),
(r" just)", r#" "$1")"#),
( (
r"local i cur prev opts cmd", r"local i cur prev opts cmd",
r"local i cur prev words cword opts cmd", r"local i cur prev words cword opts cmd",

View File

@ -2,7 +2,6 @@ use {
super::*, super::*,
clap::{ clap::{
builder::{styling::AnsiColor, FalseyValueParser, PossibleValuesParser, Styles}, builder::{styling::AnsiColor, FalseyValueParser, PossibleValuesParser, Styles},
parser::ValuesRef,
value_parser, Arg, ArgAction, ArgGroup, ArgMatches, Command, value_parser, Arg, ArgAction, ArgGroup, ArgMatches, Command,
}, },
}; };
@ -40,8 +39,6 @@ pub(crate) struct Config {
pub(crate) shell_args: Option<Vec<String>>, pub(crate) shell_args: Option<Vec<String>>,
pub(crate) shell_command: bool, pub(crate) shell_command: bool,
pub(crate) subcommand: Subcommand, pub(crate) subcommand: Subcommand,
pub(crate) timestamp: bool,
pub(crate) timestamp_format: String,
pub(crate) unsorted: bool, pub(crate) unsorted: bool,
pub(crate) unstable: bool, pub(crate) unstable: bool,
pub(crate) verbosity: Verbosity, pub(crate) verbosity: Verbosity,
@ -82,8 +79,9 @@ mod cmd {
VARIABLES, VARIABLES,
]; ];
pub(crate) const ARGLESS: &[&str] = pub(crate) const ARGLESS: &[&str] = &[
&[CHANGELOG, DUMP, EDIT, FORMAT, INIT, MAN, SUMMARY, VARIABLES]; CHANGELOG, DUMP, EDIT, FORMAT, INIT, LIST, MAN, SUMMARY, VARIABLES,
];
} }
mod arg { mod arg {
@ -111,8 +109,6 @@ mod arg {
pub(crate) const SHELL: &str = "SHELL"; pub(crate) const SHELL: &str = "SHELL";
pub(crate) const SHELL_ARG: &str = "SHELL-ARG"; pub(crate) const SHELL_ARG: &str = "SHELL-ARG";
pub(crate) const SHELL_COMMAND: &str = "SHELL-COMMAND"; pub(crate) const SHELL_COMMAND: &str = "SHELL-COMMAND";
pub(crate) const TIMESTAMP: &str = "TIMESTAMP";
pub(crate) const TIMESTAMP_FORMAT: &str = "TIMESTAMP_FORMAT";
pub(crate) const UNSORTED: &str = "UNSORTED"; pub(crate) const UNSORTED: &str = "UNSORTED";
pub(crate) const UNSTABLE: &str = "UNSTABLE"; pub(crate) const UNSTABLE: &str = "UNSTABLE";
pub(crate) const VERBOSE: &str = "VERBOSE"; pub(crate) const VERBOSE: &str = "VERBOSE";
@ -419,11 +415,8 @@ impl Config {
Arg::new(cmd::LIST) Arg::new(cmd::LIST)
.short('l') .short('l')
.long("list") .long("list")
.num_args(0..) .action(ArgAction::SetTrue)
.value_name("PATH") .help("List available recipes and their arguments"),
.action(ArgAction::Set)
.conflicts_with(arg::ARGUMENTS)
.help("List available recipes"),
) )
.arg( .arg(
Arg::new(cmd::GROUPS) Arg::new(cmd::GROUPS)
@ -441,11 +434,10 @@ impl Config {
Arg::new(cmd::SHOW) Arg::new(cmd::SHOW)
.short('s') .short('s')
.long("show") .long("show")
.num_args(1..)
.action(ArgAction::Set) .action(ArgAction::Set)
.value_name("PATH") .value_name("RECIPE")
.conflicts_with(arg::ARGUMENTS) .conflicts_with(arg::ARGUMENTS)
.help("Show recipe at <PATH>"), .help("Show information about <RECIPE>"),
) )
.arg( .arg(
Arg::new(cmd::SUMMARY) Arg::new(cmd::SUMMARY)
@ -490,21 +482,6 @@ impl Config {
.conflicts_with(arg::WORKING_DIRECTORY) .conflicts_with(arg::WORKING_DIRECTORY)
.help("Use global justfile") .help("Use global justfile")
) )
.arg(
Arg::new(arg::TIMESTAMP)
.action(ArgAction::SetTrue)
.long("timestamp")
.env("JUST_TIMESTAMP")
.help("Print recipe command timestamps")
)
.arg(
Arg::new(arg::TIMESTAMP_FORMAT)
.action(ArgAction::Set)
.long("timestamp-format")
.env("JUST_TIMESTAMP_FORMAT")
.default_value("%H:%M:%S")
.help("Timestamp format string")
)
} }
fn color_from_matches(matches: &ArgMatches) -> ConfigResult<Color> { fn color_from_matches(matches: &ArgMatches) -> ConfigResult<Color> {
@ -560,18 +537,6 @@ impl Config {
} }
} }
fn parse_module_path(path: ValuesRef<String>) -> ConfigResult<ModulePath> {
path
.clone()
.map(|s| (*s).as_str())
.collect::<Vec<&str>>()
.as_slice()
.try_into()
.map_err(|()| ConfigError::ModulePath {
path: path.cloned().collect(),
})
}
fn search_config(matches: &ArgMatches, positional: &Positional) -> ConfigResult<SearchConfig> { fn search_config(matches: &ArgMatches, positional: &Positional) -> ConfigResult<SearchConfig> {
if matches.get_flag(arg::GLOBAL_JUSTFILE) { if matches.get_flag(arg::GLOBAL_JUSTFILE) {
return Ok(SearchConfig::GlobalJustfile); return Ok(SearchConfig::GlobalJustfile);
@ -689,18 +654,14 @@ impl Config {
Subcommand::Format Subcommand::Format
} else if matches.get_flag(cmd::INIT) { } else if matches.get_flag(cmd::INIT) {
Subcommand::Init Subcommand::Init
} else if let Some(path) = matches.get_many::<String>(cmd::LIST) { } else if matches.get_flag(cmd::LIST) {
Subcommand::List { Subcommand::List
path: Self::parse_module_path(path)?,
}
} else if matches.get_flag(cmd::GROUPS) { } else if matches.get_flag(cmd::GROUPS) {
Subcommand::Groups Subcommand::Groups
} else if matches.get_flag(cmd::MAN) { } else if matches.get_flag(cmd::MAN) {
Subcommand::Man Subcommand::Man
} else if let Some(path) = matches.get_many::<String>(cmd::SHOW) { } else if let Some(name) = matches.get_one::<String>(cmd::SHOW).map(Into::into) {
Subcommand::Show { Subcommand::Show { name }
path: Self::parse_module_path(path)?,
}
} else if matches.get_flag(cmd::EVALUATE) { } else if matches.get_flag(cmd::EVALUATE) {
if positional.arguments.len() > 1 { if positional.arguments.len() > 1 {
return Err(ConfigError::SubcommandArguments { return Err(ConfigError::SubcommandArguments {
@ -762,11 +723,6 @@ impl Config {
shell_args, shell_args,
shell_command: matches.get_flag(arg::SHELL_COMMAND), shell_command: matches.get_flag(arg::SHELL_COMMAND),
subcommand, subcommand,
timestamp: matches.get_flag(arg::TIMESTAMP),
timestamp_format: matches
.get_one::<String>(arg::TIMESTAMP_FORMAT)
.unwrap()
.into(),
unsorted: matches.get_flag(arg::UNSORTED), unsorted: matches.get_flag(arg::UNSORTED),
unstable, unstable,
verbosity, verbosity,
@ -1307,42 +1263,30 @@ mod tests {
test! { test! {
name: subcommand_list_long, name: subcommand_list_long,
args: ["--list"], args: ["--list"],
subcommand: Subcommand::List{ path: ModulePath { path: Vec::new(), spaced: false } }, subcommand: Subcommand::List,
} }
test! { test! {
name: subcommand_list_short, name: subcommand_list_short,
args: ["-l"], args: ["-l"],
subcommand: Subcommand::List{ path: ModulePath { path: Vec::new(), spaced: false } }, subcommand: Subcommand::List,
}
test! {
name: subcommand_list_arguments,
args: ["--list", "bar"],
subcommand: Subcommand::List{ path: ModulePath { path: vec!["bar".into()], spaced: false } },
} }
test! { test! {
name: subcommand_show_long, name: subcommand_show_long,
args: ["--show", "build"], args: ["--show", "build"],
subcommand: Subcommand::Show { path: ModulePath { path: vec!["build".into()], spaced: false } }, subcommand: Subcommand::Show { name: String::from("build") },
} }
test! { test! {
name: subcommand_show_short, name: subcommand_show_short,
args: ["-s", "build"], args: ["-s", "build"],
subcommand: Subcommand::Show { path: ModulePath { path: vec!["build".into()], spaced: false } }, subcommand: Subcommand::Show { name: String::from("build") },
} }
test! { error! {
name: subcommand_show_multiple_args, name: subcommand_show_no_arg,
args: ["--show", "foo", "bar"], args: ["--show"],
subcommand: Subcommand::Show {
path: ModulePath {
path: vec!["foo".into(), "bar".into()],
spaced: true,
},
},
} }
test! { test! {
@ -1557,6 +1501,16 @@ mod tests {
}, },
} }
error! {
name: list_arguments,
args: ["--list", "bar"],
error: ConfigError::SubcommandArguments { subcommand, arguments },
check: {
assert_eq!(subcommand, cmd::LIST);
assert_eq!(arguments, &["bar"]);
},
}
error! { error! {
name: dump_arguments, name: dump_arguments,
args: ["--dump", "bar"], args: ["--dump", "bar"],
@ -1617,6 +1571,20 @@ mod tests {
}, },
} }
error_matches! {
name: show_arguments,
args: ["--show", "foo", "bar"],
error: error,
check: {
assert_eq!(error.kind(), clap::error::ErrorKind::ArgumentConflict);
assert_eq!(error.context().collect::<Vec<_>>(), vec![
(ContextKind::InvalidArg, &ContextValue::String("--show <RECIPE>".into())),
(ContextKind::PriorArg, &ContextValue::String("[ARGUMENTS]...".into())),
(ContextKind::Usage, &ContextValue::StyledStr("\u{1b}[33mUsage:\u{1b}[0m \u{1b}[32mjust\u{1b}[0m \u{1b}[32m--show\u{1b}[0m\u{1b}[32m \u{1b}[0m\u{1b}[32m<RECIPE>\u{1b}[0m \u{1b}[32m[ARGUMENTS]...\u{1b}[0m".into())),
]);
},
}
error! { error! {
name: summary_arguments, name: summary_arguments,
args: ["--summary", "bar"], args: ["--summary", "bar"],

View File

@ -6,12 +6,11 @@ pub(crate) enum ConfigError {
#[snafu(display("Failed to get current directory: {}", source))] #[snafu(display("Failed to get current directory: {}", source))]
CurrentDir { source: io::Error }, CurrentDir { source: io::Error },
#[snafu(display( #[snafu(display(
"Internal config error, this may indicate a bug in just: {message} \ "Internal config error, this may indicate a bug in just: {} \
consider filing an issue: https://github.com/casey/just/issues/new", consider filing an issue: https://github.com/casey/just/issues/new",
message
))] ))]
Internal { message: String }, Internal { message: String },
#[snafu(display("Invalid module path `{}`", path.join(" ")))]
ModulePath { path: Vec<String> },
#[snafu(display( #[snafu(display(
"Path-prefixed recipes may not be used with `--working-directory` or `--justfile`." "Path-prefixed recipes may not be used with `--working-directory` or `--justfile`."
))] ))]

View File

@ -160,9 +160,6 @@ pub(crate) enum Error<'src> {
recipe: &'src str, recipe: &'src str,
line_number: Option<usize>, line_number: Option<usize>,
}, },
UnknownSubmodule {
path: ModulePath,
},
UnknownOverrides { UnknownOverrides {
overrides: Vec<String>, overrides: Vec<String>,
}, },
@ -435,9 +432,6 @@ impl<'src> ColorDisplay for Error<'src> {
write!(f, "Recipe `{recipe}` failed for an unknown reason")?; write!(f, "Recipe `{recipe}` failed for an unknown reason")?;
} }
} }
UnknownSubmodule { path } => {
write!(f, "Justfile does not contain submodule `{path}`")?;
}
UnknownOverrides { overrides } => { UnknownOverrides { overrides } => {
let count = Count("Variable", overrides.len()); let count = Count("Variable", overrides.len());
let overrides = List::and_ticked(overrides); let overrides = List::and_ticked(overrides);

View File

@ -4,10 +4,9 @@ pub(crate) struct Evaluator<'src: 'run, 'run> {
pub(crate) assignments: Option<&'run Table<'src, Assignment<'src>>>, pub(crate) assignments: Option<&'run Table<'src, Assignment<'src>>>,
pub(crate) config: &'run Config, pub(crate) config: &'run Config,
pub(crate) dotenv: &'run BTreeMap<String, String>, pub(crate) dotenv: &'run BTreeMap<String, String>,
pub(crate) module_source: &'run Path,
pub(crate) scope: Scope<'src, 'run>, pub(crate) scope: Scope<'src, 'run>,
pub(crate) search: &'run Search,
pub(crate) settings: &'run Settings<'run>, pub(crate) settings: &'run Settings<'run>,
pub(crate) search: &'run Search,
} }
impl<'src, 'run> Evaluator<'src, 'run> { impl<'src, 'run> Evaluator<'src, 'run> {
@ -15,19 +14,17 @@ impl<'src, 'run> Evaluator<'src, 'run> {
assignments: &'run Table<'src, Assignment<'src>>, assignments: &'run Table<'src, Assignment<'src>>,
config: &'run Config, config: &'run Config,
dotenv: &'run BTreeMap<String, String>, dotenv: &'run BTreeMap<String, String>,
module_source: &'run Path, overrides: Scope<'src, 'run>,
scope: Scope<'src, 'run>,
search: &'run Search,
settings: &'run Settings<'run>, settings: &'run Settings<'run>,
search: &'run Search,
) -> RunResult<'src, Scope<'src, 'run>> { ) -> RunResult<'src, Scope<'src, 'run>> {
let mut evaluator = Self { let mut evaluator = Self {
scope: overrides,
assignments: Some(assignments), assignments: Some(assignments),
config, config,
dotenv, dotenv,
module_source,
scope,
search,
settings, settings,
search,
}; };
for assignment in assignments.values() { for assignment in assignments.values() {
@ -253,23 +250,21 @@ impl<'src, 'run> Evaluator<'src, 'run> {
} }
pub(crate) fn evaluate_parameters( pub(crate) fn evaluate_parameters(
arguments: &[String],
config: &'run Config, config: &'run Config,
dotenv: &'run BTreeMap<String, String>, dotenv: &'run BTreeMap<String, String>,
module_source: &'run Path,
parameters: &[Parameter<'src>], parameters: &[Parameter<'src>],
arguments: &[String],
scope: &'run Scope<'src, 'run>, scope: &'run Scope<'src, 'run>,
search: &'run Search,
settings: &'run Settings, settings: &'run Settings,
search: &'run Search,
) -> RunResult<'src, (Scope<'src, 'run>, Vec<String>)> { ) -> RunResult<'src, (Scope<'src, 'run>, Vec<String>)> {
let mut evaluator = Self { let mut evaluator = Self {
assignments: None, assignments: None,
config,
dotenv,
module_source,
scope: scope.child(), scope: scope.child(),
search, search,
settings, settings,
dotenv,
config,
}; };
let mut scope = scope.child(); let mut scope = scope.child();
@ -312,19 +307,17 @@ impl<'src, 'run> Evaluator<'src, 'run> {
pub(crate) fn recipe_evaluator( pub(crate) fn recipe_evaluator(
config: &'run Config, config: &'run Config,
dotenv: &'run BTreeMap<String, String>, dotenv: &'run BTreeMap<String, String>,
module_source: &'run Path,
scope: &'run Scope<'src, 'run>, scope: &'run Scope<'src, 'run>,
search: &'run Search,
settings: &'run Settings, settings: &'run Settings,
) -> Self { search: &'run Search,
) -> Evaluator<'src, 'run> {
Self { Self {
assignments: None, assignments: None,
config,
dotenv,
module_source,
scope: Scope::child(scope), scope: Scope::child(scope),
search, search,
settings, settings,
dotenv,
config,
} }
} }
} }

View File

@ -67,8 +67,6 @@ pub(crate) fn get(name: &str) -> Option<Function> {
"kebabcase" => Unary(kebabcase), "kebabcase" => Unary(kebabcase),
"lowercamelcase" => Unary(lowercamelcase), "lowercamelcase" => Unary(lowercamelcase),
"lowercase" => Unary(lowercase), "lowercase" => Unary(lowercase),
"module_directory" => Nullary(module_directory),
"module_file" => Nullary(module_file),
"num_cpus" => Nullary(num_cpus), "num_cpus" => Nullary(num_cpus),
"os" => Nullary(os), "os" => Nullary(os),
"os_family" => Nullary(os_family), "os_family" => Nullary(os_family),
@ -408,44 +406,6 @@ fn lowercase(_context: Context, s: &str) -> Result<String, String> {
Ok(s.to_lowercase()) Ok(s.to_lowercase())
} }
fn module_directory(context: Context) -> Result<String, String> {
context
.evaluator
.search
.justfile
.parent()
.unwrap()
.join(context.evaluator.module_source)
.parent()
.unwrap()
.to_str()
.map(str::to_owned)
.ok_or_else(|| {
format!(
"Module directory is not valid unicode: {}",
context.evaluator.module_source.parent().unwrap().display(),
)
})
}
fn module_file(context: Context) -> Result<String, String> {
context
.evaluator
.search
.justfile
.parent()
.unwrap()
.join(context.evaluator.module_source)
.to_str()
.map(str::to_owned)
.ok_or_else(|| {
format!(
"Module file path is not valid unicode: {}",
context.evaluator.module_source.display(),
)
})
}
fn num_cpus(_context: Context) -> Result<String, String> { fn num_cpus(_context: Context) -> Result<String, String> {
let num = num_cpus::get(); let num = num_cpus::get();
Ok(num.to_string()) Ok(num.to_string())

View File

@ -3,10 +3,9 @@ use {super::*, serde::Serialize};
#[derive(Debug)] #[derive(Debug)]
struct Invocation<'src: 'run, 'run> { struct Invocation<'src: 'run, 'run> {
arguments: Vec<&'run str>, arguments: Vec<&'run str>,
module_source: &'run Path,
recipe: &'run Recipe<'src>, recipe: &'run Recipe<'src>,
scope: &'run Scope<'src, 'run>,
settings: &'run Settings<'src>, settings: &'run Settings<'src>,
scope: &'run Scope<'src, 'run>,
} }
#[derive(Debug, PartialEq, Serialize)] #[derive(Debug, PartialEq, Serialize)]
@ -22,8 +21,6 @@ pub(crate) struct Justfile<'src> {
pub(crate) name: Option<Name<'src>>, pub(crate) name: Option<Name<'src>>,
pub(crate) recipes: Table<'src, Rc<Recipe<'src>>>, pub(crate) recipes: Table<'src, Rc<Recipe<'src>>>,
pub(crate) settings: Settings<'src>, pub(crate) settings: Settings<'src>,
#[serde(skip)]
pub(crate) source: PathBuf,
pub(crate) warnings: Vec<Warning>, pub(crate) warnings: Vec<Warning>,
} }
@ -109,10 +106,9 @@ impl<'src> Justfile<'src> {
&self.assignments, &self.assignments,
config, config,
dotenv, dotenv,
&self.source,
scope, scope,
search,
&self.settings, &self.settings,
search,
) )
} }
@ -280,12 +276,10 @@ impl<'src> Justfile<'src> {
let mut ran = Ran::default(); let mut ran = Ran::default();
for invocation in invocations { for invocation in invocations {
let context = RecipeContext { let context = RecipeContext {
settings: invocation.settings,
config, config,
dotenv: &dotenv,
module_source: invocation.module_source,
scope: invocation.scope, scope: invocation.scope,
search, search,
settings: invocation.settings,
}; };
Self::run_recipe( Self::run_recipe(
@ -296,6 +290,7 @@ impl<'src> Justfile<'src> {
.map(str::to_string) .map(str::to_string)
.collect::<Vec<String>>(), .collect::<Vec<String>>(),
&context, &context,
&dotenv,
&mut ran, &mut ran,
invocation.recipe, invocation.recipe,
search, search,
@ -351,7 +346,6 @@ impl<'src> Justfile<'src> {
recipe, recipe,
arguments: Vec::new(), arguments: Vec::new(),
scope, scope,
module_source: &self.source,
}, },
depth, depth,
))); )));
@ -379,7 +373,6 @@ impl<'src> Justfile<'src> {
recipe, recipe,
scope: parent, scope: parent,
settings: &self.settings, settings: &self.settings,
module_source: &self.source,
}, },
depth, depth,
))) )))
@ -401,7 +394,6 @@ impl<'src> Justfile<'src> {
recipe, recipe,
scope: parent, scope: parent,
settings: &self.settings, settings: &self.settings,
module_source: &self.source,
}, },
depth + argument_count, depth + argument_count,
))) )))
@ -418,6 +410,7 @@ impl<'src> Justfile<'src> {
fn run_recipe( fn run_recipe(
arguments: &[String], arguments: &[String],
context: &RecipeContext<'src, '_>, context: &RecipeContext<'src, '_>,
dotenv: &BTreeMap<String, String>,
ran: &mut Ran<'src>, ran: &mut Ran<'src>,
recipe: &Recipe<'src>, recipe: &Recipe<'src>,
search: &Search, search: &Search,
@ -433,26 +426,19 @@ impl<'src> Justfile<'src> {
} }
let (outer, positional) = Evaluator::evaluate_parameters( let (outer, positional) = Evaluator::evaluate_parameters(
arguments,
context.config, context.config,
context.dotenv, dotenv,
context.module_source,
&recipe.parameters, &recipe.parameters,
arguments,
context.scope, context.scope,
search,
context.settings, context.settings,
search,
)?; )?;
let scope = outer.child(); let scope = outer.child();
let mut evaluator = Evaluator::recipe_evaluator( let mut evaluator =
context.config, Evaluator::recipe_evaluator(context.config, dotenv, &scope, context.settings, search);
context.dotenv,
context.module_source,
&scope,
search,
context.settings,
);
if !context.config.no_dependencies { if !context.config.no_dependencies {
for Dependency { recipe, arguments } in recipe.dependencies.iter().take(recipe.priors) { for Dependency { recipe, arguments } in recipe.dependencies.iter().take(recipe.priors) {
@ -461,11 +447,11 @@ impl<'src> Justfile<'src> {
.map(|argument| evaluator.evaluate_expression(argument)) .map(|argument| evaluator.evaluate_expression(argument))
.collect::<RunResult<Vec<String>>>()?; .collect::<RunResult<Vec<String>>>()?;
Self::run_recipe(&arguments, context, ran, recipe, search)?; Self::run_recipe(&arguments, context, dotenv, ran, recipe, search)?;
} }
} }
recipe.run(context, &scope, &positional)?; recipe.run(context, dotenv, scope.child(), search, &positional)?;
if !context.config.no_dependencies { if !context.config.no_dependencies {
let mut ran = Ran::default(); let mut ran = Ran::default();
@ -477,12 +463,11 @@ impl<'src> Justfile<'src> {
evaluated.push(evaluator.evaluate_expression(argument)?); evaluated.push(evaluator.evaluate_expression(argument)?);
} }
Self::run_recipe(&evaluated, context, &mut ran, recipe, search)?; Self::run_recipe(&evaluated, context, dotenv, &mut ran, recipe, search)?;
} }
} }
ran.ran(&recipe.namepath, arguments.to_vec()); ran.ran(&recipe.namepath, arguments.to_vec());
Ok(()) Ok(())
} }

View File

@ -273,12 +273,12 @@ impl<'src> Lexer<'src> {
} }
/// True if `c` can be the first character of an identifier /// True if `c` can be the first character of an identifier
pub(crate) fn is_identifier_start(c: char) -> bool { fn is_identifier_start(c: char) -> bool {
matches!(c, 'a'..='z' | 'A'..='Z' | '_') matches!(c, 'a'..='z' | 'A'..='Z' | '_')
} }
/// True if `c` can be a continuation character of an identifier /// True if `c` can be a continuation character of an identifier
pub(crate) fn is_identifier_continue(c: char) -> bool { fn is_identifier_continue(c: char) -> bool {
Self::is_identifier_start(c) || matches!(c, '0'..='9' | '-') Self::is_identifier_start(c) || matches!(c, '0'..='9' | '-')
} }

View File

@ -25,15 +25,14 @@ pub(crate) use {
evaluator::Evaluator, expression::Expression, fragment::Fragment, function::Function, evaluator::Evaluator, expression::Expression, fragment::Fragment, function::Function,
interrupt_guard::InterruptGuard, interrupt_handler::InterruptHandler, item::Item, interrupt_guard::InterruptGuard, interrupt_handler::InterruptHandler, item::Item,
justfile::Justfile, keyed::Keyed, keyword::Keyword, lexer::Lexer, line::Line, list::List, justfile::Justfile, keyed::Keyed, keyword::Keyword, lexer::Lexer, line::Line, list::List,
load_dotenv::load_dotenv, loader::Loader, module_path::ModulePath, name::Name, load_dotenv::load_dotenv, loader::Loader, name::Name, namepath::Namepath, ordinal::Ordinal,
namepath::Namepath, ordinal::Ordinal, output::output, output_error::OutputError, output::output, output_error::OutputError, parameter::Parameter, parameter_kind::ParameterKind,
parameter::Parameter, parameter_kind::ParameterKind, parser::Parser, platform::Platform, parser::Parser, platform::Platform, platform_interface::PlatformInterface, position::Position,
platform_interface::PlatformInterface, position::Position, positional::Positional, ran::Ran, positional::Positional, ran::Ran, range_ext::RangeExt, recipe::Recipe,
range_ext::RangeExt, recipe::Recipe, recipe_context::RecipeContext, recipe_context::RecipeContext, recipe_resolver::RecipeResolver,
recipe_resolver::RecipeResolver, recipe_signature::RecipeSignature, scope::Scope, recipe_signature::RecipeSignature, scope::Scope, search::Search, search_config::SearchConfig,
search::Search, search_config::SearchConfig, search_error::SearchError, set::Set, search_error::SearchError, set::Set, setting::Setting, settings::Settings, shebang::Shebang,
setting::Setting, settings::Settings, shebang::Shebang, shell::Shell, shell::Shell, show_whitespace::ShowWhitespace, source::Source, string_kind::StringKind,
show_whitespace::ShowWhitespace, source::Source, string_kind::StringKind,
string_literal::StringLiteral, subcommand::Subcommand, suggestion::Suggestion, table::Table, string_literal::StringLiteral, subcommand::Subcommand, suggestion::Suggestion, table::Table,
thunk::Thunk, token::Token, token_kind::TokenKind, unresolved_dependency::UnresolvedDependency, thunk::Thunk, token::Token, token_kind::TokenKind, unresolved_dependency::UnresolvedDependency,
unresolved_recipe::UnresolvedRecipe, use_color::UseColor, variables::Variables, unresolved_recipe::UnresolvedRecipe, use_color::UseColor, variables::Variables,
@ -153,7 +152,6 @@ mod line;
mod list; mod list;
mod load_dotenv; mod load_dotenv;
mod loader; mod loader;
mod module_path;
mod name; mod name;
mod namepath; mod namepath;
mod ordinal; mod ordinal;

View File

@ -1,100 +0,0 @@
use super::*;
#[derive(Debug, PartialEq, Clone)]
pub(crate) struct ModulePath {
pub(crate) path: Vec<String>,
pub(crate) spaced: bool,
}
impl TryFrom<&[&str]> for ModulePath {
type Error = ();
fn try_from(path: &[&str]) -> Result<Self, Self::Error> {
let spaced = path.len() > 1;
let path = if path.len() == 1 {
let first = path[0];
if first.starts_with(':') || first.ends_with(':') || first.contains(":::") {
return Err(());
}
first
.split("::")
.map(str::to_string)
.collect::<Vec<String>>()
} else {
path.iter().map(|s| (*s).to_string()).collect()
};
for name in &path {
if name.is_empty() {
return Err(());
}
for (i, c) in name.chars().enumerate() {
if i == 0 {
if !Lexer::is_identifier_start(c) {
return Err(());
}
} else if !Lexer::is_identifier_continue(c) {
return Err(());
}
}
}
Ok(Self { path, spaced })
}
}
impl Display for ModulePath {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
for (i, name) in self.path.iter().enumerate() {
if i > 0 {
if self.spaced {
write!(f, " ")?;
} else {
write!(f, "::")?;
}
}
write!(f, "{name}")?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn try_from_ok() {
#[track_caller]
fn case(path: &[&str], expected: &[&str], display: &str) {
let actual = ModulePath::try_from(path).unwrap();
assert_eq!(actual.path, expected);
assert_eq!(actual.to_string(), display);
}
case(&[], &[], "");
case(&["foo"], &["foo"], "foo");
case(&["foo0"], &["foo0"], "foo0");
case(&["foo", "bar"], &["foo", "bar"], "foo bar");
case(&["foo::bar"], &["foo", "bar"], "foo::bar");
}
#[test]
fn try_from_err() {
#[track_caller]
fn case(path: &[&str]) {
assert!(ModulePath::try_from(path).is_err());
}
case(&[":foo"]);
case(&["foo:"]);
case(&["foo:::bar"]);
case(&["0foo"]);
case(&["f$oo"]);
case(&[""]);
}
}

View File

@ -18,11 +18,11 @@ use {super::*, TokenKind::*};
/// All methods starting with `parse_*` parse and return a language construct. /// All methods starting with `parse_*` parse and return a language construct.
/// ///
/// The parser tracks an expected set of tokens as it parses. This set contains /// The parser tracks an expected set of tokens as it parses. This set contains
/// all tokens which would have been accepted at the current point in the /// all tokens which would have been accepted at the current point in the parse.
/// parse. Whenever the parser tests for a token that would be accepted, but /// Whenever the parser tests for a token that would be accepted, but does not
/// does not find it, it adds that token to the set. When the parser accepts a /// find it, it adds that token to the set. When the parser accepts a token, the
/// token, the set is cleared. If the parser finds a token which is unexpected, /// set is cleared. If the parser finds a token which is unexpected, the
/// the elements of the set are printed in the resultant error message. /// contents of the set is printed in the resultant error message.
pub(crate) struct Parser<'run, 'src> { pub(crate) struct Parser<'run, 'src> {
expected_tokens: BTreeSet<TokenKind>, expected_tokens: BTreeSet<TokenKind>,
file_depth: u32, file_depth: u32,

View File

@ -147,7 +147,9 @@ impl<'src, D> Recipe<'src, D> {
pub(crate) fn run<'run>( pub(crate) fn run<'run>(
&self, &self,
context: &RecipeContext<'src, 'run>, context: &RecipeContext<'src, 'run>,
scope: &Scope<'src, 'run>, dotenv: &BTreeMap<String, String>,
scope: Scope<'src, 'run>,
search: &'run Search,
positional: &[String], positional: &[String],
) -> RunResult<'src, ()> { ) -> RunResult<'src, ()> {
let config = &context.config; let config = &context.config;
@ -162,25 +164,20 @@ impl<'src, D> Recipe<'src, D> {
); );
} }
let evaluator = Evaluator::recipe_evaluator( let evaluator =
context.config, Evaluator::recipe_evaluator(context.config, dotenv, &scope, context.settings, search);
context.dotenv,
context.module_source,
scope,
context.search,
context.settings,
);
if self.shebang { if self.shebang {
self.run_shebang(context, scope, positional, config, evaluator) self.run_shebang(context, dotenv, &scope, positional, config, evaluator)
} else { } else {
self.run_linewise(context, scope, positional, config, evaluator) self.run_linewise(context, dotenv, &scope, positional, config, evaluator)
} }
} }
fn run_linewise<'run>( fn run_linewise<'run>(
&self, &self,
context: &RecipeContext<'src, 'run>, context: &RecipeContext<'src, 'run>,
dotenv: &BTreeMap<String, String>,
scope: &Scope<'src, 'run>, scope: &Scope<'src, 'run>,
positional: &[String], positional: &[String],
config: &Config, config: &Config,
@ -237,24 +234,12 @@ impl<'src, D> Recipe<'src, D> {
|| (context.settings.quiet && !self.no_quiet()) || (context.settings.quiet && !self.no_quiet())
|| config.verbosity.quiet()) || config.verbosity.quiet())
{ {
let color = config let color = if config.highlight {
.highlight config.color.command(config.command_color)
.then(|| config.color.command(config.command_color)) } else {
.unwrap_or(config.color) config.color
.stderr(); };
eprintln!("{}", color.stderr().paint(command));
if config.timestamp {
eprint!(
"[{}] ",
color.paint(
&chrono::Local::now()
.format(&config.timestamp_format)
.to_string()
),
);
}
eprintln!("{}", color.paint(command));
} }
if config.dry_run { if config.dry_run {
@ -279,7 +264,7 @@ impl<'src, D> Recipe<'src, D> {
cmd.stdout(Stdio::null()); cmd.stdout(Stdio::null());
} }
cmd.export(context.settings, context.dotenv, scope); cmd.export(context.settings, dotenv, scope);
match InterruptHandler::guard(|| cmd.status()) { match InterruptHandler::guard(|| cmd.status()) {
Ok(exit_status) => { Ok(exit_status) => {
@ -313,6 +298,7 @@ impl<'src, D> Recipe<'src, D> {
pub(crate) fn run_shebang<'run>( pub(crate) fn run_shebang<'run>(
&self, &self,
context: &RecipeContext<'src, 'run>, context: &RecipeContext<'src, 'run>,
dotenv: &BTreeMap<String, String>,
scope: &Scope<'src, 'run>, scope: &Scope<'src, 'run>,
positional: &[String], positional: &[String],
config: &Config, config: &Config,
@ -425,7 +411,7 @@ impl<'src, D> Recipe<'src, D> {
command.args(positional); command.args(positional);
} }
command.export(context.settings, context.dotenv, scope); command.export(context.settings, dotenv, scope);
// run it! // run it!
match InterruptHandler::guard(|| command.status()) { match InterruptHandler::guard(|| command.status()) {

View File

@ -2,8 +2,6 @@ use super::*;
pub(crate) struct RecipeContext<'src: 'run, 'run> { pub(crate) struct RecipeContext<'src: 'run, 'run> {
pub(crate) config: &'run Config, pub(crate) config: &'run Config,
pub(crate) dotenv: &'run BTreeMap<String, String>,
pub(crate) module_source: &'run Path,
pub(crate) scope: &'run Scope<'src, 'run>, pub(crate) scope: &'run Scope<'src, 'run>,
pub(crate) search: &'run Search, pub(crate) search: &'run Search,
pub(crate) settings: &'run Settings<'src>, pub(crate) settings: &'run Settings<'src>,

View File

@ -31,16 +31,14 @@ pub(crate) enum Subcommand {
Format, Format,
Groups, Groups,
Init, Init,
List { List,
path: ModulePath,
},
Man, Man,
Run { Run {
arguments: Vec<String>, arguments: Vec<String>,
overrides: BTreeMap<String, String>, overrides: BTreeMap<String, String>,
}, },
Show { Show {
path: ModulePath, name: String,
}, },
Summary, Summary,
Variables, Variables,
@ -90,8 +88,8 @@ impl Subcommand {
Dump => Self::dump(config, ast, justfile)?, Dump => Self::dump(config, ast, justfile)?,
Format => Self::format(config, &search, src, ast)?, Format => Self::format(config, &search, src, ast)?,
Groups => Self::groups(config, justfile), Groups => Self::groups(config, justfile),
List { path } => Self::list(config, justfile, path)?, List => Self::list(config, 0, justfile),
Show { path } => Self::show(config, justfile, path)?, Show { ref name } => Self::show(config, name, justfile)?,
Summary => Self::summary(config, justfile), Summary => Self::summary(config, justfile),
Variables => Self::variables(justfile), Variables => Self::variables(justfile),
Changelog | Completions { .. } | Edit | Init | Man | Run { .. } => unreachable!(), Changelog | Completions { .. } | Edit | Init | Man | Run { .. } => unreachable!(),
@ -479,19 +477,16 @@ impl Subcommand {
Ok(()) Ok(())
} }
fn list(config: &Config, mut module: &Justfile, path: &ModulePath) -> Result<(), Error<'static>> { fn list(config: &Config, level: usize, justfile: &Justfile) {
for name in &path.path {
module = module
.modules
.get(name)
.ok_or_else(|| Error::UnknownSubmodule { path: path.clone() })?;
}
let aliases = if config.no_aliases { let aliases = if config.no_aliases {
BTreeMap::new() BTreeMap::new()
} else { } else {
let mut aliases = BTreeMap::<&str, Vec<&str>>::new(); let mut aliases = BTreeMap::<&str, Vec<&str>>::new();
for alias in module.aliases.values().filter(|alias| !alias.is_private()) { for alias in justfile
.aliases
.values()
.filter(|alias| !alias.is_private())
{
aliases aliases
.entry(alias.target.name.lexeme()) .entry(alias.target.name.lexeme())
.or_default() .or_default()
@ -503,7 +498,7 @@ impl Subcommand {
let signature_widths = { let signature_widths = {
let mut signature_widths: BTreeMap<&str, usize> = BTreeMap::new(); let mut signature_widths: BTreeMap<&str, usize> = BTreeMap::new();
for (name, recipe) in &module.recipes { for (name, recipe) in &justfile.recipes {
if !recipe.is_public() { if !recipe.is_public() {
continue; continue;
} }
@ -531,11 +526,13 @@ impl Subcommand {
.max() .max()
.unwrap_or(0); .unwrap_or(0);
print!("{}", config.list_heading); if level == 0 {
print!("{}", config.list_heading);
}
let groups = { let groups = {
let mut groups = BTreeMap::<Option<String>, Vec<&Recipe>>::new(); let mut groups = BTreeMap::<Option<String>, Vec<&Recipe>>::new();
for recipe in module.public_recipes(config) { for recipe in justfile.public_recipes(config) {
let recipe_groups = recipe.groups(); let recipe_groups = recipe.groups();
if recipe_groups.is_empty() { if recipe_groups.is_empty() {
groups.entry(None).or_default().push(recipe); groups.entry(None).or_default().push(recipe);
@ -556,7 +553,7 @@ impl Subcommand {
let no_groups = groups.contains_key(&None) && groups.len() == 1; let no_groups = groups.contains_key(&None) && groups.len() == 1;
if !no_groups { if !no_groups {
print!("{}", config.list_prefix); print!("{}", config.list_prefix.repeat(level + 1));
if let Some(group_name) = group { if let Some(group_name) = group {
println!("[{group_name}]"); println!("[{group_name}]");
} else { } else {
@ -580,7 +577,7 @@ impl Subcommand {
for line in doc.lines() { for line in doc.lines() {
println!( println!(
"{}{} {}", "{}{} {}",
config.list_prefix, config.list_prefix.repeat(level + 1),
config.color.stdout().doc().paint("#"), config.color.stdout().doc().paint("#"),
config.color.stdout().doc().paint(line), config.color.stdout().doc().paint(line),
); );
@ -590,7 +587,7 @@ impl Subcommand {
print!( print!(
"{}{}", "{}{}",
config.list_prefix, config.list_prefix.repeat(level + 1),
RecipeSignature { name, recipe }.color_display(config.color.stdout()) RecipeSignature { name, recipe }.color_display(config.color.stdout())
); );
@ -610,39 +607,29 @@ impl Subcommand {
} }
} }
for submodule in module.modules(config) { for (i, module) in justfile.modules(config).into_iter().enumerate() {
println!("{}{} ...", config.list_prefix, submodule.name(),); if i + groups.len() > 0 {
} println!();
}
Ok(()) println!("{}{}:", config.list_prefix.repeat(level + 1), module.name());
Self::list(config, level + 1, module);
}
} }
fn show<'src>( fn show<'src>(config: &Config, name: &str, justfile: &Justfile<'src>) -> Result<(), Error<'src>> {
config: &Config, if let Some(alias) = justfile.get_alias(name) {
mut module: &Justfile<'src>, let recipe = justfile.get_recipe(alias.target.name.lexeme()).unwrap();
path: &ModulePath,
) -> Result<(), Error<'src>> {
for name in &path.path[0..path.path.len() - 1] {
module = module
.modules
.get(name)
.ok_or_else(|| Error::UnknownSubmodule { path: path.clone() })?;
}
let name = path.path.last().unwrap();
if let Some(alias) = module.get_alias(name) {
let recipe = module.get_recipe(alias.target.name.lexeme()).unwrap();
println!("{alias}"); println!("{alias}");
println!("{}", recipe.color_display(config.color.stdout())); println!("{}", recipe.color_display(config.color.stdout()));
Ok(()) Ok(())
} else if let Some(recipe) = module.get_recipe(name) { } else if let Some(recipe) = justfile.get_recipe(name) {
println!("{}", recipe.color_display(config.color.stdout())); println!("{}", recipe.color_display(config.color.stdout()));
Ok(()) Ok(())
} else { } else {
Err(Error::UnknownRecipes { Err(Error::UnknownRecipes {
recipes: vec![name.to_owned()], recipes: vec![name.to_owned()],
suggestion: module.suggest_recipe(name), suggestion: justfile.suggest_recipe(name),
}) })
} }
} }

View File

@ -904,126 +904,3 @@ fn source_directory() {
.stdout_regex(r".*[/\\]foo\n") .stdout_regex(r".*[/\\]foo\n")
.run(); .run();
} }
#[test]
fn module_paths() {
Test::new()
.write(
"foo/bar.just",
"
imf := module_file()
imd := module_directory()
import-outer: import-inner
@import-inner pmf=module_file() pmd=module_directory():
echo import
echo '{{ imf }}'
echo '{{ imd }}'
echo '{{ pmf }}'
echo '{{ pmd }}'
echo '{{ module_file() }}'
echo '{{ module_directory() }}'
",
)
.write(
"baz/mod.just",
"
import 'foo/bar.just'
mmf := module_file()
mmd := module_directory()
outer: inner
@inner pmf=module_file() pmd=module_directory():
echo module
echo '{{ mmf }}'
echo '{{ mmd }}'
echo '{{ pmf }}'
echo '{{ pmd }}'
echo '{{ module_file() }}'
echo '{{ module_directory() }}'
",
)
.write(
"baz/foo/bar.just",
"
imf := module_file()
imd := module_directory()
import-outer: import-inner
@import-inner pmf=module_file() pmd=module_directory():
echo import
echo '{{ imf }}'
echo '{{ imd }}'
echo '{{ pmf }}'
echo '{{ pmd }}'
echo '{{ module_file() }}'
echo '{{ module_directory() }}'
",
)
.justfile(
"
import 'foo/bar.just'
mod baz
rmf := module_file()
rmd := module_directory()
outer: inner
@inner pmf=module_file() pmd=module_directory():
echo root
echo '{{ rmf }}'
echo '{{ rmd }}'
echo '{{ pmf }}'
echo '{{ pmd }}'
echo '{{ module_file() }}'
echo '{{ module_directory() }}'
",
)
.test_round_trip(false)
.args([
"--unstable",
"outer",
"import-outer",
"baz",
"outer",
"baz",
"import-outer",
])
.stdout_regex(
r"root
.*[/\\]just-test-tempdir......[/\\]justfile
.*[/\\]just-test-tempdir......
.*[/\\]just-test-tempdir......[/\\]justfile
.*[/\\]just-test-tempdir......
.*[/\\]just-test-tempdir......[/\\]justfile
.*[/\\]just-test-tempdir......
import
.*[/\\]just-test-tempdir......[/\\]justfile
.*[/\\]just-test-tempdir......
.*[/\\]just-test-tempdir......[/\\]justfile
.*[/\\]just-test-tempdir......
.*[/\\]just-test-tempdir......[/\\]justfile
.*[/\\]just-test-tempdir......
module
.*[/\\]just-test-tempdir......[/\\]baz[/\\]mod.just
.*[/\\]just-test-tempdir......[/\\]baz
.*[/\\]just-test-tempdir......[/\\]baz[/\\]mod.just
.*[/\\]just-test-tempdir......[/\\]baz
.*[/\\]just-test-tempdir......[/\\]baz[/\\]mod.just
.*[/\\]just-test-tempdir......[/\\]baz
import
.*[/\\]just-test-tempdir......[/\\]baz[/\\]mod.just
.*[/\\]just-test-tempdir......[/\\]baz
.*[/\\]just-test-tempdir......[/\\]baz[/\\]mod.just
.*[/\\]just-test-tempdir......[/\\]baz
.*[/\\]just-test-tempdir......[/\\]baz[/\\]mod.just
.*[/\\]just-test-tempdir......[/\\]baz
",
)
.run();
}

View File

@ -144,3 +144,31 @@ fn list_groups_with_custom_prefix() {
) )
.run(); .run();
} }
#[test]
fn list_with_groups_in_modules() {
Test::new()
.justfile(
"
[group('FOO')]
foo:
mod bar
",
)
.write("bar.just", "[group('BAZ')]\nbaz:")
.test_round_trip(false)
.args(["--unstable", "--list"])
.stdout(
"
Available recipes:
[FOO]
foo
bar:
[BAZ]
baz
",
)
.run();
}

View File

@ -102,7 +102,6 @@ mod string;
mod subsequents; mod subsequents;
mod summary; mod summary;
mod tempdir; mod tempdir;
mod timestamps;
mod undefined_variables; mod undefined_variables;
mod unstable; mod unstable;
#[cfg(target_family = "windows")] #[cfg(target_family = "windows")]

View File

@ -1,5 +1,54 @@
use super::*; use super::*;
#[test]
fn list_displays_recipes_in_submodules() {
Test::new()
.write("foo.just", "bar:\n @echo FOO")
.justfile(
"
mod foo
",
)
.test_round_trip(false)
.arg("--unstable")
.arg("--list")
.stdout(
"
Available recipes:
foo:
bar
",
)
.run();
}
#[test]
fn modules_are_space_separated_in_output() {
Test::new()
.write("foo.just", "foo:")
.write("bar.just", "bar:")
.justfile(
"
mod foo
mod bar
",
)
.test_round_trip(false)
.args(["--unstable", "--list"])
.stdout(
"
Available recipes:
bar:
bar
foo:
foo
",
)
.run();
}
#[test] #[test]
fn modules_unsorted() { fn modules_unsorted() {
Test::new() Test::new()
@ -17,13 +66,72 @@ fn modules_unsorted() {
.stdout( .stdout(
" "
Available recipes: Available recipes:
foo ... foo:
bar ... foo
bar:
bar
", ",
) )
.run(); .run();
} }
#[test]
fn module_recipe_list_alignment_ignores_private_recipes() {
Test::new()
.write(
"foo.just",
"
# foos
foo:
@echo FOO
[private]
barbarbar:
@echo BAR
@_bazbazbaz:
@echo BAZ
",
)
.justfile("mod foo")
.test_round_trip(false)
.arg("--unstable")
.arg("--list")
.stdout(
"
Available recipes:
foo:
foo # foos
",
)
.run();
}
#[test]
fn nested_modules_are_properly_indented() {
Test::new()
.write("foo.just", "mod bar")
.write("bar.just", "baz:\n @echo FOO")
.justfile(
"
mod foo
",
)
.test_round_trip(false)
.arg("--unstable")
.arg("--list")
.stdout(
"
Available recipes:
foo:
bar:
baz
",
)
.run();
}
#[test] #[test]
fn unsorted_list_order() { fn unsorted_list_order() {
Test::new() Test::new()
@ -146,80 +254,3 @@ fn unsorted_list_order() {
) )
.run(); .run();
} }
#[test]
fn list_submodule() {
Test::new()
.write("foo.just", "bar:")
.justfile(
"
mod foo
",
)
.test_round_trip(false)
.args(["--unstable", "--list", "foo"])
.stdout(
"
Available recipes:
bar
",
)
.run();
}
#[test]
fn list_nested_submodule() {
Test::new()
.write("foo.just", "mod bar")
.write("bar.just", "baz:")
.justfile(
"
mod foo
",
)
.test_round_trip(false)
.args(["--unstable", "--list", "foo", "bar"])
.stdout(
"
Available recipes:
baz
",
)
.run();
Test::new()
.write("foo.just", "mod bar")
.write("bar.just", "baz:")
.justfile(
"
mod foo
",
)
.test_round_trip(false)
.args(["--unstable", "--list", "foo::bar"])
.stdout(
"
Available recipes:
baz
",
)
.run();
}
#[test]
fn list_invalid_path() {
Test::new()
.args(["--unstable", "--list", "$hello"])
.stderr("error: Invalid module path `$hello`\n")
.status(1)
.run();
}
#[test]
fn list_unknown_submodule() {
Test::new()
.args(["--unstable", "--list", "hello"])
.stderr("error: Justfile does not contain submodule `hello`\n")
.status(1)
.run();
}

View File

@ -100,27 +100,3 @@ a Z="\t z":
stderr: "error: Justfile does not contain recipe `fooooooo`.\n", stderr: "error: Justfile does not contain recipe `fooooooo`.\n",
status: EXIT_FAILURE, status: EXIT_FAILURE,
} }
#[test]
fn show_recipe_at_path() {
Test::new()
.write("foo.just", "bar:\n @echo MODULE")
.justfile(
"
mod foo
",
)
.test_round_trip(false)
.args(["--unstable", "--show", "foo::bar"])
.stdout("bar:\n @echo MODULE\n")
.run();
}
#[test]
fn show_invalid_path() {
Test::new()
.args(["--show", "$hello"])
.stderr("error: Invalid module path `$hello`\n")
.status(1)
.run();
}

View File

@ -1,31 +0,0 @@
use super::*;
#[test]
fn print_timestamps() {
Test::new()
.justfile(
"
recipe:
echo 'one'
",
)
.arg("--timestamp")
.stderr_regex(concat!(r"\[\d\d:\d\d:\d\d\] echo 'one'", "\n"))
.stdout("one\n")
.run();
}
#[test]
fn print_timestamps_with_format_string() {
Test::new()
.justfile(
"
recipe:
echo 'one'
",
)
.args(["--timestamp", "--timestamp-format", "%H:%M:%S.%3f"])
.stderr_regex(concat!(r"\[\d\d:\d\d:\d\d\.\d\d\d] echo 'one'", "\n"))
.stdout("one\n")
.run();
}