mirror of
https://github.com/casey/just.git
synced 2025-12-23 11:37:29 +00:00
Merge branch 'master' into fish-shell-completion-fix
This commit is contained in:
commit
ef98cedf4d
38 changed files with 1548 additions and 538 deletions
10
.github/workflows/ci.yaml
vendored
10
.github/workflows/ci.yaml
vendored
|
|
@ -20,7 +20,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
|
|
@ -45,11 +45,11 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
toolchain: 1.77
|
||||
toolchain: 1.80.0
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
|
|
@ -60,7 +60,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
|
|
@ -93,7 +93,7 @@ jobs:
|
|||
runs-on: ${{matrix.os}}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Remove Broken WSL bash executable
|
||||
if: ${{ matrix.os == 'windows-latest' }}
|
||||
|
|
|
|||
63
.github/workflows/release.yaml
vendored
63
.github/workflows/release.yaml
vendored
|
|
@ -34,17 +34,21 @@ jobs:
|
|||
matrix:
|
||||
target:
|
||||
- aarch64-apple-darwin
|
||||
- aarch64-pc-windows-msvc
|
||||
- aarch64-unknown-linux-musl
|
||||
- arm-unknown-linux-musleabihf
|
||||
- armv7-unknown-linux-musleabihf
|
||||
- loongarch64-unknown-linux-musl
|
||||
- x86_64-apple-darwin
|
||||
- x86_64-pc-windows-msvc
|
||||
- aarch64-pc-windows-msvc
|
||||
- x86_64-unknown-linux-musl
|
||||
include:
|
||||
- target: aarch64-apple-darwin
|
||||
os: macos-latest
|
||||
target_rustflags: ''
|
||||
- target: aarch64-pc-windows-msvc
|
||||
os: windows-latest
|
||||
target_rustflags: ''
|
||||
- target: aarch64-unknown-linux-musl
|
||||
os: ubuntu-latest
|
||||
target_rustflags: '--codegen linker=aarch64-linux-gnu-gcc'
|
||||
|
|
@ -54,14 +58,14 @@ jobs:
|
|||
- target: armv7-unknown-linux-musleabihf
|
||||
os: ubuntu-latest
|
||||
target_rustflags: '--codegen linker=arm-linux-gnueabihf-gcc'
|
||||
- target: loongarch64-unknown-linux-musl
|
||||
os: ubuntu-latest
|
||||
target_rustflags: '--codegen linker=loongarch64-linux-gnu-gcc-14'
|
||||
- target: x86_64-apple-darwin
|
||||
os: macos-latest
|
||||
target_rustflags: ''
|
||||
- target: x86_64-pc-windows-msvc
|
||||
os: windows-latest
|
||||
- target: aarch64-pc-windows-msvc
|
||||
os: windows-latest
|
||||
target_rustflags: ''
|
||||
- target: x86_64-unknown-linux-musl
|
||||
os: ubuntu-latest
|
||||
target_rustflags: ''
|
||||
|
|
@ -72,33 +76,32 @@ jobs:
|
|||
- prerelease
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Install AArch64 Toolchain
|
||||
if: ${{ matrix.target == 'aarch64-unknown-linux-musl' }}
|
||||
- name: Install Target Dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install gcc-aarch64-linux-gnu libc6-dev-i386
|
||||
case ${{ matrix.target }} in
|
||||
aarch64-pc-windows-msvc)
|
||||
rustup target add aarch64-pc-windows-msvc
|
||||
;;
|
||||
aarch64-unknown-linux-musl)
|
||||
sudo apt-get update
|
||||
sudo apt-get install gcc-aarch64-linux-gnu libc6-dev-i386
|
||||
;;
|
||||
arm-unknown-linux-musleabihf|armv7-unknown-linux-musleabihf)
|
||||
sudo apt-get update
|
||||
sudo apt-get install gcc-arm-linux-gnueabihf
|
||||
;;
|
||||
loongarch64-unknown-linux-musl)
|
||||
sudo apt-get update
|
||||
sudo apt-get install gcc-14-loongarch64-linux-gnu
|
||||
;;
|
||||
esac
|
||||
|
||||
- name: Install ARM Toolchain
|
||||
if: ${{ matrix.target == 'arm-unknown-linux-musleabihf' || matrix.target == 'armv7-unknown-linux-musleabihf' }}
|
||||
run: |
|
||||
sudo apt-get update
|
||||
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: Generate Completion Scripts and Manpage
|
||||
- name: Generate Manpage
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
cargo build
|
||||
mkdir -p completions
|
||||
for shell in bash elvish fish nu powershell zsh; do
|
||||
./target/debug/just --completions $shell > completions/just.$shell
|
||||
done
|
||||
mkdir -p man
|
||||
./target/debug/just --man > man/just.1
|
||||
|
||||
|
|
@ -113,7 +116,7 @@ jobs:
|
|||
shell: bash
|
||||
|
||||
- name: Publish Archive
|
||||
uses: softprops/action-gh-release@v2.3.2
|
||||
uses: softprops/action-gh-release@v2.4.2
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
with:
|
||||
draft: false
|
||||
|
|
@ -123,7 +126,7 @@ jobs:
|
|||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Publish Changelog
|
||||
uses: softprops/action-gh-release@v2.3.2
|
||||
uses: softprops/action-gh-release@v2.4.2
|
||||
if: >-
|
||||
${{
|
||||
startsWith(github.ref, 'refs/tags/')
|
||||
|
|
@ -160,7 +163,7 @@ jobs:
|
|||
shasum -a 256 * > ../SHA256SUMS
|
||||
|
||||
- name: Publish Checksums
|
||||
uses: softprops/action-gh-release@v2.3.2
|
||||
uses: softprops/action-gh-release@v2.4.2
|
||||
with:
|
||||
draft: false
|
||||
files: SHA256SUMS
|
||||
|
|
@ -178,7 +181,7 @@ jobs:
|
|||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
|
|
@ -202,7 +205,7 @@ jobs:
|
|||
|
||||
- name: Deploy Pages
|
||||
uses: peaceiris/actions-gh-pages@v4
|
||||
if: ${{ needs.prerelease.outputs.value }}
|
||||
if: ${{ !needs.prerelease.outputs.value }}
|
||||
with:
|
||||
github_token: ${{secrets.GITHUB_TOKEN}}
|
||||
publish_branch: gh-pages
|
||||
|
|
|
|||
36
CHANGELOG.md
36
CHANGELOG.md
|
|
@ -1,6 +1,42 @@
|
|||
Changelog
|
||||
=========
|
||||
|
||||
[1.43.1](https://github.com/casey/just/releases/tag/1.43.1) - 2025-11-12
|
||||
------------------------------------------------------------------------
|
||||
|
||||
### Fixed
|
||||
- Only initialize signal handler once ([#2953](https://github.com/casey/just/pull/2953) by [casey](https://github.com/casey))
|
||||
- Preserve module docs when formatting ([#2931](https://github.com/casey/just/pull/2931) by [casey](https://github.com/casey))
|
||||
- Preserve module groups when formatting ([#2930](https://github.com/casey/just/pull/2930) by [casey](https://github.com/casey))
|
||||
- Don't suggest private recipes and aliases ([#2916](https://github.com/casey/just/pull/2916) by [casey](https://github.com/casey))
|
||||
|
||||
### Misc
|
||||
- Update softprops/action-gh-release to 2.4.2 ([#2948](https://github.com/casey/just/pull/2948) by [app/dependabot](https://github.com/app/dependabot))
|
||||
- Fix `env()` usage in readme ([#2936](https://github.com/casey/just/pull/2936) by [laniakea64](https://github.com/laniakea64))
|
||||
- Use a case statement to install target dependencies ([#2929](https://github.com/casey/just/pull/2929) by [casey](https://github.com/casey))
|
||||
- Build loongarch64 release binaries ([#2886](https://github.com/casey/just/pull/2886) by [SkyBird233](https://github.com/SkyBird233))
|
||||
- Bump softprops/action-gh-release to 2.4.1 ([#2919](https://github.com/casey/just/pull/2919) by [app/dependabot](https://github.com/app/dependabot))
|
||||
- Update softprops/action-gh-release to 2.3.4 ([#2910](https://github.com/casey/just/pull/2910) by [app/dependabot](https://github.com/app/dependabot))
|
||||
|
||||
[1.43.0](https://github.com/casey/just/releases/tag/1.43.0) - 2025-09-27
|
||||
------------------------------------------------------------------------
|
||||
|
||||
### Added
|
||||
- Add `[default]` attribute ([#2878](https://github.com/casey/just/pull/2878) by [casey](https://github.com/casey))
|
||||
- Do not ascend above `--ceiling` when looking for justfile ([#2870](https://github.com/casey/just/pull/2870) by [casey](https://github.com/casey))
|
||||
|
||||
### Misc
|
||||
- Don't generate completions at runtime ([#2896](https://github.com/casey/just/pull/2896) by [casey](https://github.com/casey))
|
||||
- Update `softprops/action-gh-release` to 2.3.3 ([#2879](https://github.com/casey/just/pull/2879) by [app/dependabot](https://github.com/app/dependabot))
|
||||
- Add submodule alias and dependency targets to grammar ([#2877](https://github.com/casey/just/pull/2877) by [casey](https://github.com/casey))
|
||||
- Bump `actions/checkout` to v5 ([#2864](https://github.com/casey/just/pull/2864) by [app/dependabot](https://github.com/app/dependabot))
|
||||
- Fix Windows `PATH_SEP` value in readme ([#2859](https://github.com/casey/just/pull/2859) by [casey](https://github.com/casey))
|
||||
- Fix lints for Rust 1.89 ([#2860](https://github.com/casey/just/pull/2860) by [casey](https://github.com/casey))
|
||||
- Note that Debian 13 has been released ([#2856](https://github.com/casey/just/pull/2856) by [sblondon](https://github.com/sblondon))
|
||||
- Mention `just-mcp` in readme ([#2843](https://github.com/casey/just/pull/2843) by [casey](https://github.com/casey))
|
||||
- Expand Windows instructions in readme ([#2842](https://github.com/casey/just/pull/2842) by [casey](https://github.com/casey))
|
||||
- Note `[parallel]` attribute in parallelism section ([#2837](https://github.com/casey/just/pull/2837) by [casey](https://github.com/casey))
|
||||
|
||||
[1.42.4](https://github.com/casey/just/releases/tag/1.42.4) - 2025-07-24
|
||||
------------------------------------------------------------------------
|
||||
|
||||
|
|
|
|||
548
Cargo.lock
generated
548
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
10
Cargo.toml
10
Cargo.toml
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "just"
|
||||
version = "1.42.4"
|
||||
version = "1.43.1"
|
||||
authors = ["Casey Rodarmor <casey@rodarmor.com>"]
|
||||
autotests = false
|
||||
categories = ["command-line-utilities", "development-tools"]
|
||||
|
|
@ -12,7 +12,7 @@ keywords = ["command-line", "task", "runner", "development", "utility"]
|
|||
license = "CC0-1.0"
|
||||
readme = "crates-io-readme.md"
|
||||
repository = "https://github.com/casey/just"
|
||||
rust-version = "1.77"
|
||||
rust-version = "1.80.0"
|
||||
|
||||
[workspace]
|
||||
members = [".", "crates/*"]
|
||||
|
|
@ -23,9 +23,7 @@ blake3 = { version = "1.5.0", features = ["rayon", "mmap"] }
|
|||
camino = "1.0.4"
|
||||
chrono = "0.4.38"
|
||||
clap = { version = "4.0.0", features = ["derive", "env", "wrap_help"] }
|
||||
clap_complete = "=4.5.48"
|
||||
clap_mangen = "0.2.20"
|
||||
ctrlc = { version = "3.1.1", features = ["termination"] }
|
||||
derive-where = "1.2.7"
|
||||
dirs = "6.0.0"
|
||||
dotenvy = "0.15"
|
||||
|
|
@ -35,7 +33,6 @@ is_executable = "1.0.4"
|
|||
lexiclean = "0.0.1"
|
||||
libc = "0.2.0"
|
||||
num_cpus = "1.15.0"
|
||||
once_cell = "1.19.0"
|
||||
percent-encoding = "2.3.1"
|
||||
rand = "0.9.0"
|
||||
regex = "1.10.4"
|
||||
|
|
@ -55,12 +52,13 @@ unicode-width = "0.2.0"
|
|||
uuid = { version = "1.0.0", features = ["v4"] }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
nix = { version = "0.30.1", features = ["user"] }
|
||||
nix = { version = "0.30.1", features = ["signal", "user"] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
ctrlc = { version = "3.1.1", features = ["termination"] }
|
||||
|
||||
[dev-dependencies]
|
||||
clap_complete = "=4.5.48"
|
||||
executable-path = "1.0.0"
|
||||
pretty_assertions = "1.0.0"
|
||||
temptree = "0.2.0"
|
||||
|
|
|
|||
|
|
@ -55,7 +55,9 @@ item : alias
|
|||
eol : NEWLINE
|
||||
| COMMENT NEWLINE
|
||||
|
||||
alias : 'alias' NAME ':=' NAME eol
|
||||
alias : 'alias' NAME ':=' target eol
|
||||
|
||||
target : NAME ('::' NAME)*
|
||||
|
||||
assignment : NAME ':=' expression eol
|
||||
|
||||
|
|
@ -138,8 +140,8 @@ variadic : '*' parameter
|
|||
|
||||
dependencies : dependency* ('&&' dependency+)?
|
||||
|
||||
dependency : NAME
|
||||
| '(' NAME expression* ')'
|
||||
dependency : target
|
||||
| '(' target expression* ')'
|
||||
|
||||
body : INDENT line+ DEDENT
|
||||
|
||||
|
|
|
|||
12
README.md
12
README.md
|
|
@ -723,9 +723,12 @@ Features
|
|||
|
||||
### The Default Recipe
|
||||
|
||||
When `just` is invoked without a recipe, it runs the first recipe in the
|
||||
`justfile`. This recipe might be the most frequently run command in the
|
||||
project, like running the tests:
|
||||
When `just` is invoked without a recipe, it runs the recipe with the
|
||||
`[default]` attribute, or the first recipe in the `justfile` if no recipe has
|
||||
the `[default]` attribute.
|
||||
|
||||
This recipe might be the most frequently run command in the project, like
|
||||
running the tests:
|
||||
|
||||
```just
|
||||
test:
|
||||
|
|
@ -1718,7 +1721,7 @@ A default can be substituted for an empty environment variable value with the
|
|||
```just
|
||||
set unstable
|
||||
|
||||
foo := env('FOO') || 'DEFAULT_VALUE'
|
||||
foo := env('FOO', '') || 'DEFAULT_VALUE'
|
||||
```
|
||||
|
||||
#### Executables
|
||||
|
|
@ -2107,6 +2110,7 @@ change their behavior.
|
|||
|------|------|-------------|
|
||||
| `[confirm]`<sup>1.17.0</sup> | recipe | Require confirmation prior to executing recipe. |
|
||||
| `[confirm(PROMPT)]`<sup>1.23.0</sup> | recipe | Require confirmation prior to executing recipe with a custom prompt. |
|
||||
| `[default]`<sup>1.43.0</sup> | recipe | Use recipe as module's default recipe. |
|
||||
| `[doc(DOC)]`<sup>1.27.0</sup> | module, recipe | Set recipe or module's [documentation comment](#documentation-comments) to `DOC`. |
|
||||
| `[extension(EXT)]`<sup>1.32.0</sup> | recipe | Set shebang recipe script's file extension to `EXT`. `EXT` should include a period if one is desired. |
|
||||
| `[group(NAME)]`<sup>1.27.0</sup> | module, recipe | Put recipe or module in in [group](#groups) `NAME`. |
|
||||
|
|
|
|||
185
completions/just.bash
Normal file
185
completions/just.bash
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
_just() {
|
||||
local i cur prev words cword opts cmd
|
||||
COMPREPLY=()
|
||||
|
||||
# Modules use "::" as the separator, which is considered a wordbreak character in bash.
|
||||
# The _get_comp_words_by_ref function is a hack to allow for exceptions to this rule without
|
||||
# modifying the global COMP_WORDBREAKS environment variable.
|
||||
if type _get_comp_words_by_ref &>/dev/null; then
|
||||
_get_comp_words_by_ref -n : cur prev words cword
|
||||
else
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||
words=$COMP_WORDS
|
||||
cword=$COMP_CWORD
|
||||
fi
|
||||
|
||||
cmd=""
|
||||
opts=""
|
||||
|
||||
for i in ${words[@]}
|
||||
do
|
||||
case "${cmd},${i}" in
|
||||
",$1")
|
||||
cmd="just"
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
case "${cmd}" in
|
||||
just)
|
||||
opts="-E -n -g -f -q -u -v -d -c -e -l -s -h -V --alias-style --ceiling --check --chooser --clear-shell-args --color --command-color --cygpath --dotenv-filename --dotenv-path --dry-run --dump-format --explain --global-justfile --highlight --justfile --list-heading --list-prefix --list-submodules --no-aliases --no-deps --no-dotenv --no-highlight --one --quiet --allow-missing --set --shell --shell-arg --shell-command --tempdir --timestamp --timestamp-format --unsorted --unstable --verbose --working-directory --yes --changelog --choose --command --completions --dump --edit --evaluate --fmt --groups --init --list --man --request --show --summary --variables --help --version [ARGUMENTS]..."
|
||||
if [[ ${cur} == -* ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
else
|
||||
local recipes=$(just --summary 2> /dev/null)
|
||||
|
||||
if echo "${cur}" | \grep -qF '/'; then
|
||||
local path_prefix=$(echo "${cur}" | sed 's/[/][^/]*$/\//')
|
||||
local recipes=$(just --summary 2> /dev/null -- "${path_prefix}")
|
||||
local recipes=$(printf "${path_prefix}%s\t" $recipes)
|
||||
fi
|
||||
|
||||
if [[ $? -eq 0 ]]; then
|
||||
COMPREPLY=( $(compgen -W "${recipes}" -- "${cur}") )
|
||||
if type __ltrim_colon_completions &>/dev/null; then
|
||||
__ltrim_colon_completions "$cur"
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
case "${prev}" in
|
||||
--alias-style)
|
||||
COMPREPLY=($(compgen -W "left right separate" -- "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
--ceiling)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
--chooser)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
--color)
|
||||
COMPREPLY=($(compgen -W "always auto never" -- "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
--command-color)
|
||||
COMPREPLY=($(compgen -W "black blue cyan green purple red yellow" -- "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
--cygpath)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
--dotenv-filename)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
--dotenv-path)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
-E)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
--dump-format)
|
||||
COMPREPLY=($(compgen -W "json just" -- "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
--justfile)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
-f)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
--list-heading)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
--list-prefix)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
--set)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
--shell)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
--shell-arg)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
--tempdir)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
--timestamp-format)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
--working-directory)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
-d)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
--command)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
-c)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
--completions)
|
||||
COMPREPLY=($(compgen -W "bash elvish fish nushell powershell zsh" -- "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
--list)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
-l)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
--request)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
--show)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
-s)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
COMPREPLY=()
|
||||
;;
|
||||
esac
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
if [[ "${BASH_VERSINFO[0]}" -eq 4 && "${BASH_VERSINFO[1]}" -ge 4 || "${BASH_VERSINFO[0]}" -gt 4 ]]; then
|
||||
complete -F _just -o nosort -o bashdefault -o default just
|
||||
else
|
||||
complete -F _just -o bashdefault -o default just
|
||||
fi
|
||||
93
completions/just.elvish
Normal file
93
completions/just.elvish
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
use builtin;
|
||||
use str;
|
||||
|
||||
set edit:completion:arg-completer[just] = {|@words|
|
||||
fn spaces {|n|
|
||||
builtin:repeat $n ' ' | str:join ''
|
||||
}
|
||||
fn cand {|text desc|
|
||||
edit:complex-candidate $text &display=$text' '(spaces (- 14 (wcswidth $text)))$desc
|
||||
}
|
||||
var command = 'just'
|
||||
for word $words[1..-1] {
|
||||
if (str:has-prefix $word '-') {
|
||||
break
|
||||
}
|
||||
set command = $command';'$word
|
||||
}
|
||||
var completions = [
|
||||
&'just'= {
|
||||
cand --alias-style 'Set list command alias display style'
|
||||
cand --ceiling 'Do not ascend above <CEILING> directory when searching for a justfile.'
|
||||
cand --chooser 'Override binary invoked by `--choose`'
|
||||
cand --color 'Print colorful output'
|
||||
cand --command-color 'Echo recipe lines in <COMMAND-COLOR>'
|
||||
cand --cygpath 'Use binary at <CYGPATH> to convert between unix and Windows paths.'
|
||||
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 --dotenv-path 'Load <DOTENV-PATH> as environment file instead of searching for one'
|
||||
cand --dump-format 'Dump justfile as <FORMAT>'
|
||||
cand -f 'Use <JUSTFILE> as justfile'
|
||||
cand --justfile 'Use <JUSTFILE> as justfile'
|
||||
cand --list-heading 'Print <TEXT> before list'
|
||||
cand --list-prefix 'Print <TEXT> before each list item'
|
||||
cand --set 'Override <VARIABLE> with <VALUE>'
|
||||
cand --shell 'Invoke <SHELL> to run recipes'
|
||||
cand --shell-arg 'Invoke shell with <SHELL-ARG> as an argument'
|
||||
cand --tempdir 'Save temporary files to <TEMPDIR>.'
|
||||
cand --timestamp-format 'Timestamp format string'
|
||||
cand -d 'Use <WORKING-DIRECTORY> as working directory. --justfile must also be set'
|
||||
cand --working-directory 'Use <WORKING-DIRECTORY> as working directory. --justfile must also be 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 --completions 'Print shell completion script for <SHELL>'
|
||||
cand -l 'List available recipes in <MODULE> or root if omitted'
|
||||
cand --list 'List available recipes in <MODULE> or root if omitted'
|
||||
cand --request 'Execute <REQUEST>. For internal testing purposes only. May be changed or removed at any time.'
|
||||
cand -s 'Show recipe at <PATH>'
|
||||
cand --show 'Show recipe at <PATH>'
|
||||
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 --clear-shell-args 'Clear shell arguments'
|
||||
cand -n 'Print what just would do without doing it'
|
||||
cand --dry-run 'Print what just would do without doing it'
|
||||
cand --explain 'Print recipe doc comment before running it'
|
||||
cand -g 'Use global justfile'
|
||||
cand --global-justfile 'Use global justfile'
|
||||
cand --highlight 'Highlight echoed recipe lines in bold'
|
||||
cand --list-submodules 'List recipes in submodules'
|
||||
cand --no-aliases 'Don''t show aliases in list'
|
||||
cand --no-deps 'Don''t run recipe dependencies'
|
||||
cand --no-dotenv 'Don''t load `.env` file'
|
||||
cand --no-highlight 'Don''t highlight echoed recipe lines in bold'
|
||||
cand --one 'Forbid multiple recipes from being invoked on the command line'
|
||||
cand -q 'Suppress all output'
|
||||
cand --quiet 'Suppress all output'
|
||||
cand --allow-missing 'Ignore missing recipe and module errors'
|
||||
cand --shell-command 'Invoke <COMMAND> with the shell used to run recipe lines and backticks'
|
||||
cand --timestamp 'Print recipe command timestamps'
|
||||
cand -u 'Return list and summary entries in source order'
|
||||
cand --unsorted 'Return list and summary entries in source order'
|
||||
cand --unstable 'Enable unstable features'
|
||||
cand -v 'Use verbose output'
|
||||
cand --verbose 'Use verbose output'
|
||||
cand --yes 'Automatically confirm all recipes.'
|
||||
cand --changelog 'Print changelog'
|
||||
cand --choose 'Select one or more recipes to run using a binary chooser. If `--chooser` is not passed the chooser defaults to the value of $JUST_CHOOSER, falling back to `fzf`'
|
||||
cand --dump 'Print justfile'
|
||||
cand -e 'Edit justfile with editor given by $VISUAL or $EDITOR, falling back to `vim`'
|
||||
cand --edit 'Edit justfile with editor given by $VISUAL or $EDITOR, falling back to `vim`'
|
||||
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 --groups 'List recipe groups'
|
||||
cand --init 'Initialize new justfile in project root'
|
||||
cand --man 'Print man page'
|
||||
cand --summary 'List names of available recipes'
|
||||
cand --variables 'List names of variables'
|
||||
cand -h 'Print help'
|
||||
cand --help 'Print help'
|
||||
cand -V 'Print version'
|
||||
cand --version 'Print version'
|
||||
}
|
||||
]
|
||||
$completions[$command]
|
||||
}
|
||||
86
completions/just.fish
Normal file
86
completions/just.fish
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
function __fish_just_complete_recipes
|
||||
if string match -rq '(-f|--justfile)\s*=?(?<justfile>[^\s]+)' -- (string split -- ' -- ' (commandline -pc))[1]
|
||||
set -fx JUST_JUSTFILE "$justfile"
|
||||
end
|
||||
printf "%s\n" (string split " " (just --summary))
|
||||
end
|
||||
|
||||
# don't suggest files right off
|
||||
complete -c just -n "__fish_is_first_arg" --no-files
|
||||
|
||||
# complete recipes
|
||||
complete -c just -a '(__fish_just_complete_recipes)'
|
||||
|
||||
# autogenerated completions
|
||||
complete -c just -l alias-style -d 'Set list command alias display style' -r -f -a "left\t''
|
||||
right\t''
|
||||
separate\t''"
|
||||
complete -c just -l ceiling -d 'Do not ascend above <CEILING> directory when searching for a justfile.' -r -F
|
||||
complete -c just -l chooser -d 'Override binary invoked by `--choose`' -r
|
||||
complete -c just -l color -d 'Print colorful output' -r -f -a "always\t''
|
||||
auto\t''
|
||||
never\t''"
|
||||
complete -c just -l command-color -d 'Echo recipe lines in <COMMAND-COLOR>' -r -f -a "black\t''
|
||||
blue\t''
|
||||
cyan\t''
|
||||
green\t''
|
||||
purple\t''
|
||||
red\t''
|
||||
yellow\t''"
|
||||
complete -c just -l cygpath -d 'Use binary at <CYGPATH> to convert between unix and Windows paths.' -r -F
|
||||
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 -l dump-format -d 'Dump justfile as <FORMAT>' -r -f -a "json\t''
|
||||
just\t''"
|
||||
complete -c just -s f -l justfile -d 'Use <JUSTFILE> as justfile' -r -F
|
||||
complete -c just -l list-heading -d 'Print <TEXT> before list' -r
|
||||
complete -c just -l list-prefix -d 'Print <TEXT> before each list item' -r
|
||||
complete -c just -l set -d 'Override <VARIABLE> with <VALUE>' -r
|
||||
complete -c just -l shell -d 'Invoke <SHELL> to run recipes' -r
|
||||
complete -c just -l shell-arg -d 'Invoke shell with <SHELL-ARG> as an argument' -r
|
||||
complete -c just -l tempdir -d 'Save temporary files to <TEMPDIR>.' -r -F
|
||||
complete -c just -l timestamp-format -d 'Timestamp format string' -r
|
||||
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 -l completions -d 'Print shell completion script for <SHELL>' -r -f -a "bash\t''
|
||||
elvish\t''
|
||||
fish\t''
|
||||
nushell\t''
|
||||
powershell\t''
|
||||
zsh\t''"
|
||||
complete -c just -s l -l list -d 'List available recipes in <MODULE> or root if omitted' -r
|
||||
complete -c just -l request -d 'Execute <REQUEST>. For internal testing purposes only. May be changed or removed at any time.' -r
|
||||
complete -c just -s s -l show -d 'Show recipe at <PATH>' -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 clear-shell-args -d 'Clear shell arguments'
|
||||
complete -c just -s n -l dry-run -d 'Print what just would do without doing it'
|
||||
complete -c just -l explain -d 'Print recipe doc comment before running it'
|
||||
complete -c just -s g -l global-justfile -d 'Use global justfile'
|
||||
complete -c just -l highlight -d 'Highlight echoed recipe lines in bold'
|
||||
complete -c just -l list-submodules -d 'List recipes in submodules'
|
||||
complete -c just -l no-aliases -d 'Don\'t show aliases in list'
|
||||
complete -c just -l no-deps -d 'Don\'t run recipe dependencies'
|
||||
complete -c just -l no-dotenv -d 'Don\'t load `.env` file'
|
||||
complete -c just -l no-highlight -d 'Don\'t highlight echoed recipe lines in bold'
|
||||
complete -c just -l one -d 'Forbid multiple recipes from being invoked on the command line'
|
||||
complete -c just -s q -l quiet -d 'Suppress all output'
|
||||
complete -c just -l allow-missing -d 'Ignore missing recipe and module errors'
|
||||
complete -c just -l shell-command -d 'Invoke <COMMAND> with the shell used to run recipe lines and backticks'
|
||||
complete -c just -l timestamp -d 'Print recipe command timestamps'
|
||||
complete -c just -s u -l unsorted -d 'Return list and summary entries in source order'
|
||||
complete -c just -l unstable -d 'Enable unstable features'
|
||||
complete -c just -s v -l verbose -d 'Use verbose output'
|
||||
complete -c just -l yes -d 'Automatically confirm all recipes.'
|
||||
complete -c just -l changelog -d 'Print changelog'
|
||||
complete -c just -l choose -d 'Select one or more recipes to run using a binary chooser. If `--chooser` is not passed the chooser defaults to the value of $JUST_CHOOSER, falling back to `fzf`'
|
||||
complete -c just -l dump -d 'Print justfile'
|
||||
complete -c just -s e -l edit -d 'Edit justfile with editor given by $VISUAL or $EDITOR, falling back to `vim`'
|
||||
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 groups -d 'List recipe groups'
|
||||
complete -c just -l init -d 'Initialize new justfile in project root'
|
||||
complete -c just -l man -d 'Print man page'
|
||||
complete -c just -l summary -d 'List names of available recipes'
|
||||
complete -c just -l variables -d 'List names of variables'
|
||||
complete -c just -s h -l help -d 'Print help'
|
||||
complete -c just -s V -l version -d 'Print version'
|
||||
8
completions/just.nu
Normal file
8
completions/just.nu
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
def "nu-complete just" [] {
|
||||
(^just --dump --unstable --dump-format json | from json).recipes | transpose recipe data | flatten | where {|row| $row.private == false } | select recipe doc parameters | rename value description
|
||||
}
|
||||
|
||||
# Just: A Command Runner
|
||||
export extern "just" [
|
||||
...recipe: string@"nu-complete just", # Recipe(s) to run, may be with argument(s)
|
||||
]
|
||||
119
completions/just.powershell
Normal file
119
completions/just.powershell
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
using namespace System.Management.Automation
|
||||
using namespace System.Management.Automation.Language
|
||||
|
||||
Register-ArgumentCompleter -Native -CommandName 'just' -ScriptBlock {
|
||||
param($wordToComplete, $commandAst, $cursorPosition)
|
||||
|
||||
$commandElements = $commandAst.CommandElements
|
||||
$command = @(
|
||||
'just'
|
||||
for ($i = 1; $i -lt $commandElements.Count; $i++) {
|
||||
$element = $commandElements[$i]
|
||||
if ($element -isnot [StringConstantExpressionAst] -or
|
||||
$element.StringConstantType -ne [StringConstantType]::BareWord -or
|
||||
$element.Value.StartsWith('-') -or
|
||||
$element.Value -eq $wordToComplete) {
|
||||
break
|
||||
}
|
||||
$element.Value
|
||||
}) -join ';'
|
||||
|
||||
$completions = @(switch ($command) {
|
||||
'just' {
|
||||
[CompletionResult]::new('--alias-style', '--alias-style', [CompletionResultType]::ParameterName, 'Set list command alias display style')
|
||||
[CompletionResult]::new('--ceiling', '--ceiling', [CompletionResultType]::ParameterName, 'Do not ascend above <CEILING> directory when searching for a justfile.')
|
||||
[CompletionResult]::new('--chooser', '--chooser', [CompletionResultType]::ParameterName, 'Override binary invoked by `--choose`')
|
||||
[CompletionResult]::new('--color', '--color', [CompletionResultType]::ParameterName, 'Print colorful output')
|
||||
[CompletionResult]::new('--command-color', '--command-color', [CompletionResultType]::ParameterName, 'Echo recipe lines in <COMMAND-COLOR>')
|
||||
[CompletionResult]::new('--cygpath', '--cygpath', [CompletionResultType]::ParameterName, 'Use binary at <CYGPATH> to convert between unix and Windows paths.')
|
||||
[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('--dotenv-path', '--dotenv-path', [CompletionResultType]::ParameterName, 'Load <DOTENV-PATH> as environment file instead of searching for one')
|
||||
[CompletionResult]::new('--dump-format', '--dump-format', [CompletionResultType]::ParameterName, 'Dump justfile as <FORMAT>')
|
||||
[CompletionResult]::new('-f', '-f', [CompletionResultType]::ParameterName, 'Use <JUSTFILE> as justfile')
|
||||
[CompletionResult]::new('--justfile', '--justfile', [CompletionResultType]::ParameterName, 'Use <JUSTFILE> as justfile')
|
||||
[CompletionResult]::new('--list-heading', '--list-heading', [CompletionResultType]::ParameterName, 'Print <TEXT> before list')
|
||||
[CompletionResult]::new('--list-prefix', '--list-prefix', [CompletionResultType]::ParameterName, 'Print <TEXT> before each list item')
|
||||
[CompletionResult]::new('--set', '--set', [CompletionResultType]::ParameterName, 'Override <VARIABLE> with <VALUE>')
|
||||
[CompletionResult]::new('--shell', '--shell', [CompletionResultType]::ParameterName, 'Invoke <SHELL> to run recipes')
|
||||
[CompletionResult]::new('--shell-arg', '--shell-arg', [CompletionResultType]::ParameterName, 'Invoke shell with <SHELL-ARG> as an argument')
|
||||
[CompletionResult]::new('--tempdir', '--tempdir', [CompletionResultType]::ParameterName, 'Save temporary files to <TEMPDIR>.')
|
||||
[CompletionResult]::new('--timestamp-format', '--timestamp-format', [CompletionResultType]::ParameterName, 'Timestamp format string')
|
||||
[CompletionResult]::new('-d', '-d', [CompletionResultType]::ParameterName, 'Use <WORKING-DIRECTORY> as working directory. --justfile must also be set')
|
||||
[CompletionResult]::new('--working-directory', '--working-directory', [CompletionResultType]::ParameterName, 'Use <WORKING-DIRECTORY> as working directory. --justfile must also be 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('--completions', '--completions', [CompletionResultType]::ParameterName, 'Print shell completion script for <SHELL>')
|
||||
[CompletionResult]::new('-l', '-l', [CompletionResultType]::ParameterName, 'List available recipes in <MODULE> or root if omitted')
|
||||
[CompletionResult]::new('--list', '--list', [CompletionResultType]::ParameterName, 'List available recipes in <MODULE> or root if omitted')
|
||||
[CompletionResult]::new('--request', '--request', [CompletionResultType]::ParameterName, 'Execute <REQUEST>. For internal testing purposes only. May be changed or removed at any time.')
|
||||
[CompletionResult]::new('-s', '-s', [CompletionResultType]::ParameterName, 'Show recipe at <PATH>')
|
||||
[CompletionResult]::new('--show', '--show', [CompletionResultType]::ParameterName, 'Show recipe at <PATH>')
|
||||
[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('--clear-shell-args', '--clear-shell-args', [CompletionResultType]::ParameterName, 'Clear shell arguments')
|
||||
[CompletionResult]::new('-n', '-n', [CompletionResultType]::ParameterName, 'Print what just would do without doing it')
|
||||
[CompletionResult]::new('--dry-run', '--dry-run', [CompletionResultType]::ParameterName, 'Print what just would do without doing it')
|
||||
[CompletionResult]::new('--explain', '--explain', [CompletionResultType]::ParameterName, 'Print recipe doc comment before running it')
|
||||
[CompletionResult]::new('-g', '-g', [CompletionResultType]::ParameterName, 'Use global justfile')
|
||||
[CompletionResult]::new('--global-justfile', '--global-justfile', [CompletionResultType]::ParameterName, 'Use global justfile')
|
||||
[CompletionResult]::new('--highlight', '--highlight', [CompletionResultType]::ParameterName, 'Highlight echoed recipe lines in bold')
|
||||
[CompletionResult]::new('--list-submodules', '--list-submodules', [CompletionResultType]::ParameterName, 'List recipes in submodules')
|
||||
[CompletionResult]::new('--no-aliases', '--no-aliases', [CompletionResultType]::ParameterName, 'Don''t show aliases in list')
|
||||
[CompletionResult]::new('--no-deps', '--no-deps', [CompletionResultType]::ParameterName, 'Don''t run recipe dependencies')
|
||||
[CompletionResult]::new('--no-dotenv', '--no-dotenv', [CompletionResultType]::ParameterName, 'Don''t load `.env` file')
|
||||
[CompletionResult]::new('--no-highlight', '--no-highlight', [CompletionResultType]::ParameterName, 'Don''t highlight echoed recipe lines in bold')
|
||||
[CompletionResult]::new('--one', '--one', [CompletionResultType]::ParameterName, 'Forbid multiple recipes from being invoked on the command line')
|
||||
[CompletionResult]::new('-q', '-q', [CompletionResultType]::ParameterName, 'Suppress all output')
|
||||
[CompletionResult]::new('--quiet', '--quiet', [CompletionResultType]::ParameterName, 'Suppress all output')
|
||||
[CompletionResult]::new('--allow-missing', '--allow-missing', [CompletionResultType]::ParameterName, 'Ignore missing recipe and module errors')
|
||||
[CompletionResult]::new('--shell-command', '--shell-command', [CompletionResultType]::ParameterName, 'Invoke <COMMAND> with the shell used to run recipe lines and backticks')
|
||||
[CompletionResult]::new('--timestamp', '--timestamp', [CompletionResultType]::ParameterName, 'Print recipe command timestamps')
|
||||
[CompletionResult]::new('-u', '-u', [CompletionResultType]::ParameterName, 'Return list and summary entries in source order')
|
||||
[CompletionResult]::new('--unsorted', '--unsorted', [CompletionResultType]::ParameterName, 'Return list and summary entries in source order')
|
||||
[CompletionResult]::new('--unstable', '--unstable', [CompletionResultType]::ParameterName, 'Enable unstable features')
|
||||
[CompletionResult]::new('-v', '-v', [CompletionResultType]::ParameterName, 'Use verbose output')
|
||||
[CompletionResult]::new('--verbose', '--verbose', [CompletionResultType]::ParameterName, 'Use verbose output')
|
||||
[CompletionResult]::new('--yes', '--yes', [CompletionResultType]::ParameterName, 'Automatically confirm all recipes.')
|
||||
[CompletionResult]::new('--changelog', '--changelog', [CompletionResultType]::ParameterName, 'Print changelog')
|
||||
[CompletionResult]::new('--choose', '--choose', [CompletionResultType]::ParameterName, 'Select one or more recipes to run using a binary chooser. If `--chooser` is not passed the chooser defaults to the value of $JUST_CHOOSER, falling back to `fzf`')
|
||||
[CompletionResult]::new('--dump', '--dump', [CompletionResultType]::ParameterName, 'Print justfile')
|
||||
[CompletionResult]::new('-e', '-e', [CompletionResultType]::ParameterName, 'Edit justfile with editor given by $VISUAL or $EDITOR, falling back to `vim`')
|
||||
[CompletionResult]::new('--edit', '--edit', [CompletionResultType]::ParameterName, 'Edit justfile with editor given by $VISUAL or $EDITOR, falling back to `vim`')
|
||||
[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('--groups', '--groups', [CompletionResultType]::ParameterName, 'List recipe groups')
|
||||
[CompletionResult]::new('--init', '--init', [CompletionResultType]::ParameterName, 'Initialize new justfile in project root')
|
||||
[CompletionResult]::new('--man', '--man', [CompletionResultType]::ParameterName, 'Print man page')
|
||||
[CompletionResult]::new('--summary', '--summary', [CompletionResultType]::ParameterName, 'List names of available recipes')
|
||||
[CompletionResult]::new('--variables', '--variables', [CompletionResultType]::ParameterName, 'List names of variables')
|
||||
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version')
|
||||
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
function Get-JustFileRecipes([string[]]$CommandElements) {
|
||||
$justFileIndex = $commandElements.IndexOf("--justfile");
|
||||
|
||||
if ($justFileIndex -ne -1 -and $justFileIndex + 1 -le $commandElements.Length) {
|
||||
$justFileLocation = $commandElements[$justFileIndex + 1]
|
||||
}
|
||||
|
||||
$justArgs = @("--summary")
|
||||
|
||||
if (Test-Path $justFileLocation) {
|
||||
$justArgs += @("--justfile", $justFileLocation)
|
||||
}
|
||||
|
||||
$recipes = $(just @justArgs) -split ' '
|
||||
return $recipes | ForEach-Object { [CompletionResult]::new($_) }
|
||||
}
|
||||
|
||||
$elementValues = $commandElements | Select-Object -ExpandProperty Value
|
||||
$recipes = Get-JustFileRecipes -CommandElements $elementValues
|
||||
$completions += $recipes
|
||||
$completions.Where{ $_.CompletionText -like "$wordToComplete*" } |
|
||||
Sort-Object -Property ListItemText
|
||||
}
|
||||
180
completions/just.zsh
Normal file
180
completions/just.zsh
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
#compdef just
|
||||
|
||||
autoload -U is-at-least
|
||||
|
||||
_just() {
|
||||
typeset -A opt_args
|
||||
typeset -a _arguments_options
|
||||
local ret=1
|
||||
|
||||
if is-at-least 5.2; then
|
||||
_arguments_options=(-s -S -C)
|
||||
else
|
||||
_arguments_options=(-s -C)
|
||||
fi
|
||||
|
||||
local context curcontext="$curcontext" state line
|
||||
local common=(
|
||||
'(--no-aliases)--alias-style=[Set list command alias display style]: :(left right separate)' \
|
||||
'--ceiling=[Do not ascend above <CEILING> directory when searching for a justfile.]: :_files' \
|
||||
'--chooser=[Override binary invoked by \`--choose\`]: :_default' \
|
||||
'--color=[Print colorful output]: :(always auto never)' \
|
||||
'--command-color=[Echo recipe lines in <COMMAND-COLOR>]: :(black blue cyan green purple red yellow)' \
|
||||
'--cygpath=[Use binary at <CYGPATH> to convert between unix and Windows paths.]: :_files' \
|
||||
'(-E --dotenv-path)--dotenv-filename=[Search for environment file named <DOTENV-FILENAME> instead of \`.env\`]: :_default' \
|
||||
'-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' \
|
||||
'--dump-format=[Dump justfile as <FORMAT>]:FORMAT:(json just)' \
|
||||
'-f+[Use <JUSTFILE> as justfile]: :_files' \
|
||||
'--justfile=[Use <JUSTFILE> as justfile]: :_files' \
|
||||
'--list-heading=[Print <TEXT> before list]:TEXT:_default' \
|
||||
'--list-prefix=[Print <TEXT> before each list item]:TEXT:_default' \
|
||||
'*--set=[Override <VARIABLE> with <VALUE>]: :(_just_variables)' \
|
||||
'--shell=[Invoke <SHELL> to run recipes]: :_default' \
|
||||
'*--shell-arg=[Invoke shell with <SHELL-ARG> as an argument]: :_default' \
|
||||
'--tempdir=[Save temporary files to <TEMPDIR>.]: :_files' \
|
||||
'--timestamp-format=[Timestamp format string]: :_default' \
|
||||
'-d+[Use <WORKING-DIRECTORY> as working directory. --justfile must also be set]: :_files' \
|
||||
'--working-directory=[Use <WORKING-DIRECTORY> as working directory. --justfile must also be set]: :_files' \
|
||||
'*-c+[Run an arbitrary command with the working directory, \`.env\`, overrides, and exports set]: :_default' \
|
||||
'*--command=[Run an arbitrary command with the working directory, \`.env\`, overrides, and exports set]: :_default' \
|
||||
'--completions=[Print shell completion script for <SHELL>]:SHELL:(bash elvish fish nushell powershell zsh)' \
|
||||
'()-l+[List available recipes in <MODULE> or root if omitted]' \
|
||||
'()--list=[List available recipes in <MODULE> or root if omitted]' \
|
||||
'--request=[Execute <REQUEST>. For internal testing purposes only. May be changed or removed at any time.]: :_default' \
|
||||
'-s+[Show recipe at <PATH>]: :(_just_commands)' \
|
||||
'--show=[Show recipe at <PATH>]: :(_just_commands)' \
|
||||
'--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.]' \
|
||||
'--clear-shell-args[Clear shell arguments]' \
|
||||
'(-q --quiet)-n[Print what just would do without doing it]' \
|
||||
'(-q --quiet)--dry-run[Print what just would do without doing it]' \
|
||||
'--explain[Print recipe doc comment before running it]' \
|
||||
'(-f --justfile -d --working-directory)-g[Use global justfile]' \
|
||||
'(-f --justfile -d --working-directory)--global-justfile[Use global justfile]' \
|
||||
'--highlight[Highlight echoed recipe lines in bold]' \
|
||||
'--list-submodules[List recipes in submodules]' \
|
||||
'--no-aliases[Don'\''t show aliases in list]' \
|
||||
'--no-deps[Don'\''t run recipe dependencies]' \
|
||||
'--no-dotenv[Don'\''t load \`.env\` file]' \
|
||||
'--no-highlight[Don'\''t highlight echoed recipe lines in bold]' \
|
||||
'--one[Forbid multiple recipes from being invoked on the command line]' \
|
||||
'(-n --dry-run)-q[Suppress all output]' \
|
||||
'(-n --dry-run)--quiet[Suppress all output]' \
|
||||
'--allow-missing[Ignore missing recipe and module errors]' \
|
||||
'--shell-command[Invoke <COMMAND> with the shell used to run recipe lines and backticks]' \
|
||||
'--timestamp[Print recipe command timestamps]' \
|
||||
'-u[Return list and summary entries in source order]' \
|
||||
'--unsorted[Return list and summary entries in source order]' \
|
||||
'--unstable[Enable unstable features]' \
|
||||
'*-v[Use verbose output]' \
|
||||
'*--verbose[Use verbose output]' \
|
||||
'--yes[Automatically confirm all recipes.]' \
|
||||
'--changelog[Print changelog]' \
|
||||
'--choose[Select one or more recipes to run using a binary chooser. If \`--chooser\` is not passed the chooser defaults to the value of \$JUST_CHOOSER, falling back to \`fzf\`]' \
|
||||
'--dump[Print justfile]' \
|
||||
'-e[Edit justfile with editor given by \$VISUAL or \$EDITOR, falling back to \`vim\`]' \
|
||||
'--edit[Edit justfile with editor given by \$VISUAL or \$EDITOR, falling back to \`vim\`]' \
|
||||
'--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]' \
|
||||
'--groups[List recipe groups]' \
|
||||
'--init[Initialize new justfile in project root]' \
|
||||
'--man[Print man page]' \
|
||||
'--summary[List names of available recipes]' \
|
||||
'--variables[List names of variables]' \
|
||||
'-h[Print help]' \
|
||||
'--help[Print help]' \
|
||||
'-V[Print version]' \
|
||||
'--version[Print version]' \
|
||||
)
|
||||
|
||||
_arguments "${_arguments_options[@]}" $common \
|
||||
'1: :_just_commands' \
|
||||
'*: :->args' \
|
||||
&& ret=0
|
||||
|
||||
case $state in
|
||||
args)
|
||||
curcontext="${curcontext%:*}-${words[2]}:"
|
||||
|
||||
local lastarg=${words[${#words}]}
|
||||
local recipe
|
||||
|
||||
local cmds; cmds=(
|
||||
${(s: :)$(_call_program commands just --summary)}
|
||||
)
|
||||
|
||||
# Find first recipe name
|
||||
for ((i = 2; i < $#words; i++ )) do
|
||||
if [[ ${cmds[(I)${words[i]}]} -gt 0 ]]; then
|
||||
recipe=${words[i]}
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $lastarg = */* ]]; then
|
||||
# Arguments contain slash would be recognised as a file
|
||||
_arguments -s -S $common '*:: :_files'
|
||||
elif [[ $lastarg = *=* ]]; then
|
||||
# Arguments contain equal would be recognised as a variable
|
||||
_message "value"
|
||||
elif [[ $recipe ]]; then
|
||||
# Show usage message
|
||||
_message "`just --show $recipe`"
|
||||
# Or complete with other commands
|
||||
#_arguments -s -S $common '*:: :_just_commands'
|
||||
else
|
||||
_arguments -s -S $common '*:: :_just_commands'
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
return ret
|
||||
|
||||
}
|
||||
|
||||
(( $+functions[_just_commands] )) ||
|
||||
_just_commands() {
|
||||
[[ $PREFIX = -* ]] && return 1
|
||||
integer ret=1
|
||||
local variables; variables=(
|
||||
${(s: :)$(_call_program commands just --variables)}
|
||||
)
|
||||
local commands; commands=(
|
||||
${${${(M)"${(f)$(_call_program commands just --list)}":# *}/ ##/}/ ##/:Args: }
|
||||
)
|
||||
|
||||
if compset -P '*='; then
|
||||
case "${${words[-1]%=*}#*=}" in
|
||||
*) _message 'value' && ret=0 ;;
|
||||
esac
|
||||
else
|
||||
_describe -t variables 'variables' variables -qS "=" && ret=0
|
||||
_describe -t commands 'just commands' commands "$@"
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
if [ "$funcstack[1]" = "_just" ]; then
|
||||
(( $+functions[_just_variables] )) ||
|
||||
_just_variables() {
|
||||
[[ $PREFIX = -* ]] && return 1
|
||||
integer ret=1
|
||||
local variables; variables=(
|
||||
${(s: :)$(_call_program commands just --variables)}
|
||||
)
|
||||
|
||||
if compset -P '*='; then
|
||||
case "${${words[-1]%=*}#*=}" in
|
||||
*) _message 'value' && ret=0 ;;
|
||||
esac
|
||||
else
|
||||
_describe -t variables 'variables' variables && ret=0
|
||||
fi
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
_just "$@"
|
||||
else
|
||||
compdef _just just
|
||||
fi
|
||||
|
|
@ -25,8 +25,9 @@ impl<'src> Alias<'src, Namepath<'src>> {
|
|||
}
|
||||
|
||||
impl Alias<'_> {
|
||||
pub(crate) fn is_private(&self) -> bool {
|
||||
self.name.lexeme().starts_with('_') || self.attributes.contains(AttributeDiscriminant::Private)
|
||||
pub(crate) fn is_public(&self) -> bool {
|
||||
!self.name.lexeme().starts_with('_')
|
||||
&& !self.attributes.contains(AttributeDiscriminant::Private)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ impl<'run, 'src> Analyzer<'run, 'src> {
|
|||
pub(crate) fn analyze(
|
||||
asts: &'run HashMap<PathBuf, Ast<'src>>,
|
||||
doc: Option<String>,
|
||||
groups: &[String],
|
||||
groups: &[StringLiteral<'src>],
|
||||
loaded: &[PathBuf],
|
||||
name: Option<Name<'src>>,
|
||||
paths: &HashMap<PathBuf, PathBuf>,
|
||||
|
|
@ -28,7 +28,7 @@ impl<'run, 'src> Analyzer<'run, 'src> {
|
|||
mut self,
|
||||
asts: &'run HashMap<PathBuf, Ast<'src>>,
|
||||
doc: Option<String>,
|
||||
groups: &[String],
|
||||
groups: &[StringLiteral<'src>],
|
||||
loaded: &[PathBuf],
|
||||
name: Option<Name<'src>>,
|
||||
paths: &HashMap<PathBuf, PathBuf>,
|
||||
|
|
@ -94,7 +94,7 @@ impl<'run, 'src> Analyzer<'run, 'src> {
|
|||
}
|
||||
Item::Unexport { name } => {
|
||||
if !self.unexports.insert(name.lexeme().to_string()) {
|
||||
return Err(name.token.error(DuplicateUnexport {
|
||||
return Err(name.error(DuplicateUnexport {
|
||||
variable: name.lexeme(),
|
||||
}));
|
||||
}
|
||||
|
|
@ -112,7 +112,7 @@ impl<'run, 'src> Analyzer<'run, 'src> {
|
|||
let variable = assignment.name.lexeme();
|
||||
|
||||
if !settings.allow_duplicate_variables && assignments.contains_key(variable) {
|
||||
return Err(assignment.name.token.error(DuplicateVariable { variable }));
|
||||
return Err(assignment.name.error(DuplicateVariable { variable }));
|
||||
}
|
||||
|
||||
if assignments.get(variable).map_or(true, |original| {
|
||||
|
|
@ -122,7 +122,7 @@ impl<'run, 'src> Analyzer<'run, 'src> {
|
|||
}
|
||||
|
||||
if self.unexports.contains(variable) {
|
||||
return Err(assignment.name.token.error(ExportUnexported { variable }));
|
||||
return Err(assignment.name.error(ExportUnexported { variable }));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -172,10 +172,21 @@ impl<'run, 'src> Analyzer<'run, 'src> {
|
|||
let source = root.to_owned();
|
||||
let root = paths.get(root).unwrap();
|
||||
|
||||
Ok(Justfile {
|
||||
aliases,
|
||||
assignments,
|
||||
default: recipes
|
||||
let mut default = None;
|
||||
for recipe in recipes.values() {
|
||||
if recipe.attributes.contains(AttributeDiscriminant::Default) {
|
||||
if default.is_some() {
|
||||
return Err(recipe.name.error(CompileErrorKind::DuplicateDefault {
|
||||
recipe: recipe.name.lexeme(),
|
||||
}));
|
||||
}
|
||||
|
||||
default = Some(Arc::clone(recipe));
|
||||
}
|
||||
}
|
||||
|
||||
let default = default.or_else(|| {
|
||||
recipes
|
||||
.values()
|
||||
.filter(|recipe| recipe.name.path == root)
|
||||
.fold(None, |accumulator, next| match accumulator {
|
||||
|
|
@ -185,7 +196,13 @@ impl<'run, 'src> Analyzer<'run, 'src> {
|
|||
} else {
|
||||
Arc::clone(next)
|
||||
}),
|
||||
}),
|
||||
})
|
||||
});
|
||||
|
||||
Ok(Justfile {
|
||||
aliases,
|
||||
assignments,
|
||||
default,
|
||||
doc: doc.filter(|doc| !doc.is_empty()),
|
||||
groups: groups.into(),
|
||||
loaded: loaded.into(),
|
||||
|
|
@ -236,7 +253,7 @@ impl<'run, 'src> Analyzer<'run, 'src> {
|
|||
|
||||
for parameter in &recipe.parameters {
|
||||
if parameters.contains(parameter.name.lexeme()) {
|
||||
return Err(parameter.name.token.error(DuplicateParameter {
|
||||
return Err(parameter.name.error(DuplicateParameter {
|
||||
recipe: recipe.name.lexeme(),
|
||||
parameter: parameter.name.lexeme(),
|
||||
}));
|
||||
|
|
@ -304,7 +321,7 @@ impl<'run, 'src> Analyzer<'run, 'src> {
|
|||
) -> CompileResult<'src, Alias<'src>> {
|
||||
match Self::resolve_recipe(&alias.target, modules, recipes) {
|
||||
Some(target) => Ok(alias.resolve(target)),
|
||||
None => Err(alias.name.token.error(UnknownAliasTarget {
|
||||
None => Err(alias.name.error(UnknownAliasTarget {
|
||||
alias: alias.name.lexeme(),
|
||||
target: alias.target,
|
||||
})),
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ use super::*;
|
|||
#[strum_discriminants(strum(serialize_all = "kebab-case"))]
|
||||
pub(crate) enum Attribute<'src> {
|
||||
Confirm(Option<StringLiteral<'src>>),
|
||||
Default,
|
||||
Doc(Option<StringLiteral<'src>>),
|
||||
ExitMessage,
|
||||
Extension(StringLiteral<'src>),
|
||||
|
|
@ -34,7 +35,8 @@ impl AttributeDiscriminant {
|
|||
fn argument_range(self) -> RangeInclusive<usize> {
|
||||
match self {
|
||||
Self::Confirm | Self::Doc => 0..=1,
|
||||
Self::ExitMessage
|
||||
Self::Default
|
||||
| Self::ExitMessage
|
||||
| Self::Linux
|
||||
| Self::Macos
|
||||
| Self::NoCd
|
||||
|
|
@ -83,6 +85,7 @@ impl<'src> Attribute<'src> {
|
|||
|
||||
Ok(match discriminant {
|
||||
AttributeDiscriminant::Confirm => Self::Confirm(arguments.into_iter().next()),
|
||||
AttributeDiscriminant::Default => Self::Default,
|
||||
AttributeDiscriminant::Doc => Self::Doc(arguments.into_iter().next()),
|
||||
AttributeDiscriminant::ExitMessage => Self::ExitMessage,
|
||||
AttributeDiscriminant::Extension => Self::Extension(arguments.into_iter().next().unwrap()),
|
||||
|
|
@ -131,6 +134,7 @@ impl Display for Attribute<'_> {
|
|||
|
||||
match self {
|
||||
Self::Confirm(None)
|
||||
| Self::Default
|
||||
| Self::Doc(None)
|
||||
| Self::ExitMessage
|
||||
| Self::Linux
|
||||
|
|
|
|||
|
|
@ -103,6 +103,10 @@ impl Display for CompileError<'_> {
|
|||
first.ordinal(),
|
||||
self.token.line.ordinal(),
|
||||
),
|
||||
DuplicateDefault { recipe } => write!(
|
||||
f,
|
||||
"Recipe `{recipe}` has duplicate `[default]` attribute, which may only appear once per module",
|
||||
),
|
||||
DuplicateParameter { recipe, parameter } => {
|
||||
write!(f, "Recipe `{recipe}` has duplicate parameter `{parameter}`")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,9 @@ pub(crate) enum CompileErrorKind<'src> {
|
|||
attribute: &'src str,
|
||||
first: usize,
|
||||
},
|
||||
DuplicateDefault {
|
||||
recipe: &'src str,
|
||||
},
|
||||
DuplicateParameter {
|
||||
recipe: &'src str,
|
||||
parameter: &'src str,
|
||||
|
|
|
|||
|
|
@ -12,88 +12,95 @@ pub(crate) enum Shell {
|
|||
}
|
||||
|
||||
impl Shell {
|
||||
pub(crate) fn script(self) -> RunResult<'static, String> {
|
||||
pub(crate) fn script(self) -> &'static str {
|
||||
match self {
|
||||
Self::Bash => completions::clap(clap_complete::Shell::Bash),
|
||||
Self::Elvish => completions::clap(clap_complete::Shell::Elvish),
|
||||
Self::Fish => completions::clap(clap_complete::Shell::Fish),
|
||||
Self::Nushell => Ok(completions::NUSHELL_COMPLETION_SCRIPT.into()),
|
||||
Self::Powershell => completions::clap(clap_complete::Shell::PowerShell),
|
||||
Self::Zsh => completions::clap(clap_complete::Shell::Zsh),
|
||||
Self::Bash => include_str!("../completions/just.bash"),
|
||||
Self::Elvish => include_str!("../completions/just.elvish"),
|
||||
Self::Fish => include_str!("../completions/just.fish"),
|
||||
Self::Nushell => include_str!("../completions/just.nu"),
|
||||
Self::Powershell => include_str!("../completions/just.powershell"),
|
||||
Self::Zsh => include_str!("../completions/just.zsh"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn clap(shell: clap_complete::Shell) -> RunResult<'static, String> {
|
||||
fn replace(haystack: &mut String, needle: &str, replacement: &str) -> RunResult<'static, ()> {
|
||||
if let Some(index) = haystack.find(needle) {
|
||||
haystack.replace_range(index..index + needle.len(), replacement);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::internal(format!(
|
||||
"Failed to find text:\n{needle}\n…in completion script:\n{haystack}"
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
let mut script = {
|
||||
let mut tempfile = tempfile().map_err(|io_error| Error::TempfileIo { io_error })?;
|
||||
|
||||
clap_complete::generate(
|
||||
shell,
|
||||
&mut crate::config::Config::app(),
|
||||
env!("CARGO_PKG_NAME"),
|
||||
&mut tempfile,
|
||||
);
|
||||
|
||||
tempfile
|
||||
.rewind()
|
||||
.map_err(|io_error| Error::TempfileIo { io_error })?;
|
||||
|
||||
let mut buffer = String::new();
|
||||
|
||||
tempfile
|
||||
.read_to_string(&mut buffer)
|
||||
.map_err(|io_error| Error::TempfileIo { io_error })?;
|
||||
|
||||
buffer
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use {
|
||||
super::*,
|
||||
pretty_assertions::assert_eq,
|
||||
std::io::{Read, Seek},
|
||||
tempfile::tempfile,
|
||||
};
|
||||
|
||||
match shell {
|
||||
clap_complete::Shell::Bash => {
|
||||
for (needle, replacement) in completions::BASH_COMPLETION_REPLACEMENTS {
|
||||
replace(&mut script, needle, replacement)?;
|
||||
}
|
||||
}
|
||||
clap_complete::Shell::Fish => {
|
||||
script.insert_str(0, completions::FISH_RECIPE_COMPLETIONS);
|
||||
}
|
||||
clap_complete::Shell::PowerShell => {
|
||||
for (needle, replacement) in completions::POWERSHELL_COMPLETION_REPLACEMENTS {
|
||||
replace(&mut script, needle, replacement)?;
|
||||
}
|
||||
}
|
||||
clap_complete::Shell::Zsh => {
|
||||
for (needle, replacement) in completions::ZSH_COMPLETION_REPLACEMENTS {
|
||||
replace(&mut script, needle, replacement)?;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
#[test]
|
||||
fn scripts() {
|
||||
assert_eq!(Shell::Bash.script(), clap(clap_complete::Shell::Bash));
|
||||
assert_eq!(Shell::Elvish.script(), clap(clap_complete::Shell::Elvish));
|
||||
assert_eq!(Shell::Fish.script(), clap(clap_complete::Shell::Fish));
|
||||
assert_eq!(
|
||||
Shell::Powershell.script(),
|
||||
clap(clap_complete::Shell::PowerShell)
|
||||
);
|
||||
assert_eq!(Shell::Zsh.script(), clap(clap_complete::Shell::Zsh));
|
||||
}
|
||||
|
||||
Ok(script.trim().into())
|
||||
}
|
||||
fn clap(shell: clap_complete::Shell) -> String {
|
||||
fn replace(haystack: &mut String, needle: &str, replacement: &str) {
|
||||
if let Some(index) = haystack.find(needle) {
|
||||
haystack.replace_range(index..index + needle.len(), replacement);
|
||||
} else {
|
||||
panic!("Failed to find text:\n{needle}\n…in completion script:\n{haystack}")
|
||||
}
|
||||
}
|
||||
|
||||
const NUSHELL_COMPLETION_SCRIPT: &str = r#"def "nu-complete just" [] {
|
||||
(^just --dump --unstable --dump-format json | from json).recipes | transpose recipe data | flatten | where {|row| $row.private == false } | select recipe doc parameters | rename value description
|
||||
}
|
||||
let mut script = {
|
||||
let mut tempfile = tempfile().unwrap();
|
||||
|
||||
# Just: A Command Runner
|
||||
export extern "just" [
|
||||
...recipe: string@"nu-complete just", # Recipe(s) to run, may be with argument(s)
|
||||
]"#;
|
||||
clap_complete::generate(
|
||||
shell,
|
||||
&mut crate::config::Config::app(),
|
||||
env!("CARGO_PKG_NAME"),
|
||||
&mut tempfile,
|
||||
);
|
||||
|
||||
const FISH_RECIPE_COMPLETIONS: &str = r#"function __fish_just_complete_recipes
|
||||
tempfile.rewind().unwrap();
|
||||
|
||||
let mut buffer = String::new();
|
||||
|
||||
tempfile.read_to_string(&mut buffer).unwrap();
|
||||
|
||||
buffer
|
||||
};
|
||||
|
||||
match shell {
|
||||
clap_complete::Shell::Bash => {
|
||||
for (needle, replacement) in BASH_COMPLETION_REPLACEMENTS {
|
||||
replace(&mut script, needle, replacement);
|
||||
}
|
||||
}
|
||||
clap_complete::Shell::Fish => {
|
||||
script.insert_str(0, FISH_RECIPE_COMPLETIONS);
|
||||
}
|
||||
clap_complete::Shell::PowerShell => {
|
||||
for (needle, replacement) in POWERSHELL_COMPLETION_REPLACEMENTS {
|
||||
replace(&mut script, needle, replacement);
|
||||
}
|
||||
}
|
||||
clap_complete::Shell::Zsh => {
|
||||
for (needle, replacement) in ZSH_COMPLETION_REPLACEMENTS {
|
||||
replace(&mut script, needle, replacement);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let mut script = script.trim().to_string();
|
||||
script.push('\n');
|
||||
script
|
||||
}
|
||||
|
||||
const FISH_RECIPE_COMPLETIONS: &str = r#"function __fish_just_complete_recipes
|
||||
if string match -rq '\s(-f|--justfile)\s*=?(?<justfile>[^\s]+)' -- (string split -- ' -- ' (commandline -pc))[1]
|
||||
set -fx JUST_JUSTFILE "$justfile"
|
||||
end
|
||||
|
|
@ -109,26 +116,26 @@ complete -c just -a '(__fish_just_complete_recipes)'
|
|||
# autogenerated completions
|
||||
"#;
|
||||
|
||||
const ZSH_COMPLETION_REPLACEMENTS: &[(&str, &str)] = &[
|
||||
(
|
||||
r#" _arguments "${_arguments_options[@]}" : \"#,
|
||||
r" local common=(",
|
||||
),
|
||||
(
|
||||
r"'*--set=[Override <VARIABLE> with <VALUE>]:VARIABLE:_default:VARIABLE:_default' \",
|
||||
r"'*--set=[Override <VARIABLE> with <VALUE>]: :(_just_variables)' \",
|
||||
),
|
||||
(
|
||||
r"'()-s+[Show recipe at <PATH>]:PATH:_default' \
|
||||
const ZSH_COMPLETION_REPLACEMENTS: &[(&str, &str)] = &[
|
||||
(
|
||||
r#" _arguments "${_arguments_options[@]}" : \"#,
|
||||
r" local common=(",
|
||||
),
|
||||
(
|
||||
r"'*--set=[Override <VARIABLE> with <VALUE>]:VARIABLE:_default:VARIABLE:_default' \",
|
||||
r"'*--set=[Override <VARIABLE> with <VALUE>]: :(_just_variables)' \",
|
||||
),
|
||||
(
|
||||
r"'()-s+[Show recipe at <PATH>]:PATH:_default' \
|
||||
'()--show=[Show recipe at <PATH>]:PATH:_default' \",
|
||||
r"'-s+[Show recipe at <PATH>]: :(_just_commands)' \
|
||||
r"'-s+[Show recipe at <PATH>]: :(_just_commands)' \
|
||||
'--show=[Show recipe at <PATH>]: :(_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 \
|
||||
justfile:_default' \\
|
||||
&& ret=0",
|
||||
r#")
|
||||
r#")
|
||||
|
||||
_arguments "${_arguments_options[@]}" $common \
|
||||
'1: :_just_commands' \
|
||||
|
|
@ -173,10 +180,10 @@ const ZSH_COMPLETION_REPLACEMENTS: &[(&str, &str)] = &[
|
|||
|
||||
return ret
|
||||
"#,
|
||||
),
|
||||
(
|
||||
" local commands; commands=()",
|
||||
r#" [[ $PREFIX = -* ]] && return 1
|
||||
),
|
||||
(
|
||||
" local commands; commands=()",
|
||||
r#" [[ $PREFIX = -* ]] && return 1
|
||||
integer ret=1
|
||||
local variables; variables=(
|
||||
${(s: :)$(_call_program commands just --variables)}
|
||||
|
|
@ -185,10 +192,10 @@ const ZSH_COMPLETION_REPLACEMENTS: &[(&str, &str)] = &[
|
|||
${${${(M)"${(f)$(_call_program commands just --list)}":# *}/ ##/}/ ##/:Args: }
|
||||
)
|
||||
"#,
|
||||
),
|
||||
(
|
||||
r#" _describe -t commands 'just commands' commands "$@""#,
|
||||
r#" if compset -P '*='; then
|
||||
),
|
||||
(
|
||||
r#" _describe -t commands 'just commands' commands "$@""#,
|
||||
r#" if compset -P '*='; then
|
||||
case "${${words[-1]%=*}#*=}" in
|
||||
*) _message 'value' && ret=0 ;;
|
||||
esac
|
||||
|
|
@ -197,10 +204,10 @@ const ZSH_COMPLETION_REPLACEMENTS: &[(&str, &str)] = &[
|
|||
_describe -t commands 'just commands' commands "$@"
|
||||
fi
|
||||
"#,
|
||||
),
|
||||
(
|
||||
r#"_just "$@""#,
|
||||
r#"(( $+functions[_just_variables] )) ||
|
||||
),
|
||||
(
|
||||
r#"_just "$@""#,
|
||||
r#"(( $+functions[_just_variables] )) ||
|
||||
_just_variables() {
|
||||
[[ $PREFIX = -* ]] && return 1
|
||||
integer ret=1
|
||||
|
|
@ -220,13 +227,13 @@ _just_variables() {
|
|||
}
|
||||
|
||||
_just "$@""#,
|
||||
),
|
||||
];
|
||||
),
|
||||
];
|
||||
|
||||
const POWERSHELL_COMPLETION_REPLACEMENTS: &[(&str, &str)] = &[(
|
||||
r#"$completions.Where{ $_.CompletionText -like "$wordToComplete*" } |
|
||||
const POWERSHELL_COMPLETION_REPLACEMENTS: &[(&str, &str)] = &[(
|
||||
r#"$completions.Where{ $_.CompletionText -like "$wordToComplete*" } |
|
||||
Sort-Object -Property ListItemText"#,
|
||||
r#"function Get-JustFileRecipes([string[]]$CommandElements) {
|
||||
r#"function Get-JustFileRecipes([string[]]$CommandElements) {
|
||||
$justFileIndex = $commandElements.IndexOf("--justfile");
|
||||
|
||||
if ($justFileIndex -ne -1 -and $justFileIndex + 1 -le $commandElements.Length) {
|
||||
|
|
@ -248,15 +255,15 @@ const POWERSHELL_COMPLETION_REPLACEMENTS: &[(&str, &str)] = &[(
|
|||
$completions += $recipes
|
||||
$completions.Where{ $_.CompletionText -like "$wordToComplete*" } |
|
||||
Sort-Object -Property ListItemText"#,
|
||||
)];
|
||||
)];
|
||||
|
||||
const BASH_COMPLETION_REPLACEMENTS: &[(&str, &str)] = &[
|
||||
(
|
||||
r#" if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
|
||||
const BASH_COMPLETION_REPLACEMENTS: &[(&str, &str)] = &[
|
||||
(
|
||||
r#" if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
fi"#,
|
||||
r#" if [[ ${cur} == -* ]] ; then
|
||||
r#" if [[ ${cur} == -* ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
elif [[ ${COMP_CWORD} -eq 1 ]]; then
|
||||
|
|
@ -273,15 +280,15 @@ const BASH_COMPLETION_REPLACEMENTS: &[(&str, &str)] = &[
|
|||
return 0
|
||||
fi
|
||||
fi"#,
|
||||
),
|
||||
(
|
||||
r"local i cur prev opts cmd",
|
||||
r"local i cur prev words cword opts cmd",
|
||||
),
|
||||
(
|
||||
r#" cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
),
|
||||
(
|
||||
r"local i cur prev opts cmd",
|
||||
r"local i cur prev words cword opts cmd",
|
||||
),
|
||||
(
|
||||
r#" cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
prev="${COMP_WORDS[COMP_CWORD-1]}""#,
|
||||
r#"
|
||||
r#"
|
||||
# Modules use "::" as the separator, which is considered a wordbreak character in bash.
|
||||
# The _get_comp_words_by_ref function is a hack to allow for exceptions to this rule without
|
||||
# modifying the global COMP_WORDBREAKS environment variable.
|
||||
|
|
@ -294,14 +301,15 @@ const BASH_COMPLETION_REPLACEMENTS: &[(&str, &str)] = &[
|
|||
cword=$COMP_CWORD
|
||||
fi
|
||||
"#,
|
||||
),
|
||||
(r"for i in ${COMP_WORDS[@]}", r"for i in ${words[@]}"),
|
||||
(r"elif [[ ${COMP_CWORD} -eq 1 ]]; then", r"else"),
|
||||
(
|
||||
r#"COMPREPLY=( $(compgen -W "${recipes}" -- "${cur}") )"#,
|
||||
r#"COMPREPLY=( $(compgen -W "${recipes}" -- "${cur}") )
|
||||
),
|
||||
(r"for i in ${COMP_WORDS[@]}", r"for i in ${words[@]}"),
|
||||
(r"elif [[ ${COMP_CWORD} -eq 1 ]]; then", r"else"),
|
||||
(
|
||||
r#"COMPREPLY=( $(compgen -W "${recipes}" -- "${cur}") )"#,
|
||||
r#"COMPREPLY=( $(compgen -W "${recipes}" -- "${cur}") )
|
||||
if type __ltrim_colon_completions &>/dev/null; then
|
||||
__ltrim_colon_completions "$cur"
|
||||
fi"#,
|
||||
),
|
||||
];
|
||||
),
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ use {
|
|||
pub(crate) struct Config {
|
||||
pub(crate) alias_style: AliasStyle,
|
||||
pub(crate) allow_missing: bool,
|
||||
pub(crate) ceiling: Option<PathBuf>,
|
||||
pub(crate) check: bool,
|
||||
pub(crate) color: Color,
|
||||
pub(crate) command_color: Option<ansi_term::Color>,
|
||||
|
|
@ -92,6 +93,7 @@ mod arg {
|
|||
pub(crate) const ALIAS_STYLE: &str = "ALIAS_STYLE";
|
||||
pub(crate) const ALLOW_MISSING: &str = "ALLOW-MISSING";
|
||||
pub(crate) const ARGUMENTS: &str = "ARGUMENTS";
|
||||
pub(crate) const CEILING: &str = "CEILING";
|
||||
pub(crate) const CHECK: &str = "CHECK";
|
||||
pub(crate) const CHOOSER: &str = "CHOOSER";
|
||||
pub(crate) const CLEAR_SHELL_ARGS: &str = "CLEAR-SHELL-ARGS";
|
||||
|
|
@ -161,6 +163,14 @@ impl Config {
|
|||
.help("Set list command alias display style")
|
||||
.conflicts_with(arg::NO_ALIASES),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(arg::CEILING)
|
||||
.long("ceiling")
|
||||
.env("JUST_CEILING")
|
||||
.action(ArgAction::Set)
|
||||
.value_parser(value_parser!(PathBuf))
|
||||
.help("Do not ascend above <CEILING> directory when searching for a justfile."),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(arg::CHECK)
|
||||
.long("check")
|
||||
|
|
@ -777,6 +787,7 @@ impl Config {
|
|||
.unwrap()
|
||||
.clone(),
|
||||
allow_missing: matches.get_flag(arg::ALLOW_MISSING),
|
||||
ceiling: matches.get_one::<PathBuf>(arg::CEILING).cloned(),
|
||||
check: matches.get_flag(arg::CHECK),
|
||||
color: (*matches.get_one::<UseColor>(arg::COLOR).unwrap()).into(),
|
||||
command_color: matches
|
||||
|
|
|
|||
|
|
@ -185,9 +185,6 @@ pub(crate) enum Error<'src> {
|
|||
recipe: &'src str,
|
||||
io_error: io::Error,
|
||||
},
|
||||
TempfileIo {
|
||||
io_error: io::Error,
|
||||
},
|
||||
Unknown {
|
||||
recipe: &'src str,
|
||||
line_number: Option<usize>,
|
||||
|
|
@ -501,9 +498,6 @@ impl ColorDisplay for Error<'_> {
|
|||
write!(f, "Recipe `{recipe}` could not be run because of an IO error while trying to create a temporary \
|
||||
directory or write a file to that directory: {io_error}")?;
|
||||
}
|
||||
TempfileIo { io_error } => {
|
||||
write!(f, "Tempfile I/O error: {io_error}")?;
|
||||
}
|
||||
Unknown { recipe, line_number} => {
|
||||
if let Some(n) = line_number {
|
||||
write!(f, "Recipe `{recipe}` failed on line {n} for an unknown reason")?;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ pub(crate) struct Interpreter<'src> {
|
|||
|
||||
impl Interpreter<'_> {
|
||||
pub(crate) fn default_script_interpreter() -> &'static Interpreter<'static> {
|
||||
static INSTANCE: Lazy<Interpreter<'static>> = Lazy::new(|| Interpreter {
|
||||
static INSTANCE: LazyLock<Interpreter<'static>> = LazyLock::new(|| Interpreter {
|
||||
arguments: vec![StringLiteral::from_raw("-eu")],
|
||||
command: StringLiteral::from_raw("sh"),
|
||||
});
|
||||
|
|
|
|||
14
src/item.rs
14
src/item.rs
|
|
@ -15,7 +15,7 @@ pub(crate) enum Item<'src> {
|
|||
Module {
|
||||
absolute: Option<PathBuf>,
|
||||
doc: Option<String>,
|
||||
groups: Vec<String>,
|
||||
groups: Vec<StringLiteral<'src>>,
|
||||
name: Name<'src>,
|
||||
optional: bool,
|
||||
relative: Option<StringLiteral<'src>>,
|
||||
|
|
@ -45,11 +45,21 @@ impl Display for Item<'_> {
|
|||
write!(f, " {relative}")
|
||||
}
|
||||
Self::Module {
|
||||
doc,
|
||||
groups,
|
||||
name,
|
||||
relative,
|
||||
optional,
|
||||
relative,
|
||||
..
|
||||
} => {
|
||||
if let Some(doc) = doc {
|
||||
writeln!(f, "# {doc}")?;
|
||||
}
|
||||
|
||||
for group in groups {
|
||||
writeln!(f, "[group: {group}]")?;
|
||||
}
|
||||
|
||||
write!(f, "mod")?;
|
||||
|
||||
if *optional {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ pub(crate) struct Justfile<'src> {
|
|||
#[serde(rename = "first", serialize_with = "keyed::serialize_option")]
|
||||
pub(crate) default: Option<Arc<Recipe<'src>>>,
|
||||
pub(crate) doc: Option<String>,
|
||||
pub(crate) groups: Vec<String>,
|
||||
pub(crate) groups: Vec<StringLiteral<'src>>,
|
||||
#[serde(skip)]
|
||||
pub(crate) loaded: Vec<PathBuf>,
|
||||
#[serde(skip)]
|
||||
|
|
@ -49,12 +49,22 @@ impl<'src> Justfile<'src> {
|
|||
input,
|
||||
self
|
||||
.recipes
|
||||
.keys()
|
||||
.map(|name| Suggestion { name, target: None })
|
||||
.chain(self.aliases.iter().map(|(name, alias)| Suggestion {
|
||||
name,
|
||||
target: Some(alias.target.name.lexeme()),
|
||||
})),
|
||||
.values()
|
||||
.filter(|recipe| recipe.is_public())
|
||||
.map(|recipe| Suggestion {
|
||||
name: recipe.name(),
|
||||
target: None,
|
||||
})
|
||||
.chain(
|
||||
self
|
||||
.aliases
|
||||
.values()
|
||||
.filter(|alias| alias.is_public())
|
||||
.map(|alias| Suggestion {
|
||||
name: alias.name.lexeme(),
|
||||
target: Some(alias.target.name.lexeme()),
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -458,8 +468,12 @@ impl<'src> Justfile<'src> {
|
|||
recipes
|
||||
}
|
||||
|
||||
pub(crate) fn groups(&self) -> &[String] {
|
||||
&self.groups
|
||||
pub(crate) fn groups(&self) -> Vec<&str> {
|
||||
self
|
||||
.groups
|
||||
.iter()
|
||||
.map(|group| group.cooked.as_str())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn public_groups(&self, config: &Config) -> Vec<String> {
|
||||
|
|
|
|||
|
|
@ -104,7 +104,6 @@ pub(crate) use {
|
|||
edit_distance::edit_distance,
|
||||
lexiclean::Lexiclean,
|
||||
libc::EXIT_FAILURE,
|
||||
once_cell::sync::Lazy,
|
||||
rand::seq::IndexedRandom,
|
||||
regex::Regex,
|
||||
serde::{
|
||||
|
|
@ -120,7 +119,7 @@ pub(crate) use {
|
|||
ffi::OsString,
|
||||
fmt::{self, Debug, Display, Formatter},
|
||||
fs,
|
||||
io::{self, Read, Seek, Write},
|
||||
io::{self, Write},
|
||||
iter::{self, FromIterator},
|
||||
mem,
|
||||
ops::Deref,
|
||||
|
|
@ -128,11 +127,11 @@ pub(crate) use {
|
|||
path::{self, Path, PathBuf},
|
||||
process::{self, Command, ExitStatus, Stdio},
|
||||
str::{self, Chars},
|
||||
sync::{Arc, Mutex, MutexGuard, OnceLock},
|
||||
sync::{Arc, LazyLock, Mutex, MutexGuard, OnceLock},
|
||||
thread, vec,
|
||||
},
|
||||
strum::{Display, EnumDiscriminants, EnumString, IntoStaticStr},
|
||||
tempfile::{tempfile, TempDir},
|
||||
tempfile::TempDir,
|
||||
typed_arena::Arena,
|
||||
unicode_width::{UnicodeWidthChar, UnicodeWidthStr},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -411,7 +411,7 @@ impl<'run, 'src> Parser<'run, 'src> {
|
|||
let mut groups = Vec::new();
|
||||
for attribute in attributes {
|
||||
if let Attribute::Group(group) = attribute {
|
||||
groups.push(group.cooked);
|
||||
groups.push(group);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
14
src/run.rs
14
src/run.rs
|
|
@ -34,3 +34,17 @@ pub fn run(args: impl Iterator<Item = impl Into<OsString> + Clone>) -> Result<()
|
|||
error.code().unwrap_or(EXIT_FAILURE)
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn run_can_be_called_more_than_once() {
|
||||
let tmp = testing::tempdir();
|
||||
fs::write(tmp.path().join("justfile"), "foo:").unwrap();
|
||||
let search_directory = format!("{}/", tmp.path().to_str().unwrap());
|
||||
run(["just", &search_directory].iter()).unwrap();
|
||||
run(["just", &search_directory].iter()).unwrap();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,14 +31,17 @@ impl Search {
|
|||
|
||||
/// Find justfile given search configuration and invocation directory
|
||||
pub(crate) fn find(
|
||||
search_config: &SearchConfig,
|
||||
ceiling: Option<&Path>,
|
||||
invocation_directory: &Path,
|
||||
search_config: &SearchConfig,
|
||||
) -> SearchResult<Self> {
|
||||
match search_config {
|
||||
SearchConfig::FromInvocationDirectory => Self::find_in_directory(invocation_directory),
|
||||
SearchConfig::FromInvocationDirectory => {
|
||||
Self::find_in_directory(ceiling, invocation_directory)
|
||||
}
|
||||
SearchConfig::FromSearchDirectory { search_directory } => {
|
||||
let search_directory = Self::clean(invocation_directory, search_directory);
|
||||
let justfile = Self::justfile(&search_directory)?;
|
||||
let justfile = Self::justfile(ceiling, &search_directory)?;
|
||||
let working_directory = Self::working_directory_from_justfile(&justfile)?;
|
||||
Ok(Self {
|
||||
justfile,
|
||||
|
|
@ -47,7 +50,7 @@ impl Search {
|
|||
}
|
||||
SearchConfig::GlobalJustfile => Ok(Self {
|
||||
justfile: Self::find_global_justfile()?,
|
||||
working_directory: Self::project_root(invocation_directory)?,
|
||||
working_directory: Self::project_root(ceiling, invocation_directory)?,
|
||||
}),
|
||||
SearchConfig::WithJustfile { justfile } => {
|
||||
let justfile = Self::clean(invocation_directory, justfile);
|
||||
|
|
@ -88,7 +91,7 @@ impl Search {
|
|||
}
|
||||
|
||||
/// Find justfile starting from parent directory of current justfile
|
||||
pub(crate) fn search_parent_directory(&self) -> SearchResult<Self> {
|
||||
pub(crate) fn search_parent_directory(&self, ceiling: Option<&Path>) -> SearchResult<Self> {
|
||||
let parent = self
|
||||
.justfile
|
||||
.parent()
|
||||
|
|
@ -96,12 +99,12 @@ impl Search {
|
|||
.ok_or_else(|| SearchError::JustfileHadNoParent {
|
||||
path: self.justfile.clone(),
|
||||
})?;
|
||||
Self::find_in_directory(parent)
|
||||
Self::find_in_directory(ceiling, parent)
|
||||
}
|
||||
|
||||
/// Find justfile starting in given directory searching upwards in directory tree
|
||||
fn find_in_directory(starting_dir: &Path) -> SearchResult<Self> {
|
||||
let justfile = Self::justfile(starting_dir)?;
|
||||
fn find_in_directory(ceiling: Option<&Path>, starting_dir: &Path) -> SearchResult<Self> {
|
||||
let justfile = Self::justfile(ceiling, starting_dir)?;
|
||||
let working_directory = Self::working_directory_from_justfile(&justfile)?;
|
||||
Ok(Self {
|
||||
justfile,
|
||||
|
|
@ -113,10 +116,11 @@ impl Search {
|
|||
pub(crate) fn init(
|
||||
search_config: &SearchConfig,
|
||||
invocation_directory: &Path,
|
||||
ceiling: Option<&Path>,
|
||||
) -> SearchResult<Self> {
|
||||
match search_config {
|
||||
SearchConfig::FromInvocationDirectory => {
|
||||
let working_directory = Self::project_root(invocation_directory)?;
|
||||
let working_directory = Self::project_root(ceiling, invocation_directory)?;
|
||||
let justfile = working_directory.join(DEFAULT_JUSTFILE_NAME);
|
||||
Ok(Self {
|
||||
justfile,
|
||||
|
|
@ -125,7 +129,7 @@ impl Search {
|
|||
}
|
||||
SearchConfig::FromSearchDirectory { search_directory } => {
|
||||
let search_directory = Self::clean(invocation_directory, search_directory);
|
||||
let working_directory = Self::project_root(&search_directory)?;
|
||||
let working_directory = Self::project_root(ceiling, &search_directory)?;
|
||||
let justfile = working_directory.join(DEFAULT_JUSTFILE_NAME);
|
||||
Ok(Self {
|
||||
justfile,
|
||||
|
|
@ -153,7 +157,7 @@ impl Search {
|
|||
|
||||
/// Search upwards from `directory` for a file whose name matches one of
|
||||
/// `JUSTFILE_NAMES`
|
||||
fn justfile(directory: &Path) -> SearchResult<PathBuf> {
|
||||
fn justfile(ceiling: Option<&Path>, directory: &Path) -> SearchResult<PathBuf> {
|
||||
for directory in directory.ancestors() {
|
||||
let mut candidates = BTreeSet::new();
|
||||
|
||||
|
|
@ -181,6 +185,12 @@ impl Search {
|
|||
1 => return Ok(candidates.into_iter().next().unwrap()),
|
||||
_ => return Err(SearchError::MultipleCandidates { candidates }),
|
||||
}
|
||||
|
||||
if let Some(ceiling) = ceiling {
|
||||
if directory == ceiling {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(SearchError::NotFound)
|
||||
|
|
@ -207,7 +217,7 @@ impl Search {
|
|||
/// Search upwards from `directory` for the root directory of a software
|
||||
/// project, as determined by the presence of one of the version control
|
||||
/// system directories given in `PROJECT_ROOT_CHILDREN`
|
||||
fn project_root(directory: &Path) -> SearchResult<PathBuf> {
|
||||
fn project_root(ceiling: Option<&Path>, directory: &Path) -> SearchResult<PathBuf> {
|
||||
for directory in directory.ancestors() {
|
||||
let entries = fs::read_dir(directory).map_err(|io_error| SearchError::Io {
|
||||
io_error,
|
||||
|
|
@ -225,6 +235,12 @@ impl Search {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ceiling) = ceiling {
|
||||
if directory == ceiling {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(directory.to_owned())
|
||||
|
|
@ -250,7 +266,7 @@ mod tests {
|
|||
#[test]
|
||||
fn not_found() {
|
||||
let tmp = testing::tempdir();
|
||||
match Search::justfile(tmp.path()) {
|
||||
match Search::justfile(None, tmp.path()) {
|
||||
Err(SearchError::NotFound) => {}
|
||||
_ => panic!("No justfile found error was expected"),
|
||||
}
|
||||
|
|
@ -270,7 +286,7 @@ mod tests {
|
|||
}
|
||||
fs::write(&path, "default:\n\techo ok").unwrap();
|
||||
path.pop();
|
||||
match Search::justfile(path.as_path()) {
|
||||
match Search::justfile(None, path.as_path()) {
|
||||
Err(SearchError::MultipleCandidates { .. }) => {}
|
||||
_ => panic!("Multiple candidates error was expected"),
|
||||
}
|
||||
|
|
@ -283,7 +299,7 @@ mod tests {
|
|||
path.push(DEFAULT_JUSTFILE_NAME);
|
||||
fs::write(&path, "default:\n\techo ok").unwrap();
|
||||
path.pop();
|
||||
if let Err(err) = Search::justfile(path.as_path()) {
|
||||
if let Err(err) = Search::justfile(None, path.as_path()) {
|
||||
panic!("No errors were expected: {err}");
|
||||
}
|
||||
}
|
||||
|
|
@ -306,7 +322,7 @@ mod tests {
|
|||
path.push(spongebob_case);
|
||||
fs::write(&path, "default:\n\techo ok").unwrap();
|
||||
path.pop();
|
||||
if let Err(err) = Search::justfile(path.as_path()) {
|
||||
if let Err(err) = Search::justfile(None, path.as_path()) {
|
||||
panic!("No errors were expected: {err}");
|
||||
}
|
||||
}
|
||||
|
|
@ -322,7 +338,7 @@ mod tests {
|
|||
fs::create_dir(&path).expect("test justfile search: failed to create intermediary directory");
|
||||
path.push("b");
|
||||
fs::create_dir(&path).expect("test justfile search: failed to create intermediary directory");
|
||||
if let Err(err) = Search::justfile(path.as_path()) {
|
||||
if let Err(err) = Search::justfile(None, path.as_path()) {
|
||||
panic!("No errors were expected: {err}");
|
||||
}
|
||||
}
|
||||
|
|
@ -341,7 +357,7 @@ mod tests {
|
|||
path.pop();
|
||||
path.push("b");
|
||||
fs::create_dir(&path).expect("test justfile search: failed to create intermediary directory");
|
||||
match Search::justfile(path.as_path()) {
|
||||
match Search::justfile(None, path.as_path()) {
|
||||
Ok(found_path) => {
|
||||
path.pop();
|
||||
path.push(DEFAULT_JUSTFILE_NAME);
|
||||
|
|
@ -370,7 +386,7 @@ mod tests {
|
|||
|
||||
let search_config = SearchConfig::FromInvocationDirectory;
|
||||
|
||||
let search = Search::find(&search_config, &sub).unwrap();
|
||||
let search = Search::find(None, &sub, &search_config).unwrap();
|
||||
|
||||
assert_eq!(search.justfile, justfile);
|
||||
assert_eq!(search.working_directory, sub);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ use super::*;
|
|||
pub(crate) struct SignalHandler {
|
||||
caught: Option<Signal>,
|
||||
children: BTreeMap<i32, Command>,
|
||||
initialized: bool,
|
||||
verbosity: Verbosity,
|
||||
}
|
||||
|
||||
|
|
@ -10,7 +11,11 @@ impl SignalHandler {
|
|||
pub(crate) fn install(verbosity: Verbosity) -> RunResult<'static> {
|
||||
let mut instance = Self::instance();
|
||||
instance.verbosity = verbosity;
|
||||
Platform::install_signal_handler(|signal| Self::instance().interrupt(signal))
|
||||
if !instance.initialized {
|
||||
Platform::install_signal_handler(|signal| Self::instance().handle(signal))?;
|
||||
instance.initialized = true;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn instance() -> MutexGuard<'static, Self> {
|
||||
|
|
@ -35,11 +40,12 @@ impl SignalHandler {
|
|||
Self {
|
||||
caught: None,
|
||||
children: BTreeMap::new(),
|
||||
initialized: false,
|
||||
verbosity: Verbosity::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn interrupt(&mut self, signal: Signal) {
|
||||
fn handle(&mut self, signal: Signal) {
|
||||
if signal.is_fatal() {
|
||||
if self.children.is_empty() {
|
||||
process::exit(signal.code());
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use {
|
|||
},
|
||||
std::{
|
||||
fs::File,
|
||||
io::Read,
|
||||
os::fd::{BorrowedFd, IntoRawFd},
|
||||
sync::atomic::{self, AtomicI32},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -63,14 +63,21 @@ impl Subcommand {
|
|||
Self::changelog();
|
||||
return Ok(());
|
||||
}
|
||||
Completions { shell } => return Self::completions(*shell),
|
||||
Completions { shell } => {
|
||||
Self::completions(*shell);
|
||||
return Ok(());
|
||||
}
|
||||
Init => return Self::init(config),
|
||||
Man => return Self::man(),
|
||||
Request { request } => return Self::request(request),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let search = Search::find(&config.search_config, &config.invocation_directory)?;
|
||||
let search = Search::find(
|
||||
config.ceiling.as_deref(),
|
||||
&config.invocation_directory,
|
||||
&config.search_config,
|
||||
)?;
|
||||
|
||||
if let Edit = self {
|
||||
return Self::edit(&search);
|
||||
|
|
@ -132,7 +139,9 @@ impl Subcommand {
|
|||
|
||||
if fallback {
|
||||
if let Err(err @ (Error::UnknownRecipe { .. } | Error::UnknownSubmodule { .. })) = result {
|
||||
search = search.search_parent_directory().map_err(|_| err)?;
|
||||
search = search
|
||||
.search_parent_directory(config.ceiling.as_deref())
|
||||
.map_err(|_| err)?;
|
||||
|
||||
if config.verbosity.loquacious() {
|
||||
eprintln!(
|
||||
|
|
@ -277,9 +286,8 @@ impl Subcommand {
|
|||
justfile.run(config, search, overrides, &recipes)
|
||||
}
|
||||
|
||||
fn completions(shell: completions::Shell) -> RunResult<'static, ()> {
|
||||
println!("{}", shell.script()?);
|
||||
Ok(())
|
||||
fn completions(shell: completions::Shell) {
|
||||
print!("{}", shell.script());
|
||||
}
|
||||
|
||||
fn dump(config: &Config, compilation: Compilation) -> RunResult<'static> {
|
||||
|
|
@ -366,7 +374,11 @@ impl Subcommand {
|
|||
}
|
||||
|
||||
fn init(config: &Config) -> RunResult<'static> {
|
||||
let search = Search::init(&config.search_config, &config.invocation_directory)?;
|
||||
let search = Search::init(
|
||||
&config.search_config,
|
||||
&config.invocation_directory,
|
||||
config.ceiling.as_deref(),
|
||||
)?;
|
||||
|
||||
if search.justfile.is_file() {
|
||||
return Err(Error::InitExists {
|
||||
|
|
@ -509,7 +521,7 @@ impl Subcommand {
|
|||
BTreeMap::new()
|
||||
} else {
|
||||
let mut aliases = BTreeMap::<&str, Vec<&str>>::new();
|
||||
for alias in module.aliases.values().filter(|alias| !alias.is_private()) {
|
||||
for alias in module.aliases.values().filter(|alias| alias.is_public()) {
|
||||
aliases
|
||||
.entry(alias.target.name.lexeme())
|
||||
.or_default()
|
||||
|
|
|
|||
84
tests/ceiling.rs
Normal file
84
tests/ceiling.rs
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn justfile_run_search_stops_at_ceiling_dir() {
|
||||
let tempdir = tempdir();
|
||||
|
||||
let ceiling = tempdir.path().join("foo");
|
||||
|
||||
fs::create_dir(&ceiling).unwrap();
|
||||
|
||||
#[cfg(not(windows))]
|
||||
let ceiling = ceiling.canonicalize().unwrap();
|
||||
|
||||
Test::with_tempdir(tempdir)
|
||||
.justfile(
|
||||
"
|
||||
foo:
|
||||
echo bar
|
||||
",
|
||||
)
|
||||
.create_dir("foo/bar")
|
||||
.current_dir("foo/bar")
|
||||
.args(["--ceiling", ceiling.to_str().unwrap()])
|
||||
.stderr("error: No justfile found\n")
|
||||
.status(EXIT_FAILURE)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ceiling_can_be_passed_as_environment_variable() {
|
||||
let tempdir = tempdir();
|
||||
|
||||
let ceiling = tempdir.path().join("foo");
|
||||
|
||||
fs::create_dir(&ceiling).unwrap();
|
||||
|
||||
#[cfg(not(windows))]
|
||||
let ceiling = ceiling.canonicalize().unwrap();
|
||||
|
||||
Test::with_tempdir(tempdir)
|
||||
.justfile(
|
||||
"
|
||||
foo:
|
||||
echo bar
|
||||
",
|
||||
)
|
||||
.create_dir("foo/bar")
|
||||
.current_dir("foo/bar")
|
||||
.env("JUST_CEILING", ceiling.to_str().unwrap())
|
||||
.stderr("error: No justfile found\n")
|
||||
.status(EXIT_FAILURE)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn justfile_init_search_stops_at_ceiling_dir() {
|
||||
let tempdir = tempdir();
|
||||
|
||||
let ceiling = tempdir.path().join("foo");
|
||||
|
||||
fs::create_dir(&ceiling).unwrap();
|
||||
|
||||
#[cfg(not(windows))]
|
||||
let ceiling = ceiling.canonicalize().unwrap();
|
||||
|
||||
let Output { tempdir, .. } = Test::with_tempdir(tempdir)
|
||||
.no_justfile()
|
||||
.test_round_trip(false)
|
||||
.create_dir(".git")
|
||||
.create_dir("foo/bar")
|
||||
.current_dir("foo/bar")
|
||||
.args(["--init", "--ceiling", ceiling.to_str().unwrap()])
|
||||
.stderr_regex(if cfg!(windows) {
|
||||
r"Wrote justfile to `.*\\foo\\bar\\justfile`\n"
|
||||
} else {
|
||||
"Wrote justfile to `.*/foo/bar/justfile`\n"
|
||||
})
|
||||
.run();
|
||||
|
||||
assert_eq!(
|
||||
fs::read_to_string(tempdir.path().join("foo/bar/justfile")).unwrap(),
|
||||
just::INIT_JUSTFILE
|
||||
);
|
||||
}
|
||||
43
tests/default.rs
Normal file
43
tests/default.rs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn default_attribute_overrides_first_recipe() {
|
||||
Test::new()
|
||||
.justfile(
|
||||
"
|
||||
foo:
|
||||
@echo FOO
|
||||
|
||||
[default]
|
||||
bar:
|
||||
@echo BAR
|
||||
",
|
||||
)
|
||||
.stdout("BAR\n")
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_attribute_may_only_appear_once_per_justfile() {
|
||||
Test::new()
|
||||
.justfile(
|
||||
"
|
||||
[default]
|
||||
foo:
|
||||
|
||||
[default]
|
||||
bar:
|
||||
",
|
||||
)
|
||||
.stderr(
|
||||
"
|
||||
error: Recipe `foo` has duplicate `[default]` attribute, which may only appear once per module
|
||||
——▶ justfile:2:1
|
||||
│
|
||||
2 │ foo:
|
||||
│ ^^^
|
||||
"
|
||||
)
|
||||
.status(EXIT_FAILURE)
|
||||
.run();
|
||||
}
|
||||
|
|
@ -1589,3 +1589,45 @@ fn private_variable() {
|
|||
)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn module_groups_are_preserved() {
|
||||
Test::new()
|
||||
.justfile(
|
||||
r#"
|
||||
[group('bar')]
|
||||
[group("baz")]
|
||||
mod foo
|
||||
"#,
|
||||
)
|
||||
.write("foo.just", "")
|
||||
.arg("--dump")
|
||||
.stdout(
|
||||
r#"
|
||||
[group: 'bar']
|
||||
[group: "baz"]
|
||||
mod foo
|
||||
"#,
|
||||
)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn module_docs_are_preserved() {
|
||||
Test::new()
|
||||
.justfile(
|
||||
r"
|
||||
# bar
|
||||
mod foo
|
||||
",
|
||||
)
|
||||
.write("foo.just", "")
|
||||
.arg("--dump")
|
||||
.stdout(
|
||||
r"
|
||||
# bar
|
||||
mod foo
|
||||
",
|
||||
)
|
||||
.run();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ mod assignment;
|
|||
mod attributes;
|
||||
mod backticks;
|
||||
mod byte_order_mark;
|
||||
mod ceiling;
|
||||
mod changelog;
|
||||
mod choose;
|
||||
mod command;
|
||||
|
|
@ -60,6 +61,7 @@ mod conditional;
|
|||
mod confirm;
|
||||
mod constants;
|
||||
mod datetime;
|
||||
mod default;
|
||||
mod delimiters;
|
||||
mod dependencies;
|
||||
mod directories;
|
||||
|
|
|
|||
|
|
@ -1351,19 +1351,62 @@ b:
|
|||
fn run_suggestion() {
|
||||
Test::new()
|
||||
.arg("hell")
|
||||
.justfile(
|
||||
r#"
|
||||
hello a b='B ' c='C':
|
||||
echo {{a}} {{b}} {{c}}
|
||||
|
||||
a Z="\t z":
|
||||
"#,
|
||||
)
|
||||
.justfile("hello:")
|
||||
.stderr("error: Justfile does not contain recipe `hell`\nDid you mean `hello`?\n")
|
||||
.status(EXIT_FAILURE)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_recipes_are_not_suggested() {
|
||||
Test::new()
|
||||
.arg("hell")
|
||||
.justfile(
|
||||
"
|
||||
[private]
|
||||
hello:
|
||||
",
|
||||
)
|
||||
.stderr("error: Justfile does not contain recipe `hell`\n")
|
||||
.status(EXIT_FAILURE)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alias_suggestion() {
|
||||
Test::new()
|
||||
.arg("hell")
|
||||
.justfile(
|
||||
"
|
||||
alias hello := bar
|
||||
|
||||
bar:
|
||||
",
|
||||
)
|
||||
.stderr(
|
||||
"error: Justfile does not contain recipe `hell`\nDid you mean `hello`, an alias for `bar`?\n",
|
||||
)
|
||||
.status(EXIT_FAILURE)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_aliases_are_not_suggested() {
|
||||
Test::new()
|
||||
.arg("hell")
|
||||
.justfile(
|
||||
"
|
||||
[private]
|
||||
alias hello := bar
|
||||
|
||||
bar:
|
||||
",
|
||||
)
|
||||
.stderr("error: Justfile does not contain recipe `hell`\n")
|
||||
.status(EXIT_FAILURE)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn line_continuation_with_space() {
|
||||
Test::new()
|
||||
|
|
|
|||
|
|
@ -147,7 +147,6 @@ impl Test {
|
|||
self
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) fn test_round_trip(mut self, test_round_trip: bool) -> Self {
|
||||
self.test_round_trip = test_round_trip;
|
||||
self
|
||||
|
|
|
|||
|
|
@ -134,6 +134,7 @@ if [ -z "${target-}" ]; then
|
|||
arm64-Darwin) target=aarch64-apple-darwin;;
|
||||
armv6l-Linux) target=arm-unknown-linux-musleabihf;;
|
||||
armv7l-Linux) target=armv7-unknown-linux-musleabihf;;
|
||||
loongarch64-Linux) target=loongarch64-unknown-linux-musl;;
|
||||
x86_64-Darwin) target=x86_64-apple-darwin;;
|
||||
x86_64-Linux) target=x86_64-unknown-linux-musl;;
|
||||
x86_64-MINGW64_NT) target=x86_64-pc-windows-msvc;;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue