use std::io::{Read, Error, ErrorKind};
use std::process::{Command, Stdio, ExitStatus,Child, ChildStdout};
use std::collections::VecDeque;
pub type FunResult = Result<String, std::io::Error>;
pub type CmdResult = Result<(), std::io::Error>;
type PipeResult = Result<(Child, ChildStdout), std::io::Error>;
#[macro_export]
macro_rules! info {
($($arg:tt)*) => {
eprintln!("INFO: {}", format!($($arg)*));
}
}
#[macro_export]
macro_rules! warn {
($($arg:tt)*) => {
eprintln!("WARN: {}", format!($($arg)*));
}
}
#[macro_export]
macro_rules! err {
($($arg:tt)*) => {
eprintln!("ERROR: {}", format!($($arg)*));
}
}
#[macro_export]
macro_rules! die {
($($arg:tt)*) => {
eprintln!("FATAL: {}", format!($($arg)*));
std::process::exit(1);
}
}
#[macro_export]
macro_rules! output {
($($arg:tt)*) => {
Ok(format!($($arg)*)) as FunResult
}
}
#[macro_export]
macro_rules! run_cmd {
($($arg:tt)*) => {
match run_fun!($($arg)*) {
Err(e) => Err(e),
Ok(s) => {
print!("{}", s);
Ok(())
}
}
};
}
#[macro_export]
macro_rules! run_fun {
($x:ident $($other:tt)*) => {{
let mut s = String::from(stringify!($x));
run_fun!(&s; $($other)*)
}};
(&$s:expr; $x:ident $($other:tt)*) => {{
$s += " ";
$s += stringify!($x);
run_fun!(&$s; $($other)*)
}};
(&$s:expr; --$x:ident $($other:tt)*) => {{
$s += " --";
$s += stringify!($x);
run_fun!(&$s; $($other)*)
}};
(&$s:expr; -$x:ident $($other:tt)*) => {{
$s += " -";
$s += stringify!($x);
run_fun!(&$s; $($other)*)
}};
(&$s:expr; $x:tt $($other:tt)*) => {{
$s += " ";
$s += stringify!($x);
run_fun!(&$s; $($other)*)
}};
(&$s:expr;) => {
$crate::run($s)
};
($($arg:tt)*) => {
$crate::run(format!($($arg)*))
};
}
#[doc(hidden)]
pub fn run_pipe(full_command: &str) -> PipeResult {
let pipe_args = parse_pipes(full_command);
let pipe_argv = parse_argv(&pipe_args);
let n = pipe_argv.len();
let mut pipe_procs = VecDeque::with_capacity(n);
let mut pipe_outputs = VecDeque::with_capacity(n);
info!("Running \"{}\" ...", full_command);
for (i, pipe_cmd) in pipe_argv.iter().enumerate() {
let args = parse_args(pipe_cmd);
let argv = parse_argv(&args);
if i == 0 {
pipe_procs.push_back(Command::new(&argv[0])
.args(&argv[1..])
.stdout(Stdio::piped())
.spawn()?);
} else {
pipe_procs.push_back(Command::new(&argv[0])
.args(&argv[1..])
.stdin(pipe_outputs.pop_front().unwrap())
.stdout(Stdio::piped())
.spawn()?);
pipe_procs.pop_front().unwrap().wait()?;
}
pipe_outputs.push_back(pipe_procs.back_mut().unwrap().stdout.take().ok_or_else(|| {
std::io::Error::new(std::io::ErrorKind::BrokenPipe, "Broken pipe")
})?);
}
Ok((pipe_procs.pop_front().unwrap(), pipe_outputs.pop_front().unwrap()))
}
#[doc(hidden)]
pub fn run(full_command: String) -> FunResult {
let (mut proc, mut output) = run_pipe(&full_command)?;
let status = proc.wait()?;
if !status.success() {
Err(to_io_error(&full_command, status))
} else {
let mut s = String::new();
output.read_to_string(&mut s)?;
Ok(s)
}
}
fn to_io_error(command: &str, status: ExitStatus) -> Error {
if let Some(code) = status.code() {
Error::new(ErrorKind::Other, format!("{} exit with {}", command, code))
} else {
Error::new(ErrorKind::Other, "Unknown error")
}
}
fn parse_args(s: &str) -> String {
let mut in_single_quote = false;
let mut in_double_quote = false;
s.chars()
.map(|c| {
if c == '"' && !in_single_quote {
in_double_quote = !in_double_quote;
'\n'
} else if c == '\'' && !in_double_quote {
in_single_quote = !in_single_quote;
'\n'
} else if !in_single_quote && !in_double_quote && char::is_whitespace(c) {
'\n'
} else {
c
}
})
.collect()
}
fn parse_pipes(s: &str) -> String {
let mut in_single_quote = false;
let mut in_double_quote = false;
s.chars()
.map(|c| {
if c == '"' && !in_single_quote {
in_double_quote = !in_double_quote;
} else if c == '\'' && !in_double_quote {
in_single_quote = !in_single_quote;
}
if c == '|' && !in_single_quote && !in_double_quote {
'\n'
} else {
c
}
})
.collect()
}
fn parse_argv(s: &str) -> Vec<&str> {
s.split("\n")
.filter(|s| !s.is_empty())
.collect::<Vec<&str>>()
}