wayver's git archive


an obsidian renderer
git clone https://git.wayver.dev/sable

sable-markdown/src/parser/inline/autolink.rs@main

raw
Date Commit Message Author Files + -
2026-02-23 01:55 initial mvp wayverd 139 17808 0
...

1use nom::{
2    IResult, Parser,
3    branch::alt,
4    bytes::complete::{take_while, take_while1},
5    character::complete::{char, satisfy},
6    combinator::{map, recognize},
7    sequence::{delimited, pair, terminated},
8};
9
10pub(super) fn autolink(input: &str) -> IResult<&str, String> {
11    delimited(char('<'), alt((uri, email)), char('>')).parse(input)
12}
13
14/// uri: scheme ":" [^<>\u0000-\u0020]*
15fn uri(input: &str) -> IResult<&str, String> {
16    map(
17        pair(
18            terminated(scheme, char(':')),
19            take_while(|c: char| {
20                !c.is_ascii_control() && !c.is_ascii_whitespace() && c != '<' && c != '>'
21            }),
22        ),
23        |(scheme_part, rest): (&str, &str)| {
24            let mut s = String::from(scheme_part);
25            s.push(':');
26            s.push_str(rest);
27            s
28        },
29    )
30    .parse(input)
31}
32
33/// email: simplified form for <[email protected]>
34fn email(input: &str) -> IResult<&str, String> {
35    map(
36        recognize(pair(
37            take_while1(|c: char| c.is_ascii_alphanumeric() || "_-.".contains(c)),
38            pair(
39                char('@'),
40                take_while1(|c: char| c.is_ascii_alphanumeric() || ".-".contains(c)),
41            ),
42        )),
43        |v: &str| v.to_string(),
44    )
45    .parse(input)
46}
47
48/// scheme: [a-zA-Z][a-zA-Z0-9+.-]{1,31}
49fn scheme(input: &str) -> IResult<&str, &str> {
50    recognize(pair(satisfy(is_scheme_start), take_while1(is_scheme_char))).parse(input)
51}
52
53const fn is_scheme_char(c: char) -> bool {
54    c.is_ascii_alphanumeric() || matches!(c, '+' | '.' | '-')
55}
56
57const fn is_scheme_start(c: char) -> bool {
58    c.is_ascii_alphabetic()
59}
60