bt_string_utils/
lib.rs

1//! Multiple String related functions
2
3use rand::{distr::Alphanumeric, Rng};
4use regex::Regex;
5
6/// Splits the given string at the first occurrence of the specified separator.
7///
8/// # Arguments
9///
10/// * `s` - A string slice to be split.
11/// * `separator` - The substring used as a separator.
12///
13/// # Returns
14///
15/// A tuple containing two strings:
16/// - The first part of the string before the separator.
17/// - The second part of the string after the separator.
18///
19/// If the separator is not found, returns the original string and an empty string.
20///
21/// # Examples
22///
23/// ```
24/// use bt_string_utils::get_first_of_split;
25/// let (part1, part2) = get_first_of_split("hello=world", "=");
26/// assert_eq!(part1, "hello");
27/// assert_eq!(part2, "world");
28///
29/// let (part1, part2) = get_first_of_split("key:value", ":");
30/// assert_eq!(part1, "key");
31/// assert_eq!(part2, "value");
32///
33/// let (part1, part2) = get_first_of_split("no=separator", " ");
34/// assert_eq!(part1, "no=separator");
35/// assert_eq!(part2, "");
36/// ```
37pub fn get_first_of_split(s: &str, separator: &str) -> (String, String){
38    if let Some(position) = s.find(separator){
39        let str1 = s[..position].to_owned();
40        let str2 = s[position + 1..].to_owned();
41        (str1, str2)
42    }else{
43        (s.to_owned(),"".to_owned())
44    }
45}
46
47/// Finds and returns the substring before the first occurrence of a given separator.
48///
49/// # Arguments
50///
51/// * `s` - A string slice that holds the text to search within.
52/// * `separator` - A string slice that specifies the character(s) to look for as a separator.
53///
54/// # Returns
55///
56/// Returns a new `String` containing the substring before the first occurrence of the separator.
57/// If the separator is not found, an empty `String` is returned.
58///
59/// # Examples
60///
61/// ```
62/// use bt_string_utils::get_first_occurrance;
63/// let result = get_first_occurrance("Hello, world!", ", ");
64/// assert_eq!(result, "Hello");
65///
66/// let result = get_first_occurrance("No separator here", ",");
67/// assert_eq!(result, "");
68/// ```
69
70pub fn get_first_occurrance(s: &str, separator: &str) -> String{
71    if let Some(position) = s.find(separator){
72        s[..position].to_owned()
73    }else{
74        "".to_owned()
75    }
76}
77
78/// Finds and returns the value corresponding to a given key in a vector of key-value pairs.
79///
80/// # Arguments
81///
82/// * `kv_pairs` - A reference to a vector of strings where each string represents a key-value pair separated by '='.
83/// * `key_to_find` - The key for which the corresponding value is to be found.
84///
85/// # Returns
86///
87/// Returns an `Option`:
88/// - `Some(value)` if a matching key is found, containing the value associated with that key.
89/// - `None` if no matching key is found.
90///
91/// # Examples
92///
93/// ```
94/// use bt_string_utils::find_value_by_key;
95/// let pairs = vec!["name=John".to_owned(), "age=30".to_owned(), "city=New York".to_owned()];
96/// assert_eq!(find_value_by_key(&pairs, "name"), Some("John".to_string()));
97/// assert_eq!(find_value_by_key(&pairs, "gender"), None);
98/// ```
99pub fn find_value_by_key(kv_pairs: &Vec<String>, key_to_find: &str) -> Option<String> {
100    for item in kv_pairs {
101        // Split the string at the '=' character
102        if let Some((key, value)) = item.split_once('=') {
103            if key == key_to_find {
104                return Some(value.to_owned());
105            }
106        }
107    }
108    None
109}
110
111/// Remove Location for remove_char function
112pub enum RemoveLocationEnum {
113    Begin,
114    End,
115}
116
117/// Removes the first or last character of a string if it matches the given target character.
118///
119/// # Arguments
120///
121/// * `begin` - A boolean indicating whether to remove the first (`true`) or last (`false`) character.
122/// * `input` - A `String` to process.
123/// * `target` - The character to remove.
124///
125/// # Returns
126///
127/// Returns a new `String` with the character removed if it matched.
128///
129/// # Examples
130///
131/// ```
132/// use bt_string_utils::{remove_char, RemoveLocationEnum};
133/// let modified = remove_char(RemoveLocationEnum::Begin, &"hello".to_string(), 'h');
134/// assert_eq!(modified, "ello");
135///
136/// let modified = remove_char(RemoveLocationEnum::End, &"world!".to_string(), '!');
137/// assert_eq!(modified, "world");
138/// ```
139///
140/// If the character doesn't match, the original string is returned:
141///
142/// ```
143/// use bt_string_utils::{remove_char, RemoveLocationEnum};
144/// let modified = remove_char(RemoveLocationEnum::Begin, &"rust".to_string(), 'x');
145/// assert_eq!(modified, "rust");
146/// ```
147pub fn remove_char(remove_from: RemoveLocationEnum, input: &String, target: char) -> String {
148    match remove_from{
149        RemoveLocationEnum::Begin => if input.starts_with(target) {
150                                        return input.chars().skip(1).collect();
151                                     },
152        RemoveLocationEnum::End => if input.ends_with(target) {
153                                        return input.chars().take(input.len() - 1).collect();
154                                    },
155    }
156    /*if begin {
157        if input.starts_with(target) {
158            return input.chars().skip(1).collect();
159        }
160    } else {
161        if input.ends_with(target) {
162            return input.chars().take(input.len() - 1).collect();
163        }
164    }*/
165    input.to_string() // Return unchanged if no removal occurs
166}
167
168/// Generates a random URL-safe string of the specified length.
169///
170/// # Arguments
171///
172/// * `n` - The length of the generated string.
173///
174/// # Returns
175///
176/// A `String` containing `n` random alphanumeric characters (`A-Z`, `a-z`, `0-9`).
177///
178/// # Examples
179///
180/// ```
181/// use bt_string_utils::generate_url_safe_string;
182/// let random_string = generate_url_safe_string(16);
183/// println!("Generated string: {}", random_string);
184/// ```
185///
186/// # Notes
187///
188/// - Uses the `rand` crate to generate random alphanumeric characters.
189/// - Ensures the output contains only **URL-safe** characters.
190/// - May require the `rand` crate in your Cargo.toml:
191///
192/// ```toml
193/// [dependencies]
194/// rand = "0.8"
195/// ```
196pub fn generate_url_safe_string(n: usize) -> String {
197    rand::rng()
198        .sample_iter(&Alphanumeric)
199        .take(n)
200        .map(char::from)
201        .collect()
202}
203
204/// Checks whether a given `haystack` string contains the specified `word`
205/// as a whole word, using word boundaries.
206///
207/// A whole word match means the `word` must be surrounded by non-word characters
208/// (e.g., spaces, punctuation) or string boundaries. Substrings within longer words
209/// will not match.
210///
211/// # Arguments
212///
213/// * `text` - The string to search within.
214/// * `word` - The target word to search for.
215///
216/// # Returns
217///
218/// * `true` if `word` appears as a whole word in `haystack`.
219/// * `false` otherwise.
220///
221/// # Examples
222///
223/// ```
224/// use bt_string_utils::contains_whole_word;
225/// assert_eq!(contains_whole_word("this is a target match", "target"), true);
226/// assert_eq!(contains_whole_word("this is a targeted match", "target"), false);
227/// assert_eq!(contains_whole_word("no-target", "target"), false);
228/// ```
229pub fn contains_whole_word(text: &str, word: &str) -> bool {
230    //let pattern = format!(r"\b{}\b", regex::escape(word));
231    //let pattern = format!(r"(?i)(?<!\w){}(?!\w)", regex::escape(word));
232    //let pattern =  format!(r"(?<![A-Za-z0-9-]){}(?![A-Za-z0-9-])",regex::escape(word) );
233    let pattern = format!(r"(?:^|[^A-Za-z0-9-]){}(?:[^A-Za-z0-9-]|$)", regex::escape(word));    
234
235    let re = Regex::new(&pattern).unwrap();
236    re.is_match(text)
237}