use crate::chunk::Chunk;
use crate::value::Value;
pub trait ShellHost: Send {
fn glob(&mut self, pattern: &str, recursive: bool) -> Vec<String> {
let _ = recursive;
glob::glob(pattern)
.into_iter()
.flat_map(|paths| paths.filter_map(|p| p.ok()))
.map(|p| p.to_string_lossy().into_owned())
.collect()
}
fn tilde_expand(&mut self, s: &str) -> String {
s.to_string()
}
fn brace_expand(&mut self, s: &str) -> Vec<String> {
vec![s.to_string()]
}
fn word_split(&mut self, s: &str) -> Vec<String> {
s.split_whitespace().map(|w| w.to_string()).collect()
}
fn expand_param(&mut self, name: &str, modifier: u8, args: &[Value]) -> Value {
let _ = (name, modifier, args);
Value::str("")
}
fn array_index(&mut self, name: &str, index: &Value) -> Value {
let _ = (name, index);
Value::Undef
}
fn cmd_subst(&mut self, sub: &Chunk) -> String {
let _ = sub;
String::new()
}
fn process_sub_in(&mut self, sub: &Chunk) -> String {
let _ = sub;
String::new()
}
fn process_sub_out(&mut self, sub: &Chunk) -> String {
let _ = sub;
String::new()
}
fn redirect(&mut self, fd: u8, op: u8, target: &str) {
let _ = (fd, op, target);
}
fn heredoc(&mut self, content: &str) {
let _ = content;
}
fn herestring(&mut self, content: &str) {
let _ = content;
}
fn pipeline_begin(&mut self, n: u8) {
let _ = n;
}
fn pipeline_stage(&mut self) {}
fn pipeline_end(&mut self) -> i32 {
0
}
fn subshell_begin(&mut self) {}
fn subshell_end(&mut self) -> Option<i32> {
None
}
fn trap_set(&mut self, sig: &str, handler: &Chunk) {
let _ = (sig, handler);
}
fn trap_check(&mut self) {}
fn with_redirects_begin(&mut self, count: u8) {
let _ = count;
}
fn with_redirects_end(&mut self) {}
fn call_function(&mut self, name: &str, args: Vec<String>) -> Option<i32> {
let _ = (name, args);
None
}
fn exec(&mut self, args: Vec<String>) -> i32 {
use std::process::{Command, Stdio};
let cmd = match args.first() {
Some(c) => c,
None => return 0,
};
Command::new(cmd)
.args(&args[1..])
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()
.map(|s| s.code().unwrap_or(1))
.unwrap_or(127)
}
fn exec_bg(&mut self, args: Vec<String>) -> i32 {
use std::process::{Command, Stdio};
let cmd = match args.first() {
Some(c) => c,
None => return 0,
};
Command::new(cmd)
.args(&args[1..])
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.map(|c| c.id() as i32)
.unwrap_or(0)
}
fn str_match(&mut self, s: &str, pat: &str) -> bool {
s == pat
}
fn regex_match(&mut self, s: &str, regex: &str) -> bool {
let _ = (s, regex);
false
}
}
pub struct DefaultHost;
impl ShellHost for DefaultHost {}
#[cfg(test)]
mod tests {
use super::*;
use crate::chunk::Chunk;
#[test]
fn tilde_expand_is_identity_by_default() {
let mut h = DefaultHost;
assert_eq!(h.tilde_expand("~/foo"), "~/foo");
assert_eq!(h.tilde_expand(""), "");
}
#[test]
fn brace_expand_returns_single_element_vec_by_default() {
let mut h = DefaultHost;
assert_eq!(h.brace_expand("{a,b}"), vec!["{a,b}".to_string()]);
assert_eq!(h.brace_expand("plain"), vec!["plain".to_string()]);
}
#[test]
fn word_split_splits_on_whitespace() {
let mut h = DefaultHost;
assert_eq!(h.word_split("one two three"), vec!["one", "two", "three"]);
assert!(h.word_split("").is_empty());
assert!(h.word_split(" \t ").is_empty());
}
#[test]
fn expand_param_default_returns_empty_string() {
let mut h = DefaultHost;
let v = h.expand_param("VAR", 0, &[]);
assert_eq!(v, Value::str(""));
}
#[test]
fn array_index_default_returns_undef() {
let mut h = DefaultHost;
assert_eq!(h.array_index("arr", &Value::Int(0)), Value::Undef);
}
#[test]
fn cmd_subst_and_process_sub_default_to_empty_string() {
let mut h = DefaultHost;
let c = Chunk::new();
assert_eq!(h.cmd_subst(&c), "");
assert_eq!(h.process_sub_in(&c), "");
assert_eq!(h.process_sub_out(&c), "");
}
#[test]
fn pipeline_end_default_is_success() {
let mut h = DefaultHost;
h.pipeline_begin(2);
h.pipeline_stage();
assert_eq!(h.pipeline_end(), 0);
}
#[test]
fn call_function_default_returns_none() {
let mut h = DefaultHost;
assert_eq!(h.call_function("fn", vec!["a".into()]), None);
}
#[test]
fn str_match_default_is_exact_equality() {
let mut h = DefaultHost;
assert!(h.str_match("foo", "foo"));
assert!(!h.str_match("foo", "bar"));
assert!(!h.str_match("foo", "f*"), "default does not glob");
}
#[test]
fn regex_match_default_is_false() {
let mut h = DefaultHost;
assert!(!h.regex_match("anything", "."));
}
#[test]
fn noop_methods_do_not_panic() {
let mut h = DefaultHost;
h.redirect(1, 0, "file");
h.heredoc("body");
h.herestring("body");
h.subshell_begin();
h.subshell_end();
h.trap_check();
h.with_redirects_begin(1);
h.with_redirects_end();
h.trap_set("INT", &Chunk::new());
}
#[test]
fn exec_with_empty_args_returns_zero() {
let mut h = DefaultHost;
assert_eq!(h.exec(vec![]), 0);
assert_eq!(h.exec_bg(vec![]), 0);
}
}