const SHELL_PARAMLEN_SENTINEL: &str = "\u{E000}ANODIZER_SHELL_PARAMLEN\u{E000}";
pub(crate) fn protect_shell_param_length(s: &str) -> anyhow::Result<std::borrow::Cow<'_, str>> {
if s.contains(SHELL_PARAMLEN_SENTINEL) {
anyhow::bail!("template contains the reserved shell-guard sentinel");
}
if s.contains("${#") {
Ok(std::borrow::Cow::Owned(
s.replace("${#", SHELL_PARAMLEN_SENTINEL),
))
} else {
Ok(std::borrow::Cow::Borrowed(s))
}
}
pub(crate) fn restore_shell_param_length(s: &str) -> std::borrow::Cow<'_, str> {
if s.contains(SHELL_PARAMLEN_SENTINEL) {
std::borrow::Cow::Owned(s.replace(SHELL_PARAMLEN_SENTINEL, "${#"))
} else {
std::borrow::Cow::Borrowed(s)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn round_trip(original: &str) -> String {
let protected = protect_shell_param_length(original).expect("protect must not error");
restore_shell_param_length(&protected).into_owned()
}
#[test]
fn protect_replaces_dollar_brace_hash() {
let protected = protect_shell_param_length("n=${#f[@]}").unwrap();
assert!(!protected.contains("${#"));
assert!(!protected.contains("{#"));
}
#[test]
fn round_trip_is_exact() {
let original = "(( ${#f[@]} )) || exit 1; m=${#g[*]}";
assert_eq!(round_trip(original), original);
}
#[test]
fn bare_tera_comment_open_is_untouched() {
let protected = protect_shell_param_length("pre {# c #} post").unwrap();
assert_eq!(protected, "pre {# c #} post");
}
#[test]
fn dollar_brace_hash_at_start_round_trips() {
assert_eq!(round_trip("${#x}"), "${#x}");
}
#[test]
fn dollar_brace_hash_at_end_round_trips() {
assert_eq!(round_trip("a=${#"), "a=${#");
}
#[test]
fn back_to_back_dollar_brace_hash_round_trips() {
let protected = protect_shell_param_length("${#${#").unwrap();
let sentinels = protected.matches(SHELL_PARAMLEN_SENTINEL).count();
assert_eq!(sentinels, 2);
assert_eq!(
restore_shell_param_length(&protected).into_owned(),
"${#${#"
);
}
#[test]
fn pre_existing_sentinel_is_rejected() {
let poisoned = format!("echo {SHELL_PARAMLEN_SENTINEL}");
assert!(protect_shell_param_length(&poisoned).is_err());
}
}