1use crate::error::ErrorKind;
2use crate::error::OcelotError;
3use crate::result::OcelotResult;
4use std::fmt::Write as _;
5use std::process::ExitCode;
6
7pub fn try_main(run: impl FnOnce() -> OcelotResult<()>) -> ExitCode {
12 match run() {
13 Ok(()) => ExitCode::SUCCESS,
14 Err(error) => {
15 eprint!("{}", format_cli_error("operation failed", &error));
16 ExitCode::FAILURE
17 }
18 }
19}
20
21pub fn try_main_with_headline(headline: &str, run: impl FnOnce() -> OcelotResult<()>) -> ExitCode {
26 match run() {
27 Ok(()) => ExitCode::SUCCESS,
28 Err(error) => {
29 eprint!("{}", format_cli_error(headline, &error));
30 ExitCode::FAILURE
31 }
32 }
33}
34
35pub fn format_cli_error(headline: &str, error: &OcelotError) -> String {
40 if let Some(rendered_diagnostics) = compilation_diagnostics_output(error) {
41 return rendered_diagnostics.to_string();
42 }
43
44 let mut rendered = String::new();
45 let _ = writeln!(&mut rendered, "\u{1b}[1;31m━━ {}\u{1b}[0m", headline);
46 if error.write_to(&mut rendered).is_err() {
47 let _ = writeln!(
48 &mut rendered,
49 "\u{1b}[1;31m× error\u{1b}[0m failed to render detailed error output"
50 );
51 }
52
53 let mut causes = Vec::new();
54 let mut current = error.source();
55 while let Some(cause) = current {
56 causes.push((cause.kind().to_string(), cause.location()));
57 current = cause.source();
58 }
59
60 if !causes.is_empty() {
61 let simple_causes: Vec<_> = causes
62 .iter()
63 .filter(|(cause, _)| !cause.contains('\n'))
64 .collect();
65 if simple_causes.is_empty() {
66 return rendered;
67 }
68
69 rendered.push('\n');
70 rendered.push_str("\u{1b}[1;33m━━ cause chain\u{1b}[0m\n");
71 for (cause, location) in simple_causes {
72 let _ = writeln!(&mut rendered, " • {}", cause);
73 let _ = writeln!(
74 &mut rendered,
75 " at {}:{}:{}",
76 location.file(),
77 location.line(),
78 location.column()
79 );
80 }
81 }
82
83 rendered
84}
85
86fn compilation_diagnostics_output(error: &OcelotError) -> Option<&str> {
87 match error.kind() {
88 ErrorKind::CompilationError(_) => error
89 .source()
90 .and_then(|source| source.kind().as_message())
91 .map(|message| message.as_str()),
92 ErrorKind::Message(_) | ErrorKind::Std(_) => None,
93 }
94}