mirror of
https://github.com/casey/just.git
synced 2025-08-04 15:08:39 +00:00
Merge branch 'master' into module_deps1
This commit is contained in:
commit
665dff3989
23 changed files with 624 additions and 404 deletions
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"
|
||||
|
|
141
README.md
141
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>
|
||||
|
@ -992,6 +997,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. |
|
||||
|
@ -1063,8 +1069,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.
|
||||
|
@ -1089,6 +1096,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
|
||||
|
@ -2014,35 +2024,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:
|
||||
|
@ -2899,6 +2911,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
|
||||
|
@ -2934,23 +2986,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
|
||||
|
||||
|
@ -4029,7 +4064,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`
|
||||
|
@ -4043,7 +4078,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
|
||||
|
||||
|
@ -4430,7 +4465,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,
|
||||
|
|
|
@ -36,6 +36,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,
|
||||
|
@ -116,6 +117,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";
|
||||
|
@ -373,6 +375,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)
|
||||
|
@ -792,6 +802,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}",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -9,6 +9,7 @@ pub(crate) enum Keyword {
|
|||
Assert,
|
||||
DotenvFilename,
|
||||
DotenvLoad,
|
||||
DotenvOverride,
|
||||
DotenvPath,
|
||||
DotenvRequired,
|
||||
Else,
|
||||
|
|
|
@ -133,7 +133,7 @@ pub(crate) use {
|
|||
vec,
|
||||
},
|
||||
strum::{Display, EnumDiscriminants, EnumString, IntoStaticStr},
|
||||
tempfile::tempfile,
|
||||
tempfile::{tempfile, TempDir},
|
||||
typed_arena::Arena,
|
||||
unicode_width::{UnicodeWidthChar, UnicodeWidthStr},
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -303,6 +303,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)
|
||||
|
|
|
@ -1119,6 +1119,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()?)),
|
||||
|
|
|
@ -390,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| {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
"
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue