links_notation/
reference_literal.rs1pub fn encode_reference_literal(text: &str) -> String {
5 use std::fmt::Write;
6 let mut result = String::with_capacity(text.len() * 2 + 4);
7 result.push_str("~1{");
8 for byte in text.bytes() {
9 write!(result, "{byte:02x}").expect("writing to a string cannot fail");
10 }
11 result.push('}');
12 result
13}
14
15pub fn decode_reference_literal(literal: &str) -> Result<String, &'static str> {
17 let prefix = prefix_end(literal).ok_or("invalid reference literal")?;
18 if &literal[..prefix] != "~1{" || !literal.ends_with('}') {
19 return Err("invalid or unsupported reference literal");
20 }
21 let hex = &literal[prefix..literal.len() - 1];
22 if !hex.len().is_multiple_of(2) || !hex.bytes().all(|b| b.is_ascii_hexdigit()) {
23 return Err("reference literal requires an even number of hex digits");
24 }
25 let bytes = (0..hex.len())
26 .step_by(2)
27 .map(|i| u8::from_str_radix(&hex[i..i + 2], 16).expect("validated hex"))
28 .collect();
29 String::from_utf8(bytes).map_err(|_| "reference literal must contain valid UTF-8")
30}
31
32pub(crate) fn prefix_end(text: &str) -> Option<usize> {
33 let bytes = text.as_bytes();
34 if bytes.first() != Some(&b'~') {
35 return None;
36 }
37 let mut position = 1;
38 while bytes.get(position).is_some_and(u8::is_ascii_digit) {
39 position += 1;
40 }
41 (position > 1 && bytes.get(position) == Some(&b'{')).then_some(position + 1)
42}
43
44pub(crate) fn reference_literal_end(text: &str, start: usize) -> Option<usize> {
48 let rest = text.get(start..)?;
49 let body = prefix_end(rest)?;
50 let bytes = rest.as_bytes();
51 let mut end = body;
52 while end < bytes.len() && !b"} \t\r\n()".contains(&bytes[end]) {
53 end += 1;
54 }
55 (bytes.get(end) == Some(&b'}')).then_some(start + end + 1)
56}
57
58pub(crate) fn format_reference(text: &str) -> String {
59 if text.is_empty() || text.chars().any(|c| c < ' ' || c == '\u{7f}') {
60 return encode_reference_literal(text);
61 }
62 if prefix_end(text).is_none()
63 && !text.starts_with('#')
64 && !text.chars().any(|c| {
65 c.is_whitespace() || matches!(c, '\u{feff}' | '(' | ')' | ':' | '\'' | '"' | '`')
66 })
67 {
68 return text.to_string();
69 }
70 let first = text.chars().next();
71 let (quote, count) = ['\'', '"', '`']
72 .into_iter()
73 .filter(|"e| first != Some(quote))
74 .map(|quote| {
75 let (mut longest, mut current) = (0, 0);
76 for c in text.chars() {
77 current = if c == quote { current + 1 } else { 0 };
78 longest = longest.max(current);
79 }
80 (quote, (longest + 1) | 1)
81 })
82 .min_by_key(|&(_, count)| count)
83 .expect("at least two eligible delimiters");
84 let delimiter = quote.to_string().repeat(count);
85 format!("{delimiter}{text}{delimiter}")
86}