mirror of
https://github.com/casey/just.git
synced 2025-08-04 23:17:59 +00:00
Merge branch 'master' into feat/enums
This commit is contained in:
commit
724fd13444
49 changed files with 1192 additions and 556 deletions
6
.github/workflows/release.yaml
vendored
6
.github/workflows/release.yaml
vendored
|
@ -113,7 +113,7 @@ jobs:
|
|||
shell: bash
|
||||
|
||||
- name: Publish Archive
|
||||
uses: softprops/action-gh-release@v2.2.1
|
||||
uses: softprops/action-gh-release@v2.3.2
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
with:
|
||||
draft: false
|
||||
|
@ -123,7 +123,7 @@ jobs:
|
|||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Publish Changelog
|
||||
uses: softprops/action-gh-release@v2.2.1
|
||||
uses: softprops/action-gh-release@v2.3.2
|
||||
if: >-
|
||||
${{
|
||||
startsWith(github.ref, 'refs/tags/')
|
||||
|
@ -160,7 +160,7 @@ jobs:
|
|||
shasum -a 256 * > ../SHA256SUMS
|
||||
|
||||
- name: Publish Checksums
|
||||
uses: softprops/action-gh-release@v2.2.1
|
||||
uses: softprops/action-gh-release@v2.3.2
|
||||
with:
|
||||
draft: false
|
||||
files: SHA256SUMS
|
||||
|
|
37
CHANGELOG.md
37
CHANGELOG.md
|
@ -1,6 +1,43 @@
|
|||
Changelog
|
||||
=========
|
||||
|
||||
[1.41.0](https://github.com/casey/just/releases/tag/1.41.0) - 2025-07-01
|
||||
------------------------------------------------------------------------
|
||||
|
||||
### Changed
|
||||
- Treat SIGINFO as non-fatal ([#2788](https://github.com/casey/just/pull/2788) by [casey](https://github.com/casey))
|
||||
- Improve signal handling ([#2488](https://github.com/casey/just/pull/2488) by [casey](https://github.com/casey))
|
||||
|
||||
### Added
|
||||
- Add `dotenv-override` setting ([#2785](https://github.com/casey/just/pull/2785) by [Lun4m](https://github.com/Lun4m))
|
||||
- Add `PATH_SEP` and `PATH_VAR_SEP` constants ([#2679](https://github.com/casey/just/pull/2679) by [casey](https://github.com/casey))
|
||||
- Add `--tempdir` command-line option ([#2798](https://github.com/casey/just/pull/2798) by [casey](https://github.com/casey))
|
||||
|
||||
### Fixed
|
||||
- Pin `clap_complete` to last compatible version ([#2800](https://github.com/casey/just/pull/2800) by [casey](https://github.com/casey))
|
||||
|
||||
### Misc
|
||||
- Add `arkade` to readme ([#2700](https://github.com/casey/just/pull/2700) by [rgee0](https://github.com/rgee0))
|
||||
- Link to pipx instead of pypi in readme ([#2799](https://github.com/casey/just/pull/2799) by [casey](https://github.com/casey))
|
||||
- Add reasons to `#[ignore]` attributes ([#2797](https://github.com/casey/just/pull/2797) by [casey](https://github.com/casey))
|
||||
- Mention that command-line environment variables are inherited ([#2783](https://github.com/casey/just/pull/2783) by [philipmgrant](https://github.com/philipmgrant))
|
||||
- Fix attribute grammar and update documentation ([#2790](https://github.com/casey/just/pull/2790) by [casey](https://github.com/casey))
|
||||
- Tweak prose in groups section of readme ([#2767](https://github.com/casey/just/pull/2767) by [offby1](https://github.com/offby1))
|
||||
- Update `extractions/setup-just` version in readme ([#2735](https://github.com/casey/just/pull/2735) by [esadek](https://github.com/esadek))
|
||||
- Remove `return` in `Recipe::confirm` ([#2789](https://github.com/casey/just/pull/2789) by [casey](https://github.com/casey))
|
||||
- Add `mise` to alternatives in readem ([#2758](https://github.com/casey/just/pull/2758) by [koppor](https://github.com/koppor))
|
||||
- Update `softprops/action-gh-release` ([#2781](https://github.com/casey/just/pull/2781) by [app/dependabot](https://github.com/app/dependabot))
|
||||
- Use `default` as name of `--init` justfile default recipe ([#2777](https://github.com/casey/just/pull/2777) by [casey](https://github.com/casey))
|
||||
- Add `just.systems` link to `--init` justfile ([#2776](https://github.com/casey/just/pull/2776) by [casey](https://github.com/casey))
|
||||
- Fix `kitchen-sink.just` comment ([#2738](https://github.com/casey/just/pull/2738) by [azarmadr](https://github.com/azarmadr))
|
||||
- Update `softprops/action-gh-release` ([#2716](https://github.com/casey/just/pull/2716) by [app/dependabot](https://github.com/app/dependabot))
|
||||
- Fix clippy lints ([#2768](https://github.com/casey/just/pull/2768) by [casey](https://github.com/casey))
|
||||
- Add back-to-the-top link to readme ([#2707](https://github.com/casey/just/pull/2707) by [bravesasha](https://github.com/bravesasha))
|
||||
- Placate clippy lints for 1.86 ([#2708](https://github.com/casey/just/pull/2708) by [casey](https://github.com/casey))
|
||||
- Use `-S` in `uv` example ([#2696](https://github.com/casey/just/pull/2696) by [rmoorman](https://github.com/rmoorman))
|
||||
- Handle `--request` without parsing justfile ([#2683](https://github.com/casey/just/pull/2683) by [casey](https://github.com/casey))
|
||||
- Bump MSRV to 1.77 and enforce on CI ([#2674](https://github.com/casey/just/pull/2674) by [casey](https://github.com/casey))
|
||||
|
||||
[1.40.0](https://github.com/casey/just/releases/tag/1.40.0) - 2025-03-09
|
||||
------------------------------------------------------------------------
|
||||
|
||||
|
|
501
Cargo.lock
generated
501
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "just"
|
||||
version = "1.40.0"
|
||||
version = "1.41.0"
|
||||
authors = ["Casey Rodarmor <casey@rodarmor.com>"]
|
||||
autotests = false
|
||||
categories = ["command-line-utilities", "development-tools"]
|
||||
|
@ -23,7 +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.0.0"
|
||||
clap_complete = "=4.5.48"
|
||||
clap_mangen = "0.2.20"
|
||||
ctrlc = { version = "3.1.1", features = ["termination"] }
|
||||
derive-where = "1.2.7"
|
||||
|
@ -55,7 +55,7 @@ unicode-width = "0.2.0"
|
|||
uuid = { version = "1.0.0", features = ["v4"] }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
nix = { version = "0.29.0", features = ["user"] }
|
||||
nix = { version = "0.30.1", features = ["user"] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
ctrlc = { version = "3.1.1", features = ["termination"] }
|
||||
|
@ -64,7 +64,7 @@ ctrlc = { version = "3.1.1", features = ["termination"] }
|
|||
executable-path = "1.0.0"
|
||||
pretty_assertions = "1.0.0"
|
||||
temptree = "0.2.0"
|
||||
which = "7.0.0"
|
||||
which = "8.0.0"
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] }
|
||||
|
@ -73,6 +73,7 @@ unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] }
|
|||
all = { level = "deny", priority = -1 }
|
||||
arbitrary-source-item-ordering = "deny"
|
||||
enum_glob_use = "allow"
|
||||
ignore_without_reason = "allow"
|
||||
needless_pass_by_value = "allow"
|
||||
pedantic = { level = "deny", priority = -1 }
|
||||
similar_names = "allow"
|
||||
|
|
|
@ -124,9 +124,11 @@ sequence : expression ',' sequence
|
|||
|
||||
recipe : attributes* '@'? NAME parameter* variadic? ':' dependencies eol body?
|
||||
|
||||
attributes : '[' attribute* ']' eol
|
||||
attributes : '[' attribute (',' attribute)* ']' eol
|
||||
|
||||
attribute : NAME ( '(' string ')' )?
|
||||
attribute : NAME
|
||||
| NAME ':' string
|
||||
| NAME '(' string (',' string)* ')'
|
||||
|
||||
parameter : '$'? NAME
|
||||
| '$'? NAME '=' value
|
||||
|
|
203
README.md
203
README.md
|
@ -139,6 +139,11 @@ most Windows users.)
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><a href=https://github.com/alexellis/arkade>arkade</a></td>
|
||||
<td>just</td>
|
||||
<td><code>arkade get just</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href=https://asdf-vm.com>asdf</a></td>
|
||||
<td><a href=https://github.com/olofvndrhr/asdf-just>just</a></td>
|
||||
|
@ -173,7 +178,7 @@ most Windows users.)
|
|||
<td><code>npm install -g rust-just</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href=https://pypi.org/>PyPI</a></td>
|
||||
<td><a href=https://pipx.pypa.io/stable/>pipx</a></td>
|
||||
<td><a href=https://pypi.org/project/rust-just/>rust-just</a></td>
|
||||
<td><code>pipx install rust-just</code></td>
|
||||
</tr>
|
||||
|
@ -387,6 +392,10 @@ to determine the latest version of `just` to install, and those API calls are
|
|||
rate-limited on a per-IP basis. To make `install.sh` more reliable in such
|
||||
circumstances, pass a specific tag to install with `--tag`.
|
||||
|
||||
Another way to avoid rate-limiting is to pass a GitHub authentication token to
|
||||
`install.sh` as an environment variable named `GITHUB_TOKEN`, allowing it to
|
||||
authenticate its requests.
|
||||
|
||||
[Releases](https://github.com/casey/just/releases) include a `SHA256SUM` file
|
||||
which can be used to verify the integrity of pre-built binary archives.
|
||||
|
||||
|
@ -407,7 +416,7 @@ Using package managers pre-installed on GitHub Actions runners on MacOS with
|
|||
With [extractions/setup-just](https://github.com/extractions/setup-just):
|
||||
|
||||
```yaml
|
||||
- uses: extractions/setup-just@v2
|
||||
- uses: extractions/setup-just@v3
|
||||
with:
|
||||
just-version: 1.5.0 # optional semver specification, otherwise latest
|
||||
```
|
||||
|
@ -685,6 +694,14 @@ cc main.c foo.c bar.c -o main
|
|||
testing… all tests passed!
|
||||
```
|
||||
|
||||
Recipes may depend on recipes in submodules:
|
||||
|
||||
```justfile
|
||||
mod foo
|
||||
|
||||
baz: foo::bar
|
||||
```
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
|
@ -984,6 +1001,7 @@ foo:
|
|||
| `allow-duplicate-variables` | boolean | `false` | Allow variables appearing later in a `justfile` to override earlier variables with the same name. |
|
||||
| `dotenv-filename` | string | - | Load a `.env` file with a custom name, if present. |
|
||||
| `dotenv-load` | boolean | `false` | Load a `.env` file, if present. |
|
||||
| `dotenv-override` | boolean | `false` | Override existing environment variables with values from the `.env` file. |
|
||||
| `dotenv-path` | string | - | Load a `.env` file from a custom path and error if not present. Overrides `dotenv-filename`. |
|
||||
| `dotenv-required` | boolean | `false` | Error if a `.env` file isn't found. |
|
||||
| `export` | boolean | `false` | Export all variables as environment variables. |
|
||||
|
@ -1055,8 +1073,9 @@ bar
|
|||
|
||||
#### Dotenv Settings
|
||||
|
||||
If any of `dotenv-load`, `dotenv-filename`, `dotenv-path`, or `dotenv-required`
|
||||
are set, `just` will try to load environment variables from a file.
|
||||
If any of `dotenv-load`, `dotenv-filename`, `dotenv-override`, `dotenv-path`,
|
||||
or `dotenv-required` are set, `just` will try to load environment variables
|
||||
from a file.
|
||||
|
||||
If `dotenv-path` is set, `just` will look for a file at the given path, which
|
||||
may be absolute, or relative to the working directory.
|
||||
|
@ -1081,6 +1100,9 @@ It is not an error if an environment file is not found, unless
|
|||
The loaded variables are environment variables, not `just` variables, and so
|
||||
must be accessed using `$VARIABLE_NAME` in recipes and backticks.
|
||||
|
||||
If `dotenv-override` is set, variables from the environment file will override
|
||||
existing environment variables.
|
||||
|
||||
For example, if your `.env` file contains:
|
||||
|
||||
```console
|
||||
|
@ -2006,35 +2028,37 @@ xdg_config_dir := if env('XDG_CONFIG_HOME', '') =~ '^/' {
|
|||
|
||||
A number of constants are predefined:
|
||||
|
||||
| Name | Value |
|
||||
|------|-------------|
|
||||
| `HEX`<sup>1.27.0</sup> | `"0123456789abcdef"` |
|
||||
| `HEXLOWER`<sup>1.27.0</sup> | `"0123456789abcdef"` |
|
||||
| `HEXUPPER`<sup>1.27.0</sup> | `"0123456789ABCDEF"` |
|
||||
| `CLEAR`<sup>1.37.0</sup> | `"\ec"` |
|
||||
| `NORMAL`<sup>1.37.0</sup> | `"\e[0m"` |
|
||||
| `BOLD`<sup>1.37.0</sup> | `"\e[1m"` |
|
||||
| `ITALIC`<sup>1.37.0</sup> | `"\e[3m"` |
|
||||
| `UNDERLINE`<sup>1.37.0</sup> | `"\e[4m"` |
|
||||
| `INVERT`<sup>1.37.0</sup> | `"\e[7m"` |
|
||||
| `HIDE`<sup>1.37.0</sup> | `"\e[8m"` |
|
||||
| `STRIKETHROUGH`<sup>1.37.0</sup> | `"\e[9m"` |
|
||||
| `BLACK`<sup>1.37.0</sup> | `"\e[30m"` |
|
||||
| `RED`<sup>1.37.0</sup> | `"\e[31m"` |
|
||||
| `GREEN`<sup>1.37.0</sup> | `"\e[32m"` |
|
||||
| `YELLOW`<sup>1.37.0</sup> | `"\e[33m"` |
|
||||
| `BLUE`<sup>1.37.0</sup> | `"\e[34m"` |
|
||||
| `MAGENTA`<sup>1.37.0</sup> | `"\e[35m"` |
|
||||
| `CYAN`<sup>1.37.0</sup> | `"\e[36m"` |
|
||||
| `WHITE`<sup>1.37.0</sup> | `"\e[37m"` |
|
||||
| `BG_BLACK`<sup>1.37.0</sup> | `"\e[40m"` |
|
||||
| `BG_RED`<sup>1.37.0</sup> | `"\e[41m"` |
|
||||
| `BG_GREEN`<sup>1.37.0</sup> | `"\e[42m"` |
|
||||
| `BG_YELLOW`<sup>1.37.0</sup> | `"\e[43m"` |
|
||||
| `BG_BLUE`<sup>1.37.0</sup> | `"\e[44m"` |
|
||||
| `BG_MAGENTA`<sup>1.37.0</sup> | `"\e[45m"` |
|
||||
| `BG_CYAN`<sup>1.37.0</sup> | `"\e[46m"` |
|
||||
| `BG_WHITE`<sup>1.37.0</sup> | `"\e[47m"` |
|
||||
| Name | Value | Value on Windows |
|
||||
|---|---|---|
|
||||
| `HEX`<sup>1.27.0</sup> | `"0123456789abcdef"` | |
|
||||
| `HEXLOWER`<sup>1.27.0</sup> | `"0123456789abcdef"` | |
|
||||
| `HEXUPPER`<sup>1.27.0</sup> | `"0123456789ABCDEF"` | |
|
||||
| `PATH_SEP`<sup>1.41.0</sup> | `"/"` | "\" |
|
||||
| `PATH_VAR_SEP`<sup>1.41.0</sup> | `":"` | ";" |
|
||||
| `CLEAR`<sup>1.37.0</sup> | `"\ec"` | |
|
||||
| `NORMAL`<sup>1.37.0</sup> | `"\e[0m"` | |
|
||||
| `BOLD`<sup>1.37.0</sup> | `"\e[1m"` | |
|
||||
| `ITALIC`<sup>1.37.0</sup> | `"\e[3m"` | |
|
||||
| `UNDERLINE`<sup>1.37.0</sup> | `"\e[4m"` | |
|
||||
| `INVERT`<sup>1.37.0</sup> | `"\e[7m"` | |
|
||||
| `HIDE`<sup>1.37.0</sup> | `"\e[8m"` | |
|
||||
| `STRIKETHROUGH`<sup>1.37.0</sup> | `"\e[9m"` | |
|
||||
| `BLACK`<sup>1.37.0</sup> | `"\e[30m"` | |
|
||||
| `RED`<sup>1.37.0</sup> | `"\e[31m"` | |
|
||||
| `GREEN`<sup>1.37.0</sup> | `"\e[32m"` | |
|
||||
| `YELLOW`<sup>1.37.0</sup> | `"\e[33m"` | |
|
||||
| `BLUE`<sup>1.37.0</sup> | `"\e[34m"` | |
|
||||
| `MAGENTA`<sup>1.37.0</sup> | `"\e[35m"` | |
|
||||
| `CYAN`<sup>1.37.0</sup> | `"\e[36m"` | |
|
||||
| `WHITE`<sup>1.37.0</sup> | `"\e[37m"` | |
|
||||
| `BG_BLACK`<sup>1.37.0</sup> | `"\e[40m"` | |
|
||||
| `BG_RED`<sup>1.37.0</sup> | `"\e[41m"` | |
|
||||
| `BG_GREEN`<sup>1.37.0</sup> | `"\e[42m"` | |
|
||||
| `BG_YELLOW`<sup>1.37.0</sup> | `"\e[43m"` | |
|
||||
| `BG_BLUE`<sup>1.37.0</sup> | `"\e[44m"` | |
|
||||
| `BG_MAGENTA`<sup>1.37.0</sup> | `"\e[45m"` | |
|
||||
| `BG_CYAN`<sup>1.37.0</sup> | `"\e[46m"` | |
|
||||
| `BG_WHITE`<sup>1.37.0</sup> | `"\e[47m"` | |
|
||||
|
||||
```just
|
||||
@foo:
|
||||
|
@ -2073,12 +2097,13 @@ change their behavior.
|
|||
| Name | Type | Description |
|
||||
|------|------|-------------|
|
||||
| `[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. |
|
||||
| `[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`. |
|
||||
| `[confirm(PROMPT)]`<sup>1.23.0</sup> | recipe | Require confirmation prior to executing recipe with a custom prompt. |
|
||||
| `[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`. |
|
||||
| `[linux]`<sup>1.8.0</sup> | recipe | Enable recipe on Linux. |
|
||||
| `[macos]`<sup>1.8.0</sup> | recipe | Enable recipe on MacOS. |
|
||||
| `[metadata(METADATA)]`<sup>master</sup> | recipe | Attach `METADATA` to recipe. |
|
||||
| `[no-cd]`<sup>1.9.0</sup> | recipe | Don't change directory before executing recipe. |
|
||||
| `[no-exit-message]`<sup>1.7.0</sup> | recipe | Don't print an error message if recipe fails. |
|
||||
| `[no-quiet]`<sup>1.23.0</sup> | recipe | Override globally quiet recipes and always echo out the recipe. |
|
||||
|
@ -2108,6 +2133,13 @@ foo:
|
|||
echo "foo"
|
||||
```
|
||||
|
||||
Attributes with a single argument may be written with a colon:
|
||||
|
||||
```just
|
||||
[group: 'bar']
|
||||
foo:
|
||||
```
|
||||
|
||||
#### Enabling and Disabling Recipes<sup>1.8.0</sup>
|
||||
|
||||
The `[linux]`, `[macos]`, `[unix]`, and `[windows]` attributes are
|
||||
|
@ -2182,7 +2214,7 @@ delete-everything:
|
|||
|
||||
### Groups
|
||||
|
||||
Recipes and modules may be annotated with a group name:
|
||||
Recipes and modules may be annotated with one or more group names:
|
||||
|
||||
```just
|
||||
[group('lint')]
|
||||
|
@ -2884,6 +2916,46 @@ the final argument. For example, on Windows, if a recipe starts with `#! py`,
|
|||
the final command the OS runs will be something like
|
||||
`py C:\Temp\PATH_TO_SAVED_RECIPE_BODY`.
|
||||
|
||||
### Script Recipes
|
||||
|
||||
Recipes with a `[script(COMMAND)]`<sup>1.32.0</sup> attribute are run as
|
||||
scripts interpreted by `COMMAND`. This avoids some of the issues with shebang
|
||||
recipes, such as the use of `cygpath` on Windows, the need to use
|
||||
`/usr/bin/env`, inconsistencies in shebang line splitting across Unix OSs, and
|
||||
requiring a temporary directory from which files can be executed.
|
||||
|
||||
Recipes with an empty `[script]` attribute are executed with the value of `set
|
||||
script-interpreter := […]`<sup>1.33.0</sup>, defaulting to `sh -eu`, and *not*
|
||||
the value of `set shell`.
|
||||
|
||||
The body of the recipe is evaluated, written to disk in the temporary
|
||||
directory, and run by passing its path as an argument to `COMMAND`.
|
||||
|
||||
The `[script(…)]` attribute is unstable, so you'll need to use `set unstable`,
|
||||
set the `JUST_UNSTABLE` environment variable, or pass `--unstable` on the
|
||||
command line.
|
||||
|
||||
### Script and Shebang Recipe Temporary Files
|
||||
|
||||
Both script and shebang recipes write the recipe body to a temporary file for
|
||||
execution. Script recipes execute that file by passing it to a command, while
|
||||
shebang recipes execute the file directly. Shebang recipe execution will fail
|
||||
if the filesystem containing the temporary file is mounted with `noexec` or is
|
||||
otherwise non-executable.
|
||||
|
||||
The directory that `just` writes temporary files to may be configured in a
|
||||
number of ways, from highest to lowest precedence:
|
||||
|
||||
- Globally with the `--tempdir` command-line option or the `JUST_TEMPDIR`
|
||||
environment variable<sup>1.41.0</sup>.
|
||||
|
||||
- On a per-module basis with the `tempdir` setting.
|
||||
|
||||
- Globally on Linux with the `XDG_RUNTIME_DIR` environment variable.
|
||||
|
||||
- Falling back to the directory returned by
|
||||
[std::env::temp_dir](https://doc.rust-lang.org/std/env/fn.temp_dir.html).
|
||||
|
||||
### Python Recipes with `uv`
|
||||
|
||||
[`uv`](https://github.com/astral-sh/uv) is an excellent cross-platform python
|
||||
|
@ -2919,23 +2991,6 @@ hello:
|
|||
print("Hello from Python!")
|
||||
```
|
||||
|
||||
### Script Recipes
|
||||
|
||||
Recipes with a `[script(COMMAND)]`<sup>1.32.0</sup> attribute are run as
|
||||
scripts interpreted by `COMMAND`. This avoids some of the issues with shebang
|
||||
recipes, such as the use of `cygpath` on Windows, the need to use
|
||||
`/usr/bin/env`, and inconsistencies in shebang line splitting across Unix OSs.
|
||||
|
||||
Recipes with an empty `[script]` attribute are executed with the value of `set
|
||||
script-interpreter := […]`<sup>1.33.0</sup>, defaulting to `sh -eu`, and *not*
|
||||
the value of `set shell`.
|
||||
|
||||
The body of the recipe is evaluated, written to disk in the temporary
|
||||
directory, and run by passing its path as an argument to `COMMAND`.
|
||||
|
||||
The `[script(…)]` attribute is unstable, so you'll need to use `set unstable`,
|
||||
set the `JUST_UNSTABLE` environment variable, or pass `--unstable` on the
|
||||
command line.
|
||||
|
||||
### Safer Bash Shebang Recipes
|
||||
|
||||
|
@ -3271,9 +3326,9 @@ recipe:
|
|||
echo 'back to recipe body'
|
||||
```
|
||||
|
||||
### Command Line Options
|
||||
### Command-line Options
|
||||
|
||||
`just` supports a number of useful command line options for listing, dumping,
|
||||
`just` supports a number of useful command-line options for listing, dumping,
|
||||
and debugging recipes and variables:
|
||||
|
||||
```console
|
||||
|
@ -3292,21 +3347,29 @@ $ just --show polyglot
|
|||
polyglot: python js perl sh ruby
|
||||
```
|
||||
|
||||
Some command-line options can be set with environment variables. For example:
|
||||
#### Setting Command-line Options with Environment Variables
|
||||
|
||||
Some command-line options can be set with environment variables
|
||||
|
||||
For example, unstable features can be enabled either with the `--unstable`
|
||||
flag:
|
||||
|
||||
```console
|
||||
$ just --unstable
|
||||
```
|
||||
|
||||
Or by setting the `JUST_UNSTABLE` environment variable:
|
||||
|
||||
```console
|
||||
$ export JUST_UNSTABLE=1
|
||||
$ just
|
||||
```
|
||||
|
||||
Is equivalent to:
|
||||
Since environment variables are inherited by child processes, command-line
|
||||
options set with environment variables are inherited by recursive invocations
|
||||
of `just`, where as command line options set with arguments are not.
|
||||
|
||||
```console
|
||||
$ just --unstable
|
||||
```
|
||||
|
||||
Consult `just --help` to see which options can be set from environment
|
||||
variables.
|
||||
Consult `just --help` for which options can be set with environment variables.
|
||||
|
||||
### Private Recipes
|
||||
|
||||
|
@ -4006,7 +4069,7 @@ When a child process *is* running, `just` will wait until it terminates, to
|
|||
avoid leaving it behind.
|
||||
|
||||
Additionally, on receipt of `SIGTERM`, `just` will forward `SIGTERM` to any
|
||||
running children<sup>master</sup>, since unlike other fatal signals, `SIGTERM`,
|
||||
running children<sup>1.41.0</sup>, since unlike other fatal signals, `SIGTERM`,
|
||||
was likely sent to `just` alone.
|
||||
|
||||
Regardless of whether a child process terminates successfully after `just`
|
||||
|
@ -4020,7 +4083,7 @@ user types `ctrl-t` on
|
|||
operating systems, including MacOS, but not Linux.
|
||||
|
||||
`just` responds by printing a list of all child process IDs and
|
||||
commands<sup>master</sup>.
|
||||
commands<sup>1.41.0</sup>.
|
||||
|
||||
#### Windows
|
||||
|
||||
|
@ -4329,6 +4392,8 @@ to `just` include:
|
|||
runner written in AWK and shell.
|
||||
- [haku](https://github.com/VladimirMarkelov/haku): A make-like command runner
|
||||
written in Rust.
|
||||
- [mise](https://mise.jdx.dev/): A development environment tool manager written
|
||||
in Rust supporing tasks in TOML files and standalone scripts.
|
||||
|
||||
Contributing
|
||||
------------
|
||||
|
@ -4405,7 +4470,9 @@ and checking the program's stdout, stderr, and exit code .
|
|||
|
||||
5. Implement the feature.
|
||||
|
||||
6. Run `just ci` to make sure that all tests, lints, and checks pass.
|
||||
6. Run `just ci` to make sure that all tests, lints, and checks pass. Requires
|
||||
[mdBook](https://github.com/rust-lang/mdBook) and
|
||||
[mdbook-linkcheck](https://github.com/Michael-F-Bryan/mdbook-linkcheck).
|
||||
|
||||
7. Open a PR with the new code that is editable by maintainers. PRs often
|
||||
require rebasing and minor tweaks. If the PR is not editable by maintainers,
|
||||
|
|
|
@ -47,7 +47,7 @@ x := '''
|
|||
bar
|
||||
'''
|
||||
|
||||
# this string will evaluate to `abc\n wuv\nbar\n`
|
||||
# this string will evaluate to `abc\n wuv\nxyz\n`
|
||||
y := """
|
||||
abc
|
||||
wuv
|
||||
|
|
|
@ -150,8 +150,13 @@ impl<'run, 'src> Analyzer<'run, 'src> {
|
|||
}
|
||||
}
|
||||
|
||||
let recipes =
|
||||
RecipeResolver::resolve_recipes(&assignments, &self.enums, &settings, deduplicated_recipes)?;
|
||||
let recipes = RecipeResolver::resolve_recipes(
|
||||
&assignments,
|
||||
&self.enums,
|
||||
&self.modules,
|
||||
&settings,
|
||||
deduplicated_recipes,
|
||||
)?;
|
||||
|
||||
let mut aliases = Table::new();
|
||||
while let Some(alias) = self.aliases.pop() {
|
||||
|
@ -302,7 +307,7 @@ impl<'run, 'src> Analyzer<'run, 'src> {
|
|||
recipes: &'a Table<'src, Rc<Recipe<'src>>>,
|
||||
alias: Alias<'src, Namepath<'src>>,
|
||||
) -> CompileResult<'src, Alias<'src>> {
|
||||
match Self::alias_target(&alias.target, modules, recipes) {
|
||||
match Self::resolve_recipe(&alias.target, modules, recipes) {
|
||||
Some(target) => Ok(alias.resolve(target)),
|
||||
None => Err(alias.name.token.error(UnknownAliasTarget {
|
||||
alias: alias.name.lexeme(),
|
||||
|
@ -311,7 +316,7 @@ impl<'run, 'src> Analyzer<'run, 'src> {
|
|||
}
|
||||
}
|
||||
|
||||
fn alias_target<'a>(
|
||||
pub(crate) fn resolve_recipe<'a>(
|
||||
path: &Namepath<'src>,
|
||||
mut modules: &'a Table<'src, Justfile<'src>>,
|
||||
mut recipes: &'a Table<'src, Rc<Recipe<'src>>>,
|
||||
|
|
|
@ -16,6 +16,7 @@ pub(crate) enum Attribute<'src> {
|
|||
Group(StringLiteral<'src>),
|
||||
Linux,
|
||||
Macos,
|
||||
Metadata(Vec<StringLiteral<'src>>),
|
||||
NoCd,
|
||||
NoExitMessage,
|
||||
NoQuiet,
|
||||
|
@ -32,7 +33,6 @@ impl AttributeDiscriminant {
|
|||
fn argument_range(self) -> RangeInclusive<usize> {
|
||||
match self {
|
||||
Self::Confirm | Self::Doc => 0..=1,
|
||||
Self::Group | Self::Extension | Self::WorkingDirectory => 1..=1,
|
||||
Self::ExitMessage
|
||||
| Self::Linux
|
||||
| Self::Macos
|
||||
|
@ -44,6 +44,8 @@ impl AttributeDiscriminant {
|
|||
| Self::Private
|
||||
| Self::Unix
|
||||
| Self::Windows => 0..=0,
|
||||
Self::Extension | Self::Group | Self::WorkingDirectory => 1..=1,
|
||||
Self::Metadata => 1..=usize::MAX,
|
||||
Self::Script => 0..=usize::MAX,
|
||||
}
|
||||
}
|
||||
|
@ -85,6 +87,7 @@ impl<'src> Attribute<'src> {
|
|||
AttributeDiscriminant::Group => Self::Group(arguments.into_iter().next().unwrap()),
|
||||
AttributeDiscriminant::Linux => Self::Linux,
|
||||
AttributeDiscriminant::Macos => Self::Macos,
|
||||
AttributeDiscriminant::Metadata => Self::Metadata(arguments),
|
||||
AttributeDiscriminant::NoCd => Self::NoCd,
|
||||
AttributeDiscriminant::NoExitMessage => Self::NoExitMessage,
|
||||
AttributeDiscriminant::NoQuiet => Self::NoQuiet,
|
||||
|
@ -115,7 +118,7 @@ impl<'src> Attribute<'src> {
|
|||
}
|
||||
|
||||
pub(crate) fn repeatable(&self) -> bool {
|
||||
matches!(self, Attribute::Group(_))
|
||||
matches!(self, Attribute::Group(_) | Attribute::Metadata(_))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,12 +127,6 @@ impl Display for Attribute<'_> {
|
|||
write!(f, "{}", self.name())?;
|
||||
|
||||
match self {
|
||||
Self::Confirm(Some(argument))
|
||||
| Self::Doc(Some(argument))
|
||||
| Self::Extension(argument)
|
||||
| Self::Group(argument)
|
||||
| Self::WorkingDirectory(argument) => write!(f, "({argument})")?,
|
||||
Self::Script(Some(shell)) => write!(f, "({shell})")?,
|
||||
Self::Confirm(None)
|
||||
| Self::Doc(None)
|
||||
| Self::ExitMessage
|
||||
|
@ -144,6 +141,22 @@ impl Display for Attribute<'_> {
|
|||
| Self::Script(None)
|
||||
| Self::Unix
|
||||
| Self::Windows => {}
|
||||
Self::Confirm(Some(argument))
|
||||
| Self::Doc(Some(argument))
|
||||
| Self::Extension(argument)
|
||||
| Self::Group(argument)
|
||||
| Self::WorkingDirectory(argument) => write!(f, "({argument})")?,
|
||||
Self::Metadata(arguments) => {
|
||||
write!(f, "(")?;
|
||||
for (i, argument) in arguments.iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
write!(f, "{argument}")?;
|
||||
}
|
||||
write!(f, ")")?;
|
||||
}
|
||||
Self::Script(Some(shell)) => write!(f, "({shell})")?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -18,7 +18,7 @@ pub(crate) enum CompileErrorKind<'src> {
|
|||
circle: Vec<&'src str>,
|
||||
},
|
||||
DependencyArgumentCountMismatch {
|
||||
dependency: &'src str,
|
||||
dependency: Namepath<'src>,
|
||||
found: usize,
|
||||
min: usize,
|
||||
max: usize,
|
||||
|
@ -148,7 +148,7 @@ pub(crate) enum CompileErrorKind<'src> {
|
|||
},
|
||||
UnknownDependency {
|
||||
recipe: &'src str,
|
||||
unknown: &'src str,
|
||||
unknown: Namepath<'src>,
|
||||
},
|
||||
UnknownFunction {
|
||||
function: &'src str,
|
||||
|
|
|
@ -296,10 +296,7 @@ const BASH_COMPLETION_REPLACEMENTS: &[(&str, &str)] = &[
|
|||
"#,
|
||||
),
|
||||
(r"for i in ${COMP_WORDS[@]}", r"for i in ${words[@]}"),
|
||||
(
|
||||
r"elif [[ ${COMP_CWORD} -eq 1 ]]; then",
|
||||
r"elif [[ ${cword} -eq 1 ]]; then",
|
||||
),
|
||||
(r"elif [[ ${COMP_CWORD} -eq 1 ]]; then", r"else"),
|
||||
(
|
||||
r#"COMPREPLY=( $(compgen -W "${recipes}" -- "${cur}") )"#,
|
||||
r#"COMPREPLY=( $(compgen -W "${recipes}" -- "${cur}") )
|
||||
|
|
|
@ -17,6 +17,7 @@ pub(crate) struct Config {
|
|||
pub(crate) check: bool,
|
||||
pub(crate) color: Color,
|
||||
pub(crate) command_color: Option<ansi_term::Color>,
|
||||
pub(crate) cygpath: PathBuf,
|
||||
pub(crate) dotenv_filename: Option<String>,
|
||||
pub(crate) dotenv_path: Option<PathBuf>,
|
||||
pub(crate) dry_run: bool,
|
||||
|
@ -36,6 +37,7 @@ pub(crate) struct Config {
|
|||
pub(crate) shell_args: Option<Vec<String>>,
|
||||
pub(crate) shell_command: bool,
|
||||
pub(crate) subcommand: Subcommand,
|
||||
pub(crate) tempdir: Option<PathBuf>,
|
||||
pub(crate) timestamp: bool,
|
||||
pub(crate) timestamp_format: String,
|
||||
pub(crate) unsorted: bool,
|
||||
|
@ -95,6 +97,7 @@ mod arg {
|
|||
pub(crate) const CLEAR_SHELL_ARGS: &str = "CLEAR-SHELL-ARGS";
|
||||
pub(crate) const COLOR: &str = "COLOR";
|
||||
pub(crate) const COMMAND_COLOR: &str = "COMMAND-COLOR";
|
||||
pub(crate) const CYGPATH: &str = "CYGPATH";
|
||||
pub(crate) const DOTENV_FILENAME: &str = "DOTENV-FILENAME";
|
||||
pub(crate) const DOTENV_PATH: &str = "DOTENV-PATH";
|
||||
pub(crate) const DRY_RUN: &str = "DRY-RUN";
|
||||
|
@ -116,6 +119,7 @@ mod arg {
|
|||
pub(crate) const SHELL: &str = "SHELL";
|
||||
pub(crate) const SHELL_ARG: &str = "SHELL-ARG";
|
||||
pub(crate) const SHELL_COMMAND: &str = "SHELL-COMMAND";
|
||||
pub(crate) const TEMPDIR: &str = "TEMPDIR";
|
||||
pub(crate) const TIMESTAMP: &str = "TIMESTAMP";
|
||||
pub(crate) const TIMESTAMP_FORMAT: &str = "TIMESTAMP-FORMAT";
|
||||
pub(crate) const UNSORTED: &str = "UNSORTED";
|
||||
|
@ -198,6 +202,15 @@ impl Config {
|
|||
.value_parser(clap::value_parser!(CommandColor))
|
||||
.help("Echo recipe lines in <COMMAND-COLOR>"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(arg::CYGPATH)
|
||||
.long("cygpath")
|
||||
.env("JUST_CYGPATH")
|
||||
.action(ArgAction::Set)
|
||||
.value_parser(value_parser!(PathBuf))
|
||||
.default_value("cygpath")
|
||||
.help("Use binary at <CYGPATH> to convert between unix and Windows paths."),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(arg::DOTENV_FILENAME)
|
||||
.long("dotenv-filename")
|
||||
|
@ -373,6 +386,14 @@ impl Config {
|
|||
.action(ArgAction::SetTrue)
|
||||
.help("Invoke <COMMAND> with the shell used to run recipe lines and backticks"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(arg::TEMPDIR)
|
||||
.action(ArgAction::Set)
|
||||
.env("JUST_TEMPDIR")
|
||||
.long("tempdir")
|
||||
.value_parser(value_parser!(PathBuf))
|
||||
.help("Save temporary files to <TEMPDIR>."),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(arg::TIMESTAMP)
|
||||
.action(ArgAction::SetTrue)
|
||||
|
@ -762,6 +783,7 @@ impl Config {
|
|||
.get_one::<CommandColor>(arg::COMMAND_COLOR)
|
||||
.copied()
|
||||
.map(CommandColor::into),
|
||||
cygpath: matches.get_one::<PathBuf>(arg::CYGPATH).unwrap().clone(),
|
||||
dotenv_filename: matches
|
||||
.get_one::<String>(arg::DOTENV_FILENAME)
|
||||
.map(Into::into),
|
||||
|
@ -792,6 +814,7 @@ impl Config {
|
|||
},
|
||||
shell_command: matches.get_flag(arg::SHELL_COMMAND),
|
||||
subcommand,
|
||||
tempdir: matches.get_one::<PathBuf>(arg::TEMPDIR).map(Into::into),
|
||||
timestamp: matches.get_flag(arg::TIMESTAMP),
|
||||
timestamp_format: matches
|
||||
.get_one::<String>(arg::TIMESTAMP_FORMAT)
|
||||
|
|
|
@ -1,41 +1,53 @@
|
|||
use super::*;
|
||||
|
||||
const CONSTANTS: [(&str, &str, &str); 27] = [
|
||||
("HEX", "0123456789abcdef", "1.27.0"),
|
||||
("HEXLOWER", "0123456789abcdef", "1.27.0"),
|
||||
("HEXUPPER", "0123456789ABCDEF", "1.27.0"),
|
||||
("CLEAR", "\x1bc", "master"),
|
||||
("NORMAL", "\x1b[0m", "master"),
|
||||
("BOLD", "\x1b[1m", "master"),
|
||||
("ITALIC", "\x1b[3m", "master"),
|
||||
("UNDERLINE", "\x1b[4m", "master"),
|
||||
("INVERT", "\x1b[7m", "master"),
|
||||
("HIDE", "\x1b[8m", "master"),
|
||||
("STRIKETHROUGH", "\x1b[9m", "master"),
|
||||
("BLACK", "\x1b[30m", "master"),
|
||||
("RED", "\x1b[31m", "master"),
|
||||
("GREEN", "\x1b[32m", "master"),
|
||||
("YELLOW", "\x1b[33m", "master"),
|
||||
("BLUE", "\x1b[34m", "master"),
|
||||
("MAGENTA", "\x1b[35m", "master"),
|
||||
("CYAN", "\x1b[36m", "master"),
|
||||
("WHITE", "\x1b[37m", "master"),
|
||||
("BG_BLACK", "\x1b[40m", "master"),
|
||||
("BG_RED", "\x1b[41m", "master"),
|
||||
("BG_GREEN", "\x1b[42m", "master"),
|
||||
("BG_YELLOW", "\x1b[43m", "master"),
|
||||
("BG_BLUE", "\x1b[44m", "master"),
|
||||
("BG_MAGENTA", "\x1b[45m", "master"),
|
||||
("BG_CYAN", "\x1b[46m", "master"),
|
||||
("BG_WHITE", "\x1b[47m", "master"),
|
||||
const CONSTANTS: &[(&str, &str, Option<&str>, &str)] = &[
|
||||
("HEX", "0123456789abcdef", None, "1.27.0"),
|
||||
("HEXLOWER", "0123456789abcdef", None, "1.27.0"),
|
||||
("HEXUPPER", "0123456789ABCDEF", None, "1.27.0"),
|
||||
("PATH_SEP", "/", Some("\\"), "1.41.0"),
|
||||
("PATH_VAR_SEP", ":", Some(";"), "1.41.0"),
|
||||
("CLEAR", "\x1bc", None, "1.37.0"),
|
||||
("NORMAL", "\x1b[0m", None, "1.37.0"),
|
||||
("BOLD", "\x1b[1m", None, "1.37.0"),
|
||||
("ITALIC", "\x1b[3m", None, "1.37.0"),
|
||||
("UNDERLINE", "\x1b[4m", None, "1.37.0"),
|
||||
("INVERT", "\x1b[7m", None, "1.37.0"),
|
||||
("HIDE", "\x1b[8m", None, "1.37.0"),
|
||||
("STRIKETHROUGH", "\x1b[9m", None, "1.37.0"),
|
||||
("BLACK", "\x1b[30m", None, "1.37.0"),
|
||||
("RED", "\x1b[31m", None, "1.37.0"),
|
||||
("GREEN", "\x1b[32m", None, "1.37.0"),
|
||||
("YELLOW", "\x1b[33m", None, "1.37.0"),
|
||||
("BLUE", "\x1b[34m", None, "1.37.0"),
|
||||
("MAGENTA", "\x1b[35m", None, "1.37.0"),
|
||||
("CYAN", "\x1b[36m", None, "1.37.0"),
|
||||
("WHITE", "\x1b[37m", None, "1.37.0"),
|
||||
("BG_BLACK", "\x1b[40m", None, "1.37.0"),
|
||||
("BG_RED", "\x1b[41m", None, "1.37.0"),
|
||||
("BG_GREEN", "\x1b[42m", None, "1.37.0"),
|
||||
("BG_YELLOW", "\x1b[43m", None, "1.37.0"),
|
||||
("BG_BLUE", "\x1b[44m", None, "1.37.0"),
|
||||
("BG_MAGENTA", "\x1b[45m", None, "1.37.0"),
|
||||
("BG_CYAN", "\x1b[46m", None, "1.37.0"),
|
||||
("BG_WHITE", "\x1b[47m", None, "1.37.0"),
|
||||
];
|
||||
|
||||
pub(crate) fn constants() -> &'static HashMap<&'static str, &'static str> {
|
||||
static MAP: OnceLock<HashMap<&str, &str>> = OnceLock::new();
|
||||
MAP.get_or_init(|| {
|
||||
CONSTANTS
|
||||
.into_iter()
|
||||
.map(|(name, value, _version)| (name, value))
|
||||
.iter()
|
||||
.copied()
|
||||
.map(|(name, unix, windows, _version)| {
|
||||
(
|
||||
name,
|
||||
if cfg!(windows) {
|
||||
windows.unwrap_or(unix)
|
||||
} else {
|
||||
unix
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
@ -46,13 +58,26 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn readme_table() {
|
||||
println!("| Name | Value |");
|
||||
println!("|------|-------------|");
|
||||
for (name, value, version) in CONSTANTS {
|
||||
println!(
|
||||
"| `{name}`<sup>{version}</sup> | `\"{}\"` |",
|
||||
value.replace('\x1b', "\\e")
|
||||
);
|
||||
let mut table = Vec::<String>::new();
|
||||
table.push("| Name | Value | Value on Windows |".into());
|
||||
table.push("|---|---|---|".into());
|
||||
for (name, unix, windows, version) in CONSTANTS {
|
||||
table.push(format!(
|
||||
"| `{name}`<sup>{version}</sup> | `\"{}\"` | {} |",
|
||||
unix.replace('\x1b', "\\e"),
|
||||
windows
|
||||
.map(|value| format!("\"{value}\""))
|
||||
.unwrap_or_default(),
|
||||
));
|
||||
}
|
||||
|
||||
let table = table.join("\n");
|
||||
|
||||
let readme = fs::read_to_string("README.md").unwrap();
|
||||
|
||||
assert!(
|
||||
readme.contains(&table),
|
||||
"could not find table in readme:\n{table}",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -235,7 +235,7 @@ impl<'src> Error<'src> {
|
|||
output_error: OutputError::Interrupted(signal),
|
||||
..
|
||||
}
|
||||
| Self::Interrupted { signal } => signal.code(),
|
||||
| Self::Interrupted { signal } => Some(signal.code()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,36 @@ pub(crate) struct ExecutionContext<'src: 'run, 'run> {
|
|||
}
|
||||
|
||||
impl<'src: 'run, 'run> ExecutionContext<'src, 'run> {
|
||||
pub(crate) fn tempdir<D>(&self, recipe: &Recipe<'src, D>) -> RunResult<'src, TempDir> {
|
||||
let mut tempdir_builder = tempfile::Builder::new();
|
||||
|
||||
tempdir_builder.prefix("just-");
|
||||
|
||||
if let Some(tempdir) = &self.config.tempdir {
|
||||
tempdir_builder.tempdir_in(self.search.working_directory.join(tempdir))
|
||||
} else {
|
||||
match &self.module.settings.tempdir {
|
||||
Some(tempdir) => tempdir_builder.tempdir_in(self.search.working_directory.join(tempdir)),
|
||||
None => {
|
||||
if let Some(runtime_dir) = dirs::runtime_dir() {
|
||||
let path = runtime_dir.join("just");
|
||||
fs::create_dir_all(&path).map_err(|io_error| Error::RuntimeDirIo {
|
||||
io_error,
|
||||
path: path.clone(),
|
||||
})?;
|
||||
tempdir_builder.tempdir_in(path)
|
||||
} else {
|
||||
tempdir_builder.tempdir()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.map_err(|error| Error::TempdirIo {
|
||||
recipe: recipe.name(),
|
||||
io_error: error,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn working_directory(&self) -> PathBuf {
|
||||
let base = if self.module.is_submodule() {
|
||||
&self.module.working_directory
|
||||
|
|
|
@ -8,6 +8,7 @@ pub(crate) enum Executor<'a> {
|
|||
impl Executor<'_> {
|
||||
pub(crate) fn command<'src>(
|
||||
&self,
|
||||
config: &Config,
|
||||
path: &Path,
|
||||
recipe: &'src str,
|
||||
working_directory: Option<&Path>,
|
||||
|
@ -36,12 +37,12 @@ impl Executor<'_> {
|
|||
})?;
|
||||
|
||||
// create command to run script
|
||||
Platform::make_shebang_command(path, working_directory, *shebang).map_err(|output_error| {
|
||||
Error::Cygpath {
|
||||
Platform::make_shebang_command(config, path, *shebang, working_directory).map_err(
|
||||
|output_error| Error::Cygpath {
|
||||
recipe,
|
||||
output_error,
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -333,6 +333,7 @@ fn file_stem(_context: Context, path: &str) -> FunctionResult {
|
|||
|
||||
fn invocation_directory(context: Context) -> FunctionResult {
|
||||
Platform::convert_native_path(
|
||||
context.evaluator.context.config,
|
||||
&context.evaluator.context.search.working_directory,
|
||||
&context.evaluator.context.config.invocation_directory,
|
||||
)
|
||||
|
|
|
@ -9,6 +9,7 @@ pub(crate) enum Keyword {
|
|||
Assert,
|
||||
DotenvFilename,
|
||||
DotenvLoad,
|
||||
DotenvOverride,
|
||||
DotenvPath,
|
||||
DotenvRequired,
|
||||
Else,
|
||||
|
|
|
@ -134,7 +134,7 @@ pub(crate) use {
|
|||
vec,
|
||||
},
|
||||
strum::{Display, EnumDiscriminants, EnumString, IntoStaticStr},
|
||||
tempfile::tempfile,
|
||||
tempfile::{tempfile, TempDir},
|
||||
typed_arena::Arena,
|
||||
unicode_width::{UnicodeWidthChar, UnicodeWidthStr},
|
||||
};
|
||||
|
@ -152,7 +152,7 @@ use request::Request;
|
|||
|
||||
// Used in integration tests.
|
||||
#[doc(hidden)]
|
||||
pub use {request::Response, unindent::unindent};
|
||||
pub use {request::Response, subcommand::INIT_JUSTFILE, unindent::unindent};
|
||||
|
||||
type CompileResult<'a, T = ()> = Result<T, CompileError<'a>>;
|
||||
type ConfigResult<T> = Result<T, ConfigError>;
|
||||
|
|
|
@ -16,9 +16,10 @@ pub(crate) fn load_dotenv(
|
|||
.or(settings.dotenv_path.as_ref());
|
||||
|
||||
if !settings.dotenv_load
|
||||
&& !settings.dotenv_override
|
||||
&& !settings.dotenv_required
|
||||
&& dotenv_filename.is_none()
|
||||
&& dotenv_path.is_none()
|
||||
&& !settings.dotenv_required
|
||||
{
|
||||
return Ok(BTreeMap::new());
|
||||
}
|
||||
|
@ -26,7 +27,7 @@ pub(crate) fn load_dotenv(
|
|||
if let Some(path) = dotenv_path {
|
||||
let path = working_directory.join(path);
|
||||
if path.is_file() {
|
||||
return load_from_file(&path);
|
||||
return load_from_file(&path, settings);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -35,7 +36,7 @@ pub(crate) fn load_dotenv(
|
|||
for directory in working_directory.ancestors() {
|
||||
let path = directory.join(filename);
|
||||
if path.is_file() {
|
||||
return load_from_file(&path);
|
||||
return load_from_file(&path, settings);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,12 +47,15 @@ pub(crate) fn load_dotenv(
|
|||
}
|
||||
}
|
||||
|
||||
fn load_from_file(path: &Path) -> RunResult<'static, BTreeMap<String, String>> {
|
||||
fn load_from_file(
|
||||
path: &Path,
|
||||
settings: &Settings,
|
||||
) -> RunResult<'static, BTreeMap<String, String>> {
|
||||
let iter = dotenvy::from_path_iter(path)?;
|
||||
let mut dotenv = BTreeMap::new();
|
||||
for result in iter {
|
||||
let (key, value) = result?;
|
||||
if env::var_os(&key).is_none() {
|
||||
if settings.dotenv_override || env::var_os(&key).is_none() {
|
||||
dotenv.insert(key, value);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,8 +32,7 @@ impl<'src> Namepath<'src> {
|
|||
self.0.iter()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn len(&self) -> usize {
|
||||
pub(crate) fn components(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ impl<'src> Node<'src> for Item<'src> {
|
|||
|
||||
impl<'src> Node<'src> for Namepath<'src> {
|
||||
fn tree(&self) -> Tree<'src> {
|
||||
match self.len() {
|
||||
match self.components() {
|
||||
1 => Tree::atom(self.last().lexeme()),
|
||||
_ => Tree::list(
|
||||
self
|
||||
|
@ -238,7 +238,7 @@ impl<'src> Node<'src> for UnresolvedRecipe<'src> {
|
|||
let mut subsequents = Tree::atom("sups");
|
||||
|
||||
for (i, dependency) in self.dependencies.iter().enumerate() {
|
||||
let mut d = Tree::atom(dependency.recipe.lexeme());
|
||||
let mut d = dependency.recipe.tree();
|
||||
|
||||
for argument in &dependency.arguments {
|
||||
d.push_mut(argument.tree());
|
||||
|
@ -304,6 +304,7 @@ impl<'src> Node<'src> for Set<'src> {
|
|||
Setting::AllowDuplicateRecipes(value)
|
||||
| Setting::AllowDuplicateVariables(value)
|
||||
| Setting::DotenvLoad(value)
|
||||
| Setting::DotenvOverride(value)
|
||||
| Setting::DotenvRequired(value)
|
||||
| Setting::Export(value)
|
||||
| Setting::Fallback(value)
|
||||
|
|
|
@ -246,10 +246,10 @@ impl<'run, 'src> Parser<'run, 'src> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Accept a token of kind `Identifier` and parse into a `Name`
|
||||
fn accept_name(&mut self) -> CompileResult<'src, Option<Name<'src>>> {
|
||||
/// Accept a double-colon separated sequence of identifiers
|
||||
fn accept_namepath(&mut self) -> CompileResult<'src, Option<Namepath<'src>>> {
|
||||
if self.next_is(Identifier) {
|
||||
Ok(Some(self.parse_name()?))
|
||||
Ok(Some(self.parse_namepath()?))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
|
@ -268,13 +268,13 @@ impl<'run, 'src> Parser<'run, 'src> {
|
|||
|
||||
/// Accept a dependency
|
||||
fn accept_dependency(&mut self) -> CompileResult<'src, Option<UnresolvedDependency<'src>>> {
|
||||
if let Some(recipe) = self.accept_name()? {
|
||||
if let Some(recipe) = self.accept_namepath()? {
|
||||
Ok(Some(UnresolvedDependency {
|
||||
arguments: Vec::new(),
|
||||
recipe,
|
||||
}))
|
||||
} else if self.accepted(ParenL)? {
|
||||
let recipe = self.parse_name()?;
|
||||
let recipe = self.parse_namepath()?;
|
||||
|
||||
let mut arguments = Vec::new();
|
||||
|
||||
|
@ -1157,6 +1157,7 @@ impl<'run, 'src> Parser<'run, 'src> {
|
|||
Some(Setting::AllowDuplicateVariables(self.parse_set_bool()?))
|
||||
}
|
||||
Keyword::DotenvLoad => Some(Setting::DotenvLoad(self.parse_set_bool()?)),
|
||||
Keyword::DotenvOverride => Some(Setting::DotenvOverride(self.parse_set_bool()?)),
|
||||
Keyword::DotenvRequired => Some(Setting::DotenvRequired(self.parse_set_bool()?)),
|
||||
Keyword::Export => Some(Setting::Export(self.parse_set_bool()?)),
|
||||
Keyword::Fallback => Some(Setting::Fallback(self.parse_set_bool()?)),
|
||||
|
@ -1695,6 +1696,24 @@ mod tests {
|
|||
tree: (justfile (recipe foo (body ("bar")))),
|
||||
}
|
||||
|
||||
test! {
|
||||
name: recipe_dependency_module,
|
||||
text: "foo: bar::baz",
|
||||
tree: (justfile (recipe foo (deps (bar baz)))),
|
||||
}
|
||||
|
||||
test! {
|
||||
name: recipe_dependency_parenthesis_module,
|
||||
text: "foo: (bar::baz)",
|
||||
tree: (justfile (recipe foo (deps (bar baz)))),
|
||||
}
|
||||
|
||||
test! {
|
||||
name: recipe_dependency_module_mixed,
|
||||
text: "foo: bar::baz qux",
|
||||
tree: (justfile (recipe foo (deps (bar baz) qux))),
|
||||
}
|
||||
|
||||
test! {
|
||||
name: recipe_line_multiple,
|
||||
text: "foo:\n bar\n baz\n {{\"bob\"}}biz",
|
||||
|
@ -2598,7 +2617,7 @@ mod tests {
|
|||
column: 9,
|
||||
width: 1,
|
||||
kind: UnexpectedToken{
|
||||
expected: vec![AmpersandAmpersand, Comment, Eof, Eol, Identifier, ParenL],
|
||||
expected: vec![AmpersandAmpersand, ColonColon, Comment, Eof, Eol, Identifier, ParenL],
|
||||
found: Equals
|
||||
},
|
||||
}
|
||||
|
|
|
@ -2,9 +2,10 @@ use super::*;
|
|||
|
||||
impl PlatformInterface for Platform {
|
||||
fn make_shebang_command(
|
||||
_config: &Config,
|
||||
path: &Path,
|
||||
working_directory: Option<&Path>,
|
||||
_shebang: Shebang,
|
||||
working_directory: Option<&Path>,
|
||||
) -> Result<Command, OutputError> {
|
||||
// shebang scripts can be executed directly on unix
|
||||
let mut command = Command::new(path);
|
||||
|
@ -35,7 +36,11 @@ impl PlatformInterface for Platform {
|
|||
exit_status.signal()
|
||||
}
|
||||
|
||||
fn convert_native_path(_working_directory: &Path, path: &Path) -> FunctionResult {
|
||||
fn convert_native_path(
|
||||
_config: &Config,
|
||||
_working_directory: &Path,
|
||||
path: &Path,
|
||||
) -> FunctionResult {
|
||||
path
|
||||
.to_str()
|
||||
.map(str::to_string)
|
||||
|
|
|
@ -2,16 +2,17 @@ use super::*;
|
|||
|
||||
impl PlatformInterface for Platform {
|
||||
fn make_shebang_command(
|
||||
config: &Config,
|
||||
path: &Path,
|
||||
working_directory: Option<&Path>,
|
||||
shebang: Shebang,
|
||||
working_directory: Option<&Path>,
|
||||
) -> Result<Command, OutputError> {
|
||||
use std::borrow::Cow;
|
||||
|
||||
// If the path contains forward slashes…
|
||||
let command = if shebang.interpreter.contains('/') {
|
||||
// …translate path to the interpreter from unix style to windows style.
|
||||
let mut cygpath = Command::new("cygpath");
|
||||
let mut cygpath = Command::new(&config.cygpath);
|
||||
|
||||
if let Some(working_directory) = working_directory {
|
||||
cygpath.current_dir(working_directory);
|
||||
|
@ -56,9 +57,9 @@ impl PlatformInterface for Platform {
|
|||
None
|
||||
}
|
||||
|
||||
fn convert_native_path(working_directory: &Path, path: &Path) -> FunctionResult {
|
||||
fn convert_native_path(config: &Config, working_directory: &Path, path: &Path) -> FunctionResult {
|
||||
// Translate path from windows style to unix style
|
||||
let mut cygpath = Command::new("cygpath");
|
||||
let mut cygpath = Command::new(&config.cygpath);
|
||||
|
||||
cygpath
|
||||
.current_dir(working_directory)
|
||||
|
|
|
@ -2,7 +2,7 @@ use super::*;
|
|||
|
||||
pub(crate) trait PlatformInterface {
|
||||
/// translate path from "native" path to path interpreter expects
|
||||
fn convert_native_path(working_directory: &Path, path: &Path) -> FunctionResult;
|
||||
fn convert_native_path(config: &Config, working_directory: &Path, path: &Path) -> FunctionResult;
|
||||
|
||||
/// install handler, may only be called once
|
||||
fn install_signal_handler<T: Fn(Signal) + Send + 'static>(handler: T) -> RunResult<'static>;
|
||||
|
@ -10,9 +10,10 @@ pub(crate) trait PlatformInterface {
|
|||
/// construct command equivalent to running script at `path` with shebang
|
||||
/// line `shebang`
|
||||
fn make_shebang_command(
|
||||
config: &Config,
|
||||
path: &Path,
|
||||
working_directory: Option<&Path>,
|
||||
shebang: Shebang,
|
||||
working_directory: Option<&Path>,
|
||||
) -> Result<Command, OutputError>;
|
||||
|
||||
/// set the execute permission on file pointed to by `path`
|
||||
|
|
|
@ -77,9 +77,10 @@ impl<'src, D> Recipe<'src, D> {
|
|||
.read_line(&mut line)
|
||||
.map_err(|io_error| Error::GetConfirmation { io_error })?;
|
||||
let line = line.trim().to_lowercase();
|
||||
return Ok(line == "y" || line == "yes");
|
||||
Ok(line == "y" || line == "yes")
|
||||
} else {
|
||||
Ok(true)
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub(crate) fn check_can_be_default_recipe(&self) -> RunResult<'src, ()> {
|
||||
|
@ -389,27 +390,8 @@ impl<'src, D> Recipe<'src, D> {
|
|||
Executor::Shebang(shebang)
|
||||
};
|
||||
|
||||
let mut tempdir_builder = tempfile::Builder::new();
|
||||
tempdir_builder.prefix("just-");
|
||||
let tempdir = match &context.module.settings.tempdir {
|
||||
Some(tempdir) => tempdir_builder.tempdir_in(context.search.working_directory.join(tempdir)),
|
||||
None => {
|
||||
if let Some(runtime_dir) = dirs::runtime_dir() {
|
||||
let path = runtime_dir.join("just");
|
||||
fs::create_dir_all(&path).map_err(|io_error| Error::RuntimeDirIo {
|
||||
io_error,
|
||||
path: path.clone(),
|
||||
})?;
|
||||
tempdir_builder.tempdir_in(path)
|
||||
} else {
|
||||
tempdir_builder.tempdir()
|
||||
}
|
||||
}
|
||||
}
|
||||
.map_err(|error| Error::TempdirIo {
|
||||
recipe: self.name(),
|
||||
io_error: error,
|
||||
})?;
|
||||
let tempdir = context.tempdir(self)?;
|
||||
|
||||
let mut path = tempdir.path().to_path_buf();
|
||||
|
||||
let extension = self.attributes.iter().find_map(|attribute| {
|
||||
|
@ -434,6 +416,7 @@ impl<'src, D> Recipe<'src, D> {
|
|||
})?;
|
||||
|
||||
let mut command = executor.command(
|
||||
config,
|
||||
&path,
|
||||
self.name(),
|
||||
self.working_directory(context).as_deref(),
|
||||
|
|
|
@ -2,6 +2,7 @@ use {super::*, CompileErrorKind::*};
|
|||
|
||||
pub(crate) struct RecipeResolver<'src: 'run, 'run> {
|
||||
assignments: &'run Table<'src, Assignment<'src>>,
|
||||
modules: &'run Table<'src, Justfile<'src>>,
|
||||
resolved_recipes: Table<'src, Rc<Recipe<'src>>>,
|
||||
unresolved_recipes: Table<'src, UnresolvedRecipe<'src>>,
|
||||
enums: &'run Table<'src, Enum<'src>>,
|
||||
|
@ -11,6 +12,7 @@ impl<'src: 'run, 'run> RecipeResolver<'src, 'run> {
|
|||
pub(crate) fn resolve_recipes(
|
||||
assignments: &'run Table<'src, Assignment<'src>>,
|
||||
enums: &'run Table<'src, Enum<'src>>,
|
||||
modules: &'run Table<'src, Justfile<'src>>,
|
||||
settings: &Settings,
|
||||
unresolved_recipes: Table<'src, UnresolvedRecipe<'src>>,
|
||||
) -> CompileResult<'src, Table<'src, Rc<Recipe<'src>>>> {
|
||||
|
@ -19,6 +21,7 @@ impl<'src: 'run, 'run> RecipeResolver<'src, 'run> {
|
|||
unresolved_recipes,
|
||||
assignments,
|
||||
enums,
|
||||
modules
|
||||
};
|
||||
|
||||
while let Some(unresolved) = resolver.unresolved_recipes.pop() {
|
||||
|
@ -101,37 +104,20 @@ impl<'src: 'run, 'run> RecipeResolver<'src, 'run> {
|
|||
|
||||
stack.push(recipe.name());
|
||||
|
||||
let mut dependencies: Vec<Rc<Recipe>> = Vec::new();
|
||||
for dependency in &recipe.dependencies {
|
||||
let name = dependency.recipe.lexeme();
|
||||
|
||||
if let Some(resolved) = self.resolved_recipes.get(name) {
|
||||
// dependency already resolved
|
||||
dependencies.push(Rc::clone(resolved));
|
||||
} else if stack.contains(&name) {
|
||||
let first = stack[0];
|
||||
stack.push(first);
|
||||
return Err(
|
||||
dependency.recipe.error(CircularRecipeDependency {
|
||||
recipe: recipe.name(),
|
||||
circle: stack
|
||||
.iter()
|
||||
.skip_while(|name| **name != dependency.recipe.lexeme())
|
||||
.copied()
|
||||
.collect(),
|
||||
}),
|
||||
);
|
||||
} else if let Some(unresolved) = self.unresolved_recipes.remove(name) {
|
||||
// resolve unresolved dependency
|
||||
dependencies.push(self.resolve_recipe(stack, unresolved)?);
|
||||
} else {
|
||||
// dependency is unknown
|
||||
return Err(dependency.recipe.error(UnknownDependency {
|
||||
recipe: recipe.name(),
|
||||
unknown: name,
|
||||
}));
|
||||
}
|
||||
}
|
||||
let dependencies = recipe
|
||||
.dependencies
|
||||
.iter()
|
||||
.map(|dependency| {
|
||||
self
|
||||
.resolve_dependency(dependency, &recipe, stack)?
|
||||
.ok_or_else(|| {
|
||||
dependency.recipe.last().error(UnknownDependency {
|
||||
recipe: recipe.name(),
|
||||
unknown: dependency.recipe.clone(),
|
||||
})
|
||||
})
|
||||
})
|
||||
.collect::<CompileResult<Vec<Rc<Recipe>>>>()?;
|
||||
|
||||
stack.pop();
|
||||
|
||||
|
@ -139,6 +125,47 @@ impl<'src: 'run, 'run> RecipeResolver<'src, 'run> {
|
|||
self.resolved_recipes.insert(Rc::clone(&resolved));
|
||||
Ok(resolved)
|
||||
}
|
||||
|
||||
fn resolve_dependency(
|
||||
&mut self,
|
||||
dependency: &UnresolvedDependency<'src>,
|
||||
recipe: &UnresolvedRecipe<'src>,
|
||||
stack: &mut Vec<&'src str>,
|
||||
) -> CompileResult<'src, Option<Rc<Recipe<'src>>>> {
|
||||
let name = dependency.recipe.last().lexeme();
|
||||
|
||||
if dependency.recipe.components() > 1 {
|
||||
// recipe is in a submodule and is thus already resovled
|
||||
Ok(Analyzer::resolve_recipe(
|
||||
&dependency.recipe,
|
||||
self.modules,
|
||||
&self.resolved_recipes,
|
||||
))
|
||||
} else if let Some(resolved) = self.resolved_recipes.get(name) {
|
||||
// recipe is the current module and has already been resolved
|
||||
Ok(Some(Rc::clone(resolved)))
|
||||
} else if stack.contains(&name) {
|
||||
// recipe depends on itself
|
||||
let first = stack[0];
|
||||
stack.push(first);
|
||||
return Err(
|
||||
dependency.recipe.last().error(CircularRecipeDependency {
|
||||
recipe: recipe.name(),
|
||||
circle: stack
|
||||
.iter()
|
||||
.skip_while(|name| **name != dependency.recipe.last().lexeme())
|
||||
.copied()
|
||||
.collect(),
|
||||
}),
|
||||
);
|
||||
} else if let Some(unresolved) = self.unresolved_recipes.remove(name) {
|
||||
// recipe is as of yet unresolved
|
||||
Ok(Some(self.resolve_recipe(stack, unresolved)?))
|
||||
} else {
|
||||
// recipe is unknown
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -172,7 +199,18 @@ mod tests {
|
|||
line: 0,
|
||||
column: 3,
|
||||
width: 1,
|
||||
kind: UnknownDependency{recipe: "a", unknown: "b"},
|
||||
kind: UnknownDependency{
|
||||
recipe: "a",
|
||||
unknown: Namepath::from(Name::from_identifier(
|
||||
Token{
|
||||
column: 3,
|
||||
kind: TokenKind::Identifier,
|
||||
length: 1,
|
||||
line: 0,
|
||||
offset: 3,
|
||||
path: Path::new("justfile"),
|
||||
src: "a: b" }))
|
||||
},
|
||||
}
|
||||
|
||||
analysis_error! {
|
||||
|
|
|
@ -11,23 +11,18 @@ pub(crate) struct Search {
|
|||
}
|
||||
|
||||
impl Search {
|
||||
fn global_justfile_paths() -> Vec<PathBuf> {
|
||||
fn global_justfile_paths() -> Vec<(PathBuf, &'static str)> {
|
||||
let mut paths = Vec::new();
|
||||
|
||||
if let Some(config_dir) = dirs::config_dir() {
|
||||
paths.push(config_dir.join("just").join(DEFAULT_JUSTFILE_NAME));
|
||||
paths.push((config_dir.join("just"), DEFAULT_JUSTFILE_NAME));
|
||||
}
|
||||
|
||||
if let Some(home_dir) = dirs::home_dir() {
|
||||
paths.push(
|
||||
home_dir
|
||||
.join(".config")
|
||||
.join("just")
|
||||
.join(DEFAULT_JUSTFILE_NAME),
|
||||
);
|
||||
paths.push((home_dir.join(".config").join("just"), DEFAULT_JUSTFILE_NAME));
|
||||
|
||||
for justfile_name in JUSTFILE_NAMES {
|
||||
paths.push(home_dir.join(justfile_name));
|
||||
paths.push((home_dir.clone(), justfile_name));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -51,11 +46,7 @@ impl Search {
|
|||
})
|
||||
}
|
||||
SearchConfig::GlobalJustfile => Ok(Self {
|
||||
justfile: Self::global_justfile_paths()
|
||||
.iter()
|
||||
.find(|path| path.exists())
|
||||
.cloned()
|
||||
.ok_or(SearchError::GlobalJustfileNotFound)?,
|
||||
justfile: Self::find_global_justfile()?,
|
||||
working_directory: Self::project_root(invocation_directory)?,
|
||||
}),
|
||||
SearchConfig::WithJustfile { justfile } => {
|
||||
|
@ -76,6 +67,26 @@ impl Search {
|
|||
}
|
||||
}
|
||||
|
||||
fn find_global_justfile() -> SearchResult<PathBuf> {
|
||||
for (directory, filename) in Self::global_justfile_paths() {
|
||||
if let Ok(read_dir) = fs::read_dir(&directory) {
|
||||
for entry in read_dir {
|
||||
let entry = entry.map_err(|io_error| SearchError::Io {
|
||||
io_error,
|
||||
directory: directory.clone(),
|
||||
})?;
|
||||
if let Some(candidate) = entry.file_name().to_str() {
|
||||
if candidate.eq_ignore_ascii_case(filename) {
|
||||
return Ok(entry.path());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(SearchError::GlobalJustfileNotFound)
|
||||
}
|
||||
|
||||
/// Find justfile starting from parent directory of current justfile
|
||||
pub(crate) fn search_parent_directory(&self) -> SearchResult<Self> {
|
||||
let parent = self
|
||||
|
@ -150,6 +161,7 @@ impl Search {
|
|||
io_error,
|
||||
directory: directory.to_owned(),
|
||||
})?;
|
||||
|
||||
for entry in entries {
|
||||
let entry = entry.map_err(|io_error| SearchError::Io {
|
||||
io_error,
|
||||
|
|
|
@ -6,6 +6,7 @@ pub(crate) enum Setting<'src> {
|
|||
AllowDuplicateVariables(bool),
|
||||
DotenvFilename(StringLiteral<'src>),
|
||||
DotenvLoad(bool),
|
||||
DotenvOverride(bool),
|
||||
DotenvPath(StringLiteral<'src>),
|
||||
DotenvRequired(bool),
|
||||
Export(bool),
|
||||
|
@ -29,6 +30,7 @@ impl Display for Setting<'_> {
|
|||
Self::AllowDuplicateRecipes(value)
|
||||
| Self::AllowDuplicateVariables(value)
|
||||
| Self::DotenvLoad(value)
|
||||
| Self::DotenvOverride(value)
|
||||
| Self::DotenvRequired(value)
|
||||
| Self::Export(value)
|
||||
| Self::Fallback(value)
|
||||
|
|
|
@ -11,6 +11,7 @@ pub(crate) struct Settings<'src> {
|
|||
pub(crate) allow_duplicate_variables: bool,
|
||||
pub(crate) dotenv_filename: Option<String>,
|
||||
pub(crate) dotenv_load: bool,
|
||||
pub(crate) dotenv_override: bool,
|
||||
pub(crate) dotenv_path: Option<PathBuf>,
|
||||
pub(crate) dotenv_required: bool,
|
||||
pub(crate) export: bool,
|
||||
|
@ -50,6 +51,9 @@ impl<'src> Settings<'src> {
|
|||
Setting::DotenvPath(path) => {
|
||||
settings.dotenv_path = Some(PathBuf::from(path.cooked));
|
||||
}
|
||||
Setting::DotenvOverride(dotenv_overrride) => {
|
||||
settings.dotenv_override = dotenv_overrride;
|
||||
}
|
||||
Setting::DotenvRequired(dotenv_required) => {
|
||||
settings.dotenv_required = dotenv_required;
|
||||
}
|
||||
|
|
|
@ -36,8 +36,23 @@ impl Signal {
|
|||
Signal::Terminate,
|
||||
];
|
||||
|
||||
pub(crate) fn code(self) -> Option<i32> {
|
||||
128i32.checked_add(self.number())
|
||||
pub(crate) fn code(self) -> i32 {
|
||||
128i32.checked_add(self.number()).unwrap()
|
||||
}
|
||||
|
||||
pub(crate) fn is_fatal(self) -> bool {
|
||||
match self {
|
||||
Self::Hangup | Self::Interrupt | Self::Quit | Self::Terminate => true,
|
||||
#[cfg(any(
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "ios",
|
||||
target_os = "macos",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd",
|
||||
))]
|
||||
Self::Info => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn number(self) -> i32 {
|
||||
|
@ -108,10 +123,7 @@ impl TryFrom<u8> for Signal {
|
|||
2 => Ok(Signal::Interrupt),
|
||||
3 => Ok(Signal::Quit),
|
||||
15 => Ok(Signal::Terminate),
|
||||
_ => Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("unexpected signal: {n}"),
|
||||
)),
|
||||
_ => Err(io::Error::other(format!("unexpected signal: {n}"))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -128,6 +140,13 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signals_have_valid_exit_codes() {
|
||||
for signal in Signal::ALL {
|
||||
signal.code();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signal_numbers_are_correct() {
|
||||
for &signal in Signal::ALL {
|
||||
|
|
|
@ -40,20 +40,14 @@ impl SignalHandler {
|
|||
}
|
||||
|
||||
fn interrupt(&mut self, signal: Signal) {
|
||||
if self.children.is_empty() {
|
||||
process::exit(signal.code().unwrap_or(1));
|
||||
}
|
||||
if signal.is_fatal() {
|
||||
if self.children.is_empty() {
|
||||
process::exit(signal.code());
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "ios",
|
||||
target_os = "macos",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd",
|
||||
))]
|
||||
if signal != Signal::Info && self.caught.is_none() {
|
||||
self.caught = Some(signal);
|
||||
if self.caught.is_none() {
|
||||
self.caught = Some(signal);
|
||||
}
|
||||
}
|
||||
|
||||
match signal {
|
||||
|
@ -123,10 +117,7 @@ impl SignalHandler {
|
|||
let pid = match child.id().try_into() {
|
||||
Err(err) => {
|
||||
return (
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("invalid child PID: {err}"),
|
||||
)),
|
||||
Err(io::Error::other(format!("invalid child PID: {err}"))),
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
use {super::*, clap_mangen::Man};
|
||||
|
||||
const INIT_JUSTFILE: &str = "default:\n echo 'Hello, world!'\n";
|
||||
pub const INIT_JUSTFILE: &str = "\
|
||||
# https://just.systems
|
||||
|
||||
default:
|
||||
echo 'Hello, world!'
|
||||
";
|
||||
|
||||
fn backtick_re() -> &'static Regex {
|
||||
static BACKTICK_RE: OnceLock<Regex> = OnceLock::new();
|
||||
|
|
|
@ -3,7 +3,7 @@ use super::*;
|
|||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub(crate) struct UnresolvedDependency<'src> {
|
||||
pub(crate) arguments: Vec<Expression<'src>>,
|
||||
pub(crate) recipe: Name<'src>,
|
||||
pub(crate) recipe: Namepath<'src>,
|
||||
}
|
||||
|
||||
impl Display for UnresolvedDependency<'_> {
|
||||
|
|
|
@ -16,21 +16,19 @@ impl<'src> UnresolvedRecipe<'src> {
|
|||
);
|
||||
|
||||
for (unresolved, resolved) in self.dependencies.iter().zip(&resolved) {
|
||||
assert_eq!(unresolved.recipe.lexeme(), resolved.name.lexeme());
|
||||
assert_eq!(unresolved.recipe.last().lexeme(), resolved.name.lexeme());
|
||||
if !resolved
|
||||
.argument_range()
|
||||
.contains(&unresolved.arguments.len())
|
||||
{
|
||||
return Err(
|
||||
unresolved
|
||||
.recipe
|
||||
.error(CompileErrorKind::DependencyArgumentCountMismatch {
|
||||
dependency: unresolved.recipe.lexeme(),
|
||||
found: unresolved.arguments.len(),
|
||||
min: resolved.min_arguments(),
|
||||
max: resolved.max_arguments(),
|
||||
}),
|
||||
);
|
||||
return Err(unresolved.recipe.last().error(
|
||||
CompileErrorKind::DependencyArgumentCountMismatch {
|
||||
dependency: unresolved.recipe.clone(),
|
||||
found: unresolved.arguments.len(),
|
||||
min: resolved.min_arguments(),
|
||||
max: resolved.max_arguments(),
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -131,6 +131,63 @@ fn unexpected_attribute_argument() {
|
|||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_metadata_attributes() {
|
||||
Test::new()
|
||||
.justfile(
|
||||
"
|
||||
[metadata('example')]
|
||||
[metadata('sample')]
|
||||
[no-exit-message]
|
||||
foo:
|
||||
exit 1
|
||||
",
|
||||
)
|
||||
.stderr("exit 1\n")
|
||||
.status(1)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_metadata_attributes_with_multiple_args() {
|
||||
Test::new()
|
||||
.justfile(
|
||||
"
|
||||
[metadata('example', 'arg1')]
|
||||
[metadata('sample', 'argument')]
|
||||
[no-exit-message]
|
||||
foo:
|
||||
exit 1
|
||||
",
|
||||
)
|
||||
.stderr("exit 1\n")
|
||||
.status(1)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expected_metadata_attribute_argument() {
|
||||
Test::new()
|
||||
.justfile(
|
||||
"
|
||||
[metadata]
|
||||
foo:
|
||||
exit 1
|
||||
",
|
||||
)
|
||||
.stderr(
|
||||
"
|
||||
error: Attribute `metadata` got 0 arguments but takes at least 1 argument
|
||||
——▶ justfile:1:2
|
||||
│
|
||||
1 │ [metadata]
|
||||
│ ^^^^^^^^
|
||||
",
|
||||
)
|
||||
.status(1)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doc_attribute() {
|
||||
Test::new()
|
||||
|
|
|
@ -134,7 +134,6 @@ fn no_choosable_recipes() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn multiple_recipes() {
|
||||
Test::new()
|
||||
.arg("--choose")
|
||||
|
|
|
@ -5,6 +5,12 @@ fn constants_are_defined() {
|
|||
assert_eval_eq("HEX", "0123456789abcdef");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constants_can_have_different_values_on_windows() {
|
||||
assert_eval_eq("PATH_SEP", if cfg!(windows) { "\\" } else { "/" });
|
||||
assert_eval_eq("PATH_VAR_SEP", if cfg!(windows) { ";" } else { ":" });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constants_are_defined_in_recipe_bodies() {
|
||||
Test::new()
|
||||
|
|
139
tests/dependencies.rs
Normal file
139
tests/dependencies.rs
Normal file
|
@ -0,0 +1,139 @@
|
|||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn recipe_doubly_nested_module_dependencies() {
|
||||
Test::new()
|
||||
.write("foo.just", "mod bar\nbaz: \n @echo FOO")
|
||||
.write("bar.just", "baz:\n @echo BAZ")
|
||||
.justfile(
|
||||
"
|
||||
mod foo
|
||||
|
||||
baz: foo::bar::baz
|
||||
",
|
||||
)
|
||||
.arg("baz")
|
||||
.stdout("BAZ\n")
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recipe_singly_nested_module_dependencies() {
|
||||
Test::new()
|
||||
.write("foo.just", "mod bar\nbaz: \n @echo BAR")
|
||||
.write("bar.just", "baz:\n @echo BAZ")
|
||||
.justfile(
|
||||
"
|
||||
mod foo
|
||||
baz: foo::baz
|
||||
",
|
||||
)
|
||||
.arg("baz")
|
||||
.stdout("BAR\n")
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dependency_not_in_submodule() {
|
||||
Test::new()
|
||||
.write("foo.just", "qux: \n @echo QUX")
|
||||
.justfile(
|
||||
"
|
||||
mod foo
|
||||
baz: foo::baz
|
||||
",
|
||||
)
|
||||
.arg("baz")
|
||||
.status(1)
|
||||
.stderr(
|
||||
"error: Recipe `baz` has unknown dependency `foo::baz`
|
||||
——▶ justfile:2:11
|
||||
│
|
||||
2 │ baz: foo::baz
|
||||
│ ^^^
|
||||
",
|
||||
)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dependency_submodule_missing() {
|
||||
Test::new()
|
||||
.justfile(
|
||||
"
|
||||
foo:
|
||||
@echo FOO
|
||||
bar:
|
||||
@echo BAR
|
||||
baz: foo::bar
|
||||
",
|
||||
)
|
||||
.arg("baz")
|
||||
.status(1)
|
||||
.stderr(
|
||||
"error: Recipe `baz` has unknown dependency `foo::bar`
|
||||
——▶ justfile:5:11
|
||||
│
|
||||
5 │ baz: foo::bar
|
||||
│ ^^^
|
||||
",
|
||||
)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recipe_dependency_on_module_fails() {
|
||||
Test::new()
|
||||
.write("foo.just", "mod bar\nbaz: \n @echo BAR")
|
||||
.write("bar.just", "baz:\n @echo BAZ")
|
||||
.justfile(
|
||||
"
|
||||
mod foo
|
||||
baz: foo::bar
|
||||
",
|
||||
)
|
||||
.arg("baz")
|
||||
.status(1)
|
||||
.stderr(
|
||||
"error: Recipe `baz` has unknown dependency `foo::bar`
|
||||
——▶ justfile:2:11
|
||||
│
|
||||
2 │ baz: foo::bar
|
||||
│ ^^^
|
||||
",
|
||||
)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recipe_module_dependency_subsequent_mix() {
|
||||
Test::new()
|
||||
.write("foo.just", "bar: \n @echo BAR")
|
||||
.justfile(
|
||||
"
|
||||
mod foo
|
||||
baz:
|
||||
@echo BAZ
|
||||
quux: foo::bar && baz
|
||||
@echo QUUX
|
||||
",
|
||||
)
|
||||
.arg("quux")
|
||||
.stdout("BAR\nQUUX\nBAZ\n")
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recipe_module_dependency_only_runs_once() {
|
||||
Test::new()
|
||||
.write("foo.just", "bar: baz \n \nbaz: \n @echo BAZ")
|
||||
.justfile(
|
||||
"
|
||||
mod foo
|
||||
qux: foo::bar foo::baz
|
||||
",
|
||||
)
|
||||
.arg("qux")
|
||||
.stdout("BAZ\n")
|
||||
.run();
|
||||
}
|
|
@ -362,7 +362,7 @@ fn no_dotenv() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn dotenv_env_var_override() {
|
||||
fn dotenv_env_var_default_no_override() {
|
||||
Test::new()
|
||||
.justfile(
|
||||
"
|
||||
|
@ -377,6 +377,41 @@ fn dotenv_env_var_override() {
|
|||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dotenv_env_var_override() {
|
||||
Test::new()
|
||||
.justfile(
|
||||
"
|
||||
set dotenv-load
|
||||
set dotenv-override := true
|
||||
echo:
|
||||
echo $DOTENV_KEY
|
||||
",
|
||||
)
|
||||
.write(".env", "DOTENV_KEY=dotenv-value")
|
||||
.env("DOTENV_KEY", "not-the-dotenv-value")
|
||||
.stdout("dotenv-value\n")
|
||||
.stderr("echo $DOTENV_KEY\n")
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dotenv_env_var_override_no_load() {
|
||||
Test::new()
|
||||
.justfile(
|
||||
"
|
||||
set dotenv-override := true
|
||||
echo:
|
||||
echo $DOTENV_KEY
|
||||
",
|
||||
)
|
||||
.write(".env", "DOTENV_KEY=dotenv-value")
|
||||
.env("DOTENV_KEY", "not-the-dotenv-value")
|
||||
.stdout("dotenv-value\n")
|
||||
.stderr("echo $DOTENV_KEY\n")
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dotenv_path_usable_from_subdir() {
|
||||
Test::new()
|
||||
|
|
|
@ -63,3 +63,20 @@ fn unix() {
|
|||
.stdout("bar\n")
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
fn case_insensitive() {
|
||||
let tempdir = tempdir();
|
||||
|
||||
let path = tempdir.path().to_owned();
|
||||
|
||||
Test::with_tempdir(tempdir)
|
||||
.no_justfile()
|
||||
.test_round_trip(false)
|
||||
.write("just/JUSTFILE", "@default:\n echo foo")
|
||||
.env("XDG_CONFIG_HOME", path.to_str().unwrap())
|
||||
.args(["--global-justfile"])
|
||||
.stdout("foo\n")
|
||||
.run();
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
use super::*;
|
||||
|
||||
const EXPECTED: &str = "default:\n echo 'Hello, world!'\n";
|
||||
use {super::*, just::INIT_JUSTFILE};
|
||||
|
||||
#[test]
|
||||
fn current_dir() {
|
||||
|
@ -16,7 +14,7 @@ fn current_dir() {
|
|||
|
||||
assert_eq!(
|
||||
fs::read_to_string(tmp.path().join("justfile")).unwrap(),
|
||||
EXPECTED
|
||||
INIT_JUSTFILE
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -72,7 +70,7 @@ fn invocation_directory() {
|
|||
.arg("--init")
|
||||
.run();
|
||||
|
||||
assert_eq!(fs::read_to_string(justfile_path).unwrap(), EXPECTED);
|
||||
assert_eq!(fs::read_to_string(justfile_path).unwrap(), INIT_JUSTFILE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -92,7 +90,7 @@ fn parent_dir() {
|
|||
|
||||
assert_eq!(
|
||||
fs::read_to_string(tmp.path().join("justfile")).unwrap(),
|
||||
EXPECTED
|
||||
INIT_JUSTFILE
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -112,7 +110,7 @@ fn alternate_marker() {
|
|||
|
||||
assert_eq!(
|
||||
fs::read_to_string(tmp.path().join("justfile")).unwrap(),
|
||||
EXPECTED
|
||||
INIT_JUSTFILE
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -135,7 +133,7 @@ fn search_directory() {
|
|||
|
||||
assert_eq!(
|
||||
fs::read_to_string(tmp.path().join("sub/justfile")).unwrap(),
|
||||
EXPECTED
|
||||
INIT_JUSTFILE
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -159,7 +157,7 @@ fn justfile() {
|
|||
|
||||
assert_eq!(
|
||||
fs::read_to_string(tmp.path().join("justfile")).unwrap(),
|
||||
EXPECTED
|
||||
INIT_JUSTFILE
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -185,7 +183,7 @@ fn justfile_and_working_directory() {
|
|||
|
||||
assert_eq!(
|
||||
fs::read_to_string(tmp.path().join("justfile")).unwrap(),
|
||||
EXPECTED
|
||||
INIT_JUSTFILE
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ fn convert_native_path(path: &Path) -> String {
|
|||
#[cfg(windows)]
|
||||
fn convert_native_path(path: &Path) -> String {
|
||||
// Translate path from windows style to unix style
|
||||
let mut cygpath = Command::new("cygpath");
|
||||
let mut cygpath = Command::new(env::var_os("JUST_CYGPATH").unwrap_or("cygpath".into()));
|
||||
cygpath.arg("--unix");
|
||||
cygpath.arg(path);
|
||||
|
||||
|
|
|
@ -79,6 +79,7 @@ struct Settings<'a> {
|
|||
allow_duplicate_variables: bool,
|
||||
dotenv_filename: Option<&'a str>,
|
||||
dotenv_load: bool,
|
||||
dotenv_override: bool,
|
||||
dotenv_path: Option<&'a str>,
|
||||
dotenv_required: bool,
|
||||
export: bool,
|
||||
|
@ -782,6 +783,88 @@ fn attribute() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_metadata_attribute() {
|
||||
case(
|
||||
"
|
||||
[metadata('example')]
|
||||
foo:
|
||||
",
|
||||
Module {
|
||||
first: Some("foo"),
|
||||
recipes: [(
|
||||
"foo",
|
||||
Recipe {
|
||||
attributes: [json!({"metadata": ["example"]})].into(),
|
||||
name: "foo",
|
||||
namepath: "foo",
|
||||
..default()
|
||||
},
|
||||
)]
|
||||
.into(),
|
||||
..default()
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_metadata_attributes() {
|
||||
case(
|
||||
"
|
||||
[metadata('example')]
|
||||
[metadata('sample')]
|
||||
foo:
|
||||
",
|
||||
Module {
|
||||
first: Some("foo"),
|
||||
recipes: [(
|
||||
"foo",
|
||||
Recipe {
|
||||
attributes: [
|
||||
json!({"metadata": ["example"]}),
|
||||
json!({"metadata": ["sample"]}),
|
||||
]
|
||||
.into(),
|
||||
name: "foo",
|
||||
namepath: "foo",
|
||||
..default()
|
||||
},
|
||||
)]
|
||||
.into(),
|
||||
..default()
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_metadata_attributes_with_multiple_arguments() {
|
||||
case(
|
||||
"
|
||||
[metadata('example', 'arg1')]
|
||||
[metadata('sample', 'argument')]
|
||||
foo:
|
||||
",
|
||||
Module {
|
||||
first: Some("foo"),
|
||||
recipes: [(
|
||||
"foo",
|
||||
Recipe {
|
||||
attributes: [
|
||||
json!({"metadata": ["example", "arg1"]}),
|
||||
json!({"metadata": ["sample", "argument"]}),
|
||||
]
|
||||
.into(),
|
||||
name: "foo",
|
||||
namepath: "foo",
|
||||
..default()
|
||||
},
|
||||
)]
|
||||
.into(),
|
||||
..default()
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn module() {
|
||||
case_with_submodule(
|
||||
|
|
|
@ -63,6 +63,7 @@ mod confirm;
|
|||
mod constants;
|
||||
mod datetime;
|
||||
mod delimiters;
|
||||
mod dependencies;
|
||||
mod directories;
|
||||
mod dotenv;
|
||||
mod edit;
|
||||
|
|
|
@ -86,9 +86,9 @@ fn interrupt_command() {
|
|||
interrupt_test(&["--command", "sleep", "1"], "");
|
||||
}
|
||||
|
||||
/// This test is sensitive to the process signal mask. Programs like
|
||||
/// `watchexec` and `cargo-watch` change the signal mask to ignore `SIGHUP`,
|
||||
/// which causes this test to fail.
|
||||
// This test is ignored because it is sensitive to the process signal mask.
|
||||
// Programs like `watchexec` and `cargo-watch` change the signal mask to ignore
|
||||
// `SIGHUP`, which causes this test to fail.
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn forwarding() {
|
||||
|
@ -155,8 +155,6 @@ fn forwarding() {
|
|||
}
|
||||
}
|
||||
|
||||
/// This test is ignored because it includes a 500ms wait, and because signal
|
||||
/// tests are often flakey.
|
||||
#[test]
|
||||
#[ignore]
|
||||
#[cfg(any(
|
||||
|
|
|
@ -16,7 +16,7 @@ pub(crate) fn tempdir() -> TempDir {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_tempdir_is_set() {
|
||||
fn setting() {
|
||||
Test::new()
|
||||
.justfile(
|
||||
"
|
||||
|
@ -28,10 +28,46 @@ fn test_tempdir_is_set() {
|
|||
)
|
||||
.shell(false)
|
||||
.tree(tree! {
|
||||
foo: {
|
||||
bar: {
|
||||
}
|
||||
})
|
||||
.current_dir("foo")
|
||||
.current_dir("bar")
|
||||
.stdout(if cfg!(windows) {
|
||||
"
|
||||
|
||||
|
||||
|
||||
cat just*/foo
|
||||
"
|
||||
} else {
|
||||
"
|
||||
#!/usr/bin/env bash
|
||||
|
||||
|
||||
cat just*/foo
|
||||
"
|
||||
})
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn argument_overrides_setting() {
|
||||
Test::new()
|
||||
.args(["--tempdir", "."])
|
||||
.justfile(
|
||||
"
|
||||
set tempdir := 'hello'
|
||||
foo:
|
||||
#!/usr/bin/env bash
|
||||
cat just*/foo
|
||||
",
|
||||
)
|
||||
.shell(false)
|
||||
.tree(tree! {
|
||||
bar: {
|
||||
}
|
||||
})
|
||||
.current_dir("bar")
|
||||
.stdout(if cfg!(windows) {
|
||||
"
|
||||
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
#!/usr/bin/env sh
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -eu
|
||||
|
||||
if [ -n "${GITHUB_ACTIONS-}" ]; then
|
||||
set -x
|
||||
fi
|
||||
|
||||
# Check pipefail support in a subshell, ignore if unsupported
|
||||
# shellcheck disable=SC3040
|
||||
(set -o pipefail 2> /dev/null) && set -o pipefail
|
||||
|
@ -55,10 +51,15 @@ download() {
|
|||
url="$1"
|
||||
output="$2"
|
||||
|
||||
args=()
|
||||
if [ -n "${GITHUB_TOKEN+x}" ]; then
|
||||
args+=(--header "Authorization: Bearer $GITHUB_TOKEN")
|
||||
fi
|
||||
|
||||
if command -v curl > /dev/null; then
|
||||
curl --proto =https --tlsv1.2 -sSfL "$url" "-o$output"
|
||||
curl --proto =https --tlsv1.2 -sSfL ${args[@]+"${args[@]}"} "$url" -o"$output"
|
||||
else
|
||||
wget --https-only --secure-protocol=TLSv1_2 --quiet "$url" "-O$output"
|
||||
wget --https-only --secure-protocol=TLSv1_2 --quiet ${args[@]+"${args[@]}"} "$url" -O"$output"
|
||||
fi
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue