Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions crates/deno_task_shell/src/grammar.pest
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ VARIABLE_EXPANSION = ${
"$" ~ (
"{" ~ INDIRECT_VARIABLE ~ VARIABLE_MODIFIER? ~ "}" |
"{" ~ VARIABLE ~ VARIABLE_MODIFIER? ~ "}" |
"{" ~ ARGNUM ~ VARIABLE_MODIFIER? ~ "}" |
"{" ~ BRACED_ALL_PARAMS ~ "}" |
SPECIAL_PARAM |
VARIABLE
)
Expand Down Expand Up @@ -117,6 +119,8 @@ BRACE_STEP = ${

SPECIAL_PARAM = ${ ARGNUM | "@" | "#" | "?" | "$" | "*" }
ARGNUM = ${ ASCII_NONZERO_DIGIT ~ ASCII_DIGIT* | "0" }
// ${@} and ${*}; modifiers such as ${@:2} (slicing) are not supported
BRACED_ALL_PARAMS = ${ "@" | "*" }
VARIABLE = ${ (ASCII_ALPHA | "_") ~ (ASCII_ALPHANUMERIC | "_")* }

VARIABLE_MODIFIER = _{
Expand Down
2 changes: 1 addition & 1 deletion crates/deno_task_shell/src/shell/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ async fn parse_shebang_args(
context: &mut ShellCommandContext,
) -> Result<Vec<String>> {
fn err_unsupported(text: &str) -> Result<Vec<String>> {
miette::bail!("unsupported shebang. Please report this as a bug (https://github.com/prefix.dev/shell).\n\nShebang: {}", text)
miette::bail!("unsupported shebang. Please report this as a bug (https://github.com/prefix.dev/shell).\n\nShebang: {}", text);
}

let mut args = crate::parser::parse(text)?;
Expand Down
8 changes: 4 additions & 4 deletions crates/deno_task_shell/src/shell/commands/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@ impl ArgKind<'_> {
pub fn bail_unsupported(&self) -> Result<()> {
match self {
ArgKind::Arg(arg) => {
bail!("unsupported argument: {}", arg)
bail!("unsupported argument: {}", arg);
}
ArgKind::LongFlag(name) => {
bail!("unsupported flag: --{}", name)
bail!("unsupported flag: --{}", name);
}
ArgKind::ShortFlag(name) => {
bail!("unsupported flag: -{}", name)
bail!("unsupported flag: -{}", name);
}
ArgKind::PlusFlag(name) => {
bail!("unsupported flag: +{}", name)
bail!("unsupported flag: +{}", name);
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions crates/deno_task_shell/src/shell/commands/cd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ fn execute_cd(
let path = if args.is_empty() {
"~".to_string()
} else if args.len() > 1 {
bail!("too many arguments")
bail!("too many arguments");
} else {
args[0].clone()
};
Expand All @@ -57,7 +57,7 @@ fn execute_cd(
.unwrap_or_else(|_| fs_util::canonicalize_path(&new_dir).unwrap());

if !new_dir.is_dir() {
bail!("{}: Not a directory", path)
bail!("{}: Not a directory", path);
}
Ok(new_dir)
}
Expand Down
6 changes: 3 additions & 3 deletions crates/deno_task_shell/src/shell/commands/cp_mv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,15 +78,15 @@ async fn do_copy_operation(
if to.path.exists() && to.path.is_file() {
bail!("destination was a file");
} else if to.path.is_symlink() {
bail!("no support for copying to symlinks")
bail!("no support for copying to symlinks");
} else if from.path.is_symlink() {
bail!("no support for copying from symlinks")
bail!("no support for copying from symlinks");
} else {
copy_dir_recursively(from.path.clone(), to.path.clone())
.await?;
}
} else {
bail!("source was a directory; maybe specify -r")
bail!("source was a directory; maybe specify -r");
}
} else {
tokio::fs::copy(&from.path, &to.path)
Expand Down
6 changes: 4 additions & 2 deletions crates/deno_task_shell/src/shell/commands/exit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,13 @@ fn parse_args(args: Vec<String>) -> Result<i32> {
let arg = paths.remove(0).to_string();
match arg.parse::<i32>() {
Ok(value) => Ok(value),
Err(_) => bail!("numeric argument required."),
Err(_) => {
bail!("numeric argument required.");
}
}
}
_ => {
bail!("too many arguments")
bail!("too many arguments");
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion crates/deno_task_shell/src/shell/commands/head.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,9 @@ fn parse_args(args: Vec<String>) -> Result<HeadFlags> {
Some(ArgKind::Arg(arg)) => {
lines = Some(arg.parse::<u64>().into_diagnostic()?);
}
_ => bail!("expected a value following -n"),
_ => {
bail!("expected a value following -n");
}
},
ArgKind::LongFlag(flag) => {
if flag == "lines" || flag == "lines=" {
Expand Down
5 changes: 4 additions & 1 deletion crates/deno_task_shell/src/shell/commands/shift.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,14 @@ impl ShellCommand for ShiftCommand {
changes.push(EnvChange::UnsetVar(idx.to_string()));
}

// Update $# (parameter count)
// Update $# (parameter count), $@ and $*
changes.push(EnvChange::SetShellVar(
"#".to_string(),
remaining.len().to_string(),
));
let joined = remaining.join(" ");
changes.push(EnvChange::SetShellVar("@".to_string(), joined.clone()));
changes.push(EnvChange::SetShellVar("*".to_string(), joined));

Box::pin(futures::future::ready(ExecuteResult::Continue(
0,
Expand Down
4 changes: 3 additions & 1 deletion crates/deno_task_shell/src/shell/commands/unset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ fn parse_names(mut args: Vec<String>) -> Result<Vec<String>> {
// Running the actual `unset` with no argument completes with success.
Ok(args)
}
Some(flag) if flag == "-f" => bail!("unsupported flag: -f"),
Some(flag) if flag == "-f" => {
bail!("unsupported flag: -f");
}
Some(flag) if flag == "-v" => {
// It's fine to use `swap_remove` (instead of `remove`) because the order
// of args doesn't matter for `unset` command.
Expand Down
20 changes: 14 additions & 6 deletions crates/deno_task_shell/src/shell/commands/xargs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,13 @@ fn delimit_blanks(text: &str) -> Result<Vec<String>> {
break;
}
match c {
'\n' => bail!("{}", UNMATCHED_MESSAGE),
'\n' => {
bail!("{}", UNMATCHED_MESSAGE);
}
_ => current.push(c),
}
if chars.peek().is_none() {
bail!("{}", UNMATCHED_MESSAGE)
bail!("{}", UNMATCHED_MESSAGE);
}
}
}
Expand Down Expand Up @@ -147,8 +149,12 @@ fn parse_args(args: Vec<String>) -> Result<XargsFlags> {
Some('t') => '\t',
Some('\\') => '\\',
Some('0') => '\0',
None => bail!("expected character following escape"),
_ => bail!("unsupported/not implemented escape character"),
None => {
bail!("expected character following escape");
}
_ => {
bail!("unsupported/not implemented escape character");
}
};
}

Expand Down Expand Up @@ -197,7 +203,9 @@ fn parse_args(args: Vec<String>) -> Result<XargsFlags> {
Some(ArgKind::Arg(arg)) => {
delimiter = Some(parse_delimiter(arg)?);
}
_ => bail!("expected delimiter argument following -d"),
_ => {
bail!("expected delimiter argument following -d");
}
},
ArgKind::LongFlag(flag) => {
if let Some(arg) = flag.strip_prefix("delimiter=") {
Expand All @@ -211,7 +219,7 @@ fn parse_args(args: Vec<String>) -> Result<XargsFlags> {
}

if is_null_delimited && delimiter.is_some() {
bail!("cannot specify both null and delimiter flag")
bail!("cannot specify both null and delimiter flag");
}

Ok(XargsFlags {
Expand Down
92 changes: 83 additions & 9 deletions crates/deno_task_shell/src/shell/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ fn execute_sequence(
sequence: Sequence,
mut state: ShellState,
stdin: ShellPipeReader,
mut stdout: ShellPipeWriter,
stdout: ShellPipeWriter,
mut stderr: ShellPipeWriter,
) -> FutureExecuteResult {
// requires boxed async because of recursive async
Expand All @@ -274,7 +274,7 @@ fn execute_sequence(

if state.print_trace() {
let _ =
stdout.write_line(&format!("+ {}={}", var.name, value));
stderr.write_line(&format!("+ {}={}", var.name, value));
}

ExecuteResult::Continue(
Expand Down Expand Up @@ -1814,7 +1814,7 @@ async fn execute_simple_command(
command: SimpleCommand,
state: &mut ShellState,
stdin: ShellPipeReader,
mut stdout: ShellPipeWriter,
stdout: ShellPipeWriter,
mut stderr: ShellPipeWriter,
) -> ExecuteResult {
let args =
Expand Down Expand Up @@ -1846,15 +1846,15 @@ async fn execute_simple_command(
changes.extend(word_result.changes);

if state.print_trace() {
let _ = stdout.write_line(&format!(
let _ = stderr.write_line(&format!(
"+ {:}={:}",
env_var.name, word_result.value
));
}
}

if state.print_trace() {
let _ = stdout.write_line(&format!("+ {:}", args.join(" ")));
let _ = stderr.write_line(&format!("+ {:}", args.join(" ")));
}

let result = execute_command_args(args, state, stdin, stdout, stderr).await;
Expand Down Expand Up @@ -2553,6 +2553,26 @@ fn expand_brace_word_parts(
Ok(result)
}

/// Whether a variable expansion is `$@` or `${@}` without a modifier.
fn is_all_params(
name: &str,
modifier: &Option<Box<VariableModifier>>,
indirect: bool,
) -> bool {
name == "@" && modifier.is_none() && !indirect
}

/// The positional parameters `$1` to `${N}`, where N is `$#`.
fn positional_params(state: &ShellState) -> Vec<String> {
let count = state
.get_var("#")
.and_then(|n| n.parse::<usize>().ok())
.unwrap_or(0);
(1..=count)
.map(|i| state.get_var(&i.to_string()).cloned().unwrap_or_default())
.collect()
}

fn evaluate_word_parts(
parts: Vec<WordPart>,
state: &mut ShellState,
Expand Down Expand Up @@ -2676,7 +2696,8 @@ fn evaluate_word_parts(
async move {
let mut result = WordPartsResult::new(Vec::new(), Vec::new());
let mut current_text = Vec::new();
for part in parts {
let mut parts = std::collections::VecDeque::from(parts);
while let Some(part) = parts.pop_front() {
let evaluation_result_text: Result<Option<Text>, Error> =
match part {
WordPart::Text(text) => {
Expand All @@ -2694,7 +2715,18 @@ fn evaluate_word_parts(
} else {
name.clone()
};
if let Some(modifier) = modifier {
if is_all_params(&resolved_name, &modifier, indirect)
{
// Unquoted $@ expands to one word per
// non-empty positional parameter
Ok(Some(Text::new(
positional_params(state)
.into_iter()
.filter(|p| !p.is_empty())
.map(OtherText)
.collect(),
)))
} else if let Some(modifier) = modifier {
let (text, env_changes) = modifier
.apply(
&resolved_name,
Expand Down Expand Up @@ -2734,9 +2766,51 @@ fn evaluate_word_parts(
.await;
Ok(Some(cmd.into()))
}
WordPart::Quoted(parts) => {
WordPart::Quoted(mut quoted_parts) => {
// "$@" expands to one word per positional
// parameter: "pre$@post" with parameters a and b
// gives "prea" and "bpost", and with no
// parameters "$@" gives no word at all.
if let Some(idx) = quoted_parts.iter().position(|p| {
matches!(p, WordPart::Variable(name, modifier, indirect)
if is_all_params(name, modifier, *indirect))
}) {
let after = quoted_parts.split_off(idx + 1);
quoted_parts.pop();
if !quoted_parts.is_empty() {
let res = evaluate_word_parts_inner(
quoted_parts,
IsQuoted::Yes,
eval_glob,
state,
stdin.clone(),
stderr.clone(),
)
.await?;
result.with_changes(res.changes);
current_text
.push(TextPart::Quoted(res.value.join(" ")));
}
let params = positional_params(state);
let count = params.len();
for (i, param) in params.into_iter().enumerate() {
current_text.push(TextPart::Quoted(param));
if i + 1 < count {
result.extend(evaluate_word_text(
state,
std::mem::take(&mut current_text),
is_quoted,
eval_glob,
)?);
}
}
if !after.is_empty() {
parts.push_front(WordPart::Quoted(after));
}
continue;
}
let res = evaluate_word_parts_inner(
parts,
quoted_parts,
IsQuoted::Yes,
eval_glob,
state,
Expand Down
4 changes: 3 additions & 1 deletion crates/shell/src/commands/set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ fn execute_set(args: Vec<String>) -> Result<(i32, Vec<EnvChange>)> {
ArgKind::PlusFlag('u') => {
env_changes.push(EnvChange::SetShellOptions(ShellOptions::NoUnset, false));
}
_ => bail!(format!("Unsupported argument: {:?}", arg)),
_ => {
bail!(format!("Unsupported argument: {:?}", arg));
}
}
}
Ok((0, env_changes))
Expand Down
Loading
Loading