use crate::error::ErrorKind;
use crate::error::OcelotError;
use crate::result::OcelotResult;
use std::fmt::Write as _;
use std::process::ExitCode;
pub fn try_main(run: impl FnOnce() -> OcelotResult<()>) -> ExitCode {
match run() {
Ok(()) => ExitCode::SUCCESS,
Err(error) => {
eprint!("{}", format_cli_error("operation failed", &error));
ExitCode::FAILURE
}
}
}
pub fn try_main_with_headline(headline: &str, run: impl FnOnce() -> OcelotResult<()>) -> ExitCode {
match run() {
Ok(()) => ExitCode::SUCCESS,
Err(error) => {
eprint!("{}", format_cli_error(headline, &error));
ExitCode::FAILURE
}
}
}
pub fn format_cli_error(headline: &str, error: &OcelotError) -> String {
if let Some(rendered_diagnostics) = compilation_diagnostics_output(error) {
return rendered_diagnostics.to_string();
}
let mut rendered = String::new();
let _ = writeln!(&mut rendered, "\u{1b}[1;31m━━ {}\u{1b}[0m", headline);
if error.write_to(&mut rendered).is_err() {
let _ = writeln!(
&mut rendered,
"\u{1b}[1;31m× error\u{1b}[0m failed to render detailed error output"
);
}
let mut causes = Vec::new();
let mut current = error.source();
while let Some(cause) = current {
causes.push((cause.kind().to_string(), cause.location()));
current = cause.source();
}
if !causes.is_empty() {
let simple_causes: Vec<_> = causes
.iter()
.filter(|(cause, _)| !cause.contains('\n'))
.collect();
if simple_causes.is_empty() {
return rendered;
}
rendered.push('\n');
rendered.push_str("\u{1b}[1;33m━━ cause chain\u{1b}[0m\n");
for (cause, location) in simple_causes {
let _ = writeln!(&mut rendered, " • {}", cause);
let _ = writeln!(
&mut rendered,
" at {}:{}:{}",
location.file(),
location.line(),
location.column()
);
}
}
rendered
}
fn compilation_diagnostics_output(error: &OcelotError) -> Option<&str> {
match error.kind() {
ErrorKind::CompilationError(_) => error
.source()
.and_then(|source| source.kind().as_message())
.map(|message| message.as_str()),
ErrorKind::Message(_) | ErrorKind::Std(_) => None,
}
}