use std::io::{Error, ErrorKind};
use std::cmp::{min};
use std::path::{PathBuf};
use std::fmt::{Display};
use std::collections::{HashMap, HashSet};
use colored::*;
use armake::config::*;
use armake::preprocess::*;
const WARNING_MAXIMUM: u32 = 10;
static mut WARNINGS_RAISED: Option<HashMap<String, u32>> = None;
pub static mut WARNINGS_MUTED: Option<HashSet<String>> = None;
pub trait ErrorExt<T> {
fn prepend_error<M: AsRef<[u8]> + Display>(self, msg: M) -> Result<T, Error>;
fn print_error(self, exit: bool) -> ();
}
impl<T> ErrorExt<T> for Result<T, Error> {
fn prepend_error<M: AsRef<[u8]> + Display>(self, msg: M) -> Result<T, Error> {
match self {
Ok(t) => Ok(t),
Err(e) => Err(Error::new(ErrorKind::Other, format!("{}\n{}", msg, e)))
}
}
fn print_error(self, exit: bool) -> () {
if let Err(error) = self {
eprintln!("{}: {}", "error".red().bold(), error);
if exit {
print_warning_summary();
std::process::exit(1);
}
}
}
}
pub trait PreprocessParseErrorExt<T> {
fn format_error(self, origin: &Option<PathBuf>, input: &String) -> Result<T, Error>;
}
impl<T> PreprocessParseErrorExt<T> for Result<T, preprocess_grammar::ParseError> {
fn format_error(self, origin: &Option<PathBuf>, input: &String) -> Result<T, Error> {
match self {
Ok(t) => Ok(t),
Err(pe) => {
let line_origin = pe.line - 1;
let file_origin = match origin {
Some(ref path) => format!("{}:", path.to_str().unwrap().to_string()),
None => "".to_string()
};
let line = input.split("\n").nth(pe.line - 1).unwrap_or("");
Err(format_parse_error(line, file_origin, line_origin, pe.column, pe.expected))
}
}
}
}
pub trait ConfigParseErrorExt<T> {
fn format_error(self, info: &PreprocessInfo, input: &String) -> Result<T, Error>;
}
impl<T> ConfigParseErrorExt<T> for Result<T, config_grammar::ParseError> {
fn format_error(self, info: &PreprocessInfo, input: &String) -> Result<T, Error> {
match self {
Ok(t) => Ok(t),
Err(pe) => {
let line_origin = info.line_origins[min(pe.line - 1, info.line_origins.len()) - 1].0 as usize + 1;
let file_origin = match info.line_origins[min(pe.line - 1, info.line_origins.len()) - 1].1 {
Some(ref path) => format!("{}:", path.to_str().unwrap().to_string()),
None => "".to_string()
};
let line = input.split("\n").nth(pe.line - 1).unwrap_or("");
Err(format_parse_error(line, file_origin, line_origin, pe.column, pe.expected))
}
}
}
}
fn format_parse_error(line: &str, file: String, line_number: usize, column_number: usize, expected: HashSet<&'static str>) -> Error {
let trimmed = line.trim_start();
let expected_list: Vec<String> = expected.iter().cloned().map(|x| format!("{:?}", x)).collect();
Error::new(ErrorKind::Other, format!("In line {}{}:\n\n {}\n {}{}\n\nUnexpected token \"{}\", expected: {}",
file,
line_number,
trimmed,
" ".to_string().repeat(column_number - 1 - (line.len() - trimmed.len())),
"^".red().bold(),
line.chars().map(|x| x.to_string()).nth(column_number - 1).unwrap_or("\\n".to_string()),
expected_list.join(", ")))
}
pub fn warning<M: AsRef<[u8]> + Display>(msg: M, name: Option<&'static str>, location: (Option<M>,Option<u32>)) -> () {
unsafe {
if WARNINGS_MUTED.is_none() {
return;
}
if WARNINGS_RAISED.is_none() {
WARNINGS_RAISED = Some(HashMap::new());
}
if let Some(name) = name {
let raised = WARNINGS_RAISED.as_ref().unwrap().get(name).unwrap_or(&0);
WARNINGS_RAISED.as_mut().unwrap().insert(name.to_string(), raised + 1);
if raised >= &WARNING_MAXIMUM {
return;
}
if WARNINGS_MUTED.as_ref().unwrap().contains(name) {
return;
}
}
}
let loc_str = if location.0.is_some() && location.1.is_some() {
format!("In file {}:{}: ", location.0.unwrap(), location.1.unwrap())
} else if location.0.is_some() {
format!("In file {}: ", location.0.unwrap())
} else if location.1.is_some() {
format!("In line {}: ", location.1.unwrap())
} else {
"".to_string()
};
let name_str = match name {
Some(name) => format!(" [{}]", name),
None => "".to_string()
};
eprintln!("{}{}: {}{}", loc_str, "warning".yellow().bold(), msg, name_str);
}
pub fn warning_suppressed(name: Option<&'static str>) -> bool {
if name.is_none() {
return false;
}
unsafe {
if WARNINGS_MUTED.is_none() {
return true;
}
if WARNINGS_MUTED.as_ref().unwrap().contains(name.unwrap()) {
return true;
}
if WARNINGS_RAISED.is_some() {
let raised = WARNINGS_RAISED.as_ref().unwrap().get(name.unwrap()).unwrap_or(&0);
raised >= &WARNING_MAXIMUM
} else {
false
}
}
}
pub fn print_warning_summary() -> () {
unsafe {
if WARNINGS_RAISED.is_none() || WARNINGS_MUTED.is_none() {
return;
}
for (name, raised) in WARNINGS_RAISED.as_ref().unwrap().iter() {
if WARNINGS_MUTED.as_ref().unwrap().contains(name) { continue; }
let excess = (*raised as i32) - (WARNING_MAXIMUM as i32);
if excess <= 0 { continue; }
if excess > 1 {
warning(format!("{} warnings of type \"{}\" were suppressed to prevent spam. Use \"-w {}\" to disable these warnings entirely.",
excess, name, name), None, (None, None));
} else {
warning(format!("{} warning of type \"{}\" was suppressed to prevent spam. Use \"-w {}\" to disable these warnings entirely.",
excess, name, name), None, (None, None));
}
}
}
}