use regex::Regex;
use std::collections::HashMap;
use thiserror::Error;
fn split_words(s: &str) -> Vec<String> {
let mut words = Vec::new();
let mut current_word = String::new();
let mut chars = s.chars().peekable();
while let Some(ch) = chars.next() {
if ch.is_whitespace() || ch == '.' || ch == '-' || ch == '_' {
if !current_word.is_empty() {
words.push(current_word.clone());
current_word.clear();
}
} else if ch.is_uppercase() && !current_word.is_empty() {
if let Some(&next_ch) = chars.peek() {
if next_ch.is_lowercase() {
words.push(current_word.clone());
current_word.clear();
}
}
current_word.push(ch);
} else {
current_word.push(ch);
}
}
if !current_word.is_empty() {
words.push(current_word);
}
words
}
#[derive(Error, Debug, PartialEq)]
pub enum StringError {
#[error("Invalid input: {message}")]
InvalidInput { message: String },
#[error("Regex error: {message}")]
RegexError { message: String },
}
pub fn gen_all_cases_combination(s: &str) -> Vec<String> {
let mut result = Vec::new();
let chars: Vec<char> = s.chars().collect();
fn dfs(chars: &[char], index: usize, current: &mut String, result: &mut Vec<String>) {
if index == chars.len() {
result.push(current.clone());
return;
}
let ch = chars[index];
if ch.is_alphabetic() {
current.push(ch.to_lowercase().next().unwrap_or(ch));
dfs(chars, index + 1, current, result);
current.pop();
current.push(ch.to_uppercase().next().unwrap_or(ch));
dfs(chars, index + 1, current, result);
current.pop();
} else {
current.push(ch);
dfs(chars, index + 1, current, result);
current.pop();
}
}
let mut current = String::new();
dfs(&chars, 0, &mut current, &mut result);
result
}
pub fn generate_uuid() -> String {
use std::fmt::Write;
let mut uuid = String::with_capacity(36);
let rng = || -> u8 { (rand::random::<f64>() * 16.0) as u8 };
for i in 0..32 {
if i == 8 || i == 12 || i == 16 || i == 20 {
uuid.push('-');
}
let digit = if i == 12 {
4 } else if i == 16 {
(rng() & 0x3) | 0x8 } else {
rng()
};
write!(&mut uuid, "{digit:x}").unwrap();
}
uuid
}
const BASE62_CHARS: &[u8] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
pub fn generate_base62_code(len: usize) -> Result<String, StringError> {
if len == 0 {
return Err(StringError::InvalidInput {
message: "Length must be greater than 0".to_string(),
});
}
let mut result = String::with_capacity(len);
for _ in 0..len {
let idx = (rand::random::<f64>() * 62.0) as usize;
result.push(BASE62_CHARS[idx] as char);
}
Ok(result)
}
pub fn fuzzy_match(search_value: &str, target_string: &str) -> bool {
target_string.to_lowercase().contains(&search_value.to_lowercase())
}
pub fn get_file_ext(filename: &str) -> &str {
filename.rfind('.').map_or("", |pos| &filename[pos + 1..])
}
pub fn capitalize(s: &str) -> String {
if s.is_empty() {
return String::new();
}
let mut chars = s.chars();
match chars.next() {
None => String::new(),
Some(first) => first.to_uppercase().collect::<String>() + &chars.as_str().to_lowercase(),
}
}
pub fn camel_case(s: &str) -> String {
let parts = split_words(s);
if parts.is_empty() {
return String::new();
}
let mut result = parts[0].to_lowercase();
for part in &parts[1..] {
result.push_str(&capitalize(part));
}
result
}
pub fn snake_case(s: &str) -> String {
let parts = split_words(s);
if parts.is_empty() {
return String::new();
}
parts.iter().map(|part| part.to_lowercase()).collect::<Vec<_>>().join("_")
}
pub fn dash_case(s: &str) -> String {
let parts = split_words(s);
if parts.is_empty() {
return String::new();
}
parts.iter().map(|part| part.to_lowercase()).collect::<Vec<_>>().join("-")
}
pub fn pascal_case(s: &str) -> String {
let parts = split_words(s);
parts.iter().map(|part| capitalize(part)).collect::<Vec<_>>().join("")
}
pub fn parse_template(
template: &str,
data: &HashMap<String, String>,
regex_pattern: Option<&str>,
) -> String {
let pattern = regex_pattern.unwrap_or(r"\{\{(.+?)\}\}");
let re = Regex::new(pattern).unwrap();
let mut result = template.to_string();
for caps in re.captures_iter(template) {
let full_match = caps.get(0).unwrap().as_str();
let key = caps.get(1).unwrap().as_str();
if let Some(value) = data.get(key) {
result = result.replace(full_match, value);
}
}
result
}
pub fn trim(s: &str, chars_to_trim: Option<&str>) -> String {
let chars = chars_to_trim.unwrap_or(" ");
s.trim_matches(|c: char| chars.contains(c)).to_string()
}
pub fn trim_start(s: &str, chars_to_trim: Option<&str>) -> String {
let chars = chars_to_trim.unwrap_or(" ");
s.trim_start_matches(|c: char| chars.contains(c)).to_string()
}
pub fn trim_end(s: &str, chars_to_trim: Option<&str>) -> String {
let chars = chars_to_trim.unwrap_or(" ");
s.trim_end_matches(|c: char| chars.contains(c)).to_string()
}
pub fn remove_prefix(s: &str, prefix: &str) -> String {
if let Some(stripped) = s.strip_prefix(prefix) {
stripped.to_string()
} else {
s.to_string()
}
}
pub fn generate_merge_paths(branches: &[String]) -> Vec<Vec<String>> {
if branches.len() < 2 {
return Vec::new();
}
let mut paths = Vec::new();
for i in 0..branches.len() - 1 {
paths.push(vec![branches[i].clone(), branches[i + 1].clone()]);
}
paths
}
pub fn pad_start(s: &str, target_length: usize, pad_string: Option<&str>) -> String {
if s.len() >= target_length {
return s.to_string();
}
let pad_char = pad_string.unwrap_or(" ");
let pad_length = target_length - s.len();
let padding = pad_char.repeat(pad_length / pad_char.len() + 1);
let trimmed_padding = &padding[..pad_length];
format!("{trimmed_padding}{s}")
}
pub fn pad_end(s: &str, target_length: usize, pad_string: Option<&str>) -> String {
if s.len() >= target_length {
return s.to_string();
}
let pad_char = pad_string.unwrap_or(" ");
let pad_length = target_length - s.len();
let padding = pad_char.repeat(pad_length / pad_char.len() + 1);
let trimmed_padding = &padding[..pad_length];
format!("{s}{trimmed_padding}")
}
pub fn repeat(s: &str, count: usize) -> String {
s.repeat(count)
}
pub fn starts_with(s: &str, search_string: &str) -> bool {
s.starts_with(search_string)
}
pub fn ends_with(s: &str, search_string: &str) -> bool {
s.ends_with(search_string)
}
pub fn includes(s: &str, search_string: &str) -> bool {
s.contains(search_string)
}
pub fn char_at(s: &str, index: usize) -> Option<char> {
s.chars().nth(index)
}
pub fn substring(s: &str, start: usize, end: Option<usize>) -> String {
let chars: Vec<char> = s.chars().collect();
let start_idx = start.min(chars.len());
let end_idx = end.unwrap_or(chars.len()).min(chars.len());
if start_idx >= end_idx {
return String::new();
}
chars[start_idx..end_idx].iter().collect()
}
pub fn split(s: &str, separator: &str) -> Vec<String> {
if separator.is_empty() {
return s.chars().map(|c| c.to_string()).collect();
}
s.split(separator).map(|s| s.to_string()).collect()
}
pub fn replace_all(s: &str, search: &str, replacement: &str) -> String {
if search.is_empty() {
return s.to_string();
}
s.replace(search, replacement)
}