asciidoc_parser/document/
revision_line.rs1use std::sync::LazyLock;
2
3use regex::Regex;
4
5use crate::{
6 HasSpan, Parser, Span,
7 content::{Content, SubstitutionGroup},
8};
9
10#[derive(Clone, Debug, Eq, PartialEq)]
15pub struct RevisionLine<'src> {
16 revnumber: Option<String>,
17 revdate: String,
18 revremark: Option<String>,
19 source: Span<'src>,
20}
21
22impl<'src> RevisionLine<'src> {
23 pub(crate) fn parse(source: Span<'src>, parser: &mut Parser) -> Self {
24 let (left_of_colon, revremark) = if let Some((loc, remark)) = source.split_once(':') {
25 (loc.to_owned(), Some(remark.trim().to_owned()))
26 } else {
27 (source.data().to_owned(), None)
28 };
29
30 let (revnumber, revdate) = if let Some((rev, date)) = left_of_colon.split_once(',') {
31 let rev_trimmed = rev.trim();
33 let cleaned_rev = strip_non_numeric_prefix(rev_trimmed);
34 (Some(cleaned_rev), date.trim().to_owned())
35 } else {
36 let trimmed = left_of_colon.trim();
38 if is_valid_standalone_revision(trimmed) {
39 let cleaned_rev = strip_non_numeric_prefix(trimmed);
41 (Some(cleaned_rev), String::new())
42 } else {
43 (None, trimmed.to_owned())
45 }
46 };
47
48 if let Some(revnumber) = revnumber.as_deref() {
49 parser.set_attribute_by_value_from_header("revnumber", revnumber);
50 }
51
52 parser.set_attribute_by_value_from_header("revdate", &revdate);
53
54 if let Some(revremark) = revremark.as_deref() {
55 parser.set_attribute_by_value_from_header("revremark", revremark);
56 }
57
58 Self {
59 revnumber: revnumber.map(|s| apply_header_subs(&s, parser)),
60 revdate: apply_header_subs(&revdate, parser),
61 revremark: revremark.map(|s| apply_header_subs(&s, parser)),
62 source,
63 }
64 }
65
66 pub fn revnumber(&self) -> Option<&str> {
77 self.revnumber.as_deref()
78 }
79
80 pub fn revdate(&self) -> &str {
88 &self.revdate
89 }
90
91 pub fn revremark(&self) -> Option<&str> {
97 self.revremark.as_deref()
98 }
99}
100
101impl<'src> HasSpan<'src> for RevisionLine<'src> {
102 fn span(&self) -> Span<'src> {
103 self.source
104 }
105}
106
107fn apply_header_subs(source: &str, parser: &Parser) -> String {
108 let span = Span::new(source);
109
110 let mut content = Content::from(span);
111 SubstitutionGroup::Header.apply(&mut content, parser, None);
112
113 content.rendered().to_string()
114}
115
116fn is_valid_standalone_revision(s: &str) -> bool {
117 STANDALONE_REVISION.is_match(s)
118}
119
120fn strip_non_numeric_prefix(s: &str) -> String {
121 NON_NUMERIC_PREFIX
122 .captures(s)
123 .and_then(|captures| captures.get(1))
124 .map_or_else(|| s.to_owned(), |m| m.as_str().to_owned())
125}
126
127static STANDALONE_REVISION: LazyLock<Regex> = LazyLock::new(|| {
128 #[allow(clippy::unwrap_used)]
129 Regex::new(r"^v\d").unwrap()
130});
131
132static NON_NUMERIC_PREFIX: LazyLock<Regex> = LazyLock::new(|| {
133 #[allow(clippy::unwrap_used)]
134 Regex::new(r"^[^0-9]*(.*)$").unwrap()
135});
136
137#[cfg(test)]
138mod tests {
139 #![allow(clippy::unwrap_used)]
140
141 use pretty_assertions_sorted::assert_eq;
142
143 use crate::{Parser, Span};
144
145 #[test]
146 fn v_prefix_standalone() {
147 let mut parser = Parser::default();
148 let result = crate::document::RevisionLine::parse(Span::new("v1.2.3"), &mut parser);
149
150 assert_eq!(result.revnumber(), Some("1.2.3"));
151 assert_eq!(result.revdate(), "");
152 assert_eq!(result.revremark(), None);
153 }
154
155 #[test]
156 fn standalone_number_without_v_prefix() {
157 let mut parser = Parser::default();
158 let result = crate::document::RevisionLine::parse(Span::new("1.2.3"), &mut parser);
159
160 assert_eq!(result.revnumber(), None);
163 assert_eq!(result.revdate(), "1.2.3");
164 assert_eq!(result.revremark(), None);
165 }
166
167 #[test]
168 fn other_prefix_standalone() {
169 let mut parser = Parser::default();
170 let result = crate::document::RevisionLine::parse(Span::new("LPR1.2.3"), &mut parser);
171
172 assert_eq!(result.revnumber(), None);
174 assert_eq!(result.revdate(), "LPR1.2.3");
175 assert_eq!(result.revremark(), None);
176 }
177
178 #[test]
179 fn v_prefix_with_comma_and_date() {
180 let mut parser = Parser::default();
181 let result =
182 crate::document::RevisionLine::parse(Span::new("v1.2.3, 2023-01-15"), &mut parser);
183
184 assert_eq!(result.revnumber(), Some("1.2.3"));
185 assert_eq!(result.revdate(), "2023-01-15");
186 assert_eq!(result.revremark(), None);
187 }
188
189 #[test]
190 fn other_prefix_with_comma_and_date() {
191 let mut parser = Parser::default();
192 let result =
193 crate::document::RevisionLine::parse(Span::new("LPR1.2.3, 2023-01-15"), &mut parser);
194
195 assert_eq!(result.revnumber(), Some("1.2.3"));
197 assert_eq!(result.revdate(), "2023-01-15");
198 assert_eq!(result.revremark(), None);
199 }
200
201 #[test]
202 fn revision_with_colon_and_remark() {
203 let mut parser = Parser::default();
204 let result =
205 crate::document::RevisionLine::parse(Span::new("v1.2.3: A great release"), &mut parser);
206
207 assert_eq!(result.revnumber(), Some("1.2.3"));
208 assert_eq!(result.revdate(), "");
209 assert_eq!(result.revremark(), Some("A great release"));
210 }
211
212 #[test]
213 fn full_revision_line() {
214 let mut parser = Parser::default();
215 let result = crate::document::RevisionLine::parse(
216 Span::new("v2.1.0, 2023-12-25: Christmas release"),
217 &mut parser,
218 );
219
220 assert_eq!(result.revnumber(), Some("2.1.0"));
221 assert_eq!(result.revdate(), "2023-12-25");
222 assert_eq!(result.revremark(), Some("Christmas release"));
223 }
224
225 #[test]
226 fn only_date() {
227 let mut parser = Parser::default();
228 let result = crate::document::RevisionLine::parse(Span::new("2023-01-15"), &mut parser);
229
230 assert_eq!(result.revnumber(), None);
232 assert_eq!(result.revdate(), "2023-01-15");
233 assert_eq!(result.revremark(), None);
234 }
235
236 #[test]
237 fn date_with_remark() {
238 let mut parser = Parser::default();
239 let result = crate::document::RevisionLine::parse(
240 Span::new("2023-01-15: New year update"),
241 &mut parser,
242 );
243
244 assert_eq!(result.revnumber(), None);
245 assert_eq!(result.revdate(), "2023-01-15");
246 assert_eq!(result.revremark(), Some("New year update"));
247 }
248
249 #[test]
250 fn whitespace_handling() {
251 let mut parser = Parser::default();
252 let result = crate::document::RevisionLine::parse(
253 Span::new(" v1.0.0 , Jan 1, 2023 : Initial release "),
254 &mut parser,
255 );
256
257 assert_eq!(result.revnumber(), Some("1.0.0"));
258 assert_eq!(result.revdate(), "Jan 1, 2023");
259 assert_eq!(result.revremark(), Some("Initial release"));
260 }
261
262 #[test]
263 fn v_only_no_digits() {
264 let mut parser = Parser::default();
265 let result = crate::document::RevisionLine::parse(Span::new("v"), &mut parser);
266
267 assert_eq!(result.revnumber(), None);
269 assert_eq!(result.revdate(), "v");
270 assert_eq!(result.revremark(), None);
271 }
272
273 #[test]
274 fn complex_version_with_v() {
275 let mut parser = Parser::default();
276 let result = crate::document::RevisionLine::parse(Span::new("v1.2.3-beta.1"), &mut parser);
277
278 assert_eq!(result.revnumber(), Some("1.2.3-beta.1"));
279 assert_eq!(result.revdate(), "");
280 assert_eq!(result.revremark(), None);
281 }
282
283 #[test]
284 fn numeric_prefix_stripped() {
285 let mut parser = Parser::default();
286 let result =
287 crate::document::RevisionLine::parse(Span::new("abc123def, 2023-01-01"), &mut parser);
288
289 assert_eq!(result.revnumber(), Some("123def"));
291 assert_eq!(result.revdate(), "2023-01-01");
292 assert_eq!(result.revremark(), None);
293 }
294
295 #[test]
296 fn no_numeric_content() {
297 let mut parser = Parser::default();
298 let result =
299 crate::document::RevisionLine::parse(Span::new("nodigits, 2023-01-01"), &mut parser);
300
301 assert_eq!(result.revnumber(), Some(""));
303 assert_eq!(result.revdate(), "2023-01-01");
304 assert_eq!(result.revremark(), None);
305 }
306
307 #[test]
308 fn sets_document_attributes_with_all_components() {
309 let mut parser = Parser::default();
310 let _result = crate::document::RevisionLine::parse(
311 Span::new("v2.1.0, 2023-12-25: Christmas release"),
312 &mut parser,
313 );
314
315 assert_eq!(
316 parser.attribute_value("revnumber").as_maybe_str(),
317 Some("2.1.0")
318 );
319
320 assert_eq!(
321 parser.attribute_value("revdate").as_maybe_str(),
322 Some("2023-12-25")
323 );
324
325 assert_eq!(
326 parser.attribute_value("revremark").as_maybe_str(),
327 Some("Christmas release")
328 );
329 }
330
331 #[test]
332 fn sets_document_attributes_revision_number_only() {
333 let mut parser = Parser::default();
334 let _result = crate::document::RevisionLine::parse(Span::new("v1.2.3"), &mut parser);
335
336 assert_eq!(
337 parser.attribute_value("revnumber").as_maybe_str(),
338 Some("1.2.3")
339 );
340
341 assert_eq!(parser.attribute_value("revdate").as_maybe_str(), Some(""));
342 assert_eq!(parser.attribute_value("revremark").as_maybe_str(), None);
343 }
344
345 #[test]
346 fn sets_document_attributes_date_only() {
347 let mut parser = Parser::default();
348 let _result = crate::document::RevisionLine::parse(Span::new("2023-01-15"), &mut parser);
349
350 assert_eq!(parser.attribute_value("revnumber").as_maybe_str(), None);
351
352 assert_eq!(
353 parser.attribute_value("revdate").as_maybe_str(),
354 Some("2023-01-15")
355 );
356
357 assert_eq!(parser.attribute_value("revremark").as_maybe_str(), None);
358 }
359
360 #[test]
361 fn sets_document_attributes_date_with_remark() {
362 let mut parser = Parser::default();
363 let _result = crate::document::RevisionLine::parse(
364 Span::new("2023-01-15: New year update"),
365 &mut parser,
366 );
367
368 assert_eq!(parser.attribute_value("revnumber").as_maybe_str(), None);
369
370 assert_eq!(
371 parser.attribute_value("revdate").as_maybe_str(),
372 Some("2023-01-15")
373 );
374
375 assert_eq!(
376 parser.attribute_value("revremark").as_maybe_str(),
377 Some("New year update")
378 );
379 }
380
381 #[test]
382 fn sets_document_attributes_revision_with_date() {
383 let mut parser = Parser::default();
384 let _result =
385 crate::document::RevisionLine::parse(Span::new("v1.2.3, 2023-01-15"), &mut parser);
386
387 assert_eq!(
388 parser.attribute_value("revnumber").as_maybe_str(),
389 Some("1.2.3")
390 );
391
392 assert_eq!(
393 parser.attribute_value("revdate").as_maybe_str(),
394 Some("2023-01-15")
395 );
396
397 assert_eq!(parser.attribute_value("revremark").as_maybe_str(), None);
398 }
399
400 #[test]
401 fn sets_document_attributes_revision_with_remark_only() {
402 let mut parser = Parser::default();
403 let _result =
404 crate::document::RevisionLine::parse(Span::new("v1.2.3: A great release"), &mut parser);
405
406 assert_eq!(
407 parser.attribute_value("revnumber").as_maybe_str(),
408 Some("1.2.3")
409 );
410
411 assert_eq!(parser.attribute_value("revdate").as_maybe_str(), Some(""));
412
413 assert_eq!(
414 parser.attribute_value("revremark").as_maybe_str(),
415 Some("A great release")
416 );
417 }
418
419 #[test]
420 fn sets_document_attributes_with_whitespace_handling() {
421 let mut parser = Parser::default();
422 let _result = crate::document::RevisionLine::parse(
423 Span::new(" v1.0.0 , Jan 1, 2023 : Initial release "),
424 &mut parser,
425 );
426
427 assert_eq!(
428 parser.attribute_value("revnumber").as_maybe_str(),
429 Some("1.0.0")
430 );
431
432 assert_eq!(
433 parser.attribute_value("revdate").as_maybe_str(),
434 Some("Jan 1, 2023")
435 );
436
437 assert_eq!(
438 parser.attribute_value("revremark").as_maybe_str(),
439 Some("Initial release")
440 );
441 }
442
443 #[test]
444 fn sets_document_attributes_with_prefix_stripping() {
445 let mut parser = Parser::default();
446 let _result =
447 crate::document::RevisionLine::parse(Span::new("abc123def, 2023-01-01"), &mut parser);
448
449 assert_eq!(
450 parser.attribute_value("revnumber").as_maybe_str(),
451 Some("123def")
452 );
453
454 assert_eq!(
455 parser.attribute_value("revdate").as_maybe_str(),
456 Some("2023-01-01")
457 );
458
459 assert_eq!(parser.attribute_value("revremark").as_maybe_str(), None);
460 }
461
462 #[test]
463 fn sets_document_attributes_complex_version() {
464 let mut parser = Parser::default();
465 let _result = crate::document::RevisionLine::parse(Span::new("v1.2.3-beta.1"), &mut parser);
466
467 assert_eq!(
468 parser.attribute_value("revnumber").as_maybe_str(),
469 Some("1.2.3-beta.1")
470 );
471
472 assert_eq!(parser.attribute_value("revdate").as_maybe_str(), Some(""));
473 assert_eq!(parser.attribute_value("revremark").as_maybe_str(), None);
474 }
475}