about summary refs log tree commit diff
diff options
context:
space:
mode:
authorNoritada Kobayashi <noritada.kobayashi@gmail.com>2022-12-14 02:12:24 +0900
committerNoritada Kobayashi <noritada.kobayashi@gmail.com>2022-12-14 02:12:24 +0900
commita77336b1aab8f8abbdca6915a2f57ae9cfe5d417 (patch)
tree33ba3cceb9afb4111468a991a03fe5e4bf29961f
parentc7c6c6b8145dbf6d0fe4676325a143f4e584c25e (diff)
downloadrust-a77336b1aab8f8abbdca6915a2f57ae9cfe5d417.tar.gz
rust-a77336b1aab8f8abbdca6915a2f57ae9cfe5d417.zip
Implement inline macro handling for AsciiDoc-to-Markdown conversion
-rw-r--r--xtask/src/publish/notes.rs182
1 files changed, 182 insertions, 0 deletions
diff --git a/xtask/src/publish/notes.rs b/xtask/src/publish/notes.rs
index 3ff0f5a2807..974baabefea 100644
--- a/xtask/src/publish/notes.rs
+++ b/xtask/src/publish/notes.rs
@@ -1,5 +1,6 @@
 use anyhow::{anyhow, bail};
 use std::{
+    borrow::Cow,
     io::{BufRead, Lines},
     iter::Peekable,
 };
@@ -431,6 +432,141 @@ impl ListMarker {
     }
 }
 
+fn process_inline_macros(line: &str) -> anyhow::Result<Cow<'_, str>> {
+    let mut chars = line.char_indices();
+    loop {
+        let (start, end, a_macro) = match get_next_line_component(&mut chars) {
+            Component::None => break,
+            Component::Text => continue,
+            Component::Macro(s, e, m) => (s, e, m),
+        };
+        let mut src = line.chars();
+        let mut processed = String::new();
+        for _ in 0..start {
+            processed.push(src.next().unwrap());
+        }
+        processed.push_str(a_macro.process()?.as_str());
+        for _ in start..end {
+            let _ = src.next().unwrap();
+        }
+        let mut pos = end;
+
+        loop {
+            let (start, end, a_macro) = match get_next_line_component(&mut chars) {
+                Component::None => break,
+                Component::Text => continue,
+                Component::Macro(s, e, m) => (s, e, m),
+            };
+            for _ in pos..start {
+                processed.push(src.next().unwrap());
+            }
+            processed.push_str(a_macro.process()?.as_str());
+            for _ in start..end {
+                let _ = src.next().unwrap();
+            }
+            pos = end;
+        }
+        for ch in src {
+            processed.push(ch);
+        }
+        return Ok(Cow::Owned(processed));
+    }
+    Ok(Cow::Borrowed(line))
+}
+
+fn get_next_line_component(chars: &mut std::str::CharIndices<'_>) -> Component {
+    let (start, mut macro_name) = match chars.next() {
+        None => return Component::None,
+        Some((_, ch)) if ch == ' ' || !ch.is_ascii() => return Component::Text,
+        Some((pos, ch)) => (pos, String::from(ch)),
+    };
+    loop {
+        match chars.next() {
+            None => return Component::None,
+            Some((_, ch)) if ch == ' ' || !ch.is_ascii() => return Component::Text,
+            Some((_, ':')) => break,
+            Some((_, ch)) => macro_name.push(ch),
+        }
+    }
+
+    let mut macro_target = String::new();
+    loop {
+        match chars.next() {
+            None => return Component::None,
+            Some((_, ' ')) => return Component::Text,
+            Some((_, '[')) => break,
+            Some((_, ch)) => macro_target.push(ch),
+        }
+    }
+
+    let mut attr_value = String::new();
+    let end = loop {
+        match chars.next() {
+            None => return Component::None,
+            Some((pos, ']')) => break pos + 1,
+            Some((_, ch)) => attr_value.push(ch),
+        }
+    };
+
+    Component::Macro(start, end, Macro::new(macro_name, macro_target, attr_value))
+}
+
+enum Component {
+    None,
+    Text,
+    Macro(usize, usize, Macro),
+}
+
+struct Macro {
+    name: String,
+    target: String,
+    attrs: String,
+}
+
+impl Macro {
+    fn new(name: String, target: String, attrs: String) -> Self {
+        Self { name, target, attrs }
+    }
+
+    fn process(&self) -> anyhow::Result<String> {
+        let name = &self.name;
+        let text = match name.as_str() {
+            "https" => {
+                let url = &self.target;
+                let anchor_text = &self.attrs;
+                format!("[{anchor_text}](https:{url})")
+            }
+            "image" => {
+                let url = &self.target;
+                let alt = &self.attrs;
+                format!("![{alt}]({url})")
+            }
+            "kbd" => {
+                let keys = self.attrs.split('+').map(|k| Cow::Owned(format!("<kbd>{k}</kbd>")));
+                keys.collect::<Vec<_>>().join("+")
+            }
+            "pr" => {
+                let pr = &self.target;
+                let url = format!("https://github.com/rust-analyzer/rust-analyzer/pull/{pr}");
+                format!("[`#{pr}`]({url})")
+            }
+            "commit" => {
+                let hash = &self.target;
+                let short = &hash[0..7];
+                let url = format!("https://github.com/rust-analyzer/rust-analyzer/commit/{hash}");
+                format!("[`{short}`]({url})")
+            }
+            "release" => {
+                let date = &self.target;
+                let url = format!("https://github.com/rust-analyzer/rust-analyzer/releases/{date}");
+                format!("[`{date}`]({url})")
+            }
+            _ => bail!("macro not supported: {name}"),
+        };
+        Ok(text)
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -612,4 +748,50 @@ This is a plain listing.
 
         assert_eq!(actual, expected);
     }
+
+    macro_rules! test_inline_macro_processing {
+        ($((
+            $name:ident,
+            $input:expr,
+            $expected:expr
+        ),)*) => ($(
+            #[test]
+            fn $name() {
+                let input = $input;
+                let actual = process_inline_macros(&input).unwrap();
+                let expected = $expected;
+                assert_eq!(actual, expected)
+            }
+        )*);
+    }
+
+    test_inline_macro_processing! {
+        (inline_macro_processing_for_empty_line, "", ""),
+        (inline_macro_processing_for_line_with_no_macro, "foo bar", "foo bar"),
+        (
+            inline_macro_processing_for_macro_in_line_start,
+            "kbd::[Ctrl+T] foo",
+            "<kbd>Ctrl</kbd>+<kbd>T</kbd> foo"
+        ),
+        (
+            inline_macro_processing_for_macro_in_line_end,
+            "foo kbd::[Ctrl+T]",
+            "foo <kbd>Ctrl</kbd>+<kbd>T</kbd>"
+        ),
+        (
+            inline_macro_processing_for_macro_in_the_middle_of_line,
+            "foo kbd::[Ctrl+T] foo",
+            "foo <kbd>Ctrl</kbd>+<kbd>T</kbd> foo"
+        ),
+        (
+            inline_macro_processing_for_several_macros,
+            "foo kbd::[Ctrl+T] foo kbd::[Enter] foo",
+            "foo <kbd>Ctrl</kbd>+<kbd>T</kbd> foo <kbd>Enter</kbd> foo"
+        ),
+        (
+            inline_macro_processing_for_several_macros_without_text_in_between,
+            "foo kbd::[Ctrl+T]kbd::[Enter] foo",
+            "foo <kbd>Ctrl</kbd>+<kbd>T</kbd><kbd>Enter</kbd> foo"
+        ),
+    }
 }