use std::fmt;
use colored::Colorize;
#[derive(Debug)]
pub struct Error(String);
impl Error {
pub fn new(message: impl Into<String>) -> Self {
Error(message.into())
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(&self.0)
}
}
impl std::error::Error for Error {}
impl From<String> for Error {
fn from(s: String) -> Self {
Error(s)
}
}
impl From<&str> for Error {
fn from(s: &str) -> Self {
Error(s.to_owned())
}
}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self {
Error(err.to_string())
}
}
impl From<serde_json::Error> for Error {
fn from(err: serde_json::Error) -> Self {
Error(err.to_string())
}
}
impl From<ureq::Error> for Error {
fn from(err: ureq::Error) -> Self {
Error(err.to_string())
}
}
pub(crate) fn render_range(
f: &mut fmt::Formatter<'_>,
file: &str,
start: usize,
end: usize,
headline: &str,
) -> fmt::Result {
write!(
f,
"{}\n>> {}",
format!("While parsing file \"{}\" at line {}:", file, start).cyan(),
headline.red().bold(),
)?;
write_excerpt(f, file, start, end)
}
pub(crate) fn render_at_line(
f: &mut fmt::Formatter<'_>,
file: &str,
error_line: usize,
headline: &str,
) -> fmt::Result {
let start = tx_start(file, error_line);
write!(
f,
"{}\n>> {}",
format!("While parsing file \"{}\" at line {}:", file, error_line).cyan(),
headline.red().bold(),
)?;
write_excerpt(f, file, start, error_line)
}
fn tx_start(file: &str, error_line: usize) -> usize {
if file.is_empty() || error_line == 0 {
return error_line;
}
let Ok(source) = std::fs::read_to_string(file) else {
return error_line;
};
let lines: Vec<&str> = source.lines().collect();
let upper = error_line.min(lines.len());
for i in (0..upper).rev() {
if lines[i]
.chars()
.next()
.is_some_and(|c| c.is_ascii_digit())
{
return i + 1;
}
}
error_line
}
fn read_block(file: &str, start: usize, end: usize) -> Option<Vec<String>> {
if file.is_empty() {
return None;
}
let source = std::fs::read_to_string(file).ok()?;
let mut out = Vec::with_capacity(end.saturating_sub(start) + 1);
for (idx, line) in source.lines().enumerate() {
let lineno = idx + 1;
if lineno < start {
continue;
}
if lineno > end {
break;
}
out.push(line.to_string());
}
if out.is_empty() {
None
} else {
Some(out)
}
}
fn write_excerpt(
f: &mut fmt::Formatter<'_>,
file: &str,
start: usize,
end: usize,
) -> fmt::Result {
let Some(block) = read_block(file, start, end) else {
return Ok(());
};
let width = end.to_string().len();
write!(f, "\n\n")?;
let mut first = true;
for (i, line) in block.iter().enumerate() {
if !first {
writeln!(f)?;
}
let lineno = start + i;
write!(
f,
"{} {}",
format!("{:>width$} |", lineno, width = width),
line,
)?;
first = false;
}
Ok(())
}