libmandoc_rs/
diagnostics.rs1#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
5#[derive(Clone, Copy, Debug, Eq, PartialEq)]
6pub enum DiagnosticLevel {
7 Unsupported,
8 Error,
9 Warning,
10 Style,
11}
12
13#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
15#[derive(Clone, Copy, Debug, Eq, PartialEq)]
16pub struct SourceLocation {
17 pub line: u32,
18 pub column: u32,
19}
20
21#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
23#[derive(Clone, Debug, Eq, PartialEq)]
24pub struct Diagnostic {
25 pub level: DiagnosticLevel,
26 pub message: String,
27 pub location: Option<SourceLocation>,
28}
29
30pub(crate) fn parse_diagnostics(output: &str) -> Vec<Diagnostic> {
31 output.lines().filter_map(parse_diagnostic).collect()
32}
33
34fn parse_diagnostic(line: &str) -> Option<Diagnostic> {
35 let line = line.trim();
36 if line.is_empty() {
37 return None;
38 }
39 let (level, marker) = [
40 (DiagnosticLevel::Unsupported, ": UNSUPP: "),
41 (DiagnosticLevel::Error, ": ERROR: "),
42 (DiagnosticLevel::Error, ": BADARG: "),
43 (DiagnosticLevel::Error, ": SYSERR: "),
44 (DiagnosticLevel::Warning, ": WARNING: "),
45 (DiagnosticLevel::Style, ": STYLE: "),
46 ]
47 .into_iter()
48 .find(|(_, marker)| line.contains(marker))
49 .unwrap_or((DiagnosticLevel::Warning, ": "));
50 let (prefix, message) = line.split_once(marker).unwrap_or(("", line));
51 Some(Diagnostic {
52 level,
53 message: message.to_owned(),
54 location: source_location(prefix),
55 })
56}
57
58fn source_location(prefix: &str) -> Option<SourceLocation> {
59 let mut fields = prefix.rsplitn(3, ':');
60 let column = fields.next()?.trim().parse().ok()?;
61 let line = fields.next()?.trim().parse().ok()?;
62 Some(SourceLocation { line, column })
63}
64
65#[cfg(test)]
66mod tests {
67 use super::{DiagnosticLevel, SourceLocation, parse_diagnostics};
68
69 #[test]
70 fn preserves_each_finding_and_classifies_known_levels() {
71 let diagnostics = parse_diagnostics(
72 "mant: page.1:8:2: UNSUPP: unsupported roff request: ab\n\
73 mant: page.1:9:1: WARNING: skipping paragraph macro\n",
74 );
75
76 assert_eq!(diagnostics.len(), 2);
77 assert_eq!(diagnostics[0].level, DiagnosticLevel::Unsupported);
78 assert_eq!(diagnostics[0].message, "unsupported roff request: ab");
79 assert_eq!(
80 diagnostics[0].location,
81 Some(SourceLocation { line: 8, column: 2 })
82 );
83 assert_eq!(diagnostics[1].level, DiagnosticLevel::Warning);
84 }
85}