use std::io::{Read, Error, ErrorKind};
use std::process::{Command, Stdio, ExitStatus,Child, ChildStdout};
use std::collections::VecDeque;
use std::collections::HashMap;
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)*) => {{
use std::process::exit;
eprintln!("FATAL: {}", format!($($arg)*));
exit(1);
}}
}
#[macro_export]
macro_rules! output {
($($arg:tt)*) => {
Ok(format!($($arg)*)) as FunResult
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! macro_str {
($macro:ident) => {{
let macro_name = stringify!($macro);
let mut macro_str = String::new();
let src = String::from(format!("{}/{}",
env!("CARGO_MANIFEST_DIR"),
file!()));
let target_line = line!() as usize;
let file: Vec<char> = std::fs::read_to_string(src)
.expect("error reading file")
.chars()
.collect();
let len = file.len();
let mut i: usize = 0;
let mut line = 1;
let mut level = 0;
while i < len {
if file[i] == '\n' {
line += 1;
}
if line == target_line {
let cmp_str: String = file[i..i+macro_name.len()].iter().collect();
if cmp_str == macro_name {
i += macro_name.len()+1;
while file[i] != '{' && file[i] != '(' {
i += 1;
}
i += 1;
level += 1;
let with_quote = file[i] == '"';
let mut in_single_quote = false;
let mut in_double_quote = false;
if with_quote {
in_double_quote = true;
i += 1;
}
loop {
if !in_single_quote &&
!in_double_quote {
if file[i] == '}' || file[i] == ')' {
level -= 1;
} else if file[i] == '{' || file[i] == '(' {
level += 1;
}
if level == 0 {
break;
}
}
if file[i] == '"' && !in_single_quote {
in_double_quote = !in_double_quote;
} else if file[i] == '\'' && !in_double_quote {
in_single_quote = !in_single_quote;
}
macro_str.push(file[i]);
i += 1;
}
if with_quote {
macro_str.pop();
}
break;
}
}
i += 1;
}
macro_str
}}
}
#[macro_export]
macro_rules! run_fun {
($cmd:ident $($arg:tt)*) => {
$crate::run_fun(&$crate::macro_str!(run_fun))
};
($($arg:tt)*) => {
$crate::run_fun(&format!($($arg)*))
};
}
#[macro_export]
macro_rules! run_cmd {
(use $($arg:tt)*) => {{
let mut sym_table = ::std::collections::HashMap::new();
run_cmd!(&sym_table; $($arg)*)
}};
(&$st:expr; $var:ident, $($arg:tt)*) => {{
$st.insert(stringify!($var).into(), format!("{}", $var));
run_cmd!(&$st; $($arg)*)
}};
(&$st:expr; $var:ident; $($arg:tt)*) => {{
$st.insert(stringify!($var).into(), format!("{}", $var));
let src = $crate::macro_str!(run_cmd);
$crate::run_cmd(&$crate::resolve_name(&src, &$st, &file!(), line!()))
}};
($cmd:ident $($arg:tt)*) => {{
$crate::run_cmd(&$crate::macro_str!(run_cmd))
}};
($($arg:tt)*) => {{
$crate::run_cmd(&format!($($arg)*))
}};
}
#[doc(hidden)]
pub fn run_pipe(full_command: &str) -> PipeResult {
let pipe_args = parse_pipes(full_command.trim());
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.trim());
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()))
}
fn run(full_cmd: &str) -> FunResult {
let (mut proc, mut output) = run_pipe(full_cmd)?;
let status = proc.wait()?;
if !status.success() {
Err(to_io_error(full_cmd, status))
} else {
let mut s = String::new();
output.read_to_string(&mut s)?;
Ok(s)
}
}
#[doc(hidden)]
pub fn run_fun(full_cmd: &str) -> FunResult {
run(full_cmd)
}
#[doc(hidden)]
pub fn run_cmd(cmds: &str) -> CmdResult {
let cmd_args = parse_cmds(cmds);
let cmd_argv = parse_argv(&cmd_args);
for cmd in cmd_argv {
match run(cmd) {
Err(e) => return Err(e),
Ok(s) => print!("{}", s),
}
}
Ok(())
}
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_cmds(s: &str) -> String {
parse_seps(s, ';')
}
fn parse_pipes(s: &str) -> String {
parse_seps(s, '|')
}
fn parse_seps(s: &str, sep: char) -> 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 == sep && !in_single_quote && !in_double_quote {
'\n'
} else {
c
}
})
.collect()
}
fn parse_argv(s: &str) -> Vec<&str> {
s.split("\n")
.filter(|s| !s.trim().is_empty())
.collect::<Vec<&str>>()
}
#[doc(hidden)]
pub fn resolve_name(src: &str, st: &HashMap<String,String>, file: &str, line: u32) -> String {
let mut output = String::new();
let input: Vec<char> = src.chars().collect();
let len = input.len();
let mut in_single_quote = false;
let mut in_double_quote = false;
let mut i = 0;
while i < len {
if i == 0 { while input[i] == ' ' || input[i] == '\t' || input[i] == '\n' {
i += 1;
}
let first = input[i..i+4].iter().collect::<String>();
if i < len-4 && first == "use " || first == "use\t" {
while input[i] != ';' {
i += 1;
}
}
}
if input[i] == '"' && !in_single_quote {
in_double_quote = !in_double_quote;
} else if input[i] == '\'' && !in_double_quote {
in_single_quote = !in_single_quote;
}
if !in_single_quote && i < len-2 &&
input[i] == '$' && input[i+1] == '{' {
i += 2;
let mut var = String::new();
while input[i] != '}' {
var.push(input[i]);
if input[i] == ';' || input[i] == '\n' || i == len-1 {
die!("invalid name {}, {}:{}\n{}", var, file, line, src);
}
i += 1;
}
match st.get(&var) {
None => {
die!("resolve {} failed, {}:{}\n{}", var, file, line, src);
},
Some(v) => {
if in_double_quote {
output += v;
} else {
output += "\"";
output += v;
output += "\"";
}
}
}
} else {
output.push(input[i]);
}
i += 1;
}
output
}