2020-02-05 08:13:14 +00:00
|
|
|
use serde_derive::{Deserialize, Serialize};
|
|
|
|
|
|
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "lowercase")]
|
|
|
|
pub enum SlugifyStrategy {
|
|
|
|
/// Classic slugification, the default
|
|
|
|
On,
|
|
|
|
/// No slugification, only remove unsafe characters for filepaths/urls
|
|
|
|
Safe,
|
|
|
|
/// Nothing is changed, hope for the best!
|
|
|
|
Off,
|
|
|
|
}
|
|
|
|
|
2019-12-21 09:44:13 +00:00
|
|
|
fn strip_chars(s: &str, chars: &str) -> String {
|
|
|
|
let mut sanitized_string = s.to_string();
|
2019-12-23 08:21:51 +00:00
|
|
|
sanitized_string.retain(|c| !chars.contains(c));
|
2019-12-21 09:44:13 +00:00
|
|
|
sanitized_string
|
|
|
|
}
|
|
|
|
|
|
|
|
fn strip_invalid_paths_chars(s: &str) -> String {
|
|
|
|
// NTFS forbidden characters : https://gist.github.com/doctaphred/d01d05291546186941e1b7ddc02034d3
|
2020-02-05 08:13:14 +00:00
|
|
|
// Also we need to trim whitespaces and `.` from the end of filename
|
2019-12-21 09:44:13 +00:00
|
|
|
let trimmed = s.trim_end_matches(|c| c == ' ' || c == '.');
|
2020-02-05 08:13:14 +00:00
|
|
|
strip_chars(&trimmed, r#"<>:"/\|?*"#)
|
2019-12-21 09:44:13 +00:00
|
|
|
}
|
|
|
|
|
2020-02-05 08:13:14 +00:00
|
|
|
pub fn slugify_paths(s: &str, strategy: SlugifyStrategy) -> String {
|
|
|
|
match strategy {
|
|
|
|
SlugifyStrategy::On => slug::slugify(s),
|
|
|
|
SlugifyStrategy::Safe => strip_invalid_paths_chars(s),
|
|
|
|
SlugifyStrategy::Off => s.to_string(),
|
2019-12-21 09:44:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-05 08:13:14 +00:00
|
|
|
pub fn slugify_anchors(s: &str, strategy: SlugifyStrategy) -> String {
|
|
|
|
match strategy {
|
|
|
|
SlugifyStrategy::On => slug::slugify(s),
|
|
|
|
SlugifyStrategy::Safe | SlugifyStrategy::Off => s.replace(|c: char| c.is_ascii_whitespace(), "_"),
|
2019-12-21 09:44:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
2020-02-05 08:13:14 +00:00
|
|
|
fn can_slugify_paths() {
|
2019-12-21 09:44:13 +00:00
|
|
|
let tests = vec![
|
2020-02-05 08:13:14 +00:00
|
|
|
// input, (on, safe, off)
|
|
|
|
("input", ("input", "input", "input")),
|
|
|
|
("test ", ("test", "test", "test ")),
|
|
|
|
("tes t", ("tes-t", "tes t", "tes t")),
|
|
|
|
// Invalid NTFS
|
|
|
|
("dot. ", ("dot", "dot", "dot. ")),
|
|
|
|
("日本", ("ri-ben", "日本", "日本")),
|
|
|
|
("héhé", ("hehe", "héhé", "héhé")),
|
|
|
|
("test (hey)", ("test-hey", "test (hey)", "test (hey)")),
|
2019-12-21 09:44:13 +00:00
|
|
|
];
|
|
|
|
|
2020-02-05 08:13:14 +00:00
|
|
|
for (input, (on, safe, off)) in tests {
|
|
|
|
assert_eq!(on, slugify_paths(input, SlugifyStrategy::On));
|
|
|
|
assert_eq!(safe, slugify_paths(input, SlugifyStrategy::Safe));
|
|
|
|
assert_eq!(off, slugify_paths(input, SlugifyStrategy::Off));
|
2019-12-21 09:44:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2020-02-05 08:13:14 +00:00
|
|
|
fn can_slugify_anchors() {
|
2019-12-21 09:44:13 +00:00
|
|
|
let tests = vec![
|
2020-02-05 08:13:14 +00:00
|
|
|
// input, (on, safe, off)
|
|
|
|
("input", ("input", "input", "input")),
|
|
|
|
("test ", ("test", "test_", "test_")),
|
|
|
|
("tes t", ("tes-t", "tes_t", "tes_t")),
|
|
|
|
// Invalid NTFS
|
|
|
|
("dot. ", ("dot", "dot._", "dot._")),
|
|
|
|
("日本", ("ri-ben", "日本", "日本")),
|
|
|
|
("héhé", ("hehe", "héhé", "héhé")),
|
|
|
|
("test (hey)", ("test-hey", "test_(hey)", "test_(hey)")),
|
2019-12-21 09:44:13 +00:00
|
|
|
];
|
|
|
|
|
2020-02-05 08:13:14 +00:00
|
|
|
for (input, (on, safe, off)) in tests {
|
|
|
|
assert_eq!(on, slugify_anchors(input, SlugifyStrategy::On));
|
|
|
|
assert_eq!(safe, slugify_anchors(input, SlugifyStrategy::Safe));
|
|
|
|
assert_eq!(off, slugify_anchors(input, SlugifyStrategy::Off));
|
2019-12-21 09:44:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|