fix: use shebang in run script to determine how arg escaping should work (#3455)

This commit is contained in:
jdx 2024-12-10 12:24:41 -06:00 committed by GitHub
parent 7556fb0c5a
commit 2f70a65ace
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 62 additions and 19 deletions

View File

@ -13,9 +13,39 @@ assert "mise run shell" "using shell bash"
assert_fail "mise run shell-invalid" "invalid shell"
cat <<EOF >mise.toml
tasks.escapeme = "echo {{arg(name='a')}}"
tasks.escapeme_var = "echo {{arg(name='a', var=true)}}"
tasks.escapeme = "echo \"{{arg(name='a')}}\""
tasks.escapeme_var = "echo \"{{arg(name='a', var=true)}}\""
tasks.bashshebang1 = """
#!/usr/bin/env -S bash
echo "{{arg(name='a')}}"
echo "{{arg(name='b', var=true)}}"
"""
tasks.bashshebang2 = """
#!/usr/bin/env bash
echo "{{arg(name='a')}}"
echo "{{arg(name='b', var=true)}}"
"""
tasks.bashshebang3 = """
#!/bin/bash
echo "{{arg(name='a')}}"
echo "{{arg(name='b', var=true)}}"
"""
tasks.escapenode = """
#!/usr/bin/env -S node
console.log("{{arg(name='a')}}")
console.log("{{arg(name='b', var=true)}}")
"""
EOF
assert "mise run escapeme 'hello world'" "hello world"
assert "mise run escapeme_var hello 'world of mise'" "hello world of mise"
assert "mise run escapeme 'hello world'" "'hello world'"
assert "mise run escapeme_var hello 'world of mise'" "hello 'world of mise'"
assert "mise run bashshebang1 'a with space' hello 'world of mise'" "'a with space'
hello 'world of mise'"
assert "mise run bashshebang2 my_a hello 'world of mise'" "my_a
hello 'world of mise'"
assert "mise run bashshebang3 my_a hello 'world of mise'" "my_a
hello 'world of mise'"
assert "mise run escapenode my_a hello 'world of mise'" "my_a
hello 'world of mise'"
assert "mise run escapenode 'a with space' hello 'world of mise'" "a with space
hello 'world of mise'"

View File

@ -6,6 +6,7 @@ use eyre::Result;
use indexmap::IndexMap;
use itertools::Itertools;
use std::collections::HashMap;
use std::iter::once;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use usage::parse::ParseValue;
@ -300,21 +301,6 @@ pub fn replace_template_placeholders_with_args(
scripts: &[String],
args: &[String],
) -> Result<Vec<String>> {
let shell_type: Option<ShellType> = task.shell().unwrap_or(SETTINGS.default_inline_shell()?)[0]
.parse()
.ok();
let escape = |v: &ParseValue| match v {
ParseValue::MultiString(_) => {
// these are already escaped
v.to_string()
}
_ => match shell_type {
Some(ShellType::Zsh | ShellType::Bash | ShellType::Fish) => {
shell_words::quote(&v.to_string()).to_string()
}
_ => v.to_string(),
},
};
let args = vec!["".to_string()]
.into_iter()
.chain(args.iter().cloned())
@ -323,6 +309,23 @@ pub fn replace_template_placeholders_with_args(
let mut out = vec![];
let re = regex!(r"MISE_TASK_ARG:(\w+):MISE_TASK_ARG");
for script in scripts {
let shell_type: Option<ShellType> = shell_from_shebang(script)
.or(task.shell())
.unwrap_or(SETTINGS.default_inline_shell()?)[0]
.parse()
.ok();
let escape = |v: &ParseValue| match v {
ParseValue::MultiString(_) => {
// these are already escaped
v.to_string()
}
_ => match shell_type {
Some(ShellType::Zsh | ShellType::Bash | ShellType::Fish) => {
shell_words::quote(&v.to_string()).to_string()
}
_ => v.to_string(),
},
};
let mut script = script.clone();
for (arg, value) in &m.args {
script = script.replace(
@ -346,6 +349,16 @@ pub fn has_any_args_defined(spec: &usage::Spec) -> bool {
!spec.cmd.args.is_empty() || !spec.cmd.flags.is_empty()
}
fn shell_from_shebang(script: &str) -> Option<Vec<String>> {
let shebang = script.lines().next()?.strip_prefix("#!")?;
let shebang = shebang.strip_prefix("/usr/bin/env -S").unwrap_or(shebang);
let shebang = shebang.strip_prefix("/usr/bin/env").unwrap_or(shebang);
let mut parts = shebang.split_whitespace();
let shell = parts.next()?;
let args = parts.map(|s| s.to_string()).collect_vec();
Some(once(shell.to_string()).chain(args).collect())
}
#[cfg(test)]
mod tests {
use super::*;