use regex::Regex;
use url::{ParseError, Url};
pub fn truncate(text: String, max: usize) -> String {
if max > text.len() {
text
} else {
let max_size = max - 3;
let str = format!("{}...", &text[..max_size]);
str
}
}
pub fn clean(text: String) -> String {
use lazy_static::lazy_static;
use regex::Regex;
use sanitize_html::rules::predefined::DEFAULT;
use sanitize_html::sanitize_str;
let cleaned = sanitize_str(&DEFAULT, text.as_str()).unwrap_or("".to_string());
lazy_static! {
static ref re: Regex = Regex::new(r"^\s+|\s+$").unwrap();
}
re.replace_all(&cleaned, "").to_string()
}
pub fn clean_truncate(text: String, max: usize) -> String {
let cleaned = clean(text);
truncate(cleaned, max)
}
pub fn uuid() -> String {
use uuid::{NoContext, Timestamp, Uuid};
let ts = Timestamp::now(NoContext);
Uuid::new_v7(ts).to_string()
}
pub const ALPHANUMERIC_CHARS: [char; 62] = [
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B',
'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
'V', 'W', 'X', 'Y', 'Z',
];
pub const SAFE_CHARS: [char; 64] = [
'_', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
];
pub fn parse_url(url: String) -> Result<String, ParseError> {
let parsed = Url::parse(&url)?;
let mut query = parsed.query().unwrap().to_string();
if !query.is_empty() {
query = format!("?{}", query);
}
let mut parser = format!("{}{}{}", parsed.host().unwrap(), parsed.path(), query);
parser = parser.replacen("www.", "", 1);
Ok(parser)
}
pub fn nanoid_format(chars: &[char], length: usize) -> String {
use nanoid::nanoid as create_nanoid;
let created = create_nanoid!(length, chars);
created
}
#[macro_export]
macro_rules! nanoid {
() => {
$crate::helper::nanoid_format(&$crate::helper::SAFE_CHARS, 15)
};
($size:tt) => {
$crate::helper::nanoid_format(&$crate::helper::SAFE_CHARS, $size)
};
($chars:expr) => {
$crate::helper::nanoid_format($chars, 15)
};
($chars:expr, $size:tt) => {
$crate::helper::nanoid_format($chars, $size)
};
}
pub fn ucwords(sentence: &str) -> String {
sentence
.split_whitespace()
.map(|word| {
let mut chars = word.chars();
chars
.next()
.map(|c| c.to_uppercase().collect::<String>())
.unwrap_or_default()
+ chars.as_str()
})
.collect::<Vec<_>>()
.join(" ")
}
pub fn first_letter_function(words: String, max: usize) -> String {
let letters = words.split_whitespace().filter_map(|word| {
word.chars()
.next()
.filter(|c| c.is_alphabetic())
.map(|c| c.to_ascii_uppercase())
});
if max == 0 {
letters.collect()
} else {
letters.take(max).collect()
}
}
#[macro_export]
macro_rules! first_letter {
($words:expr) => {
$crate::helper::first_letter_function($words, 0)
};
($words:expr,$size:tt) => {
$crate::helper::first_letter_function($words, $size)
};
}
pub fn slug(input: &str) -> String {
input
.to_lowercase()
.chars()
.filter_map(|c| {
if c.is_alphanumeric() {
Some(c)
} else if c.is_whitespace() || c == '-' || c == '_' {
Some('-')
} else {
None
}
})
.collect::<String>()
.split('-')
.filter(|s| !s.is_empty())
.collect::<Vec<_>>()
.join("-")
}
pub fn is_url(url: String) -> bool {
Url::parse(&url).is_ok()
}
pub fn is_twitter_url(url: String) -> bool {
use lazy_static::lazy_static;
use regex::Regex;
if !is_url(url.clone()) {
return false;
}
lazy_static! {
static ref re: Regex = Regex::new(r"^https?://(www.)?twitter\.com").unwrap();
}
re.is_match(url.as_str())
}
pub fn capitalize_first(s: String) -> String {
let trimmed = s.trim_start();
let mut chars = trimmed.chars();
match chars.next() {
None => String::new(),
Some(first) => {
let mut result = first.to_uppercase().collect::<String>();
result.push_str(chars.as_str());
result
}
}
}
pub fn validate_email(email: String) -> bool {
use lazy_static::lazy_static;
lazy_static! {
static ref re: Regex = Regex::new(r"^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$").unwrap();
}
re.is_match(email.as_str())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_truncate_long() {
let long_text = "This is very very very long text";
let truncated_text = truncate(long_text.to_string(), 25);
assert_eq!(truncated_text, "This is very very very...".to_string());
}
#[test]
fn test_truncate_short() {
let long_text = "This is short text";
let truncated_text = truncate(long_text.to_string(), 20);
assert_eq!(truncated_text, long_text.to_string());
}
#[test]
fn test_clean() {
let html = r#"<p>Hello World</p>"#;
let clean_text = clean(html.to_string());
assert_eq!(clean_text, "Hello World".to_string());
}
#[test]
fn test_clean_and_truncate() {
let html = r#"<p>Hello World This is Long Text</p>"#;
let clean_text = clean_truncate(html.to_string(), 19);
assert_eq!(clean_text, "Hello World This...".to_string());
}
#[test]
fn test_uuid() {
let uuid = uuid();
assert_eq!(uuid.len(), 36);
}
#[test]
fn test_nanoid() {
let str = nanoid!();
assert_eq!(str.len(), 15);
let str = nanoid!(30);
assert_eq!(str.len(), 30);
let str = nanoid!(&['1', '2', '3', '4', '5']);
assert_eq!(str.len(), 15);
assert!(
str.chars().all(|c| c.is_numeric()),
"Generated data contains non-numeric characters: {}",
str
);
let str = nanoid!(&['1', '2', '3', '4', '5'], 20);
assert_eq!(str.len(), 20);
assert!(
str.chars().all(|c| c.is_numeric()),
"Generated data contains non-numeric characters: {}",
str
);
}
#[test]
fn test_ucwords() {
let text = "hello world this is text";
let result = ucwords(text);
assert_eq!(result, "Hello World This Is Text".to_string());
}
#[test]
fn test_parse_url() {
let url = "https://www.portalnesia.com/news?foo=bar".to_string();
let parsed = parse_url(url).expect("Failed parser url");
assert_eq!(parsed, "portalnesia.com/news?foo=bar");
assert!(parse_url("error url".to_string()).is_err());
assert!(parse_url("https://err. https://".to_string()).is_err());
}
#[test]
fn test_first_letter() {
let cases = vec![
("hello world", 0, "HW"),
("Rust language", 0, "RL"),
(" hello rust world ", 0, "HRW"),
("", 0, ""),
("golang", 0, "G"),
("123 apples $banana", 0, "A"),
("#rust code!", 0, "C"),
("Hello World From Rust", 2, "HW"),
("Hello World From Rust", 3, "HWF"),
("Hello World From Rust", 5, "HWFR"), ("Hello 123 $World", 2, "H"), ];
for (input, max, expected) in cases {
let result = first_letter!(input.to_string(), max);
assert_eq!(
result, expected,
"first_letter({:?}, {}) should be {:?}, got {:?}",
input, max, expected, result
);
}
}
#[test]
fn test_slug() {
let cases = vec![
("Hello World", "hello-world"),
("Rust is awesome!", "rust-is-awesome"),
(" Hello---Rust___World!! ", "hello-rust-world"),
("Satu dua tiga empat", "satu-dua-tiga-empat"),
("Clean & Simple", "clean-simple"),
("Symbols #should @be $gone!", "symbols-should-be-gone"),
("MiXeD CaSe and123Numbers", "mixed-case-and123numbers"),
("--Already--Sluggy--", "already-sluggy"),
("", ""),
];
for (input, expected) in cases {
let result = slug(input);
assert_eq!(
result, expected,
"slug({:?}) should be {:?}, got {:?}",
input, expected, result
);
}
}
#[test]
fn test_is_url() {
let cases = vec![("https://", false), ("https://portalnesia.com", true)];
for (input, expected) in cases {
let result = is_url(input.to_string());
assert_eq!(
result, expected,
"is_url({:?}) should be {:?}, got {:?}",
input, expected, result
);
}
}
#[test]
fn test_is_twitter_url() {
let cases = vec![
("http://portalnesia.com/twitter.com/contact", false),
("http://portalnesia.com/contact", false),
("https://twitter.com/Portalnesia1", true),
];
for (input, expected) in cases {
let result = is_twitter_url(input.to_string());
assert_eq!(
result, expected,
"is_url({:?}) should be {:?}, got {:?}",
input, expected, result
);
}
}
#[test]
fn test_capitalize_first() {
let cases = vec![
("hello world".to_string(), "Hello world"),
("Hello world".to_string(), "Hello world"),
("rust".to_string(), "Rust"),
("r".to_string(), "R"),
("".to_string(), ""),
("äpfel sind lecker".to_string(), "Äpfel sind lecker"),
("123abc".to_string(), "123abc"),
(" multiple spaces".to_string(), "Multiple spaces"),
];
for (input, expected) in cases {
let got = capitalize_first(input.clone());
assert_eq!(
got, expected,
"capitalize_first({:?}) should be {:?}, got {:?}",
input, expected, got
);
}
}
#[test]
fn test_validate_email() {
let cases = vec![
("support@portalnesia".to_string(), false),
("support@portalnesia.com".to_string(), true),
(" support@portalnesia.com".to_string(), false),
("support@portalnesia.com ".to_string(), false),
];
for (input, expected) in cases {
let got = validate_email(input.clone());
assert_eq!(
got, expected,
"validate_email({:?}) should be {:?}, got {:?}",
input, expected, got
);
}
}
}