pub fn substring_text(text: &str, start: i64, count: Option<i64>) -> Option<String> {
if count.is_some_and(|count| count < 0) {
return None;
}
let chars: Vec<char> = text.chars().collect();
let start_idx = if start <= 1 {
0
} else {
usize::try_from(start - 1).unwrap_or(usize::MAX)
};
if start_idx >= chars.len() {
return Some(String::new());
}
let end_idx = match count {
Some(count) => start_idx
.saturating_add(usize::try_from(count).unwrap_or(usize::MAX))
.min(chars.len()),
None => chars.len(),
};
Some(chars[start_idx..end_idx].iter().collect())
}
pub fn substring_pattern_text(text: &str, pattern: &str) -> Option<String> {
let regex = regex::RegexBuilder::new(pattern)
.nest_limit(250)
.build()
.ok()?;
let captures = regex.captures(text)?;
if captures.len() > 1 {
return captures.get(1).map(|capture| capture.as_str().to_string());
}
captures.get(0).map(|capture| capture.as_str().to_string())
}
pub fn position_text(needle: &str, haystack: &str) -> i64 {
if needle.is_empty() {
return 1;
}
haystack
.find(needle)
.map(|byte_idx| haystack[..byte_idx].chars().count() as i64 + 1)
.unwrap_or(0)
}
pub fn slice_left_text(text: &str, count: i64) -> String {
let chars: Vec<char> = text.chars().collect();
let take = normalized_slice_len(chars.len(), count);
chars.into_iter().take(take).collect()
}
pub fn slice_right_text(text: &str, count: i64) -> String {
let chars: Vec<char> = text.chars().collect();
let take = normalized_slice_len(chars.len(), count);
let len = chars.len();
chars.into_iter().skip(len.saturating_sub(take)).collect()
}
fn normalized_slice_len(len: usize, count: i64) -> usize {
if count >= 0 {
usize::try_from(count).unwrap_or(usize::MAX).min(len)
} else {
len.saturating_sub(usize::try_from(count.unsigned_abs()).unwrap_or(usize::MAX))
}
}
pub fn quote_literal_text(text: &str) -> String {
let escaped = text.replace('\'', "''");
if text.contains('\\') {
format!("E'{}'", escaped.replace('\\', "\\\\"))
} else {
format!("'{escaped}'")
}
}
pub fn trim_text(text: &str, chars: Option<&str>, left: bool, right: bool) -> String {
match chars {
Some(chars) => {
let predicate = |ch| chars.contains(ch);
match (left, right) {
(true, true) => text.trim_matches(predicate).to_string(),
(true, false) => text.trim_start_matches(predicate).to_string(),
(false, true) => text.trim_end_matches(predicate).to_string(),
(false, false) => text.to_string(),
}
}
None => match (left, right) {
(true, true) => text.trim().to_string(),
(true, false) => text.trim_start().to_string(),
(false, true) => text.trim_end().to_string(),
(false, false) => text.to_string(),
},
}
}