1use std::str::FromStr;
2
3use anyhow::Context;
4
5use crate::content::Content;
6
7static MARKER_REGEX: std::sync::LazyLock<regex::Regex> =
8 std::sync::LazyLock::new(|| regex::Regex::new(r#"<!-- sync-readme (\w+)?\s*(\[\[|\]\])? -->"#).expect("bad regex"));
9
10#[derive(Debug)]
11enum MarkerCategory {
12 Title,
13 Badge,
14 Rustdoc,
15}
16
17impl std::fmt::Display for MarkerCategory {
18 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
19 match self {
20 Self::Title => f.write_str("title"),
21 Self::Badge => f.write_str("badge"),
22 Self::Rustdoc => f.write_str("rustdoc"),
23 }
24 }
25}
26
27impl FromStr for MarkerCategory {
28 type Err = anyhow::Error;
29
30 fn from_str(s: &str) -> Result<Self, Self::Err> {
31 match s {
32 "title" => Ok(Self::Title),
33 "badge" => Ok(Self::Badge),
34 "rustdoc" => Ok(Self::Rustdoc),
35 s => Err(anyhow::anyhow!("unknown marker {s}")),
36 }
37 }
38}
39
40#[derive(Debug)]
41struct Marker {
42 category: MarkerCategory,
43 start: usize,
44 end: usize,
45}
46
47pub fn render(readme: &str, content: &Content) -> anyhow::Result<String> {
48 let mut markers = Vec::new();
49
50 let mut iter = MARKER_REGEX.captures_iter(readme);
51
52 while let Some(capture) = iter.next() {
53 let marker = capture.get(0).expect("always a zero group");
54 let category: MarkerCategory = capture
55 .get(1)
56 .map(|s| s.as_str().parse())
57 .transpose()?
58 .context("missing category")?;
59 let open = capture.get(2).map(|c| c.as_str());
60
61 let start = marker.start();
62 let mut end = marker.end();
63 if open == Some("[[") {
64 let end_capture = iter.next().context("marker opens but never closes")?;
65 let marker = end_capture.get(0).expect("always a zero group");
66 anyhow::ensure!(
67 end_capture.get(1).is_none() && end_capture.get(2).is_some_and(|c| c.as_str() == "]]"),
68 "capture after an open must be a close: {}",
69 marker.as_str(),
70 );
71
72 end = marker.end();
73 }
74
75 markers.push(Marker { category, start, end });
76 }
77
78 let mut readme_builder = String::new();
79 let mut idx = 0;
80
81 for marker in markers {
82 readme_builder.push_str(&readme[idx..marker.start]);
83 idx = marker.end + 1;
84
85 use std::fmt::Write;
86
87 let content = match marker.category {
88 MarkerCategory::Badge => content.badge.as_str(),
89 MarkerCategory::Rustdoc => content.rustdoc.as_str(),
90 MarkerCategory::Title => content.title.as_str(),
91 }
92 .trim();
93
94 if content.is_empty() {
95 writeln!(&mut readme_builder, "<!-- sync-readme {} -->", marker.category).expect("write failed");
96 } else {
97 writeln!(
98 &mut readme_builder,
99 "<!-- sync-readme {} [[ -->\n{content}\n<!-- sync-readme ]] -->",
100 marker.category
101 )
102 .expect("write failed");
103 }
104 }
105
106 readme_builder.push_str(&readme[idx..]);
107
108 Ok(readme_builder)
109}