1
1
mirror of https://github.com/casey/just.git synced 2024-11-22 02:09:44 +03:00

Compare commits

...

13 Commits

Author SHA1 Message Date
Ben Heidemann
104e8446ce
Merge e3c9a0c199 into 17350a603e 2024-11-19 21:31:50 +00:00
Ben Heidemann
e3c9a0c199
Merge branch 'master' into feat/working-directory-attribute 2024-11-19 21:31:47 +00:00
dependabot[bot]
17350a603e
Update softprops/action-gh-release (#2471) 2024-11-18 11:04:52 -08:00
Casey Rodarmor
084a2d2de3
Add style() function (#2462) 2024-11-17 02:26:11 +00:00
Casey Rodarmor
eb6e3741b8
Make recipe doc attribute override comment (#2470) 2024-11-17 02:02:27 +00:00
Naveen Prashanth
c7b2b78dcc
Add -g to rust-just install instructions (#2459) 2024-11-17 01:34:04 +00:00
Casey Rodarmor
520cf91423
Change doc backtick color to cyan (#2469) 2024-11-17 00:21:14 +00:00
Michael Bianco
a73c0976a1
Note that set shell is not used for [script] recipes (#2468) 2024-11-16 23:58:35 +00:00
laniakea64
5db910f400
Replace derivative with derive-where (#2465) 2024-11-16 00:04:10 +00:00
Ben Heidemann
18ff6b5705 review comments 2024-10-31 19:36:42 +00:00
Ben Heidemann
d034fd1510
Merge branch 'master' into feat/working-directory-attribute 2024-10-31 14:01:56 +00:00
Ben Heidemann
d97d77ab0c
Merge branch 'master' into feat/working-directory-attribute 2024-10-29 10:08:43 +00:00
Ben Heidemann
d16727b9c4 feat: add working-directory attribute 2024-10-22 18:22:16 +01:00
18 changed files with 398 additions and 56 deletions

View File

@ -110,7 +110,7 @@ jobs:
shell: bash shell: bash
- name: Publish Archive - name: Publish Archive
uses: softprops/action-gh-release@v2.0.9 uses: softprops/action-gh-release@v2.1.0
if: ${{ startsWith(github.ref, 'refs/tags/') }} if: ${{ startsWith(github.ref, 'refs/tags/') }}
with: with:
draft: false draft: false
@ -120,7 +120,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Publish Changelog - name: Publish Changelog
uses: softprops/action-gh-release@v2.0.9 uses: softprops/action-gh-release@v2.1.0
if: >- if: >-
${{ ${{
startsWith(github.ref, 'refs/tags/') startsWith(github.ref, 'refs/tags/')
@ -157,7 +157,7 @@ jobs:
shasum -a 256 * > ../SHA256SUMS shasum -a 256 * > ../SHA256SUMS
- name: Publish Checksums - name: Publish Checksums
uses: softprops/action-gh-release@v2.0.9 uses: softprops/action-gh-release@v2.1.0
with: with:
draft: false draft: false
files: SHA256SUMS files: SHA256SUMS

37
Cargo.lock generated
View File

@ -237,7 +237,7 @@ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.79", "syn",
] ]
[[package]] [[package]]
@ -329,14 +329,14 @@ dependencies = [
] ]
[[package]] [[package]]
name = "derivative" name = "derive-where"
version = "2.2.0" version = "1.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" checksum = "62d671cc41a825ebabc75757b62d3d168c577f9149b2d49ece1dad1f72119d25"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 1.0.109", "syn",
] ]
[[package]] [[package]]
@ -531,7 +531,7 @@ dependencies = [
"clap_complete", "clap_complete",
"clap_mangen", "clap_mangen",
"ctrlc", "ctrlc",
"derivative", "derive-where",
"dirs", "dirs",
"dotenvy", "dotenvy",
"edit-distance", "edit-distance",
@ -861,7 +861,7 @@ checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.79", "syn",
] ]
[[package]] [[package]]
@ -930,7 +930,7 @@ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.79", "syn",
] ]
[[package]] [[package]]
@ -958,18 +958,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"rustversion", "rustversion",
"syn 2.0.79", "syn",
]
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
] ]
[[package]] [[package]]
@ -1038,7 +1027,7 @@ checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.79", "syn",
] ]
[[package]] [[package]]
@ -1142,7 +1131,7 @@ dependencies = [
"once_cell", "once_cell",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.79", "syn",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -1164,7 +1153,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.79", "syn",
"wasm-bindgen-backend", "wasm-bindgen-backend",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -1396,5 +1385,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.79", "syn",
] ]

View File

@ -26,7 +26,7 @@ clap = { version = "4.0.0", features = ["derive", "env", "wrap_help"] }
clap_complete = "4.0.0" clap_complete = "4.0.0"
clap_mangen = "0.2.20" clap_mangen = "0.2.20"
ctrlc = { version = "3.1.1", features = ["termination"] } ctrlc = { version = "3.1.1", features = ["termination"] }
derivative = "2.0.0" derive-where = "1.2.7"
dirs = "5.0.1" dirs = "5.0.1"
dotenvy = "0.15" dotenvy = "0.15"
edit-distance = "2.0.0" edit-distance = "2.0.0"

View File

@ -167,7 +167,7 @@ most Windows users.)
<tr> <tr>
<td><a href=https://www.npmjs.com/>npm</a></td> <td><a href=https://www.npmjs.com/>npm</a></td>
<td><a href=https://www.npmjs.com/package/rust-just>rust-just</a></td> <td><a href=https://www.npmjs.com/package/rust-just>rust-just</a></td>
<td><code>npm install rust-just</code></td> <td><code>npm install -g rust-just</code></td>
</tr> </tr>
<tr> <tr>
<td><a href=https://pypi.org/>PyPI</a></td> <td><a href=https://pypi.org/>PyPI</a></td>
@ -1851,6 +1851,24 @@ for details.
`requirement`, e.g., `">=0.1.0"`, returning `"true"` if so and `"false"` `requirement`, e.g., `">=0.1.0"`, returning `"true"` if so and `"false"`
otherwise. otherwise.
#### Style
- `style(name)`<sup>master</sup> - Return a named terminal display attribute
escape sequence used by `just`. Unlike terminal display attribute escape
sequence constants, which contain standard colors and styles, `style(name)`
returns an escape sequence used by `just` itself, and can be used to make
recipe output match `just`'s own output.
Recognized values for `name` are `'command'`, for echoed recipe lines,
`error`, and `warning`.
For example, to style an error message:
```just
scary:
@echo '{{ style("error") }}OH NO{{ NORMAL }}'
```
##### XDG Directories<sup>1.23.0</sup> ##### XDG Directories<sup>1.23.0</sup>
These functions return paths to user-specific directories for things like These functions return paths to user-specific directories for things like
@ -1954,6 +1972,7 @@ change their behavior.
| `[script(COMMAND)]`<sup>1.32.0</sup> | recipe | Execute recipe as a script interpreted by `COMMAND`. See [script recipes](#script-recipes) for more details. | | `[script(COMMAND)]`<sup>1.32.0</sup> | recipe | Execute recipe as a script interpreted by `COMMAND`. See [script recipes](#script-recipes) for more details. |
| `[unix]`<sup>1.8.0</sup> | recipe | Enable recipe on Unixes. (Includes MacOS). | | `[unix]`<sup>1.8.0</sup> | recipe | Enable recipe on Unixes. (Includes MacOS). |
| `[windows]`<sup>1.8.0</sup> | recipe | Enable recipe on Windows. | | `[windows]`<sup>1.8.0</sup> | recipe | Enable recipe on Windows. |
| `[working-directory('bar')]`<sup>1.37.0</sup> | recipe | Set the working directory for the recipe, relative to the default working directory. |
A recipe can have multiple attributes, either on multiple lines: A recipe can have multiple attributes, either on multiple lines:
@ -2017,6 +2036,23 @@ Can be used with paths that are relative to the current directory, because
`[no-cd]` prevents `just` from changing the current directory when executing `[no-cd]` prevents `just` from changing the current directory when executing
`commit`. `commit`.
#### Changing Working Directory<sup>1.37.0</sup>
`just` normally executes recipes with the current directory set to the directory
that contains the `justfile`. The execution directory can be changed with the
`[working-directory('dir')]` attribute. This can be used to create recipes which
are executed in a directory relative to the default directory.
For example, this `example` recipe:
```just
[working-directory('dir')]
example:
echo "$(pwd)"
```
Which will run in the `dir` directory.
#### Requiring Confirmation for Recipes<sup>1.17.0</sup> #### Requiring Confirmation for Recipes<sup>1.17.0</sup>
`just` normally executes all recipes unless there is an error. The `[confirm]` `just` normally executes all recipes unless there is an error. The `[confirm]`
@ -2755,8 +2791,9 @@ 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 recipes, such as the use of `cygpath` on Windows, the need to use
`/usr/bin/env`, and inconsistences in shebang line splitting across Unix OSs. `/usr/bin/env`, and inconsistences in shebang line splitting across Unix OSs.
Recipes with an empty `[script]` attribute are executed with the value of Recipes with an empty `[script]` attribute are executed with the value of `set
`set script-interpreter := […]`<sup>1.33.0</sup>, defaulting to `sh -eu`. 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 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`. directory, and run by passing its path as an argument to `COMMAND`.

View File

@ -220,6 +220,22 @@ list:
<code>asdf install just &lt;version&gt;</code> <code>asdf install just &lt;version&gt;</code>
</td> </td>
</tr> </tr>
<tr>
<td><a href="https://packaging.python.org/tutorials/installing-packages">Various</a></td>
<td><a href="https://pypi.org">PyPI</a></td>
<td><a href="https://pypi.org/project/rust-just">rust-just</a></td>
<td>
<code>pipx install rust-just</code><br>
</td>
</tr>
<tr>
<td><a href="https://docs.npmjs.com/packages-and-modules/getting-packages-from-the-registry">Various</a></td>
<td><a href="https://www.npmjs.com">npm</a></td>
<td><a href="https://www.npmjs.com/package/rust-just">rust-just</a></td>
<td>
<code>npm install -g rust-just</code><br>
</td>
</tr>
<tr> <tr>
<td><a href="https://debian.org">Debian</a> and <a href="https://ubuntu.com">Ubuntu</a> derivatives</td> <td><a href="https://debian.org">Debian</a> and <a href="https://ubuntu.com">Ubuntu</a> derivatives</td>
<td><a href="https://mpr.makedeb.org">MPR</a></td> <td><a href="https://mpr.makedeb.org">MPR</a></td>

View File

@ -23,13 +23,14 @@ pub(crate) enum Attribute<'src> {
Script(Option<Interpreter<'src>>), Script(Option<Interpreter<'src>>),
Unix, Unix,
Windows, Windows,
WorkingDirectory(StringLiteral<'src>),
} }
impl AttributeDiscriminant { impl AttributeDiscriminant {
fn argument_range(self) -> RangeInclusive<usize> { fn argument_range(self) -> RangeInclusive<usize> {
match self { match self {
Self::Confirm | Self::Doc => 0..=1, Self::Confirm | Self::Doc => 0..=1,
Self::Group | Self::Extension => 1..=1, Self::Group | Self::Extension | Self::WorkingDirectory => 1..=1,
Self::Linux Self::Linux
| Self::Macos | Self::Macos
| Self::NoCd | Self::NoCd
@ -93,6 +94,9 @@ impl<'src> Attribute<'src> {
}), }),
AttributeDiscriminant::Unix => Self::Unix, AttributeDiscriminant::Unix => Self::Unix,
AttributeDiscriminant::Windows => Self::Windows, AttributeDiscriminant::Windows => Self::Windows,
AttributeDiscriminant::WorkingDirectory => {
Self::WorkingDirectory(arguments.into_iter().next().unwrap())
}
}) })
} }
@ -109,7 +113,8 @@ impl<'src> Display for Attribute<'src> {
Self::Confirm(Some(argument)) Self::Confirm(Some(argument))
| Self::Doc(Some(argument)) | Self::Doc(Some(argument))
| Self::Extension(argument) | Self::Extension(argument)
| Self::Group(argument) => write!(f, "({argument})")?, | Self::Group(argument)
| Self::WorkingDirectory(argument) => write!(f, "({argument})")?,
Self::Script(Some(shell)) => write!(f, "({shell})")?, Self::Script(Some(shell)) => write!(f, "({shell})")?,
Self::Confirm(None) Self::Confirm(None)
| Self::Doc(None) | Self::Doc(None)

View File

@ -35,7 +35,6 @@ impl Color {
Self::default() Self::default()
} }
#[cfg(test)]
pub(crate) fn always() -> Self { pub(crate) fn always() -> Self {
Self { Self {
use_color: UseColor::Always, use_color: UseColor::Always,
@ -67,7 +66,7 @@ impl Color {
} }
pub(crate) fn doc_backtick(self) -> Self { pub(crate) fn doc_backtick(self) -> Self {
self.restyle(Style::new().fg(White).on(Black)) self.restyle(Style::new().fg(Cyan))
} }
pub(crate) fn error(self) -> Self { pub(crate) fn error(self) -> Self {

View File

@ -98,6 +98,7 @@ pub(crate) fn get(name: &str) -> Option<Function> {
"snakecase" => Unary(snakecase), "snakecase" => Unary(snakecase),
"source_directory" => Nullary(source_directory), "source_directory" => Nullary(source_directory),
"source_file" => Nullary(source_file), "source_file" => Nullary(source_file),
"style" => Unary(style),
"titlecase" => Unary(titlecase), "titlecase" => Unary(titlecase),
"trim" => Unary(trim), "trim" => Unary(trim),
"trim_end" => Unary(trim_end), "trim_end" => Unary(trim_end),
@ -623,6 +624,20 @@ fn source_file(context: Context) -> FunctionResult {
}) })
} }
fn style(context: Context, s: &str) -> FunctionResult {
match s {
"command" => Ok(
Color::always()
.command(context.evaluator.context.config.command_color)
.prefix()
.to_string(),
),
"error" => Ok(Color::always().error().prefix().to_string()),
"warning" => Ok(Color::always().warning().prefix().to_string()),
_ => Err(format!("unknown style: `{s}`")),
}
}
fn titlecase(_context: Context, s: &str) -> FunctionResult { fn titlecase(_context: Context, s: &str) -> FunctionResult {
Ok(s.to_title_case()) Ok(s.to_title_case())
} }

View File

@ -34,7 +34,7 @@ pub(crate) use {
}, },
camino::Utf8Path, camino::Utf8Path,
clap::ValueEnum, clap::ValueEnum,
derivative::Derivative, derive_where::derive_where,
edit_distance::edit_distance, edit_distance::edit_distance,
lexiclean::Lexiclean, lexiclean::Lexiclean,
libc::EXIT_FAILURE, libc::EXIT_FAILURE,

View File

@ -197,7 +197,7 @@ impl<'src> Node<'src> for UnresolvedRecipe<'src> {
t.push_mut("quiet"); t.push_mut("quiet");
} }
if let Some(doc) = self.doc { if let Some(doc) = &self.doc {
t.push_mut(Tree::string(doc)); t.push_mut(Tree::string(doc));
} }

View File

@ -936,6 +936,14 @@ impl<'run, 'src> Parser<'run, 'src> {
let private = name.lexeme().starts_with('_') || attributes.contains(&Attribute::Private); let private = name.lexeme().starts_with('_') || attributes.contains(&Attribute::Private);
let mut doc = doc.map(ToOwned::to_owned);
for attribute in &attributes {
if let Attribute::Doc(attribute_doc) = attribute {
doc = attribute_doc.as_ref().map(|doc| doc.cooked.clone());
}
}
Ok(Recipe { Ok(Recipe {
shebang: shebang || script, shebang: shebang || script,
attributes, attributes,
@ -1118,6 +1126,7 @@ impl<'run, 'src> Parser<'run, 'src> {
&mut self, &mut self,
) -> CompileResult<'src, Option<(Token<'src>, BTreeSet<Attribute<'src>>)>> { ) -> CompileResult<'src, Option<(Token<'src>, BTreeSet<Attribute<'src>>)>> {
let mut attributes = BTreeMap::new(); let mut attributes = BTreeMap::new();
let mut working_directory_attribute_line = None;
let mut token = None; let mut token = None;
@ -1144,6 +1153,17 @@ impl<'run, 'src> Parser<'run, 'src> {
let attribute = Attribute::new(name, arguments)?; let attribute = Attribute::new(name, arguments)?;
if let Attribute::WorkingDirectory(_) = &attribute {
if let Some(line) = working_directory_attribute_line {
return Err(name.error(CompileErrorKind::DuplicateAttribute {
attribute: name.lexeme(),
first: line,
}));
}
working_directory_attribute_line = Some(name.line);
}
if let Some(line) = attributes.get(&attribute) { if let Some(line) = attributes.get(&attribute) {
return Err(name.error(CompileErrorKind::DuplicateAttribute { return Err(name.error(CompileErrorKind::DuplicateAttribute {
attribute: name.lexeme(), attribute: name.lexeme(),

View File

@ -22,7 +22,7 @@ pub(crate) struct Recipe<'src, D = Dependency<'src>> {
pub(crate) attributes: BTreeSet<Attribute<'src>>, pub(crate) attributes: BTreeSet<Attribute<'src>>,
pub(crate) body: Vec<Line<'src>>, pub(crate) body: Vec<Line<'src>>,
pub(crate) dependencies: Vec<D>, pub(crate) dependencies: Vec<D>,
pub(crate) doc: Option<&'src str>, pub(crate) doc: Option<String>,
#[serde(skip)] #[serde(skip)]
pub(crate) file_depth: u32, pub(crate) file_depth: u32,
#[serde(skip)] #[serde(skip)]
@ -131,11 +131,19 @@ impl<'src, D> Recipe<'src, D> {
} }
fn working_directory<'a>(&'a self, context: &'a ExecutionContext) -> Option<PathBuf> { fn working_directory<'a>(&'a self, context: &'a ExecutionContext) -> Option<PathBuf> {
if self.change_directory() { if !self.change_directory() {
Some(context.working_directory()) return None;
} else {
None
} }
let working_directory = context.working_directory();
for attribute in &self.attributes {
if let Attribute::WorkingDirectory(dir) = attribute {
return Some(working_directory.join(&dir.cooked));
}
}
Some(working_directory)
} }
fn no_quiet(&self) -> bool { fn no_quiet(&self) -> bool {
@ -465,7 +473,8 @@ impl<'src, D> Recipe<'src, D> {
return doc.as_ref().map(|s| s.cooked.as_ref()); return doc.as_ref().map(|s| s.cooked.as_ref());
} }
} }
self.doc
self.doc.as_deref()
} }
pub(crate) fn subsequents(&self) -> impl Iterator<Item = &D> { pub(crate) fn subsequents(&self) -> impl Iterator<Item = &D> {
@ -475,8 +484,14 @@ impl<'src, D> Recipe<'src, D> {
impl<'src, D: Display> ColorDisplay for Recipe<'src, D> { impl<'src, D: Display> ColorDisplay for Recipe<'src, D> {
fn fmt(&self, f: &mut Formatter, color: Color) -> fmt::Result { fn fmt(&self, f: &mut Formatter, color: Color) -> fmt::Result {
if let Some(doc) = self.doc { if !self
writeln!(f, "# {doc}")?; .attributes
.iter()
.any(|attribute| matches!(attribute, Attribute::Doc(_)))
{
if let Some(doc) = &self.doc {
writeln!(f, "# {doc}")?;
}
} }
for attribute in &self.attributes { for attribute in &self.attributes {

View File

@ -1,46 +1,46 @@
use super::*; use super::*;
#[derive(Derivative)] #[derive_where(Debug, PartialEq)]
#[derivative(Debug, Clone, PartialEq = "feature_allow_slow_enum")] #[derive(Clone)]
pub(crate) enum Thunk<'src> { pub(crate) enum Thunk<'src> {
Nullary { Nullary {
name: Name<'src>, name: Name<'src>,
#[derivative(Debug = "ignore", PartialEq = "ignore")] #[derive_where(skip(Debug, EqHashOrd))]
function: fn(function::Context) -> FunctionResult, function: fn(function::Context) -> FunctionResult,
}, },
Unary { Unary {
name: Name<'src>, name: Name<'src>,
#[derivative(Debug = "ignore", PartialEq = "ignore")] #[derive_where(skip(Debug, EqHashOrd))]
function: fn(function::Context, &str) -> FunctionResult, function: fn(function::Context, &str) -> FunctionResult,
arg: Box<Expression<'src>>, arg: Box<Expression<'src>>,
}, },
UnaryOpt { UnaryOpt {
name: Name<'src>, name: Name<'src>,
#[derivative(Debug = "ignore", PartialEq = "ignore")] #[derive_where(skip(Debug, EqHashOrd))]
function: fn(function::Context, &str, Option<&str>) -> FunctionResult, function: fn(function::Context, &str, Option<&str>) -> FunctionResult,
args: (Box<Expression<'src>>, Box<Option<Expression<'src>>>), args: (Box<Expression<'src>>, Box<Option<Expression<'src>>>),
}, },
UnaryPlus { UnaryPlus {
name: Name<'src>, name: Name<'src>,
#[derivative(Debug = "ignore", PartialEq = "ignore")] #[derive_where(skip(Debug, EqHashOrd))]
function: fn(function::Context, &str, &[String]) -> FunctionResult, function: fn(function::Context, &str, &[String]) -> FunctionResult,
args: (Box<Expression<'src>>, Vec<Expression<'src>>), args: (Box<Expression<'src>>, Vec<Expression<'src>>),
}, },
Binary { Binary {
name: Name<'src>, name: Name<'src>,
#[derivative(Debug = "ignore", PartialEq = "ignore")] #[derive_where(skip(Debug, EqHashOrd))]
function: fn(function::Context, &str, &str) -> FunctionResult, function: fn(function::Context, &str, &str) -> FunctionResult,
args: [Box<Expression<'src>>; 2], args: [Box<Expression<'src>>; 2],
}, },
BinaryPlus { BinaryPlus {
name: Name<'src>, name: Name<'src>,
#[derivative(Debug = "ignore", PartialEq = "ignore")] #[derive_where(skip(Debug, EqHashOrd))]
function: fn(function::Context, &str, &str, &[String]) -> FunctionResult, function: fn(function::Context, &str, &str, &[String]) -> FunctionResult,
args: ([Box<Expression<'src>>; 2], Vec<Expression<'src>>), args: ([Box<Expression<'src>>; 2], Vec<Expression<'src>>),
}, },
Ternary { Ternary {
name: Name<'src>, name: Name<'src>,
#[derivative(Debug = "ignore", PartialEq = "ignore")] #[derive_where(skip(Debug, EqHashOrd))]
function: fn(function::Context, &str, &str, &str) -> FunctionResult, function: fn(function::Context, &str, &str, &str) -> FunctionResult,
args: [Box<Expression<'src>>; 3], args: [Box<Expression<'src>>; 3],
}, },

View File

@ -1096,3 +1096,27 @@ fn multi_argument_attribute() {
) )
.run(); .run();
} }
#[test]
fn doc_attribute_suppresses_comment() {
Test::new()
.justfile(
"
set unstable
# COMMENT
[doc('ATTRIBUTE')]
foo:
",
)
.arg("--dump")
.stdout(
"
set unstable := true
[doc('ATTRIBUTE')]
foo:
",
)
.run();
}

View File

@ -1183,3 +1183,78 @@ bar:
.args(["foo", "bar"]) .args(["foo", "bar"])
.run(); .run();
} }
#[test]
fn style_command_default() {
Test::new()
.justfile(
r#"
foo:
@echo '{{ style("command") }}foo{{NORMAL}}'
"#,
)
.stdout("\x1b[1mfoo\x1b[0m\n")
.run();
}
#[test]
fn style_command_non_default() {
Test::new()
.justfile(
r#"
foo:
@echo '{{ style("command") }}foo{{NORMAL}}'
"#,
)
.args(["--command-color", "red"])
.stdout("\x1b[1;31mfoo\x1b[0m\n")
.run();
}
#[test]
fn style_error() {
Test::new()
.justfile(
r#"
foo:
@echo '{{ style("error") }}foo{{NORMAL}}'
"#,
)
.stdout("\x1b[1;31mfoo\x1b[0m\n")
.run();
}
#[test]
fn style_warning() {
Test::new()
.justfile(
r#"
foo:
@echo '{{ style("warning") }}foo{{NORMAL}}'
"#,
)
.stdout("\x1b[1;33mfoo\x1b[0m\n")
.run();
}
#[test]
fn style_unknown() {
Test::new()
.justfile(
r#"
foo:
@echo '{{ style("hippo") }}foo{{NORMAL}}'
"#,
)
.stderr(
r#"
error: Call to function `style` failed: unknown style: `hippo`
justfile:2:13
2 @echo '{{ style("hippo") }}foo{{NORMAL}}'
^^^^^
"#,
)
.status(EXIT_FAILURE)
.run();
}

View File

@ -1429,3 +1429,58 @@ fn recipes_with_private_attribute_are_private() {
}), }),
); );
} }
#[test]
fn doc_attribute_overrides_comment() {
case(
"
# COMMENT
[doc('ATTRIBUTE')]
foo:
",
json!({
"aliases": {},
"assignments": {},
"first": "foo",
"doc": null,
"groups": [],
"modules": {},
"recipes": {
"foo": {
"attributes": [{"doc": "ATTRIBUTE"}],
"body": [],
"dependencies": [],
"doc": "ATTRIBUTE",
"name": "foo",
"namepath": "foo",
"parameters": [],
"priors": 0,
"private": false,
"quiet": false,
"shebang": false,
}
},
"settings": {
"allow_duplicate_recipes": false,
"allow_duplicate_variables": false,
"dotenv_filename": null,
"dotenv_load": false,
"dotenv_path": null,
"dotenv_required": false,
"export": false,
"fallback": false,
"ignore_comments": false,
"positional_arguments": false,
"quiet": false,
"shell": null,
"tempdir" : null,
"unstable": false,
"windows_powershell": false,
"windows_shell": null,
"working_directory" : null,
},
"unexports": [],
"warnings": [],
}),
);
}

View File

@ -452,7 +452,7 @@ fn backticks_highlighted() {
.stdout( .stdout(
" "
Available recipes: Available recipes:
recipe \u{1b}[34m#\u{1b}[0m \u{1b}[34mComment \u{1b}[0m\u{1b}[40;37m``\u{1b}[0m\u{1b}[34m \u{1b}[0m\u{1b}[40;37m`with backticks`\u{1b}[0m\u{1b}[34m and trailing text\u{1b}[0m recipe \u{1b}[34m#\u{1b}[0m \u{1b}[34mComment \u{1b}[0m\u{1b}[36m``\u{1b}[0m\u{1b}[34m \u{1b}[0m\u{1b}[36m`with backticks`\u{1b}[0m\u{1b}[34m and trailing text\u{1b}[0m
") ")
.run(); .run();
} }
@ -470,7 +470,7 @@ fn unclosed_backticks() {
.stdout( .stdout(
" "
Available recipes: Available recipes:
recipe \u{1b}[34m#\u{1b}[0m \u{1b}[34mComment \u{1b}[0m\u{1b}[40;37m`with unclosed backick\u{1b}[0m recipe \u{1b}[34m#\u{1b}[0m \u{1b}[34mComment \u{1b}[0m\u{1b}[36m`with unclosed backick\u{1b}[0m
") ")
.run(); .run();
} }

View File

@ -331,3 +331,95 @@ file := shell('cat file.txt')
.stdout("FILE\n") .stdout("FILE\n")
.run(); .run();
} }
#[test]
fn attribute_duplicate() {
Test::new()
.justfile(
r#"
[working-directory('bar')]
[working-directory('baz')]
print:
echo "$(basename "$PWD")"
"#,
)
.current_dir("foo")
.tree(tree! {
foo: {},
bar: {},
baz: {},
})
.args(["print"])
.stderr(
r#"error: Recipe attribute `working-directory` first used on line 1 is duplicated on line 2
justfile:2:2
2 [working-directory('baz')]
^^^^^^^^^^^^^^^^^
"#,
)
.stdout("")
.status(1)
.run();
}
#[test]
fn attribute() {
Test::new()
.justfile(
r#"
[working-directory('bar')]
print1:
echo "$(basename "$PWD")"
[working-directory('baz')]
[no-cd]
print2:
echo "$(basename "$PWD")"
"#,
)
.current_dir("foo")
.tree(tree! {
foo: {},
bar: {},
baz: {},
})
.args(["print1", "print2"])
.stderr(
r#"echo "$(basename "$PWD")"
echo "$(basename "$PWD")"
"#,
)
.stdout("bar\nfoo\n")
.run();
}
#[test]
fn setting_and_attribute() {
Test::new()
.justfile(
r#"
set working-directory := 'bar'
[working-directory('baz')]
print1:
echo "$(basename "$PWD")"
echo "$(basename "$(dirname "$PWD")")"
"#,
)
.current_dir("foo")
.tree(tree! {
foo: {},
bar: {
baz: {},
},
})
.args(["print1"])
.stderr(
r#"echo "$(basename "$PWD")"
echo "$(basename "$(dirname "$PWD")")"
"#,
)
.stdout("baz\nbar\n")
.run();
}