Skip to content

Commit

Permalink
Allow empty [script] attribute and add set script-interpreter (#2264
Browse files Browse the repository at this point in the history
)
  • Loading branch information
casey authored Jul 19, 2024
1 parent 0cd3846 commit 14489c0
Show file tree
Hide file tree
Showing 19 changed files with 224 additions and 149 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
21 changes: 12 additions & 9 deletions src/analyzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
47 changes: 18 additions & 29 deletions src/attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub(crate) enum Attribute<'src> {
NoQuiet,
PositionalArguments,
Private,
Script(Vec<StringLiteral<'src>>),
Script(Option<Interpreter<'src>>),
Unix,
Windows,
}
Expand All @@ -39,7 +39,7 @@ impl AttributeDiscriminant {
| Self::Private
| Self::Unix
| Self::Windows => 0..=0,
Self::Script => 1..=usize::MAX,
Self::Script => 0..=usize::MAX,
}
}
}
Expand Down Expand Up @@ -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,
})
Expand All @@ -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
Expand All @@ -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(())
Expand Down
30 changes: 16 additions & 14 deletions src/executor.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use super::*;

pub(crate) enum Executor<'a> {
Command(Vec<&'a str>),
Command(&'a Interpreter<'a>),
Shebang(Shebang<'a>),
}

Expand All @@ -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);
Expand Down Expand Up @@ -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(),
};

Expand All @@ -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 {
Expand Down Expand Up @@ -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
);
}
Expand Down
29 changes: 29 additions & 0 deletions src/interpreter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use super::*;

#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)]
pub(crate) struct Interpreter<'src> {
pub(crate) arguments: Vec<StringLiteral<'src>>,
pub(crate) command: StringLiteral<'src>,
}

impl<'src> Interpreter<'src> {
pub(crate) fn default_script_interpreter() -> &'static Interpreter<'static> {
static INSTANCE: Lazy<Interpreter<'static>> = 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(())
}
}
1 change: 1 addition & 0 deletions src/keyword.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub(crate) enum Keyword {
Mod,
PositionalArguments,
Quiet,
ScriptInterpreter,
Set,
Shell,
Tempdir,
Expand Down
36 changes: 19 additions & 17 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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},
Expand All @@ -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,
Expand Down Expand Up @@ -155,6 +156,7 @@ mod executor;
mod expression;
mod fragment;
mod function;
mod interpreter;
mod interrupt_guard;
mod interrupt_handler;
mod item;
Expand Down Expand Up @@ -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;
Expand Down
7 changes: 4 additions & 3 deletions src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}

Expand Down
17 changes: 9 additions & 8 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand All @@ -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()?;
Expand All @@ -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!"]`
Expand Down
9 changes: 7 additions & 2 deletions src/recipe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Loading

0 comments on commit 14489c0

Please sign in to comment.