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});