mirror of
https://github.com/casey/just.git
synced 2024-11-23 02:44:56 +03:00
Allow empty [script]
attribute and add set script-interpreter
(#2264)
This commit is contained in:
parent
0cd38464f1
commit
14489c0376
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -612,6 +612,7 @@ dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"num_cpus",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"pretty_assertions",
|
||||
"rand",
|
||||
|
@ -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"
|
||||
|
@ -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,
|
||||
|
@ -20,7 +20,7 @@ pub(crate) enum Attribute<'src> {
|
||||
NoQuiet,
|
||||
PositionalArguments,
|
||||
Private,
|
||||
Script(Vec<StringLiteral<'src>>),
|
||||
Script(Option<Interpreter<'src>>),
|
||||
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(())
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
|
29
src/interpreter.rs
Normal file
29
src/interpreter.rs
Normal file
@ -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(())
|
||||
}
|
||||
}
|
@ -21,6 +21,7 @@ pub(crate) enum Keyword {
|
||||
Mod,
|
||||
PositionalArguments,
|
||||
Quiet,
|
||||
ScriptInterpreter,
|
||||
Set,
|
||||
Shell,
|
||||
Tempdir,
|
||||
|
36
src/lib.rs
36
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;
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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!"]`
|
||||
|
@ -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()
|
||||
|
@ -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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<Shell<'src>>,
|
||||
#[serde(skip)]
|
||||
pub(crate) script_interpreter: Option<Interpreter<'src>>,
|
||||
pub(crate) shell: Option<Interpreter<'src>>,
|
||||
pub(crate) tempdir: Option<String>,
|
||||
pub(crate) unstable: bool,
|
||||
pub(crate) windows_powershell: bool,
|
||||
pub(crate) windows_shell: Option<Shell<'src>>,
|
||||
pub(crate) windows_shell: Option<Interpreter<'src>>,
|
||||
}
|
||||
|
||||
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",
|
||||
|
19
src/shell.rs
19
src/shell.rs
@ -1,19 +0,0 @@
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
pub(crate) struct Shell<'src> {
|
||||
pub(crate) arguments: Vec<StringLiteral<'src>>,
|
||||
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, "]")
|
||||
}
|
||||
}
|
6
src/string_delimiter.rs
Normal file
6
src/string_delimiter.rs
Normal file
@ -0,0 +1,6 @@
|
||||
#[derive(Debug, PartialEq, Clone, Copy, Ord, PartialOrd, Eq)]
|
||||
pub(crate) enum StringDelimiter {
|
||||
Backtick,
|
||||
QuoteDouble,
|
||||
QuoteSingle,
|
||||
}
|
@ -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 {
|
||||
|
@ -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")?;
|
||||
|
@ -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.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user