summaryrefslogtreecommitdiff
path: root/src/string_utils.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/string_utils.rs')
-rw-r--r--src/string_utils.rs145
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 &#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;
+}