use log::{debug, info};
use std::fmt;
use std::io::Write;
use std::panic;
use std::panic::PanicInfo;
use yaml_rust::{Yaml, YamlLoader};
type StrList = [&'static [&'static str]];
type Panicfn = Box<dyn Fn(&PanicInfo) + Sync + Send>;
#[derive(Debug, Clone)]
pub struct UserPanic {
pub error_msg: &'static str,
pub fix_instructions: Option<&'static StrList>,
}
impl fmt::Display for UserPanic {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.error_msg == "" {
return write!(f, "");
}
let mut s = String::from("The Program Crashed\n\n");
if self.fix_instructions.is_none() {
s += &format!("Error: {}", self.error_msg);
s += "\nIt seems like an error that can't be fixed by you!\nPlease submit a Bug report to Developer\n";
} else {
s += &format!("Error: {}", self.error_msg);
s += "\nIt seems like an error that can be fixed by you!\nPlease follow the following instructions to try and fix the Error\n";
let insts = self.fix_instructions.as_ref().unwrap();
let mut i = 1;
for inst in *insts {
s += &format!("\n\t{}: {}\n", i, inst[0]);
let inst = &inst[1..];
if inst.len() > 1 {
let mut j = 1;
for ii in inst {
s += &format!("\t\t{}. {}\n", j, ii);
j += 1;
}
}
i += 1;
}
}
write!(f, "{}", s)
}
}
pub fn set_hooks(developer: Option<&'static str>) {
let org: Panicfn = panic::take_hook();
if let Some(dev) = developer {
panic::set_hook(Box::new(move |pan_inf| {
panic_func(pan_inf, &org);
eprintln!("{}", dev);
}))
} else {
panic::set_hook(Box::new(move |pan_inf| {
panic_func(pan_inf, &org);
}));
}
}
fn panic_func(panic_info: &PanicInfo, original: &Panicfn) {
match panic_info.payload().downcast_ref::<UserPanic>() {
Some(err) => {
if err.error_msg != "" {
eprintln!("{}", err);
}
}
None => original(panic_info),
}
}
fn read_from_yml(yaml: String) -> String {
debug!("Started Reading the yaml string");
let mut file = "use user_panic::UserPanic;\n".to_string();
let yaml = YamlLoader::load_from_str(&yaml).unwrap();
let structs = &yaml[0];
if let Yaml::Hash(hash) = structs {
info!("Found Hash");
for (key, val) in hash {
let st_name = key.as_str().unwrap();
debug!("parsing key {}", st_name);
file += &format!(
"pub const {}:UserPanic = UserPanic {{{}}};",
st_name,
get_err_msg(val)
);
}
}
file
}
fn get_err_msg(hash: &Yaml) -> String {
let print_arr = |arr: &Vec<Yaml>| -> String {
let mut s = String::new();
let _ = arr
.iter()
.map(|a| {
s += &format!(",\"{}\"", a.as_str().unwrap());
})
.collect::<Vec<_>>();
s
};
let mut s = String::new();
debug!("found hash {:#?}", hash);
let err_ms = hash["message"].as_str().unwrap();
debug!("Collecting err message: {}", err_ms);
debug!("{:?}", &hash["fix instructions"]);
if let Yaml::Array(arr) = &hash["fix instructions"] {
debug!("Found fix instructions");
s += &format!("error_msg:\"{}\",fix_instructions:Some(&[", err_ms);
let items = arr.len();
debug!("Number of instuctions {}", items);
let mut i = 0;
while i < items {
if i + 1 < items {
match &arr[i + 1] {
Yaml::String(_) => {
s += &format!("&[\"{}\"],", arr[i].as_str().unwrap());
i += 1;
}
Yaml::Array(ar) => {
s += &format!("&[\"{}\"{}],", arr[i].as_str().unwrap(), print_arr(ar));
i += 2;
}
_ => {}
}
} else {
match &arr[i] {
Yaml::String(ss) => {
s += &format!("&[\"{}\"],", ss);
i += 1;
}
Yaml::Array(ar) => {
s += &format!("&[\"{}\"{}],", arr[i].as_str().unwrap(), print_arr(ar));
i += 2;
}
_ => {}
}
}
}
s += "]),";
} else {
s += &format!("error_msg:\"{}\",fix_instructions: None,", err_ms);
}
s
}
#[macro_export]
macro_rules! panic_setup {
($file_path:expr) => {
user_panic::panic_setup_function($file_path, "src/panic_structs.rs");
};
($file_path:expr,$file_out:expr) => {
user_panic::panic_setup_function($file_path, $file_out);
};
}
pub fn panic_setup_function(path_from: &str, path_to: &str) {
let file_str = std::fs::read_to_string(path_from).expect("Failed to read yaml file");
let s = read_from_yml(file_str);
let mut fp = std::fs::File::create(path_to).expect("failed to create output file");
write!(&mut fp, "{}", s).expect("failed to write to file");
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic]
fn it_works() {
const ERROR: UserPanic = UserPanic {
error_msg: "This is an error",
fix_instructions: Some(&[
&["Only one"],
&["one", "two", "tem"],
&["bem", "lem", "jem"],
]),
};
set_hooks(None);
std::panic::panic_any(ERROR);
}
#[test]
fn print_s() {
let s = "
foo:
message: this is the main error
fix instructions:
- first
- - in first
- in first second
- second
- - second first
- second second
- third
bar:
message: This is un fixable error
";
let s = read_from_yml(s.to_string());
assert_eq!("use user_panic::UserPanic;\npub const foo:UserPanic = UserPanic {error_msg:\"this is the main error\",fix_instructions:Some(&[&[\"first\",\"in first\",\"in first second\"],&[\"second\",\"second first\",\"second second\"],&[\"third\"],]),};pub const bar:UserPanic = UserPanic {error_msg:\"This is un fixable error\",fix_instructions: None,};", s);
}
#[test]
fn output_string_fixable() {
const ERR: UserPanic = UserPanic {
error_msg: "Error msg",
fix_instructions: Some(&[&["One"], &["two", "two-one", "two-two"], &["Three"]]),
};
let s = format!("{}", ERR);
let manual = "The Program Crashed\n\nError: Error msg\nIt seems like an error that can be fixed by you!\nPlease follow the following instructions to try and fix the Error\n\n\t1: One\n\n\t2: two\n\t\t1. two-one\n\t\t2. two-two\n\n\t3: Three\n";
assert_eq!(s, manual);
}
#[test]
fn output_string_unfixable() {
const ERR: UserPanic = UserPanic {
error_msg: "Unfixable Error",
fix_instructions: None,
};
let s = format!("{}", ERR);
let manual = "The Program Crashed\n\nError: Unfixable Error\nIt seems like an error that can't be fixed by you!\nPlease submit a Bug report to Developer\n";
assert_eq!(s, manual);
}
}