use crate::common;
use lazy_static::lazy_static;
use precis_core::profile::stabilize;
use precis_core::profile::{PrecisFastInvocation, Profile, Rules};
use precis_core::Error;
use precis_core::{FreeformClass, StringClass};
use std::borrow::Cow;
fn find_disallowed_space(label: &str) -> Option<usize> {
let mut begin = true;
let mut prev_space = false;
let mut last_c: Option<char> = None;
let mut offset = 0;
for (index, c) in label.chars().enumerate() {
offset = index;
if !common::is_space_separator(c) {
last_c = Some(c);
prev_space = false;
begin = false;
continue;
}
if begin {
return Some(index);
}
if prev_space {
return Some(index);
}
if c == common::SPACE {
prev_space = true;
last_c = Some(c);
} else {
return Some(index);
}
}
if let Some(common::SPACE) = last_c {
Some(offset)
} else {
None
}
}
fn trim_spaces<'a, T>(s: T) -> Result<Cow<'a, str>, Error>
where
T: Into<Cow<'a, str>>,
{
let s = s.into();
match find_disallowed_space(&s) {
None => Ok(s),
Some(pos) => {
let mut res = String::from(&s[..pos]);
res.reserve(s.len() - res.len());
let mut begin = true;
let mut prev_space = false;
for c in s[pos..].chars() {
if !common::is_space_separator(c) {
res.push(c);
prev_space = false;
begin = false;
continue;
}
if begin {
continue;
}
if !prev_space {
res.push(common::SPACE);
}
prev_space = true;
}
if let Some(c) = res.pop() {
if c != common::SPACE {
res.push(c);
}
}
Ok(res.into())
}
}
}
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
pub struct Nickname(FreeformClass);
impl Nickname {
pub fn new() -> Self {
Self(FreeformClass::default())
}
fn apply_prepare_rules<'a, T>(&self, s: T) -> Result<Cow<'a, str>, Error>
where
T: Into<Cow<'a, str>>,
{
let s = s.into();
let s = (!s.is_empty()).then_some(s).ok_or(Error::Invalid)?;
self.0.allows(&s)?;
Ok(s)
}
fn apply_enforce_rules<'a, T>(&self, s: T) -> Result<Cow<'a, str>, Error>
where
T: Into<Cow<'a, str>>,
{
let s = self.apply_prepare_rules(s)?;
let s = self.additional_mapping_rule(s)?;
let s = self.normalization_rule(s)?;
(!s.is_empty()).then_some(s).ok_or(Error::Invalid)
}
fn apply_compare_rules<'a, T>(&self, s: T) -> Result<Cow<'a, str>, Error>
where
T: Into<Cow<'a, str>>,
{
let s = self.apply_prepare_rules(s)?;
let s = self.additional_mapping_rule(s)?;
let s = self.case_mapping_rule(s)?;
self.normalization_rule(s)
}
}
impl Profile for Nickname {
fn prepare<'a, S>(&self, s: S) -> Result<Cow<'a, str>, Error>
where
S: Into<Cow<'a, str>>,
{
self.apply_prepare_rules(s)
}
fn enforce<'a, S>(&self, s: S) -> Result<Cow<'a, str>, Error>
where
S: Into<Cow<'a, str>>,
{
stabilize(s, |s| self.apply_enforce_rules(s))
}
fn compare<A, B>(&self, s1: A, s2: B) -> Result<bool, Error>
where
A: AsRef<str>,
B: AsRef<str>,
{
Ok(stabilize(s1.as_ref(), |s| self.apply_compare_rules(s))?
== stabilize(s2.as_ref(), |s| self.apply_compare_rules(s))?)
}
}
impl Rules for Nickname {
fn additional_mapping_rule<'a, T>(&self, s: T) -> Result<Cow<'a, str>, Error>
where
T: Into<Cow<'a, str>>,
{
trim_spaces(s)
}
fn case_mapping_rule<'a, T>(&self, s: T) -> Result<Cow<'a, str>, Error>
where
T: Into<Cow<'a, str>>,
{
common::case_mapping_rule(s)
}
fn normalization_rule<'a, T>(&self, s: T) -> Result<Cow<'a, str>, Error>
where
T: Into<Cow<'a, str>>,
{
common::normalization_form_nfkc(s)
}
}
fn get_nickname_profile() -> &'static Nickname {
lazy_static! {
static ref NICKNAME: Nickname = Nickname::default();
}
&NICKNAME
}
impl PrecisFastInvocation for Nickname {
fn prepare<'a, S>(s: S) -> Result<Cow<'a, str>, Error>
where
S: Into<Cow<'a, str>>,
{
get_nickname_profile().prepare(s)
}
fn enforce<'a, S>(s: S) -> Result<Cow<'a, str>, Error>
where
S: Into<Cow<'a, str>>,
{
get_nickname_profile().enforce(s)
}
fn compare<A, B>(s1: A, s2: B) -> Result<bool, Error>
where
A: AsRef<str>,
B: AsRef<str>,
{
get_nickname_profile().compare(s1, s2)
}
}
#[cfg(test)]
mod test_nicknames {
use crate::nicknames::*;
#[test]
fn test_find_disallowed_space() {
assert_eq!(find_disallowed_space(""), None);
assert_eq!(find_disallowed_space("test"), None);
assert_eq!(find_disallowed_space("test "), Some(4));
assert_eq!(find_disallowed_space("test good"), None);
assert_eq!(find_disallowed_space(" test"), Some(0));
assert_eq!(find_disallowed_space("t est"), Some(2));
assert_eq!(find_disallowed_space(" test"), Some(0));
assert_eq!(find_disallowed_space("\u{00a0}test"), Some(0));
assert_eq!(find_disallowed_space("te\u{00a0}st"), Some(2));
assert_eq!(find_disallowed_space("test\u{00a0}"), Some(4));
}
#[test]
fn test_trim_spaces() {
assert_eq!(trim_spaces(" "), Ok(Cow::from("")));
assert_eq!(trim_spaces(" test"), Ok(Cow::from("test")));
assert_eq!(trim_spaces("test "), Ok(Cow::from("test")));
assert_eq!(trim_spaces("hello world"), Ok(Cow::from("hello world")));
assert_eq!(trim_spaces(""), Ok(Cow::from("")));
assert_eq!(trim_spaces(" test"), Ok(Cow::from("test")));
assert_eq!(trim_spaces("test "), Ok(Cow::from("test")));
assert_eq!(
trim_spaces(" hello world "),
Ok(Cow::from("hello world"))
);
assert_eq!(trim_spaces("\u{205f}test\u{205f}"), Ok(Cow::from("test")));
assert_eq!(
trim_spaces("\u{205f}\u{205f}hello\u{205f}\u{205f}world\u{205f}\u{205f}"),
Ok(Cow::from("hello world"))
);
assert_eq!(trim_spaces(" \u{205f}test\u{205f} "), Ok(Cow::from("test")));
assert_eq!(
trim_spaces("\u{205f} hello \u{205f} world \u{205f} "),
Ok(Cow::from("hello world"))
);
}
#[test]
fn nick_name_profile() {
let profile = Nickname::new();
let res = profile.prepare("Foo Bar");
assert_eq!(res, Ok(Cow::from("Foo Bar")));
let res = profile.enforce("Foo Bar");
assert_eq!(res, Ok(Cow::from("Foo Bar")));
let res = profile.compare("Foo Bar", "foo bar");
assert_eq!(res, Ok(true));
}
}