124 lines
3.5 KiB
Rust
124 lines
3.5 KiB
Rust
|
use pulldown_cmark::{html, CowStr, Event, Tag};
|
||
|
use url::Url;
|
||
|
|
||
|
pub struct LinkDecorations<'a, I: Iterator<Item = Event<'a>>> {
|
||
|
iter: I,
|
||
|
}
|
||
|
|
||
|
pub fn new<'a, I: Iterator<Item = Event<'a>>>(iter: I) -> LinkDecorations<'a, I> {
|
||
|
LinkDecorations { iter }
|
||
|
}
|
||
|
|
||
|
impl<'a, I: Iterator<Item = Event<'a>>> Iterator for LinkDecorations<'a, I> {
|
||
|
type Item = Event<'a>;
|
||
|
|
||
|
fn next(&mut self) -> Option<Self::Item> {
|
||
|
match self.iter.next() {
|
||
|
Some(Event::Start(Tag::Link(_, href, title))) => {
|
||
|
let link = href_to_pretty_link(&href);
|
||
|
let mut s = format!(r#"<a href="{}" data-link="{}"#, href, link);
|
||
|
if !title.is_empty() {
|
||
|
s.push_str(" title=\"");
|
||
|
s.push_str(title.as_ref());
|
||
|
}
|
||
|
s.push_str("\">");
|
||
|
html::push_html(&mut s, LinkContents(&mut self.iter));
|
||
|
s.push_str("</a>");
|
||
|
|
||
|
Some(Event::Html(CowStr::Boxed(s.into_boxed_str())))
|
||
|
}
|
||
|
e => e,
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
struct LinkContents<'a, 'b, I: Iterator<Item = Event<'a>>>(&'b mut I);
|
||
|
|
||
|
impl<'a, 'b, I: Iterator<Item = Event<'a>>> Iterator for LinkContents<'a, 'b, I> {
|
||
|
type Item = Event<'a>;
|
||
|
|
||
|
fn next(&mut self) -> Option<Self::Item> {
|
||
|
match self.0.next() {
|
||
|
Some(Event::End(Tag::Link(_, _, _))) => None,
|
||
|
e => e,
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn href_to_pretty_link<'a>(href: &CowStr<'a>) -> CowStr<'a> {
|
||
|
match Url::parse(href) {
|
||
|
Ok(url) => {
|
||
|
let mut s = String::new();
|
||
|
let host = url.host_str();
|
||
|
if let Some(h) = host {
|
||
|
if h.starts_with("www.") {
|
||
|
s.push_str(&h[4..]);
|
||
|
} else {
|
||
|
s.push_str(h);
|
||
|
}
|
||
|
}
|
||
|
let path = url.path();
|
||
|
if path != "/" {
|
||
|
s.push_str(path);
|
||
|
}
|
||
|
|
||
|
if host.map(|s| s.ends_with("youtube.com")).unwrap_or(false) && path == "/watch" {
|
||
|
s.push('?');
|
||
|
s.push_str(url.query().unwrap());
|
||
|
}
|
||
|
|
||
|
if s.len() > 40 {
|
||
|
s = s.chars().take(40).collect();
|
||
|
s.push('…');
|
||
|
}
|
||
|
|
||
|
CowStr::Boxed(s.into_boxed_str())
|
||
|
}
|
||
|
Err(_) => {
|
||
|
// if there was an error parsing the url, we just pass it through unchanged
|
||
|
href.clone()
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[cfg(test)]
|
||
|
mod tests {
|
||
|
use pulldown_cmark::{html, CowStr, Parser};
|
||
|
|
||
|
fn pretty_link<'a>(s: &'a str) -> CowStr<'a> {
|
||
|
super::href_to_pretty_link(&CowStr::Borrowed(s))
|
||
|
}
|
||
|
|
||
|
fn render(s: &str) -> String {
|
||
|
let mut out = String::new();
|
||
|
let parser = Parser::new(s);
|
||
|
let heading_anchors = super::new(parser);
|
||
|
html::push_html(&mut out, heading_anchors);
|
||
|
out
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fn test_href_to_pretty_link() {
|
||
|
assert_eq!(
|
||
|
pretty_link("https://asahilinux.org/2022/03/asahi-linux-alpha-release/").as_ref(),
|
||
|
"asahilinux.org/2022/03/asahi-linux-alpha…"
|
||
|
);
|
||
|
assert_eq!(
|
||
|
pretty_link("https://www.youtube.com/watch?v=MFzDaBzBlL0").as_ref(),
|
||
|
"youtube.com/watch?v=MFzDaBzBlL0"
|
||
|
);
|
||
|
assert_eq!(
|
||
|
pretty_link("https://www.youtube.com/watch?v=MFzDaBzBlL0&t=10").as_ref(),
|
||
|
"youtube.com/watch?v=MFzDaBzBlL0&t=10"
|
||
|
);
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fn test_link_decorations() {
|
||
|
assert_eq!(
|
||
|
render("[foo](bar)").trim(),
|
||
|
r#"<p><a href="bar" data-link="bar">foo</a></p>"#
|
||
|
);
|
||
|
}
|
||
|
}
|