summaryrefslogtreecommitdiff
path: root/src/string_utils.rs
blob: cceb57585b49aca7b7c3abdf905982dda21079b6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
use crate::*;


#[derive(Clone)]
pub struct Name {
    raw: String,
}

impl Name {
    /// Preserve markdown syntax, return raw string.
    pub fn raw(&self) -> String {
        self.raw.clone()
    }
    /// Parse markdown syntax, return styled line.
    pub fn styled(&self) -> Line {
        Line::from_str(&self.raw)
    }
    /// Strip out markdown syntax, return plain text.
    pub fn plain(&self) -> String {
        self.styled().to_string()
    }
    /// Strip out markdown syntax, return url-safe text.
    pub fn slug(&self) -> String {
        to_slug(&self.plain())
    }
}

impl std::fmt::Display for Name {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
        self.raw.fmt(f)
    }
}

impl std::fmt::Debug for Name {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
        self.raw.fmt(f)
    }
}

impl PartialEq for Name {
    fn eq(&self, other: &string_utils::Name) -> bool {
        self.slug() == other.slug()
    }
}

impl Eq for Name {}
impl std::hash::Hash for Name {
    fn hash<H>(&self, hasher: &mut H) where H: std::hash::Hasher {
        self.slug().hash(hasher)
    }
}

impl From<String> for Name {
    fn from(raw: String) -> Self {
        Self { raw }
    }
}

impl From<&str> for Name {
    fn from(raw: &str) -> Self {
        Self { raw: raw.to_string() }
    }
}



// Turn a string into a tidy URL slug.
pub fn to_slug(text: &str) -> String {
    let mut string = String::new();
    let mut prev = ' ';
    for c in text.to_lowercase().chars() {
        let c = match c == ' ' {
            true => '-',
            false => c,
        };
        if c.is_alphanumeric()     { string.push(c) }
        if "_~.+/#".contains(c)    { string.push(c) }
        if c == '-' && prev != '-' { string.push(c) }
        prev = c;
    }
    return string;
}

// Prevent link hrefs from breaking out of quotations.
pub fn url_encode(text: &str) -> String {
    let mut output = String::new();
    for c in text.chars() {
        match c {
            '"' => output.push_str("%22"),
            '\'' => output.push_str("%27"),
            _ => output.push(c),
        }
    }
    return output;
}

/// Replace each HTML-reserved character with an HTML-escaped character.
pub fn sanitize_text(text: &str, fancy: bool) -> String {
    let mut output = String::new();
    let chars: Vec<char> = text.chars().collect();
    for (i, c) in chars.iter().enumerate() {
        let prev = match i > 0 {
            true => chars[i - 1],
            false => ' ',
        };
        let next = match i + 1 < chars.len() {
            true => chars[i + 1],
            false => ' ',
        };
        let is_whitespace = |c: char| c.is_whitespace() || "()[].,".contains(c);

        match c {
            '&' => {
                // The HTML syntax for unicode characters is &#0000
                if let Some('#') = chars.get(i+1) { output.push(*c) }
                else { output.push_str("&amp;") }
            },
            '<' => output.push_str("&lt;"),
            '>' => output.push_str("&gt;"),
            '"' => match fancy {
                true => match is_whitespace(prev) {
                    true  => output.push('“'),
                    false => output.push('”'),
                }
                false => output.push_str("&#34;"),
            },
            '\'' => match fancy {
                true => match is_whitespace(prev) {
                    true  => output.push('‘'),
                    false => output.push('’'),
                }
                false => output.push_str("&#39;"),
            },
            '-' if fancy => match prev.is_whitespace() && next.is_whitespace() {
                true => match i > 0 {
                    true => output.push('—'),  // em-dash, for mid-sentence
                    false => output.push('–'),  // en-dash, for start of line
                }
                false => output.push('-'),      // regular dash, for mid-word
            }
            _ => output.push(*c),
        }
    }
    return output;
}