From 7b050e577d589da75ac2278d36a7f5d365dd254f Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Thu, 18 Jul 2024 22:37:47 -0700 Subject: [PATCH] Allow empty `[script]` attribute and add `set script-interpreter` --- Cargo.lock | 1 + Cargo.toml | 1 + src/analyzer.rs | 21 ++++++----- src/attribute.rs | 47 +++++++++-------------- src/executor.rs | 30 ++++++++------- src/interpreter.rs | 29 ++++++++++++++ src/keyword.rs | 1 + src/lib.rs | 36 +++++++++--------- src/node.rs | 7 ++-- src/parser.rs | 17 +++++---- src/recipe.rs | 9 ++++- src/setting.rs | 17 +++++---- src/settings.rs | 17 ++++++--- src/shell.rs | 19 ---------- src/string_delimiter.rs | 6 +++ src/string_kind.rs | 11 +----- src/string_literal.rs | 16 +++++++- src/unstable_feature.rs | 4 ++ tests/script.rs | 84 +++++++++++++++++++++++++++++------------ 19 files changed, 224 insertions(+), 149 deletions(-) create mode 100644 src/interpreter.rs delete mode 100644 src/shell.rs create mode 100644 src/string_delimiter.rs diff --git a/Cargo.lock b/Cargo.lock index b00ebce91f..b12bfc5077 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -612,6 +612,7 @@ dependencies = [ "libc", "log", "num_cpus", + "once_cell", "percent-encoding", "pretty_assertions", "rand", diff --git a/Cargo.toml b/Cargo.toml index 8f64e271b7..36d370faaa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ lexiclean = "0.0.1" libc = "0.2.0" log = "0.4.4" num_cpus = "1.15.0" +once_cell = "1.19.0" percent-encoding = "2.3.1" rand = "0.8.5" regex = "1.10.4" diff --git a/src/analyzer.rs b/src/analyzer.rs index c1b1a7d63d..cdb598d8d4 100644 --- a/src/analyzer.rs +++ b/src/analyzer.rs @@ -186,17 +186,20 @@ impl<'src> Analyzer<'src> { let root = paths.get(root).unwrap(); - let unstable_features = recipes - .values() - .flat_map(|recipe| &recipe.attributes) - .filter_map(|attribute| { + let mut unstable_features = BTreeSet::new(); + + for recipe in recipes.values() { + for attribute in &recipe.attributes { if let Attribute::Script(_) = attribute { - Some(UnstableFeature::ScriptAttribute) - } else { - None + unstable_features.insert(UnstableFeature::ScriptAttribute); + break; } - }) - .collect(); + } + } + + if settings.script_interpreter.is_some() { + unstable_features.insert(UnstableFeature::ScriptInterpreterSetting); + } Ok(Justfile { aliases, diff --git a/src/attribute.rs b/src/attribute.rs index fb01606303..ebdc212745 100644 --- a/src/attribute.rs +++ b/src/attribute.rs @@ -20,7 +20,7 @@ pub(crate) enum Attribute<'src> { NoQuiet, PositionalArguments, Private, - Script(Vec>), + Script(Option>), Unix, Windows, } @@ -39,7 +39,7 @@ impl AttributeDiscriminant { | Self::Private | Self::Unix | Self::Windows => 0..=0, - Self::Script => 1..=usize::MAX, + Self::Script => 0..=usize::MAX, } } } @@ -84,7 +84,13 @@ impl<'src> Attribute<'src> { AttributeDiscriminant::NoQuiet => Self::NoQuiet, AttributeDiscriminant::PositionalArguments => Self::PositionalArguments, AttributeDiscriminant::Private => Self::Private, - AttributeDiscriminant::Script => Self::Script(arguments), + AttributeDiscriminant::Script => Self::Script({ + let mut arguments = arguments.into_iter(); + arguments.next().map(|command| Interpreter { + command, + arguments: arguments.collect(), + }) + }), AttributeDiscriminant::Unix => Self::Unix, AttributeDiscriminant::Windows => Self::Windows, }) @@ -93,14 +99,18 @@ impl<'src> Attribute<'src> { pub(crate) fn name(&self) -> &'static str { self.into() } +} + +impl<'src> Display for Attribute<'src> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}", self.name())?; - fn arguments(&self) -> &[StringLiteral] { match self { Self::Confirm(Some(argument)) | Self::Doc(Some(argument)) | Self::Extension(argument) - | Self::Group(argument) => slice::from_ref(argument), - Self::Script(arguments) => arguments, + | Self::Group(argument) => write!(f, "({argument})")?, + Self::Script(Some(shell)) => write!(f, "({shell})")?, Self::Confirm(None) | Self::Doc(None) | Self::Linux @@ -110,30 +120,9 @@ impl<'src> Attribute<'src> { | Self::NoQuiet | Self::PositionalArguments | Self::Private + | Self::Script(None) | Self::Unix - | Self::Windows => &[], - } - } -} - -impl<'src> Display for Attribute<'src> { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}", self.name())?; - - let arguments = self.arguments(); - - for (i, argument) in arguments.iter().enumerate() { - if i == 0 { - write!(f, "(")?; - } else { - write!(f, ", ")?; - } - - write!(f, "{argument}")?; - - if i + 1 == arguments.len() { - write!(f, ")")?; - } + | Self::Windows => {} } Ok(()) diff --git a/src/executor.rs b/src/executor.rs index 1345d902aa..6c31aacff5 100644 --- a/src/executor.rs +++ b/src/executor.rs @@ -1,7 +1,7 @@ use super::*; pub(crate) enum Executor<'a> { - Command(Vec<&'a str>), + Command(&'a Interpreter<'a>), Shebang(Shebang<'a>), } @@ -13,15 +13,15 @@ impl<'a> Executor<'a> { working_directory: Option<&Path>, ) -> RunResult<'src, Command> { match self { - Self::Command(args) => { - let mut command = Command::new(args[0]); + Self::Command(interpreter) => { + let mut command = Command::new(&interpreter.command.cooked); if let Some(working_directory) = working_directory { command.current_dir(working_directory); } - for arg in &args[1..] { - command.arg(arg); + for arg in &interpreter.arguments { + command.arg(&arg.cooked); } command.arg(path); @@ -49,7 +49,7 @@ impl<'a> Executor<'a> { pub(crate) fn script_filename(&self, recipe: &str, extension: Option<&str>) -> String { let extension = extension.unwrap_or_else(|| { let interpreter = match self { - Self::Command(args) => args[0], + Self::Command(interpreter) => &interpreter.command.cooked, Self::Shebang(shebang) => shebang.interpreter_filename(), }; @@ -65,14 +65,12 @@ impl<'a> Executor<'a> { pub(crate) fn error<'src>(&self, io_error: io::Error, recipe: &'src str) -> Error<'src> { match self { - Self::Command(args) => { - let mut command = String::new(); + Self::Command(Interpreter { command, arguments }) => { + let mut command = command.cooked.clone(); - for (i, arg) in args.iter().enumerate() { - if i > 0 { - command.push(' '); - } - command.push_str(arg); + for arg in arguments { + command.push(' '); + command.push_str(&arg.cooked); } Error::Script { @@ -152,7 +150,11 @@ mod tests { expected ); assert_eq!( - Executor::Command(vec![interpreter]).script_filename(recipe, extension), + Executor::Command(&Interpreter { + command: StringLiteral::from_raw(interpreter), + arguments: Vec::new() + }) + .script_filename(recipe, extension), expected ); } diff --git a/src/interpreter.rs b/src/interpreter.rs new file mode 100644 index 0000000000..eaf6c2ad5f --- /dev/null +++ b/src/interpreter.rs @@ -0,0 +1,29 @@ +use super::*; + +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)] +pub(crate) struct Interpreter<'src> { + pub(crate) arguments: Vec>, + pub(crate) command: StringLiteral<'src>, +} + +impl<'src> Interpreter<'src> { + pub(crate) fn default_script_interpreter() -> &'static Interpreter<'static> { + static INSTANCE: Lazy> = Lazy::new(|| Interpreter { + arguments: vec![StringLiteral::from_raw("-eu")], + command: StringLiteral::from_raw("sh"), + }); + &INSTANCE + } +} + +impl<'src> Display for Interpreter<'src> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}", self.command)?; + + for argument in &self.arguments { + write!(f, ", {argument}")?; + } + + Ok(()) + } +} diff --git a/src/keyword.rs b/src/keyword.rs index 7f8b5b3f45..1461c0c952 100644 --- a/src/keyword.rs +++ b/src/keyword.rs @@ -21,6 +21,7 @@ pub(crate) enum Keyword { Mod, PositionalArguments, Quiet, + ScriptInterpreter, Set, Shell, Tempdir, diff --git a/src/lib.rs b/src/lib.rs index d4efb53273..713c72defe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,21 +30,22 @@ pub(crate) use { constants::constants, count::Count, delimiter::Delimiter, dependency::Dependency, dump_format::DumpFormat, enclosure::Enclosure, error::Error, evaluator::Evaluator, execution_context::ExecutionContext, executor::Executor, expression::Expression, - fragment::Fragment, function::Function, interrupt_guard::InterruptGuard, - interrupt_handler::InterruptHandler, item::Item, justfile::Justfile, keyed::Keyed, - keyword::Keyword, lexer::Lexer, line::Line, list::List, load_dotenv::load_dotenv, - loader::Loader, module_path::ModulePath, name::Name, namepath::Namepath, ordinal::Ordinal, - output::output, output_error::OutputError, parameter::Parameter, parameter_kind::ParameterKind, - parser::Parser, platform::Platform, platform_interface::PlatformInterface, position::Position, - positional::Positional, ran::Ran, range_ext::RangeExt, recipe::Recipe, - recipe_resolver::RecipeResolver, recipe_signature::RecipeSignature, scope::Scope, - search::Search, search_config::SearchConfig, search_error::SearchError, set::Set, - setting::Setting, settings::Settings, shebang::Shebang, shell::Shell, - show_whitespace::ShowWhitespace, source::Source, string_kind::StringKind, - string_literal::StringLiteral, subcommand::Subcommand, suggestion::Suggestion, table::Table, - thunk::Thunk, token::Token, token_kind::TokenKind, unresolved_dependency::UnresolvedDependency, - unresolved_recipe::UnresolvedRecipe, unstable_feature::UnstableFeature, use_color::UseColor, - variables::Variables, verbosity::Verbosity, warning::Warning, + fragment::Fragment, function::Function, interpreter::Interpreter, + interrupt_guard::InterruptGuard, interrupt_handler::InterruptHandler, item::Item, + justfile::Justfile, keyed::Keyed, keyword::Keyword, lexer::Lexer, line::Line, list::List, + load_dotenv::load_dotenv, loader::Loader, module_path::ModulePath, name::Name, + namepath::Namepath, ordinal::Ordinal, output::output, output_error::OutputError, + parameter::Parameter, parameter_kind::ParameterKind, parser::Parser, platform::Platform, + platform_interface::PlatformInterface, position::Position, positional::Positional, ran::Ran, + range_ext::RangeExt, recipe::Recipe, recipe_resolver::RecipeResolver, + recipe_signature::RecipeSignature, scope::Scope, search::Search, search_config::SearchConfig, + search_error::SearchError, set::Set, setting::Setting, settings::Settings, shebang::Shebang, + show_whitespace::ShowWhitespace, source::Source, string_delimiter::StringDelimiter, + string_kind::StringKind, string_literal::StringLiteral, subcommand::Subcommand, + suggestion::Suggestion, table::Table, thunk::Thunk, token::Token, token_kind::TokenKind, + unresolved_dependency::UnresolvedDependency, unresolved_recipe::UnresolvedRecipe, + unstable_feature::UnstableFeature, use_color::UseColor, variables::Variables, + verbosity::Verbosity, warning::Warning, }, camino::Utf8Path, clap::ValueEnum, @@ -53,6 +54,7 @@ pub(crate) use { lexiclean::Lexiclean, libc::EXIT_FAILURE, log::{info, warn}, + once_cell::sync::Lazy, regex::Regex, serde::{ ser::{SerializeMap, SerializeSeq}, @@ -75,7 +77,6 @@ pub(crate) use { path::{self, Path, PathBuf}, process::{self, Command, ExitStatus, Stdio}, rc::Rc, - slice, str::{self, Chars}, sync::{Mutex, MutexGuard, OnceLock}, vec, @@ -155,6 +156,7 @@ mod executor; mod expression; mod fragment; mod function; +mod interpreter; mod interrupt_guard; mod interrupt_handler; mod item; @@ -193,9 +195,9 @@ mod set; mod setting; mod settings; mod shebang; -mod shell; mod show_whitespace; mod source; +mod string_delimiter; mod string_kind; mod string_literal; mod subcommand; diff --git a/src/node.rs b/src/node.rs index 1de9bdcb1f..bd5585df1a 100644 --- a/src/node.rs +++ b/src/node.rs @@ -299,15 +299,16 @@ impl<'src> Node<'src> for Set<'src> { | Setting::IgnoreComments(value) => { set.push_mut(value.to_string()); } - Setting::Shell(Shell { command, arguments }) - | Setting::WindowsShell(Shell { command, arguments }) => { + Setting::ScriptInterpreter(Interpreter { command, arguments }) + | Setting::Shell(Interpreter { command, arguments }) + | Setting::WindowsShell(Interpreter { command, arguments }) => { set.push_mut(Tree::string(&command.cooked)); for argument in arguments { set.push_mut(Tree::string(&argument.cooked)); } } Setting::DotenvFilename(value) | Setting::DotenvPath(value) | Setting::Tempdir(value) => { - set.push_mut(Tree::string(value)); + set.push_mut(Tree::string(&value.cooked)); } } diff --git a/src/parser.rs b/src/parser.rs index 27b342dadb..d9985a326f 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -961,11 +961,12 @@ impl<'run, 'src> Parser<'run, 'src> { self.expect(ColonEquals)?; let set_value = match keyword { - Keyword::DotenvFilename => Some(Setting::DotenvFilename(self.parse_string_literal()?.cooked)), - Keyword::DotenvPath => Some(Setting::DotenvPath(self.parse_string_literal()?.cooked)), - Keyword::Shell => Some(Setting::Shell(self.parse_shell()?)), - Keyword::Tempdir => Some(Setting::Tempdir(self.parse_string_literal()?.cooked)), - Keyword::WindowsShell => Some(Setting::WindowsShell(self.parse_shell()?)), + Keyword::DotenvFilename => Some(Setting::DotenvFilename(self.parse_string_literal()?)), + Keyword::DotenvPath => Some(Setting::DotenvPath(self.parse_string_literal()?)), + Keyword::ScriptInterpreter => Some(Setting::ScriptInterpreter(self.parse_interpreter()?)), + Keyword::Shell => Some(Setting::Shell(self.parse_interpreter()?)), + Keyword::Tempdir => Some(Setting::Tempdir(self.parse_string_literal()?)), + Keyword::WindowsShell => Some(Setting::WindowsShell(self.parse_interpreter()?)), _ => None, }; @@ -978,8 +979,8 @@ impl<'run, 'src> Parser<'run, 'src> { })) } - /// Parse a shell setting value - fn parse_shell(&mut self) -> CompileResult<'src, Shell<'src>> { + /// Parse interpreter setting value, i.e., `['sh', '-eu']` + fn parse_interpreter(&mut self) -> CompileResult<'src, Interpreter<'src>> { self.expect(BracketL)?; let command = self.parse_string_literal()?; @@ -998,7 +999,7 @@ impl<'run, 'src> Parser<'run, 'src> { self.expect(BracketR)?; - Ok(Shell { arguments, command }) + Ok(Interpreter { arguments, command }) } /// Item attributes, i.e., `[macos]` or `[confirm: "warning!"]` diff --git a/src/recipe.rs b/src/recipe.rs index ac95199a3d..7ae9641aee 100644 --- a/src/recipe.rs +++ b/src/recipe.rs @@ -342,12 +342,17 @@ impl<'src, D> Recipe<'src, D> { return Ok(()); } - let executor = if let Some(Attribute::Script(args)) = self + let executor = if let Some(Attribute::Script(interpreter)) = self .attributes .iter() .find(|attribute| matches!(attribute, Attribute::Script(_))) { - Executor::Command(args.iter().map(|arg| arg.cooked.as_str()).collect()) + Executor::Command( + interpreter + .as_ref() + .or(context.settings.script_interpreter.as_ref()) + .unwrap_or_else(|| Interpreter::default_script_interpreter()), + ) } else { let line = evaluated_lines .first() diff --git a/src/setting.rs b/src/setting.rs index f19dab4769..342cd878e3 100644 --- a/src/setting.rs +++ b/src/setting.rs @@ -4,20 +4,21 @@ use super::*; pub(crate) enum Setting<'src> { AllowDuplicateRecipes(bool), AllowDuplicateVariables(bool), - DotenvFilename(String), + DotenvFilename(StringLiteral<'src>), DotenvLoad(bool), - DotenvPath(String), + DotenvPath(StringLiteral<'src>), DotenvRequired(bool), Export(bool), Fallback(bool), IgnoreComments(bool), PositionalArguments(bool), Quiet(bool), - Shell(Shell<'src>), - Tempdir(String), + ScriptInterpreter(Interpreter<'src>), + Shell(Interpreter<'src>), + Tempdir(StringLiteral<'src>), Unstable(bool), WindowsPowerShell(bool), - WindowsShell(Shell<'src>), + WindowsShell(Interpreter<'src>), } impl<'src> Display for Setting<'src> { @@ -34,9 +35,11 @@ impl<'src> Display for Setting<'src> { | Self::Quiet(value) | Self::Unstable(value) | Self::WindowsPowerShell(value) => write!(f, "{value}"), - Self::Shell(shell) | Self::WindowsShell(shell) => write!(f, "{shell}"), + Self::ScriptInterpreter(shell) | Self::Shell(shell) | Self::WindowsShell(shell) => { + write!(f, "[{shell}]") + } Self::DotenvFilename(value) | Self::DotenvPath(value) | Self::Tempdir(value) => { - write!(f, "{value:?}") + write!(f, "{value}") } } } diff --git a/src/settings.rs b/src/settings.rs index 338f2cc727..7ea64dba6a 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -18,11 +18,13 @@ pub(crate) struct Settings<'src> { pub(crate) ignore_comments: bool, pub(crate) positional_arguments: bool, pub(crate) quiet: bool, - pub(crate) shell: Option>, + #[serde(skip)] + pub(crate) script_interpreter: Option>, + pub(crate) shell: Option>, pub(crate) tempdir: Option, pub(crate) unstable: bool, pub(crate) windows_powershell: bool, - pub(crate) windows_shell: Option>, + pub(crate) windows_shell: Option>, } impl<'src> Settings<'src> { @@ -38,13 +40,13 @@ impl<'src> Settings<'src> { settings.allow_duplicate_variables = allow_duplicate_variables; } Setting::DotenvFilename(filename) => { - settings.dotenv_filename = Some(filename); + settings.dotenv_filename = Some(filename.cooked); } Setting::DotenvLoad(dotenv_load) => { settings.dotenv_load = dotenv_load; } Setting::DotenvPath(path) => { - settings.dotenv_path = Some(PathBuf::from(path)); + settings.dotenv_path = Some(PathBuf::from(path.cooked)); } Setting::DotenvRequired(dotenv_required) => { settings.dotenv_required = dotenv_required; @@ -64,6 +66,9 @@ impl<'src> Settings<'src> { Setting::Quiet(quiet) => { settings.quiet = quiet; } + Setting::ScriptInterpreter(script_interpreter) => { + settings.script_interpreter = Some(script_interpreter); + } Setting::Shell(shell) => { settings.shell = Some(shell); } @@ -77,7 +82,7 @@ impl<'src> Settings<'src> { settings.windows_shell = Some(windows_shell); } Setting::Tempdir(tempdir) => { - settings.tempdir = Some(tempdir); + settings.tempdir = Some(tempdir.cooked); } } } @@ -204,7 +209,7 @@ mod tests { #[test] fn shell_cooked() { let settings = Settings { - shell: Some(Shell { + shell: Some(Interpreter { command: StringLiteral { kind: StringKind::from_token_start("\"").unwrap(), raw: "asdf.exe", diff --git a/src/shell.rs b/src/shell.rs deleted file mode 100644 index f637a7e7f3..0000000000 --- a/src/shell.rs +++ /dev/null @@ -1,19 +0,0 @@ -use super::*; - -#[derive(Debug, Clone, PartialEq, Serialize)] -pub(crate) struct Shell<'src> { - pub(crate) arguments: Vec>, - pub(crate) command: StringLiteral<'src>, -} - -impl<'src> Display for Shell<'src> { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "[{}", self.command)?; - - for argument in &self.arguments { - write!(f, ", {argument}")?; - } - - write!(f, "]") - } -} diff --git a/src/string_delimiter.rs b/src/string_delimiter.rs new file mode 100644 index 0000000000..99dd32b5cc --- /dev/null +++ b/src/string_delimiter.rs @@ -0,0 +1,6 @@ +#[derive(Debug, PartialEq, Clone, Copy, Ord, PartialOrd, Eq)] +pub(crate) enum StringDelimiter { + Backtick, + QuoteDouble, + QuoteSingle, +} diff --git a/src/string_kind.rs b/src/string_kind.rs index 06b530f3de..a03692f9c4 100644 --- a/src/string_kind.rs +++ b/src/string_kind.rs @@ -2,15 +2,8 @@ use super::*; #[derive(Debug, PartialEq, Clone, Copy, Ord, PartialOrd, Eq)] pub(crate) struct StringKind { - delimiter: StringDelimiter, - indented: bool, -} - -#[derive(Debug, PartialEq, Clone, Copy, Ord, PartialOrd, Eq)] -enum StringDelimiter { - Backtick, - QuoteDouble, - QuoteSingle, + pub(crate) delimiter: StringDelimiter, + pub(crate) indented: bool, } impl StringKind { diff --git a/src/string_literal.rs b/src/string_literal.rs index 05b9c28f75..2ca411ccdc 100644 --- a/src/string_literal.rs +++ b/src/string_literal.rs @@ -8,7 +8,21 @@ pub(crate) struct StringLiteral<'src> { pub(crate) raw: &'src str, } -impl Display for StringLiteral<'_> { +impl<'src> StringLiteral<'src> { + pub(crate) fn from_raw(raw: &'src str) -> Self { + Self { + cooked: raw.into(), + expand: false, + kind: StringKind { + delimiter: StringDelimiter::QuoteSingle, + indented: false, + }, + raw, + } + } +} + +impl<'src> Display for StringLiteral<'src> { fn fmt(&self, f: &mut Formatter) -> fmt::Result { if self.expand { write!(f, "x")?; diff --git a/src/unstable_feature.rs b/src/unstable_feature.rs index 98eb4937c1..07d99540e5 100644 --- a/src/unstable_feature.rs +++ b/src/unstable_feature.rs @@ -4,6 +4,7 @@ use super::*; pub(crate) enum UnstableFeature { FormatSubcommand, ScriptAttribute, + ScriptInterpreterSetting, } impl Display for UnstableFeature { @@ -11,6 +12,9 @@ impl Display for UnstableFeature { match self { Self::FormatSubcommand => write!(f, "The `--fmt` command is currently unstable."), Self::ScriptAttribute => write!(f, "The `[script]` attribute is currently unstable."), + Self::ScriptInterpreterSetting => { + write!(f, "The `script-interpreter` setting is currently unstable.") + } } } } diff --git a/tests/script.rs b/tests/script.rs index 95757bdc06..0a2931650e 100644 --- a/tests/script.rs +++ b/tests/script.rs @@ -8,7 +8,6 @@ fn unstable() { [script('sh', '-u')] foo: echo FOO - ", ) .stderr_regex(r"error: The `\[script\]` attribute is currently unstable\..*") @@ -16,6 +15,15 @@ fn unstable() { .run(); } +#[test] +fn script_interpreter_setting_is_unstable() { + Test::new() + .justfile("set script-interpreter := ['sh']") + .status(EXIT_FAILURE) + .stderr_regex(r"error: The `script-interpreter` setting is currently unstable\..*") + .run(); +} + #[test] fn runs_with_command() { Test::new() @@ -73,30 +81,6 @@ fn with_arguments() { .run(); } -#[test] -fn requires_argument() { - Test::new() - .justfile( - " - set unstable - - [script] - foo: - ", - ) - .stderr( - " - error: Attribute `script` got 0 arguments but takes at least 1 argument - ——▶ justfile:3:2 - │ - 3 │ [script] - │ ^^^^^^ - ", - ) - .status(EXIT_FAILURE) - .run(); -} - #[test] fn not_allowed_with_shebang() { Test::new() @@ -298,3 +282,53 @@ c ) .run(); } + +#[test] +fn no_arguments_with_default_script_interpreter() { + Test::new() + .justfile( + " + set unstable + + [script] + foo: + case $- in + *e*) echo '-e is set';; + esac + + case $- in + *u*) echo '-u is set';; + esac + ", + ) + .stdout( + " + -e is set + -u is set + ", + ) + .run(); +} + +#[test] +fn no_arguments_with_non_default_script_interpreter() { + Test::new() + .justfile( + " + set unstable + + set script-interpreter := ['sh'] + + [script] + foo: + case $- in + *e*) echo '-e is set';; + esac + + case $- in + *u*) echo '-u is set';; + esac + ", + ) + .run(); +}