diff options
Diffstat (limited to 'src/string_utils.rs')
| -rw-r--r-- | src/string_utils.rs | 145 |
1 files changed, 145 insertions, 0 deletions
diff --git a/src/string_utils.rs b/src/string_utils.rs new file mode 100644 index 0000000..cceb575 --- /dev/null +++ b/src/string_utils.rs @@ -0,0 +1,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 � + if let Some('#') = chars.get(i+1) { output.push(*c) } + else { output.push_str("&") } + }, + '<' => output.push_str("<"), + '>' => output.push_str(">"), + '"' => match fancy { + true => match is_whitespace(prev) { + true => output.push('“'), + false => output.push('”'), + } + false => output.push_str("""), + }, + '\'' => match fancy { + true => match is_whitespace(prev) { + true => output.push('‘'), + false => output.push('’'), + } + false => output.push_str("'"), + }, + '-' 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; +} |
