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