pub trait StringExtensions {
fn find_start_at(&self, search_term: &str, starting_index: usize) -> Option<usize>;
fn find_vec_start_at(&self, search_terms: Vec<&str>, starting_index: usize) -> Option<usize>;
fn char_at(&self, index: usize) -> Option<char>;
fn substring(&self, start: usize, len: usize) -> Option<String>;
fn slice(&self, start: usize, end: usize) -> Option<String>;
}
impl StringExtensions for String {
fn substring(&self, start: usize, len: usize) -> Option<String> {
substring(self, start, len)
}
fn char_at(&self, index: usize) -> Option<char> {
char_at(self, index)
}
fn find_start_at(&self, search_term: &str, starting_index: usize) -> Option<usize> {
return find_start_at(self, search_term, starting_index);
}
fn find_vec_start_at(&self, search_terms: Vec<&str>, starting_index: usize) -> Option<usize> {
return find_vec_start_at(&self, search_terms, starting_index);
}
fn slice(&self, start: usize, end: usize) -> Option<String> {
return slice(&self, start, end);
}
}
impl StringExtensions for &str {
fn substring(&self, start: usize, len: usize) -> Option<String> {
substring(&self, start, len)
}
fn char_at(&self, index: usize) -> Option<char> {
char_at(self, index)
}
fn find_start_at(&self, search_term: &str, starting_index: usize) -> Option<usize> {
return find_start_at(&self, search_term, starting_index);
}
fn find_vec_start_at(&self, search_terms: Vec<&str>, starting_index: usize) -> Option<usize> {
return find_vec_start_at(&self, search_terms, starting_index);
}
fn slice(&self, start: usize, end: usize) -> Option<String> {
return slice(&self, start, end);
}
}
fn substring(string: &str, start: usize, len: usize) -> Option<String> {
let content = string.to_string();
let mut index = start;
let end = start + len;
let mut result = String::new();
if end >= string.chars().count() {
return None;
}
while index < end {
let ch = content.char_at(index);
match ch {
Some(ch) => result.push(ch),
None => return None
}
index += 1;
}
Some(result)
}
fn char_at(string: &str, index: usize) -> Option<char> {
let length = string.chars().count();
if length > 0 || length > index {
let char = string.chars().nth(index);
return char;
}
return None;
}
fn slice(string: &str, start: usize, end: usize) -> Option<String> {
if end >= string.chars().count() {
return None;
}
let mut result = String::new();
for (i, ch) in string.char_indices() {
if i < start {
continue;
}
else if i > end {
break;
}
result.push(ch);
}
Some(result)
}
fn find_start_at(string: &str, search_term: &str, starting_index: usize) -> Option<usize> {
let slice = &string[starting_index..];
let index_in_slice = slice.find(search_term);
match index_in_slice {
Some(index_in_slice) => return Some(starting_index + index_in_slice),
None => return None
};
}
fn find_vec_start_at(string: &str, search_terms: Vec<&str>, starting_index: usize) -> Option<usize> {
let mut closest_index = usize::MAX;
for search_term in search_terms {
let start = find_start_at(string, search_term, starting_index);
match start {
Some(start) => {
if start <= closest_index {
closest_index = start
}
},
None => continue }
}
Some(closest_index)
}
#[cfg(test)]
mod tests {
use core::panic;
use super::*;
#[test]
fn test_char_at() {
let input = String::from("test");
let index = 3;
let actual = input.char_at(index);
match actual {
Some(actual) => assert_eq!('t', actual),
None => panic!()
}
}
#[test]
fn test_char_at_out_of_index_returns_none() {
let input = String::from("test");
let index = 4;
let actual = input.char_at(index);
assert_eq!(false, actual.is_some());
}
#[test]
fn find_start_at_returns_correct_index() {
let str = String::from("TestTestTest");
let expected = 5;
let actual = str.find_start_at("est", 3);
match actual {
Some(actual) => assert_eq!(expected, actual),
None => panic!()
}
}
#[test]
fn find_vec_start_at_returns_correct_index() {
let cases = vec![
("Test", vec!["e", "s", "t"], 1),
("INSERT INTO new_table\n", vec![" ", "\t", "\n"], 6),
("INSERT INTO\tnew_table\n", vec!["\t", "\n"], 11)
];
for (str, vec, expected) in cases {
let actual = str.find_vec_start_at(vec, 0);
match actual {
Some(actual) => assert_eq!(expected, actual),
None => panic!()
}
}
}
#[test]
fn slice_returns_correct_string() {
let str = String::from("This is an example string");
let expected = "example";
let actual = str.slice(11, 17);
match actual {
Some(actual) => assert_eq!(expected, actual),
None => panic!()
}
}
#[test]
fn slice_returns_none_when_end_is_out_of_bounds() {
let str = String::from("This is an example string");
let actual = str.slice(11, 25);
assert!(actual.is_none());
}
#[test]
fn substring_returns_correct_string() {
let str = String::from("This is an example string");
let expected = "example";
let actual = str.substring(11, 7);
match actual {
Some(actual) => assert_eq!(expected, actual),
None => panic!()
}
}
#[test]
fn substring_returns_none_when_end_is_out_of_bounds() {
let str = String::from("This is an example string");
let actual = str.substring(11, 25);
assert!(actual.is_none());
}
}