use crate::ascii;
use crate::underscore::replace_underscores;
use regex::Regex;
use trim_in_place::TrimInPlace;
macro_rules! regex {
($name:tt, $expr:expr) => {
lazy_static! {
static ref $name: Regex = Regex::new($expr).unwrap();
}
};
}
regex!(NON_NORMAL, r"[^a-z0-9\-:_]");
regex!(LEADING_OR_TRAILING_DASHES, r"(^-+)|(-+$)");
regex!(MULTIPLE_DASHES, r"-{2,}");
regex!(MULTIPLE_COLONS, r":{2,}");
regex!(COLON_DASH, r"(:-)|(-:)");
regex!(UNDERSCORE_DASH, r"(_-)|(-_)");
regex!(LEADING_OR_TRAILING_COLON, r"(^:)|(:$)");
pub fn normalize(text: &mut String) {
text.trim_in_place();
if text.starts_with('/') {
text.replace_range(..1, "");
}
ascii::transform_in_place(text);
text.make_ascii_lowercase();
replace_in_place(text, &*NON_NORMAL, "-");
replace_underscores(text);
replace_in_place(text, &*LEADING_OR_TRAILING_DASHES, "");
replace_in_place(text, &*MULTIPLE_DASHES, "-");
replace_in_place(text, &*MULTIPLE_COLONS, ":");
replace_in_place(text, &*COLON_DASH, ":");
replace_in_place(text, &*UNDERSCORE_DASH, "_");
replace_in_place(text, &*LEADING_OR_TRAILING_COLON, "");
if text.starts_with("_default:") {
text.replace_range(..9, "");
}
}
fn replace_in_place(text: &mut String, regex: &Regex, replace_with: &str) {
use regex::Captures;
use std::ops::Range;
fn get_range(captures: Captures) -> Range<usize> {
let mtch = captures.get(0).unwrap();
let start = mtch.start();
let end = mtch.end();
start..end
}
while let Some(captures) = regex.captures(text) {
let range = get_range(captures);
text.replace_range(range, replace_with);
}
}
#[test]
fn test_normalize() {
macro_rules! check {
($input:expr, $expected:expr $(,)?) => {{
let mut text = str!($input);
normalize(&mut text);
assert_eq!(text, $expected, "Normalized text doesn't match expected");
}};
}
check!("", "");
check!("Big Cheese Horace", "big-cheese-horace");
check!("bottom--Text", "bottom-text");
check!("Tufto's Proposal", "tufto-s-proposal");
check!(" - Test - ", "test");
check!("--TEST--", "test");
check!("-test-", "test");
check!(":test", "test");
check!("test:", "test");
check!(":test:", "test");
check!("/Some Page", "some-page");
check!("some/Page", "some-page");
check!("some,Page", "some-page");
check!("End of Death Hub", "end-of-death-hub");
check!("$100 is a lot of money", "100-is-a-lot-of-money");
check!("snake_case", "snake-case");
check!("long__snake__case", "long-snake-case");
check!("_template", "_template");
check!("_template_", "_template");
check!("__template", "_template");
check!("__template_", "_template");
check!("template_", "template");
check!("template__", "template");
check!("_Template", "_template");
check!("_Template_", "_template");
check!("__Template", "_template");
check!("__Template_", "_template");
check!("Template_", "template");
check!("Template__", "template");
check!(" <[ TEST ]> ", "test");
check!("ÄÀ-áö ðñæ_þß*řƒŦ", "aa-aoe-dnae-tss-rft");
check!("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", "");
check!("Component:image block", "component:image-block");
check!("fragment:scp-4447-2", "fragment:scp-4447-2");
check!("fragment::scp-4447-2", "fragment:scp-4447-2");
check!("FRAGMENT:SCP-4447 (2)", "fragment:scp-4447-2");
check!("protected_:fragment_:page", "protected:fragment:page");
check!("protected:_fragment_:page", "protected:_fragment:page");
check!("fragment:_template", "fragment:_template");
check!("fragment:__template", "fragment:_template");
check!("fragment:_template_", "fragment:_template");
check!("fragment::_template", "fragment:_template");
check!("_default:_template", "_template");
check!("_default:__template", "_template");
check!("_default:_template_", "_template");
check!("_default::_template", "_template");
check!("/fragment:_template", "fragment:_template");
check!("/fragment:__template", "fragment:_template");
check!("/fragment:_template_", "fragment:_template");
check!("/fragment::_template", "fragment:_template");
check!("/_default:_template", "_template");
check!("/_default:__template", "_template");
check!("/_default:_template_", "_template");
check!("/_default::_template", "_template");
check!(
"protected:fragment:_template",
"protected:fragment:_template",
);
check!(
"protected:fragment:__template",
"protected:fragment:_template",
);
check!(
"protected:fragment:_template_",
"protected:fragment:_template",
);
check!(
"protected:fragment::_template",
"protected:fragment:_template",
);
check!(
"protected::fragment:_template",
"protected:fragment:_template",
);
check!(
"protected::fragment::_template",
"protected:fragment:_template",
);
}