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(&self, hasher: &mut H) where H: std::hash::Hasher { self.slug().hash(hasher) } } impl From 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 { text.to_ascii_lowercase().chars().filter_map(|c| if c.is_alphanumeric() || "-_~.+/#".contains(c) { Some(c) } else if c == ' ' { Some('-') } else { None } ) .collect() } // 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 = 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; }