use std::cell::RefCell;
use std::rc::Rc;
use anyhow::{Result, bail};
use super::native::{self, Native};
use super::std_bridge::path_like;
use super::value::{Map, MapKey, RStr, StructData, Value};
pub(super) fn build_command(s: &StructData) -> std::process::Command {
let program = s.get("program").map(|v| path_like(&v)).unwrap_or_default();
let mut cmd = std::process::Command::new(&program);
if let Some(Value::Vec(a)) = s.get("args") {
for item in a.borrow().iter() {
cmd.arg(path_like(item));
}
}
match s.get("cwd") {
Some(Value::Unit) | None => {}
Some(cwd) => {
cmd.current_dir(path_like(&cwd));
}
}
if let Some(Value::Map(envs)) = s.get("envs") {
for (k, v) in envs.borrow().iter() {
let key = path_like(&k.to_value());
if matches!(v, Value::Unit) {
cmd.env_remove(key);
} else {
cmd.env(key, path_like(v));
}
}
}
cmd
}
pub(super) fn run_command(s: &StructData) -> Value {
let mut cmd = build_command(s);
cmd.stdin(stdio_or(s, "stdin", std::process::Stdio::null()));
cmd.stdout(stdio_or(s, "stdout", std::process::Stdio::piped()));
cmd.stderr(stdio_or(s, "stderr", std::process::Stdio::piped()));
match cmd.output() {
Ok(out) => Value::ok(make_output(out)),
Err(e) => Value::err(Value::str(e.to_string())),
}
}
pub(super) fn status_command(s: &StructData) -> Value {
let mut cmd = build_command(s);
cmd.stdin(stdio_for(s, "stdin"));
cmd.stdout(stdio_for(s, "stdout"));
cmd.stderr(stdio_for(s, "stderr"));
match cmd.status() {
Ok(status) => Value::ok(make_exit_status(status)),
Err(e) => Value::err(Value::str(e.to_string())),
}
}
pub(super) fn stdio_for(s: &StructData, key: &str) -> std::process::Stdio {
stdio_or(s, key, std::process::Stdio::inherit())
}
fn stdio_or(s: &StructData, key: &str, default: std::process::Stdio) -> std::process::Stdio {
match s.get(key) {
Some(Value::Struct(m)) if &**m.name() == "Stdio" => {
match m.get("kind").map(|v| v.display()).as_deref() {
Some("piped") => std::process::Stdio::piped(),
Some("null") => std::process::Stdio::null(),
_ => std::process::Stdio::inherit(),
}
}
_ => default,
}
}
pub(super) fn spawn_command(s: &StructData) -> Value {
let mut cmd = build_command(s);
cmd.stdin(stdio_for(s, "stdin"));
cmd.stdout(stdio_for(s, "stdout"));
cmd.stderr(stdio_for(s, "stderr"));
let mut child = match cmd.spawn() {
Ok(c) => c,
Err(e) => return Value::err(Value::str(e.to_string())),
};
let stdin = child
.stdin
.take()
.map(|w| Native::ChildStdin(w).wrap())
.map(Value::some)
.unwrap_or_else(Value::none);
let stdout = child
.stdout
.take()
.map(|r| {
Native::Reader(std::io::BufReader::new(
Box::new(r) as Box<dyn std::io::Read>
))
.wrap()
})
.map(Value::some)
.unwrap_or_else(Value::none);
let stderr = child
.stderr
.take()
.map(|r| {
Native::Reader(std::io::BufReader::new(
Box::new(r) as Box<dyn std::io::Read>
))
.wrap()
})
.map(Value::some)
.unwrap_or_else(Value::none);
Value::ok(Value::struct_of(
"Child",
[
("handle".into(), Native::Child(child).wrap()),
("stdin".into(), stdin),
("stdout".into(), stdout),
("stderr".into(), stderr),
],
))
}
pub(super) fn make_exit_status(status: std::process::ExitStatus) -> Value {
Value::struct_of(
"ExitStatus",
[
(
"code".into(),
Value::Int(status.code().unwrap_or(-1) as i64),
),
("success".into(), Value::Bool(status.success())),
],
)
}
pub(super) fn make_output(out: std::process::Output) -> Value {
Value::struct_of(
"Output",
[
(
"stdout".into(),
Value::str(super::console::decode(&out.stdout)),
),
(
"stderr".into(),
Value::str(super::console::decode(&out.stderr)),
),
("status".into(), make_exit_status(out.status)),
],
)
}
pub(super) fn command_method(s: &Rc<StructData>, name: &str, args: &[Value]) -> Result<Value> {
let cmd_value = || Value::Struct(s.clone());
Ok(match name {
"arg" => {
if let Some(Value::Vec(list)) = s.get("args") {
list.borrow_mut()
.push(args.first().cloned().unwrap_or(Value::Unit));
}
cmd_value()
}
"args" => {
if let (Some(Value::Vec(list)), Some(Value::Vec(extra))) = (s.get("args"), args.first())
{
list.borrow_mut().extend(extra.borrow().iter().cloned());
}
cmd_value()
}
"current_dir" => {
s.set("cwd", args.first().cloned().unwrap_or(Value::Unit));
cmd_value()
}
"env" => {
let key = args.first().map(|v| v.display()).unwrap_or_default();
let val = args.get(1).cloned().unwrap_or(Value::Unit);
let envs = command_envs(s);
envs.borrow_mut().insert(MapKey::Str(RStr::new(key)), val);
cmd_value()
}
"env_remove" => {
let key = args.first().map(|v| v.display()).unwrap_or_default();
let envs = command_envs(s);
envs.borrow_mut()
.insert(MapKey::Str(RStr::new(key)), Value::Unit);
cmd_value()
}
"stdin" | "stdout" | "stderr" => {
s.set(name, args.first().cloned().unwrap_or(Value::Unit));
cmd_value()
}
"spawn" => return Ok(spawn_command(s)),
"output" => run_command(s),
"status" => status_command(s),
_ => bail!("unknown method `{name}` on Command"),
})
}
fn command_envs(s: &StructData) -> Rc<RefCell<Map>> {
match s.get("envs") {
Some(Value::Map(envs)) => envs,
_ => {
let envs = Rc::new(RefCell::new(Map::default()));
s.set("envs", Value::Map(envs.clone()));
envs
}
}
}
pub(super) fn close_child_stdin(v: &Value) {
match v {
Value::Native(rc) => *rc.borrow_mut() = Native::Closed,
Value::Enum {
enum_name,
variant,
data,
} if &**enum_name == "Option" && &**variant == "Some" => {
if let Some(inner) = data.first() {
close_child_stdin(inner);
}
}
_ => {}
}
}
pub(super) fn child_method(s: &StructData, name: &str, args: &mut [Value]) -> Result<Value> {
if matches!(name, "wait" | "wait_with_output") {
if let Some(v) = s.get("stdin") {
close_child_stdin(&v);
}
s.set("stdin", Value::none());
}
if name == "wait_with_output" {
let out = drain_child_pipe(s, "stdout");
let err = drain_child_pipe(s, "stderr");
let status = {
let handle = child_handle(s)?;
let mut h = handle.borrow_mut();
if let Native::Child(c) = &mut *h {
match c.wait() {
Ok(st) => st,
Err(e) => return Ok(Value::err(Value::str(e.to_string()))),
}
} else {
bail!("child handle missing");
}
};
return Ok(Value::ok(Value::struct_of(
"Output",
[
("stdout".into(), Value::str(out)),
("stderr".into(), Value::str(err)),
("status".into(), make_exit_status(status)),
],
)));
}
let handle = child_handle(s)?;
match native::native_method(&handle, name, args)? {
Some(v) => Ok(v),
None => bail!("unknown method `{name}` on Child"),
}
}
pub(super) fn child_handle(s: &StructData) -> Result<Rc<RefCell<Native>>> {
match s.get("handle") {
Some(Value::Native(h)) => Ok(h),
_ => bail!("child handle missing"),
}
}
pub(super) fn drain_child_pipe(s: &StructData, key: &str) -> String {
let handle = match s.get(key) {
Some(Value::Enum { data, .. }) => match data.first() {
Some(Value::Native(h)) => h.clone(),
_ => return String::new(),
},
_ => return String::new(),
};
let mut target = [Value::str("")];
match native::native_method(&handle, "read_to_string", &mut target) {
Ok(_) => {}
Err(_) => return String::new(),
}
if let Value::Str(out) = &target[0] {
out.to_string()
} else {
String::new()
}
}
pub(super) fn exitstatus_method(s: &StructData, name: &str) -> Result<Value> {
let success = matches!(s.get("success"), Some(Value::Bool(true)));
let code = match s.get("code") {
Some(Value::Int(c)) => Some(c),
_ => None,
};
match super::shared::exit_status_core(name, success, code) {
Some(super::shared::ExitOut::Bool(b)) => Ok(Value::Bool(b)),
Some(super::shared::ExitOut::OptInt(Some(c))) => Ok(Value::some(Value::Int(c))),
Some(super::shared::ExitOut::OptInt(None)) => Ok(Value::none()),
None => bail!("unknown method `{name}` on ExitStatus"),
}
}