use std::ops::ControlFlow::{Break, Continue};
use yash_env::Env;
#[cfg(doc)]
use yash_env::SharedSystem;
use yash_env::semantics::{Divert, ExitStatus};
use yash_env::source::Location;
use yash_env::source::pretty::{
Report, ReportType, Snippet, Span, SpanRole, add_span, snippet_for_code,
};
#[cfg(doc)]
use yash_env::stack::Stack;
use yash_env::system::{Fcntl, Isatty, Write};
#[must_use = "returned message should be printed"]
pub fn prepare_report_message_and_divert<'e, 'r, S>(
env: &'e Env<S>,
mut report: Report<'r>,
) -> (String, yash_env::semantics::Result)
where
'e: 'r,
S: Isatty,
{
let is_special_builtin;
if let Some(builtin) = env.stack.current_builtin() {
let span = Span {
range: builtin.name.origin.byte_range(),
role: SpanRole::Supplementary {
label: format!("while executing the {} built-in", builtin.name.value).into(),
},
};
add_span(&builtin.name.origin.code, span, &mut report.snippets);
let source = &builtin.name.origin.code.source;
source.extend_with_context(&mut report.snippets);
is_special_builtin = builtin.is_special;
} else {
is_special_builtin = false;
}
let text = yash_env::io::report_to_string(env, &report);
let divert = if is_special_builtin {
Break(Divert::Interrupt(None))
} else {
Continue(())
};
(text, divert)
}
#[inline]
pub async fn report<'a, S, R>(
env: &mut Env<S>,
report: R,
exit_status: ExitStatus,
) -> yash_env::builtin::Result
where
S: Isatty + Fcntl + Write,
R: Into<Report<'a>> + 'a,
{
async fn inner<S: Isatty + Fcntl + Write>(
env: &mut Env<S>,
report: Report<'_>,
exit_status: ExitStatus,
) -> yash_env::builtin::Result {
let (message, divert) = prepare_report_message_and_divert(env, report);
env.system.print_error(&message).await;
yash_env::builtin::Result::with_exit_status_and_divert(exit_status, divert)
}
inner(env, report.into(), exit_status).await
}
#[inline]
pub async fn report_failure<'a, S, R>(env: &mut Env<S>, report: R) -> yash_env::builtin::Result
where
S: Isatty + Fcntl + Write,
R: Into<Report<'a>> + 'a,
{
self::report(env, report, ExitStatus::FAILURE).await
}
#[inline]
pub async fn report_error<'a, S, R>(env: &mut Env<S>, report: R) -> yash_env::builtin::Result
where
S: Isatty + Fcntl + Write,
R: Into<Report<'a>> + 'a,
{
self::report(env, report, ExitStatus::ERROR).await
}
pub async fn report_simple<S>(
env: &mut Env<S>,
title: &str,
exit_status: ExitStatus,
) -> yash_env::builtin::Result
where
S: Isatty + Fcntl + Write,
{
let mut report = Report::new();
report.r#type = ReportType::Error;
report.title = title.into();
self::report(env, report, exit_status).await
}
pub async fn report_simple_failure<S>(env: &mut Env<S>, title: &str) -> yash_env::builtin::Result
where
S: Isatty + Fcntl + Write,
{
report_simple(env, title, ExitStatus::FAILURE).await
}
pub async fn report_simple_error<S>(env: &mut Env<S>, title: &str) -> yash_env::builtin::Result
where
S: Isatty + Fcntl + Write,
{
report_simple(env, title, ExitStatus::ERROR).await
}
pub async fn syntax_error<S>(
env: &mut Env<S>,
label: &str,
location: &Location,
) -> yash_env::builtin::Result
where
S: Isatty + Fcntl + Write,
{
let mut report = Report::new();
report.r#type = ReportType::Error;
report.title = "command argument syntax error".into();
report.snippets = Snippet::with_primary_span(location, label.into());
report_error(env, report).await
}
#[must_use]
pub fn merge_reports<'a, I, R>(reports: I) -> Option<Report<'a>>
where
I: IntoIterator<Item = R> + 'a,
R: Into<Report<'a>> + 'a,
{
let mut reports = reports.into_iter();
let mut first = reports.next()?.into();
for report in reports {
let report = report.into();
for from_snippet in report.snippets {
let to_snippet = snippet_for_code(&mut first.snippets, from_snippet.code);
to_snippet.spans.extend(from_snippet.spans);
}
first.footnotes.extend(report.footnotes);
}
Some(first)
}
#[cfg(test)]
mod tests {
use super::*;
use yash_env::semantics::Field;
use yash_env::stack::Builtin;
use yash_env::stack::Frame;
#[test]
fn divert_without_builtin() {
let env = Env::new_virtual();
let (_message, divert) = prepare_report_message_and_divert(&env, Report::new());
assert_eq!(divert, Continue(()));
}
#[test]
fn divert_with_special_builtin() {
let mut env = Env::new_virtual();
let env = env.push_frame(Frame::Builtin(Builtin {
name: Field::dummy("builtin"),
is_special: true,
}));
let (_message, divert) = prepare_report_message_and_divert(&env, Report::new());
assert_eq!(divert, Break(Divert::Interrupt(None)));
}
#[test]
fn divert_with_non_special_builtin() {
let mut env = Env::new_virtual();
let env = env.push_frame(Frame::Builtin(Builtin {
name: Field::dummy("builtin"),
is_special: false,
}));
let (_message, divert) = prepare_report_message_and_divert(&env, Report::new());
assert_eq!(divert, Continue(()));
}
#[test]
fn merge_reports_with_common_code() {
let location1 = Location::dummy("code 1");
let location2 = Location {
range: 1..5,
..location1.clone()
};
let location3 = Location::dummy("code 2");
let mut report1 = Report::new();
report1.r#type = ReportType::Error;
report1.title = "foo".into();
report1.snippets = Snippet::with_primary_span(&location1, "label 1".into());
let mut report2 = Report::new();
report2.r#type = ReportType::Warning;
report2.title = "bar".into();
report2.snippets = Snippet::with_primary_span(&location2, "label 2".into());
let span = Span {
range: 2..4,
role: SpanRole::Primary {
label: "label 3".into(),
},
};
let snippet = Snippet::with_code_and_spans(&location3.code, vec![span]);
report2.snippets.push(snippet);
let merged = merge_reports([report1, report2]).unwrap();
assert_eq!(merged.r#type, ReportType::Error);
assert_eq!(merged.title, "foo");
assert_eq!(merged.snippets.len(), 2, "{:?}", merged.snippets);
assert_eq!(merged.snippets[0].code_string(), "code 1");
assert_eq!(
merged.snippets[0].spans.len(),
2,
"{:?}",
merged.snippets[0].spans
);
assert_eq!(merged.snippets[0].spans[0].range, 0..6);
assert_eq!(
merged.snippets[0].spans[0].role,
SpanRole::Primary {
label: "label 1".into()
}
);
assert_eq!(merged.snippets[0].spans[1].range, 1..5);
assert_eq!(
merged.snippets[0].spans[1].role,
SpanRole::Primary {
label: "label 2".into()
}
);
assert_eq!(merged.snippets[1].code_string(), "code 2");
assert_eq!(
merged.snippets[1].spans.len(),
1,
"{:?}",
merged.snippets[1].spans
);
assert_eq!(merged.snippets[1].spans[0].range, 2..4);
assert_eq!(
merged.snippets[1].spans[0].role,
SpanRole::Primary {
label: "label 3".into()
}
);
}
}