pub(crate) fn shell_quote_arg(value: &str) -> String {
if value.bytes().all(|b| {
b.is_ascii_alphanumeric() || matches!(b, b'-' | b'_' | b'.' | b'/' | b':' | b'@' | b'+')
}) {
return value.to_string();
}
format!("'{}'", value.replace('\'', "'\"'\"'"))
}
pub(crate) fn render_cli_model_arg(flag: Option<&str>, model: Option<&str>) -> String {
let Some(model) = model.filter(|m| !m.trim().is_empty()) else {
return String::new();
};
let Some(flag) = flag else {
return String::new();
};
format!(" {flag} {}", shell_quote_arg(model))
}
#[cfg(test)]
mod tests {
use super::{render_cli_model_arg, shell_quote_arg};
#[test]
fn shell_quote_leaves_safe_values_unquoted() {
assert_eq!(shell_quote_arg("gpt-5-mini"), "gpt-5-mini");
assert_eq!(shell_quote_arg("claude-opus-4-8"), "claude-opus-4-8");
assert_eq!(shell_quote_arg("a/b:c@d+e_f.g"), "a/b:c@d+e_f.g");
}
#[test]
fn shell_quote_wraps_values_with_specials() {
assert_eq!(shell_quote_arg("a b"), "'a b'");
assert_eq!(shell_quote_arg("a'b"), "'a'\"'\"'b'");
}
#[test]
fn render_model_arg_empty_when_unset() {
assert_eq!(render_cli_model_arg(Some("--model"), None), "");
assert_eq!(render_cli_model_arg(Some("--model"), Some(" ")), "");
assert_eq!(render_cli_model_arg(None, Some("opus")), "");
}
#[test]
fn render_model_arg_renders_flag_and_quoted_model() {
assert_eq!(
render_cli_model_arg(Some("--model"), Some("opus")),
" --model opus"
);
assert_eq!(
render_cli_model_arg(Some("-m"), Some("gpt 5")),
" -m 'gpt 5'"
);
}
}