pub struct Task { pub name: &'static str, pub exec: fn(&[String]) -> Result, } impl PartialEq for &Task { fn eq(&self, other: &Self) -> bool { self.name == other.name } } pub const TASKS: &[Task] = &[ Task { name: "nop", exec: no_op, }, Task { name: "exec", exec: exec, }, Task { name: "network", exec: network, }, ]; fn no_op(_: &[String]) -> Result { Ok("".to_string()) } fn exec(cmds: &[String]) -> Result { use std::process::Command; // TODO: fmt and add exit code, and add support for both stdout and stderr, and support for no output match Command::new(cmds[0].as_str()).args(&cmds[1..]).output() { Ok(o) => { if o.status.success() { Ok(String::from_utf8_lossy(&o.stdout).to_string()) } else { Err(String::from_utf8_lossy(&o.stderr).to_string()) } } Err(e) => Err(e.to_string()), } } fn network(switch: &[String]) -> Result { use std::process::{Command, Stdio}; let switch: bool = match switch[0].parse() { Ok(s) => s, Err(e) => return Err(e.to_string()), }; // TODO: windows and linux // todo let .. = Command::new('networksetup') #[cfg(target_os = "macos")] { use std::io::Read; match Command::new("networksetup") .arg("-listallhardwareports") .stdout(Stdio::piped()) .spawn() { Ok(devs) => { match Command::new("awk") .arg(r#"/Wi-Fi|AirPort/{getline; print $2}"#) .stdin(Stdio::from(match devs.stdout { Some(s) => s, None => match devs.stderr { Some(mut e) => { let mut reason = String::new(); match e.read_to_string(&mut reason) { Ok(_) => {} Err(e) => return Err(format!("networksetup failed to list hardware: {e}")) } return Err(format!( "networksetup failed to list hardware: {reason}" )); } None => Err("networksetup failed to list hardware".to_string())?, }, })) .stdout(Stdio::piped()) .spawn() { Ok(dev) => match Command::new("networksetup").arg("-setairportpower").arg( match dev.stdout { Some(mut o) => { let mut output = String::new(); match o.read_to_string(&mut output) { Ok(_) => (), Err(e) => return Err(format!("awk failed to get device from networksetup list hardware: {e}")) }; output } None => match dev.stderr { Some(mut e) => { let mut reason = String::new(); match e.read_to_string(&mut reason) { Ok(_) => {} Err(e) => return Err(format!("awk failed to get device from networksetup list hardware: {e}")) } return Err(format!("awk failed to get device from networksetup list hardware: {reason}")); } None => Err("awk failed to get device from networksetup list hardware".to_string())?, }, }, ).arg(if switch { "on" } else { "off" }).output() { Ok(o) => { if o.status.success() { Ok(String::from_utf8_lossy(&o.stdout) .to_string()) } else { Err(String::from_utf8_lossy(&o.stderr) .to_string()) } } Err(e) => Err(e.to_string()), }, Err(e) => Err(e.to_string()), } } Err(e) => Err(e.to_string()), } } #[cfg(not(target_os = "macos"))] Err("not implemented".to_string()) } #[derive(Debug)] pub enum IntoTaskErr { NotFound(String), } pub trait IntoTask { fn into_task<'a>(self) -> Result<&'a Task, IntoTaskErr>; } impl IntoTask for String { fn into_task<'a>(self) -> Result<&'a Task, IntoTaskErr> { for task in TASKS { if task.name == self { return Ok(task); } } Err(IntoTaskErr::NotFound(self)) } } impl IntoTask for &str { fn into_task<'a>(self) -> Result<&'a Task, IntoTaskErr> { for task in TASKS { if task.name == self { return Ok(task); } } Err(IntoTaskErr::NotFound(self.to_string())) } } impl IntoTask for &Task { fn into_task<'a>(self) -> Result<&'a Task, IntoTaskErr> { for task in TASKS { if task == self { return Ok(task); } } Err(IntoTaskErr::NotFound(self.name.to_string())) } }