sl_chat_log_parser/
utils.rs1#[cfg(test)]
4use ariadne::{Color, Fmt as _, Label, Report, ReportKind, Source};
5use chumsky::IterParser as _;
6use chumsky::Parser;
7use chumsky::prelude::{just, one_of};
8
9#[must_use]
15pub fn offset_datetime_parser<'src>() -> impl Parser<
16 'src,
17 &'src str,
18 time::OffsetDateTime,
19 chumsky::extra::Err<chumsky::error::Rich<'src, char>>,
20> {
21 one_of("0123456789")
22 .repeated()
23 .exactly(4)
24 .collect::<String>()
25 .then_ignore(just('-'))
26 .then(
27 one_of("0123456789")
28 .repeated()
29 .exactly(2)
30 .collect::<String>(),
31 )
32 .then_ignore(just('-'))
33 .then(
34 one_of("0123456789")
35 .repeated()
36 .exactly(2)
37 .collect::<String>(),
38 )
39 .then_ignore(just('T'))
40 .then(
41 one_of("0123456789")
42 .repeated()
43 .exactly(2)
44 .collect::<String>(),
45 )
46 .then_ignore(just(':'))
47 .then(
48 one_of("0123456789")
49 .repeated()
50 .exactly(2)
51 .collect::<String>(),
52 )
53 .then_ignore(just(':'))
54 .then(
55 one_of("0123456789")
56 .repeated()
57 .exactly(2)
58 .collect::<String>(),
59 )
60 .then_ignore(just('.'))
61 .then(
62 one_of("0123456789")
63 .repeated()
64 .exactly(6)
65 .collect::<String>(),
66 )
67 .then_ignore(just('Z'))
68 .try_map(
69 |((((((year, month), day), hour), minute), second), microsecond), span| {
70 let input = format!("{year}-{month}-{day}T{hour}:{minute}:{second}.{microsecond}Z");
71 let format = time::macros::format_description!(
72 "[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:6]Z"
73 );
74 time::PrimitiveDateTime::parse(&input, format)
75 .map(time::PrimitiveDateTime::assume_utc)
76 .map_err(|e| chumsky::error::Rich::custom(span, format!("{e:?}")))
77 },
78 )
79}
80
81#[cfg(test)]
85#[derive(Debug)]
86pub struct ChumskyError<E> {
87 pub description: String,
89 pub source: String,
91 pub errors: Vec<E>,
93}
94
95#[cfg(test)]
96impl std::fmt::Display for ChumskyError<chumsky::error::Rich<'static, char>> {
97 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98 for e in &self.errors {
99 let msg = format!(
100 "While parsing {}: {}{}, expected {}",
101 self.description,
102 if e.found().is_some() {
103 "Unexpected token"
104 } else {
105 "Unexpected end of input"
106 },
107 format_args!(" while parsing {:?}", e.contexts().collect::<Vec<_>>()),
108 if e.expected().len() == 0 {
109 "end of input".to_string()
110 } else {
111 e.expected()
112 .map(|rich_pattern| rich_pattern.to_string())
113 .collect::<Vec<_>>()
114 .join(", ")
115 },
116 );
117
118 let report = Report::build(ReportKind::Error, e.span().start..e.span().end)
119 .with_code(3)
120 .with_message(msg)
121 .with_label(
122 Label::new(e.span().start..e.span().end)
123 .with_message(format!(
124 "Unexpected {}",
125 e.found().map_or_else(
126 || "end of input".to_string(),
127 |c| format!("token {}", c.fg(Color::Red))
128 )
129 ))
130 .with_color(Color::Red),
131 );
132
133 let report = match e.reason() {
134 chumsky::error::RichReason::ExpectedFound {
135 expected: _,
136 found: _,
137 } => report,
138 chumsky::error::RichReason::Custom(msg) => report.with_label(
139 Label::new(e.span().start..e.span().end)
140 .with_message(format!("{}", msg.fg(Color::Yellow)))
141 .with_color(Color::Yellow),
142 ),
143 };
144
145 let mut s: Vec<u8> = Vec::new();
146 report
147 .finish()
148 .write(Source::from(&self.source), &mut s)
149 .map_err(|_err| <std::fmt::Error as std::default::Default>::default())?;
150 let s = std::str::from_utf8(&s)
151 .map_err(|_err| <std::fmt::Error as std::default::Default>::default())?;
152 write!(f, "{s}")?;
153 }
154 Ok(())
155 }
156}
157
158#[cfg(test)]
159impl<E> std::error::Error for ChumskyError<E>
160where
161 E: std::fmt::Debug,
162 Self: std::fmt::Display,
163{
164 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
165 None
166 }
167}