use deunicode::deunicode;
use regex::Regex;
use unicode_categories::UnicodeCategories;
use crate::options::Options;
#[derive(Debug, Clone)]
pub struct Slugifier {
options: Options,
}
impl Slugifier {
pub fn new() -> Self {
Self {
options: Options::default(),
}
}
pub fn with_options(options: Options) -> Self {
Self { options }
}
pub fn options(&self) -> &Options {
&self.options
}
pub fn options_mut(&mut self) -> &mut Options {
&mut self.options
}
pub fn separator(mut self, separator: impl Into<String>) -> Self {
self.options.separator = separator.into();
self
}
pub fn locale(mut self, locale: Option<crate::Locale>) -> Self {
self.options.locale = locale;
self
}
pub fn remove(mut self, remove: Option<Regex>) -> Self {
self.options.remove = remove;
self
}
pub fn lowercase(mut self, lowercase: bool) -> Self {
self.options.lowercase = lowercase;
self
}
pub fn trim(mut self, trim: bool) -> Self {
self.options.trim = trim;
self
}
pub fn max_length(mut self, max_length: Option<usize>) -> Self {
self.options.max_length = max_length;
self
}
pub fn slugify(&self, input: &str) -> String {
slugify_impl(input, &self.options)
}
}
impl Default for Slugifier {
fn default() -> Self {
Self::new()
}
}
pub fn slugify_with_options(input: &str, options: &Options) -> String {
slugify_impl(input, options)
}
fn slugify_impl(input: &str, options: &Options) -> String {
let mut text = input.to_string();
if let Some(ref remove_regex) = options.remove {
text = remove_regex.replace_all(&text, "").into_owned();
}
if let Some(locale) = options.locale {
text = locale.apply(&text);
}
text = text
.chars()
.filter(|c| {
c.is_letter() || c.is_number() || c.is_whitespace() || matches!(c, '-' | '_')
})
.collect::<String>();
text = deunicode(&text);
let sep = &options.separator;
let pattern = Regex::new(r"[^A-Za-z0-9]+").unwrap();
text = pattern.replace_all(&text, sep.as_str()).into_owned();
let repeated_sep_pattern = format!("(?:{})+", regex::escape(sep));
let multi_sep = Regex::new(&repeated_sep_pattern).unwrap();
text = multi_sep.replace_all(&text, sep.as_str()).into_owned();
if options.trim {
text = text
.trim_matches(|c| sep.chars().any(|s| s == c))
.to_string();
}
if options.lowercase {
text = text.to_lowercase();
}
if let Some(max_len) = options.max_length {
if text.len() > max_len {
text.truncate(max_len);
if options.trim {
text = text
.trim_matches(|c| sep.chars().any(|s| s == c))
.to_string();
}
}
}
text
}