mirror of
https://github.com/casey/just.git
synced 2025-07-07 17:45:00 +00:00
983 lines
18 KiB
Rust
983 lines
18 KiB
Rust
use super::*;
|
|
|
|
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
|
|
#[serde(deny_unknown_fields)]
|
|
struct Alias<'a> {
|
|
attributes: Vec<&'a str>,
|
|
name: &'a str,
|
|
target: &'a str,
|
|
}
|
|
|
|
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
|
|
#[serde(deny_unknown_fields)]
|
|
struct Assignment<'a> {
|
|
export: bool,
|
|
name: &'a str,
|
|
private: bool,
|
|
value: &'a str,
|
|
}
|
|
|
|
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
|
|
#[serde(deny_unknown_fields)]
|
|
struct Dependency<'a> {
|
|
arguments: Vec<Value>,
|
|
recipe: &'a str,
|
|
}
|
|
|
|
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
|
|
#[serde(deny_unknown_fields)]
|
|
struct Interpreter<'a> {
|
|
arguments: Vec<&'a str>,
|
|
command: &'a str,
|
|
}
|
|
|
|
#[derive(Debug, Default, PartialEq, Serialize, Deserialize)]
|
|
#[serde(deny_unknown_fields)]
|
|
struct Module<'a> {
|
|
aliases: BTreeMap<&'a str, Alias<'a>>,
|
|
assignments: BTreeMap<&'a str, Assignment<'a>>,
|
|
doc: Option<&'a str>,
|
|
first: Option<&'a str>,
|
|
groups: Vec<&'a str>,
|
|
modules: BTreeMap<&'a str, Module<'a>>,
|
|
recipes: BTreeMap<&'a str, Recipe<'a>>,
|
|
settings: Settings<'a>,
|
|
source: PathBuf,
|
|
unexports: Vec<&'a str>,
|
|
warnings: Vec<&'a str>,
|
|
}
|
|
|
|
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
|
|
#[serde(deny_unknown_fields)]
|
|
struct Parameter<'a> {
|
|
default: Option<&'a str>,
|
|
export: bool,
|
|
kind: &'a str,
|
|
name: &'a str,
|
|
}
|
|
|
|
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
|
|
#[serde(deny_unknown_fields)]
|
|
struct Recipe<'a> {
|
|
attributes: Vec<Value>,
|
|
body: Vec<Value>,
|
|
dependencies: Vec<Dependency<'a>>,
|
|
doc: Option<&'a str>,
|
|
name: &'a str,
|
|
namepath: &'a str,
|
|
parameters: Vec<Parameter<'a>>,
|
|
priors: u32,
|
|
private: bool,
|
|
quiet: bool,
|
|
shebang: bool,
|
|
}
|
|
|
|
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
|
|
#[serde(deny_unknown_fields)]
|
|
struct Settings<'a> {
|
|
allow_duplicate_recipes: bool,
|
|
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,
|
|
fallback: bool,
|
|
ignore_comments: bool,
|
|
no_exit_message: bool,
|
|
positional_arguments: bool,
|
|
quiet: bool,
|
|
shell: Option<Interpreter<'a>>,
|
|
tempdir: Option<&'a str>,
|
|
unstable: bool,
|
|
windows_powershell: bool,
|
|
windows_shell: Option<&'a str>,
|
|
working_directory: Option<&'a str>,
|
|
}
|
|
|
|
#[track_caller]
|
|
fn case(justfile: &str, expected: Module) {
|
|
case_with_submodule(justfile, None, expected);
|
|
}
|
|
|
|
fn fix_source(dir: &Path, module: &mut Module) {
|
|
let filename = if module.source.as_os_str().is_empty() {
|
|
Path::new("justfile")
|
|
} else {
|
|
&module.source
|
|
};
|
|
|
|
module.source = if cfg!(target_os = "macos") {
|
|
dir.canonicalize().unwrap().join(filename)
|
|
} else {
|
|
dir.join(filename)
|
|
};
|
|
|
|
for module in module.modules.values_mut() {
|
|
fix_source(dir, module);
|
|
}
|
|
}
|
|
|
|
#[track_caller]
|
|
fn case_with_submodule(justfile: &str, submodule: Option<(&str, &str)>, mut expected: Module) {
|
|
let mut test = Test::new()
|
|
.justfile(justfile)
|
|
.args(["--dump", "--dump-format", "json"])
|
|
.stdout_regex(".*");
|
|
|
|
if let Some((path, source)) = submodule {
|
|
test = test.write(path, source);
|
|
}
|
|
|
|
fix_source(test.tempdir.path(), &mut expected);
|
|
|
|
let actual = test.run().stdout;
|
|
|
|
let actual: Module = serde_json::from_str(actual.as_str()).unwrap();
|
|
pretty_assertions::assert_eq!(actual, expected);
|
|
}
|
|
|
|
#[test]
|
|
fn alias() {
|
|
case(
|
|
"
|
|
alias f := foo
|
|
|
|
foo:
|
|
",
|
|
Module {
|
|
aliases: [(
|
|
"f",
|
|
Alias {
|
|
name: "f",
|
|
target: "foo",
|
|
..default()
|
|
},
|
|
)]
|
|
.into(),
|
|
first: Some("foo"),
|
|
recipes: [(
|
|
"foo",
|
|
Recipe {
|
|
name: "foo",
|
|
namepath: "foo",
|
|
..default()
|
|
},
|
|
)]
|
|
.into(),
|
|
..default()
|
|
},
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn assignment() {
|
|
case(
|
|
"foo := 'bar'",
|
|
Module {
|
|
assignments: [(
|
|
"foo",
|
|
Assignment {
|
|
name: "foo",
|
|
value: "bar",
|
|
..default()
|
|
},
|
|
)]
|
|
.into(),
|
|
..default()
|
|
},
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn private_assignment() {
|
|
case(
|
|
"
|
|
_foo := 'foo'
|
|
[private]
|
|
bar := 'bar'
|
|
",
|
|
Module {
|
|
assignments: [
|
|
(
|
|
"_foo",
|
|
Assignment {
|
|
name: "_foo",
|
|
value: "foo",
|
|
private: true,
|
|
..default()
|
|
},
|
|
),
|
|
(
|
|
"bar",
|
|
Assignment {
|
|
name: "bar",
|
|
value: "bar",
|
|
private: true,
|
|
..default()
|
|
},
|
|
),
|
|
]
|
|
.into(),
|
|
..default()
|
|
},
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn body() {
|
|
case(
|
|
"
|
|
foo:
|
|
bar
|
|
abc{{ 'xyz' }}def
|
|
",
|
|
Module {
|
|
first: Some("foo"),
|
|
recipes: [(
|
|
"foo",
|
|
Recipe {
|
|
name: "foo",
|
|
namepath: "foo",
|
|
body: [json!(["bar"]), json!(["abc", ["xyz"], "def"])].into(),
|
|
..default()
|
|
},
|
|
)]
|
|
.into(),
|
|
..default()
|
|
},
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn dependencies() {
|
|
case(
|
|
"
|
|
foo:
|
|
bar: foo
|
|
",
|
|
Module {
|
|
first: Some("foo"),
|
|
recipes: [
|
|
(
|
|
"foo",
|
|
Recipe {
|
|
name: "foo",
|
|
namepath: "foo",
|
|
..default()
|
|
},
|
|
),
|
|
(
|
|
"bar",
|
|
Recipe {
|
|
name: "bar",
|
|
namepath: "bar",
|
|
dependencies: [Dependency {
|
|
recipe: "foo",
|
|
..default()
|
|
}]
|
|
.into(),
|
|
priors: 1,
|
|
..default()
|
|
},
|
|
),
|
|
]
|
|
.into(),
|
|
..default()
|
|
},
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn dependency_argument() {
|
|
case(
|
|
"
|
|
x := 'foo'
|
|
foo *args:
|
|
bar: (
|
|
foo
|
|
'baz'
|
|
('baz')
|
|
('a' + 'b')
|
|
`echo`
|
|
x
|
|
if 'a' == 'b' { 'c' } else { 'd' }
|
|
arch()
|
|
env_var('foo')
|
|
join('a', 'b')
|
|
replace('a', 'b', 'c')
|
|
)
|
|
",
|
|
Module {
|
|
assignments: [(
|
|
"x",
|
|
Assignment {
|
|
name: "x",
|
|
value: "foo",
|
|
..default()
|
|
},
|
|
)]
|
|
.into(),
|
|
first: Some("foo"),
|
|
recipes: [
|
|
(
|
|
"foo",
|
|
Recipe {
|
|
name: "foo",
|
|
namepath: "foo",
|
|
parameters: [Parameter {
|
|
kind: "star",
|
|
name: "args",
|
|
..default()
|
|
}]
|
|
.into(),
|
|
..default()
|
|
},
|
|
),
|
|
(
|
|
"bar",
|
|
Recipe {
|
|
name: "bar",
|
|
namepath: "bar",
|
|
dependencies: [Dependency {
|
|
recipe: "foo",
|
|
arguments: [
|
|
json!("baz"),
|
|
json!("baz"),
|
|
json!(["concatenate", "a", "b"]),
|
|
json!(["evaluate", "echo"]),
|
|
json!(["variable", "x"]),
|
|
json!(["if", ["==", "a", "b"], "c", "d"]),
|
|
json!(["call", "arch"]),
|
|
json!(["call", "env_var", "foo"]),
|
|
json!(["call", "join", "a", "b"]),
|
|
json!(["call", "replace", "a", "b", "c"]),
|
|
]
|
|
.into(),
|
|
}]
|
|
.into(),
|
|
priors: 1,
|
|
..default()
|
|
},
|
|
),
|
|
]
|
|
.into(),
|
|
..default()
|
|
},
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn duplicate_recipes() {
|
|
case(
|
|
"
|
|
set allow-duplicate-recipes
|
|
alias f := foo
|
|
|
|
foo:
|
|
foo bar:
|
|
",
|
|
Module {
|
|
aliases: [(
|
|
"f",
|
|
Alias {
|
|
name: "f",
|
|
target: "foo",
|
|
..default()
|
|
},
|
|
)]
|
|
.into(),
|
|
first: Some("foo"),
|
|
recipes: [(
|
|
"foo",
|
|
Recipe {
|
|
name: "foo",
|
|
namepath: "foo",
|
|
parameters: [Parameter {
|
|
kind: "singular",
|
|
name: "bar",
|
|
..default()
|
|
}]
|
|
.into(),
|
|
..default()
|
|
},
|
|
)]
|
|
.into(),
|
|
settings: Settings {
|
|
allow_duplicate_recipes: true,
|
|
..default()
|
|
},
|
|
..default()
|
|
},
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn duplicate_variables() {
|
|
case(
|
|
"
|
|
set allow-duplicate-variables
|
|
x := 'foo'
|
|
x := 'bar'
|
|
",
|
|
Module {
|
|
assignments: [(
|
|
"x",
|
|
Assignment {
|
|
name: "x",
|
|
value: "bar",
|
|
..default()
|
|
},
|
|
)]
|
|
.into(),
|
|
settings: Settings {
|
|
allow_duplicate_variables: true,
|
|
..default()
|
|
},
|
|
..default()
|
|
},
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn doc_comment() {
|
|
case(
|
|
"# hello\nfoo:",
|
|
Module {
|
|
first: Some("foo"),
|
|
recipes: [(
|
|
"foo",
|
|
Recipe {
|
|
doc: Some("hello"),
|
|
name: "foo",
|
|
namepath: "foo",
|
|
..default()
|
|
},
|
|
)]
|
|
.into(),
|
|
..default()
|
|
},
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn empty_justfile() {
|
|
case("", Module::default());
|
|
}
|
|
|
|
#[test]
|
|
fn parameters() {
|
|
case(
|
|
"
|
|
a:
|
|
b x:
|
|
c x='y':
|
|
d +x:
|
|
e *x:
|
|
f $x:
|
|
",
|
|
Module {
|
|
first: Some("a"),
|
|
recipes: [
|
|
(
|
|
"a",
|
|
Recipe {
|
|
name: "a",
|
|
namepath: "a",
|
|
..default()
|
|
},
|
|
),
|
|
(
|
|
"b",
|
|
Recipe {
|
|
name: "b",
|
|
namepath: "b",
|
|
parameters: [Parameter {
|
|
kind: "singular",
|
|
name: "x",
|
|
..default()
|
|
}]
|
|
.into(),
|
|
..default()
|
|
},
|
|
),
|
|
(
|
|
"c",
|
|
Recipe {
|
|
name: "c",
|
|
namepath: "c",
|
|
parameters: [Parameter {
|
|
default: Some("y"),
|
|
kind: "singular",
|
|
name: "x",
|
|
..default()
|
|
}]
|
|
.into(),
|
|
..default()
|
|
},
|
|
),
|
|
(
|
|
"d",
|
|
Recipe {
|
|
name: "d",
|
|
namepath: "d",
|
|
parameters: [Parameter {
|
|
kind: "plus",
|
|
name: "x",
|
|
..default()
|
|
}]
|
|
.into(),
|
|
..default()
|
|
},
|
|
),
|
|
(
|
|
"e",
|
|
Recipe {
|
|
name: "e",
|
|
namepath: "e",
|
|
parameters: [Parameter {
|
|
kind: "star",
|
|
name: "x",
|
|
..default()
|
|
}]
|
|
.into(),
|
|
..default()
|
|
},
|
|
),
|
|
(
|
|
"f",
|
|
Recipe {
|
|
name: "f",
|
|
namepath: "f",
|
|
parameters: [Parameter {
|
|
export: true,
|
|
kind: "singular",
|
|
name: "x",
|
|
..default()
|
|
}]
|
|
.into(),
|
|
..default()
|
|
},
|
|
),
|
|
]
|
|
.into(),
|
|
..default()
|
|
},
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn priors() {
|
|
case(
|
|
"
|
|
a:
|
|
b: a && c
|
|
c:
|
|
",
|
|
Module {
|
|
first: Some("a"),
|
|
recipes: [
|
|
(
|
|
"a",
|
|
Recipe {
|
|
name: "a",
|
|
namepath: "a",
|
|
..default()
|
|
},
|
|
),
|
|
(
|
|
"b",
|
|
Recipe {
|
|
dependencies: [
|
|
Dependency {
|
|
recipe: "a",
|
|
..default()
|
|
},
|
|
Dependency {
|
|
recipe: "c",
|
|
..default()
|
|
},
|
|
]
|
|
.into(),
|
|
name: "b",
|
|
namepath: "b",
|
|
priors: 1,
|
|
..default()
|
|
},
|
|
),
|
|
(
|
|
"c",
|
|
Recipe {
|
|
name: "c",
|
|
namepath: "c",
|
|
..default()
|
|
},
|
|
),
|
|
]
|
|
.into(),
|
|
..default()
|
|
},
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn private() {
|
|
case(
|
|
"_foo:",
|
|
Module {
|
|
first: Some("_foo"),
|
|
recipes: [(
|
|
"_foo",
|
|
Recipe {
|
|
name: "_foo",
|
|
namepath: "_foo",
|
|
private: true,
|
|
..default()
|
|
},
|
|
)]
|
|
.into(),
|
|
..default()
|
|
},
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn quiet() {
|
|
case(
|
|
"@foo:",
|
|
Module {
|
|
first: Some("foo"),
|
|
recipes: [(
|
|
"foo",
|
|
Recipe {
|
|
name: "foo",
|
|
namepath: "foo",
|
|
quiet: true,
|
|
..default()
|
|
},
|
|
)]
|
|
.into(),
|
|
..default()
|
|
},
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn settings() {
|
|
case(
|
|
"
|
|
set allow-duplicate-recipes
|
|
set dotenv-filename := \"filename\"
|
|
set dotenv-load
|
|
set dotenv-path := \"path\"
|
|
set export
|
|
set fallback
|
|
set ignore-comments
|
|
set positional-arguments
|
|
set quiet
|
|
set shell := ['a', 'b', 'c']
|
|
foo:
|
|
#!bar
|
|
",
|
|
Module {
|
|
first: Some("foo"),
|
|
recipes: [(
|
|
"foo",
|
|
Recipe {
|
|
name: "foo",
|
|
namepath: "foo",
|
|
shebang: true,
|
|
body: [json!(["#!bar"])].into(),
|
|
..default()
|
|
},
|
|
)]
|
|
.into(),
|
|
settings: Settings {
|
|
allow_duplicate_recipes: true,
|
|
dotenv_filename: Some("filename"),
|
|
dotenv_path: Some("path"),
|
|
dotenv_load: true,
|
|
export: true,
|
|
fallback: true,
|
|
ignore_comments: true,
|
|
positional_arguments: true,
|
|
quiet: true,
|
|
shell: Some(Interpreter {
|
|
arguments: ["b", "c"].into(),
|
|
command: "a",
|
|
}),
|
|
..default()
|
|
},
|
|
..default()
|
|
},
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn shebang() {
|
|
case(
|
|
"
|
|
foo:
|
|
#!bar
|
|
",
|
|
Module {
|
|
first: Some("foo"),
|
|
recipes: [(
|
|
"foo",
|
|
Recipe {
|
|
name: "foo",
|
|
namepath: "foo",
|
|
shebang: true,
|
|
body: [json!(["#!bar"])].into(),
|
|
..default()
|
|
},
|
|
)]
|
|
.into(),
|
|
..default()
|
|
},
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn simple() {
|
|
case(
|
|
"foo:",
|
|
Module {
|
|
first: Some("foo"),
|
|
recipes: [(
|
|
"foo",
|
|
Recipe {
|
|
name: "foo",
|
|
namepath: "foo",
|
|
..default()
|
|
},
|
|
)]
|
|
.into(),
|
|
..default()
|
|
},
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn attribute() {
|
|
case(
|
|
"
|
|
[no-exit-message]
|
|
foo:
|
|
",
|
|
Module {
|
|
first: Some("foo"),
|
|
recipes: [(
|
|
"foo",
|
|
Recipe {
|
|
attributes: [json!("no-exit-message")].into(),
|
|
name: "foo",
|
|
namepath: "foo",
|
|
..default()
|
|
},
|
|
)]
|
|
.into(),
|
|
..default()
|
|
},
|
|
);
|
|
}
|
|
|
|
#[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(
|
|
"
|
|
# hello
|
|
mod foo
|
|
",
|
|
Some(("foo.just", "bar:")),
|
|
Module {
|
|
modules: [(
|
|
"foo",
|
|
Module {
|
|
doc: Some("hello"),
|
|
first: Some("bar"),
|
|
source: "foo.just".into(),
|
|
recipes: [(
|
|
"bar",
|
|
Recipe {
|
|
name: "bar",
|
|
namepath: "foo::bar",
|
|
..default()
|
|
},
|
|
)]
|
|
.into(),
|
|
..default()
|
|
},
|
|
)]
|
|
.into(),
|
|
..default()
|
|
},
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn module_group() {
|
|
case_with_submodule(
|
|
"
|
|
[group('alpha')]
|
|
mod foo
|
|
",
|
|
Some(("foo.just", "bar:")),
|
|
Module {
|
|
modules: [(
|
|
"foo",
|
|
Module {
|
|
first: Some("bar"),
|
|
groups: ["alpha"].into(),
|
|
source: "foo.just".into(),
|
|
recipes: [(
|
|
"bar",
|
|
Recipe {
|
|
name: "bar",
|
|
namepath: "foo::bar",
|
|
..default()
|
|
},
|
|
)]
|
|
.into(),
|
|
..default()
|
|
},
|
|
)]
|
|
.into(),
|
|
..default()
|
|
},
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn recipes_with_private_attribute_are_private() {
|
|
case(
|
|
"
|
|
[private]
|
|
foo:
|
|
",
|
|
Module {
|
|
first: Some("foo"),
|
|
recipes: [(
|
|
"foo",
|
|
Recipe {
|
|
attributes: [json!("private")].into(),
|
|
name: "foo",
|
|
namepath: "foo",
|
|
private: true,
|
|
..default()
|
|
},
|
|
)]
|
|
.into(),
|
|
..default()
|
|
},
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn doc_attribute_overrides_comment() {
|
|
case(
|
|
"
|
|
# COMMENT
|
|
[doc('ATTRIBUTE')]
|
|
foo:
|
|
",
|
|
Module {
|
|
first: Some("foo"),
|
|
recipes: [(
|
|
"foo",
|
|
Recipe {
|
|
attributes: [json!({"doc": "ATTRIBUTE"})].into(),
|
|
doc: Some("ATTRIBUTE"),
|
|
name: "foo",
|
|
namepath: "foo",
|
|
..default()
|
|
},
|
|
)]
|
|
.into(),
|
|
..default()
|
|
},
|
|
);
|
|
}
|