diff --git a/src/compiler.rs b/src/compiler.rs index 457e889..82730bd 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -20,7 +20,7 @@ impl Compiler { let (relative, src) = loader.load(root, ¤t)?; loaded.push(relative.into()); let tokens = Lexer::lex(relative, src)?; - let mut ast = Parser::parse(&tokens)?; + let mut ast = Parser::parse(current != root, ¤t, &tokens)?; paths.insert(current.clone(), relative.into()); srcs.insert(current.clone(), src); @@ -120,7 +120,7 @@ impl Compiler { #[cfg(test)] pub(crate) fn test_compile(src: &str) -> CompileResult { let tokens = Lexer::test_lex(src)?; - let ast = Parser::parse(&tokens)?; + let ast = Parser::parse(false, &PathBuf::new(), &tokens)?; let root = PathBuf::from("justfile"); let mut asts: HashMap = HashMap::new(); asts.insert(root.clone(), ast); diff --git a/src/config.rs b/src/config.rs index ec61160..1c20aee 100644 --- a/src/config.rs +++ b/src/config.rs @@ -18,7 +18,6 @@ pub(crate) fn chooser_default(justfile: &Path) -> OsString { } #[derive(Debug, PartialEq)] -#[allow(clippy::struct_excessive_bools)] pub(crate) struct Config { pub(crate) check: bool, pub(crate) color: Color, diff --git a/src/justfile.rs b/src/justfile.rs index 2313420..148c936 100644 --- a/src/justfile.rs +++ b/src/justfile.rs @@ -299,7 +299,6 @@ impl<'src> Justfile<'src> { .or_else(|| self.aliases.get(name).map(|alias| alias.target.as_ref())) } - #[allow(clippy::too_many_arguments)] fn invocation<'run>( &'run self, depth: usize, diff --git a/src/lib.rs b/src/lib.rs index fa88ede..0ace8fb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,9 +4,13 @@ clippy::let_underscore_untyped, clippy::needless_pass_by_value, clippy::similar_names, + clippy::struct_excessive_bools, + clippy::struct_field_names, + clippy::too_many_arguments, clippy::too_many_lines, clippy::unnecessary_wraps, - clippy::wildcard_imports + clippy::wildcard_imports, + overlapping_range_endpoints )] pub(crate) use { diff --git a/src/parser.rs b/src/parser.rs index 3758988..cb254f1 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -32,22 +32,28 @@ pub(crate) struct Parser<'tokens, 'src> { expected: BTreeSet, /// Current recursion depth depth: usize, + /// Path to the file being parsed + path: PathBuf, + /// Parsing a submodule + submodule: bool, } impl<'tokens, 'src> Parser<'tokens, 'src> { /// Parse `tokens` into an `Ast` - pub(crate) fn parse(tokens: &'tokens [Token<'src>]) -> CompileResult<'src, Ast<'src>> { - Self::new(tokens).parse_ast() - } - - /// Construct a new Parser from a token stream - fn new(tokens: &'tokens [Token<'src>]) -> Parser<'tokens, 'src> { + pub(crate) fn parse( + submodule: bool, + path: &Path, + tokens: &'tokens [Token<'src>], + ) -> CompileResult<'src, Ast<'src>> { Parser { - next: 0, - expected: BTreeSet::new(), - tokens, depth: 0, + expected: BTreeSet::new(), + next: 0, + path: path.into(), + submodule, + tokens, } + .parse_ast() } fn error(&self, kind: CompileErrorKind<'src>) -> CompileResult<'src, CompileError<'src>> { @@ -707,16 +713,18 @@ impl<'tokens, 'src> Parser<'tokens, 'src> { let body = self.parse_body()?; Ok(Recipe { - parameters: positional.into_iter().chain(variadic).collect(), - private: name.lexeme().starts_with('_'), shebang: body.first().map_or(false, Line::is_shebang), attributes, - priors, body, dependencies, doc, name, + parameters: positional.into_iter().chain(variadic).collect(), + path: self.path.clone(), + priors, + private: name.lexeme().starts_with('_'), quiet, + submodule: self.submodule, }) } @@ -934,7 +942,7 @@ mod tests { fn test(text: &str, want: Tree) { let unindented = unindent(text); let tokens = Lexer::test_lex(&unindented).expect("lexing failed"); - let justfile = Parser::parse(&tokens).expect("parsing failed"); + let justfile = Parser::parse(false, &PathBuf::new(), &tokens).expect("parsing failed"); let have = justfile.tree(); if have != want { println!("parsed text: {unindented}"); @@ -972,7 +980,7 @@ mod tests { ) { let tokens = Lexer::test_lex(src).expect("Lexing failed in parse test..."); - match Parser::parse(&tokens) { + match Parser::parse(false, &PathBuf::new(), &tokens) { Ok(_) => panic!("Parsing unexpectedly succeeded"), Err(have) => { let want = CompileError { diff --git a/src/recipe.rs b/src/recipe.rs index e526b8b..598e6a1 100644 --- a/src/recipe.rs +++ b/src/recipe.rs @@ -28,10 +28,14 @@ pub(crate) struct Recipe<'src, D = Dependency<'src>> { pub(crate) doc: Option<&'src str>, pub(crate) name: Name<'src>, pub(crate) parameters: Vec>, + #[serde(skip)] + pub(crate) path: PathBuf, pub(crate) priors: usize, pub(crate) private: bool, pub(crate) quiet: bool, pub(crate) shebang: bool, + #[serde(skip)] + pub(crate) submodule: bool, } impl<'src, D> Recipe<'src, D> { @@ -222,7 +226,11 @@ impl<'src, D> Recipe<'src, D> { let mut cmd = context.settings.shell_command(config); if self.change_directory() { - cmd.current_dir(&context.search.working_directory); + cmd.current_dir(if self.submodule { + self.path.parent().unwrap() + } else { + &context.search.working_directory + }); } cmd.arg(command); @@ -358,7 +366,11 @@ impl<'src, D> Recipe<'src, D> { let mut command = Platform::make_shebang_command( &path, if self.change_directory() { - Some(&context.search.working_directory) + if self.submodule { + Some(self.path.parent().unwrap()) + } else { + Some(&context.search.working_directory) + } } else { None }, diff --git a/src/settings.rs b/src/settings.rs index 26765e0..5c5e64c 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -6,7 +6,6 @@ pub(crate) const WINDOWS_POWERSHELL_SHELL: &str = "powershell.exe"; pub(crate) const WINDOWS_POWERSHELL_ARGS: &[&str] = &["-NoLogo", "-Command"]; #[derive(Debug, PartialEq, Serialize, Default)] -#[allow(clippy::struct_excessive_bools)] pub(crate) struct Settings<'src> { pub(crate) allow_duplicate_recipes: bool, pub(crate) dotenv_filename: Option, diff --git a/src/testing.rs b/src/testing.rs index b513454..adb6243 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -59,7 +59,8 @@ pub(crate) fn analysis_error( ) { let tokens = Lexer::test_lex(src).expect("Lexing failed in parse test..."); - let ast = Parser::parse(&tokens).expect("Parsing failed in analysis test..."); + let ast = + Parser::parse(false, &PathBuf::new(), &tokens).expect("Parsing failed in analysis test..."); let root = PathBuf::from("justfile"); let mut asts: HashMap = HashMap::new(); diff --git a/src/unresolved_recipe.rs b/src/unresolved_recipe.rs index 0a49443..b7f379f 100644 --- a/src/unresolved_recipe.rs +++ b/src/unresolved_recipe.rs @@ -45,16 +45,18 @@ impl<'src> UnresolvedRecipe<'src> { .collect(); Ok(Recipe { + attributes: self.attributes, body: self.body, + dependencies, doc: self.doc, name: self.name, parameters: self.parameters, + path: self.path, + priors: self.priors, private: self.private, quiet: self.quiet, shebang: self.shebang, - priors: self.priors, - attributes: self.attributes, - dependencies, + submodule: self.submodule, }) } } diff --git a/tests/modules.rs b/tests/modules.rs index ca69340..ea4796a 100644 --- a/tests/modules.rs +++ b/tests/modules.rs @@ -493,3 +493,39 @@ fn recipes_may_be_named_mod() { .stdout("FOO\n") .run(); } + +#[test] +fn submodule_linewise_recipes_run_in_submodule_directory() { + Test::new() + .write("foo/bar", "BAR") + .write("foo/mod.just", "foo:\n @cat bar") + .justfile( + " + mod foo + ", + ) + .test_round_trip(false) + .arg("--unstable") + .arg("foo") + .arg("foo") + .stdout("BAR") + .run(); +} + +#[test] +fn submodule_shebang_recipes_run_in_submodule_directory() { + Test::new() + .write("foo/bar", "BAR") + .write("foo/mod.just", "foo:\n #!/bin/sh\n cat bar") + .justfile( + " + mod foo + ", + ) + .test_round_trip(false) + .arg("--unstable") + .arg("foo") + .arg("foo") + .stdout("BAR") + .run(); +} diff --git a/tests/shebang.rs b/tests/shebang.rs index 18091bb..04451b7 100644 --- a/tests/shebang.rs +++ b/tests/shebang.rs @@ -1,3 +1,5 @@ +use super::*; + #[cfg(windows)] test! { name: powershell, @@ -41,3 +43,17 @@ default: "#, stdout: "Hello-World\r\n", } + +#[test] +fn simple() { + Test::new() + .justfile( + " + foo: + #!/bin/sh + echo bar + ", + ) + .stdout("bar\n") + .run(); +}