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 crate::{Span, tests::prelude::*};
142
143 #[test]
144 fn v_prefix_standalone() {
145 let mut parser = Parser::default();
146 let result = crate::document::RevisionLine::parse(Span::new("v1.2.3"), &mut parser);
147
148 assert_eq!(result.revnumber(), Some("1.2.3"));
149 assert_eq!(result.revdate(), "");
150 assert_eq!(result.revremark(), None);
151 }
152
153 #[test]
154 fn standalone_number_without_v_prefix() {
155 let mut parser = Parser::default();
156 let result = crate::document::RevisionLine::parse(Span::new("1.2.3"), &mut parser);
157
158 assert_eq!(result.revnumber(), None);
161 assert_eq!(result.revdate(), "1.2.3");
162 assert_eq!(result.revremark(), None);
163 }
164
165 #[test]
166 fn other_prefix_standalone() {
167 let mut parser = Parser::default();
168 let result = crate::document::RevisionLine::parse(Span::new("LPR1.2.3"), &mut parser);
169
170 assert_eq!(result.revnumber(), None);
172 assert_eq!(result.revdate(), "LPR1.2.3");
173 assert_eq!(result.revremark(), None);
174 }
175
176 #[test]
177 fn v_prefix_with_comma_and_date() {
178 let mut parser = Parser::default();
179 let result =
180 crate::document::RevisionLine::parse(Span::new("v1.2.3, 2023-01-15"), &mut parser);
181
182 assert_eq!(result.revnumber(), Some("1.2.3"));
183 assert_eq!(result.revdate(), "2023-01-15");
184 assert_eq!(result.revremark(), None);
185 }
186
187 #[test]
188 fn other_prefix_with_comma_and_date() {
189 let mut parser = Parser::default();
190 let result =
191 crate::document::RevisionLine::parse(Span::new("LPR1.2.3, 2023-01-15"), &mut parser);
192
193 assert_eq!(result.revnumber(), Some("1.2.3"));
195 assert_eq!(result.revdate(), "2023-01-15");
196 assert_eq!(result.revremark(), None);
197 }
198
199 #[test]
200 fn revision_with_colon_and_remark() {
201 let mut parser = Parser::default();
202 let result =
203 crate::document::RevisionLine::parse(Span::new("v1.2.3: A great release"), &mut parser);
204
205 assert_eq!(result.revnumber(), Some("1.2.3"));
206 assert_eq!(result.revdate(), "");
207 assert_eq!(result.revremark(), Some("A great release"));
208 }
209
210 #[test]
211 fn full_revision_line() {
212 let mut parser = Parser::default();
213 let result = crate::document::RevisionLine::parse(
214 Span::new("v2.1.0, 2023-12-25: Christmas release"),
215 &mut parser,
216 );
217
218 assert_eq!(result.revnumber(), Some("2.1.0"));
219 assert_eq!(result.revdate(), "2023-12-25");
220 assert_eq!(result.revremark(), Some("Christmas release"));
221 }
222
223 #[test]
224 fn only_date() {
225 let mut parser = Parser::default();
226 let result = crate::document::RevisionLine::parse(Span::new("2023-01-15"), &mut parser);
227
228 assert_eq!(result.revnumber(), None);
230 assert_eq!(result.revdate(), "2023-01-15");
231 assert_eq!(result.revremark(), None);
232 }
233
234 #[test]
235 fn date_with_remark() {
236 let mut parser = Parser::default();
237 let result = crate::document::RevisionLine::parse(
238 Span::new("2023-01-15: New year update"),
239 &mut parser,
240 );
241
242 assert_eq!(result.revnumber(), None);
243 assert_eq!(result.revdate(), "2023-01-15");
244 assert_eq!(result.revremark(), Some("New year update"));
245 }
246
247 #[test]
248 fn whitespace_handling() {
249 let mut parser = Parser::default();
250 let result = crate::document::RevisionLine::parse(
251 Span::new(" v1.0.0 , Jan 1, 2023 : Initial release "),
252 &mut parser,
253 );
254
255 assert_eq!(result.revnumber(), Some("1.0.0"));
256 assert_eq!(result.revdate(), "Jan 1, 2023");
257 assert_eq!(result.revremark(), Some("Initial release"));
258 }
259
260 #[test]
261 fn v_only_no_digits() {
262 let mut parser = Parser::default();
263 let result = crate::document::RevisionLine::parse(Span::new("v"), &mut parser);
264
265 assert_eq!(result.revnumber(), None);
267 assert_eq!(result.revdate(), "v");
268 assert_eq!(result.revremark(), None);
269 }
270
271 #[test]
272 fn complex_version_with_v() {
273 let mut parser = Parser::default();
274 let result = crate::document::RevisionLine::parse(Span::new("v1.2.3-beta.1"), &mut parser);
275
276 assert_eq!(result.revnumber(), Some("1.2.3-beta.1"));
277 assert_eq!(result.revdate(), "");
278 assert_eq!(result.revremark(), None);
279 }
280
281 #[test]
282 fn numeric_prefix_stripped() {
283 let mut parser = Parser::default();
284 let result =
285 crate::document::RevisionLine::parse(Span::new("abc123def, 2023-01-01"), &mut parser);
286
287 assert_eq!(result.revnumber(), Some("123def"));
289 assert_eq!(result.revdate(), "2023-01-01");
290 assert_eq!(result.revremark(), None);
291 }
292
293 #[test]
294 fn no_numeric_content() {
295 let mut parser = Parser::default();
296 let result =
297 crate::document::RevisionLine::parse(Span::new("nodigits, 2023-01-01"), &mut parser);
298
299 assert_eq!(result.revnumber(), Some(""));
301 assert_eq!(result.revdate(), "2023-01-01");
302 assert_eq!(result.revremark(), None);
303 }
304
305 #[test]
306 fn sets_document_attributes_with_all_components() {
307 let mut parser = Parser::default();
308 let _result = crate::document::RevisionLine::parse(
309 Span::new("v2.1.0, 2023-12-25: Christmas release"),
310 &mut parser,
311 );
312
313 assert_eq!(
314 parser.attribute_value("revnumber").as_maybe_str(),
315 Some("2.1.0")
316 );
317
318 assert_eq!(
319 parser.attribute_value("revdate").as_maybe_str(),
320 Some("2023-12-25")
321 );
322
323 assert_eq!(
324 parser.attribute_value("revremark").as_maybe_str(),
325 Some("Christmas release")
326 );
327 }
328
329 #[test]
330 fn sets_document_attributes_revision_number_only() {
331 let mut parser = Parser::default();
332 let _result = crate::document::RevisionLine::parse(Span::new("v1.2.3"), &mut parser);
333
334 assert_eq!(
335 parser.attribute_value("revnumber").as_maybe_str(),
336 Some("1.2.3")
337 );
338
339 assert_eq!(parser.attribute_value("revdate").as_maybe_str(), Some(""));
340 assert_eq!(parser.attribute_value("revremark").as_maybe_str(), None);
341 }
342
343 #[test]
344 fn sets_document_attributes_date_only() {
345 let mut parser = Parser::default();
346 let _result = crate::document::RevisionLine::parse(Span::new("2023-01-15"), &mut parser);
347
348 assert_eq!(parser.attribute_value("revnumber").as_maybe_str(), None);
349
350 assert_eq!(
351 parser.attribute_value("revdate").as_maybe_str(),
352 Some("2023-01-15")
353 );
354
355 assert_eq!(parser.attribute_value("revremark").as_maybe_str(), None);
356 }
357
358 #[test]
359 fn sets_document_attributes_date_with_remark() {
360 let mut parser = Parser::default();
361 let _result = crate::document::RevisionLine::parse(
362 Span::new("2023-01-15: New year update"),
363 &mut parser,
364 );
365
366 assert_eq!(parser.attribute_value("revnumber").as_maybe_str(), None);
367
368 assert_eq!(
369 parser.attribute_value("revdate").as_maybe_str(),
370 Some("2023-01-15")
371 );
372
373 assert_eq!(
374 parser.attribute_value("revremark").as_maybe_str(),
375 Some("New year update")
376 );
377 }
378
379 #[test]
380 fn sets_document_attributes_revision_with_date() {
381 let mut parser = Parser::default();
382 let _result =
383 crate::document::RevisionLine::parse(Span::new("v1.2.3, 2023-01-15"), &mut parser);
384
385 assert_eq!(
386 parser.attribute_value("revnumber").as_maybe_str(),
387 Some("1.2.3")
388 );
389
390 assert_eq!(
391 parser.attribute_value("revdate").as_maybe_str(),
392 Some("2023-01-15")
393 );
394
395 assert_eq!(parser.attribute_value("revremark").as_maybe_str(), None);
396 }
397
398 #[test]
399 fn sets_document_attributes_revision_with_remark_only() {
400 let mut parser = Parser::default();
401 let _result =
402 crate::document::RevisionLine::parse(Span::new("v1.2.3: A great release"), &mut parser);
403
404 assert_eq!(
405 parser.attribute_value("revnumber").as_maybe_str(),
406 Some("1.2.3")
407 );
408
409 assert_eq!(parser.attribute_value("revdate").as_maybe_str(), Some(""));
410
411 assert_eq!(
412 parser.attribute_value("revremark").as_maybe_str(),
413 Some("A great release")
414 );
415 }
416
417 #[test]
418 fn sets_document_attributes_with_whitespace_handling() {
419 let mut parser = Parser::default();
420 let _result = crate::document::RevisionLine::parse(
421 Span::new(" v1.0.0 , Jan 1, 2023 : Initial release "),
422 &mut parser,
423 );
424
425 assert_eq!(
426 parser.attribute_value("revnumber").as_maybe_str(),
427 Some("1.0.0")
428 );
429
430 assert_eq!(
431 parser.attribute_value("revdate").as_maybe_str(),
432 Some("Jan 1, 2023")
433 );
434
435 assert_eq!(
436 parser.attribute_value("revremark").as_maybe_str(),
437 Some("Initial release")
438 );
439 }
440
441 #[test]
442 fn sets_document_attributes_with_prefix_stripping() {
443 let mut parser = Parser::default();
444 let _result =
445 crate::document::RevisionLine::parse(Span::new("abc123def, 2023-01-01"), &mut parser);
446
447 assert_eq!(
448 parser.attribute_value("revnumber").as_maybe_str(),
449 Some("123def")
450 );
451
452 assert_eq!(
453 parser.attribute_value("revdate").as_maybe_str(),
454 Some("2023-01-01")
455 );
456
457 assert_eq!(parser.attribute_value("revremark").as_maybe_str(), None);
458 }
459
460 #[test]
461 fn sets_document_attributes_complex_version() {
462 let mut parser = Parser::default();
463 let _result = crate::document::RevisionLine::parse(Span::new("v1.2.3-beta.1"), &mut parser);
464
465 assert_eq!(
466 parser.attribute_value("revnumber").as_maybe_str(),
467 Some("1.2.3-beta.1")
468 );
469
470 assert_eq!(parser.attribute_value("revdate").as_maybe_str(), Some(""));
471 assert_eq!(parser.attribute_value("revremark").as_maybe_str(), None);
472 }
473}