use std::{
error::Error,
fmt::{Display, Error as FmtError, Formatter},
io,
path::Path,
process::Stdio,
};
use execute::{command_args, Execute};
#[derive(Debug)]
pub enum BCError {
IOError(io::Error),
NoResult,
Timeout,
Error(String),
}
impl From<io::Error> for BCError {
#[inline]
fn from(err: io::Error) -> Self {
BCError::IOError(err)
}
}
impl From<String> for BCError {
#[inline]
fn from(err: String) -> Self {
BCError::Error(err)
}
}
impl Display for BCError {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
match self {
BCError::IOError(err) => Display::fmt(err, f),
BCError::NoResult => f.write_str("There is no result of BC."),
BCError::Timeout => f.write_str("The BC calculation has timed out."),
BCError::Error(text) => f.write_str(text.as_str()),
}
}
}
impl Error for BCError {}
pub fn bc<P: AsRef<Path>, S: AsRef<str>>(bc_path: P, statement: S) -> Result<String, BCError> {
let mut command = command_args!(bc_path.as_ref(), "-l", "-q");
command.stdout(Stdio::piped());
command.stderr(Stdio::piped());
let output = command.execute_input_output(format!("{}\n", statement.as_ref()).as_bytes())?;
if output.stderr.is_empty() {
if output.stdout.is_empty() {
Err(BCError::NoResult)
} else {
Ok(handle_output(unsafe { String::from_utf8_unchecked(output.stdout) }))
}
} else {
Err(BCError::Error(handle_output(unsafe { String::from_utf8_unchecked(output.stderr) })))
}
}
pub fn bc_timeout<PT: AsRef<Path>, P: AsRef<Path>, S: AsRef<str>>(
timeout_path: PT,
timeout_secs: u32,
bc_path: P,
statement: S,
) -> Result<String, BCError> {
let mut command = command_args!(
timeout_path.as_ref(),
format!("{}s", timeout_secs),
bc_path.as_ref(),
"-l",
"-q"
);
command.stdout(Stdio::piped());
command.stderr(Stdio::piped());
let output = command.execute_input_output(format!("{}\n", statement.as_ref()).as_bytes())?;
if let Some(code) = output.status.code() {
if code == 124 {
return Err(BCError::Timeout);
}
}
if output.stderr.is_empty() {
if output.stdout.is_empty() {
Err(BCError::NoResult)
} else {
Ok(handle_output(unsafe { String::from_utf8_unchecked(output.stdout) }))
}
} else {
Err(BCError::Error(handle_output(unsafe { String::from_utf8_unchecked(output.stderr) })))
}
}
fn handle_output(mut output: String) -> String {
let length = output.len();
unsafe {
output.as_mut_vec().set_len(length - 1);
}
match output.find("\\\n") {
Some(index) => {
let mut s = String::from(&output[..index]);
s.push_str(&output[(index + 2)..].replace("\\\n", ""));
s
},
None => output,
}
}
#[macro_export]
macro_rules! bc {
($statement:expr) => {
$crate::bc("bc", $statement)
};
($bc_path:expr, $statement:expr) => {
$crate::bc($bc_path, $statement)
};
}
#[macro_export]
macro_rules! bc_timeout {
($statement:expr) => {
$crate::bc_timeout("timeout", 15, "bc", $statement)
};
($timeout:expr, $statement:expr) => {
$crate::bc_timeout("timeout", $timeout, "bc", $statement)
};
($timeout_path:expr, $timeout:expr, $bc_path:expr, $statement:expr) => {
$crate::bc_timeout($timeout_path, $timeout, $bc_path, $statement)
};
}