rk_utils/
str.rs

1use std::collections::HashSet;
2
3/// StringUtil provides utility methods for string manipulation.
4pub trait StringUtil {
5    /// is_quoted returns true if the string is quoted.
6    fn is_quoted(&self) -> bool;
7
8    /// substring returns a substring of the string.
9    /// The start and end parameters can be negative.
10    /// If start is negative, it is treated as len(self) + start.
11    /// If end is negative, it is treated as len(self) + end.    
12    fn substring(&self, start: i64, end: i64) -> &str;
13
14    /// unquote removes the quotes from the string.
15    /// If unescape is true, it will unescape the string.
16    /// If quote_set is provided, it will only unquote if the quote character is in the set.
17    fn unquote(&self, unescape: bool, quote_set: Option<&HashSet<char>>) -> String;
18
19    /// url_to_nodes returns a vector of nodes from a URL string.
20    fn url_to_nodes(&self) -> Vec<&str>;
21
22    /// ensure_prefix ensures that the string has the prefix.
23    fn ensure_prefix(&self, prefix: &str) -> String;
24
25    /// ensure_suffix ensures that the string has the suffix.
26    fn ensure_suffix(&self, suffix: &str) -> String;
27
28    /// drop_prefix drops the prefix from the string. 
29    fn drop_prefix(&self, prefix: &str) -> String;
30
31    /// drop_suffix drops the suffix from the string.
32    fn drop_suffix(&self, suffix: &str) -> String;
33
34    /// join_path_segment joins a path segment to the URL.
35    fn join_path_segment(&self, segment: &str) -> String;
36
37    /// join_path_segments joins multiple path segments to the URL.
38    fn join_path_segments(&self, segments: Vec<&str>) -> String;
39}
40
41impl StringUtil for str {
42    #[inline]
43    fn is_quoted(&self) -> bool {
44        (self.starts_with('\'') || self.starts_with('"'))
45            && self.chars().next() == self.chars().last()
46    }
47
48    fn unquote(&self, unescape: bool, quote_set: Option<&HashSet<char>>) -> String {
49        if self.is_empty() || self.len() < 2 {
50            return self.to_string();
51        }
52
53        let start = self.chars().next().unwrap();
54        let end = self.chars().last().unwrap();
55
56        if start != end {
57            return self.to_string();
58        }
59
60        let default_quote_chars = HashSet::from(['"', '\'']);
61        let quote_set = quote_set.unwrap_or(&default_quote_chars);
62
63        if !quote_set.contains(&start) {
64            return self.to_string();
65        }
66
67        let result = self.substring(1, -1);
68
69        if unescape {
70            let escaped_quote = format!("\\{}", start);
71            return result.replace(&escaped_quote, &start.to_string());
72        }
73
74        result.to_string()
75    }
76
77    fn substring(&self, start: i64, end: i64) -> &str {
78        let _start = if start < 0 {
79            (self.len() as i64 + start) as usize
80        } else {
81            start as usize
82        };
83
84        let _end = if end <= 0 {
85            (self.len() as i64 + end) as usize
86        } else {
87            end as usize
88        };
89
90        if _start > _end {
91            ""
92        } else {
93            &self[_start.._end]
94        }
95    }
96
97    #[inline]
98    fn url_to_nodes(&self) -> Vec<&str> {
99        // split and filter empty strings and pad with a "/"" at the start of the return vector
100        let mut nodes = vec!["/"];
101        nodes.extend(self.split('/').filter(|s| !s.is_empty()));
102        nodes
103    }
104
105    #[inline]
106    fn ensure_prefix(&self, prefix: &str) -> String {
107        let mut result = self.to_string();
108        if !self.starts_with(prefix) {
109            result = format!("{}{}", prefix, self);
110        }
111        result
112    }
113
114    #[inline]
115    fn ensure_suffix(&self, suffix: &str) -> String {
116        let mut result = self.to_string();
117        if !self.ends_with(suffix) {
118            result.push_str(suffix);
119        }
120        result
121    }
122
123    #[inline]
124    fn drop_prefix(&self, prefix: &str) -> String {
125        if self.starts_with(prefix) {
126            self.substring(prefix.len() as i64, 0).to_string()
127        } else {
128            self.to_string()
129        }
130    }
131
132    #[inline]
133    fn drop_suffix(&self, suffix: &str) -> String {
134        if self.ends_with(suffix) {
135            self.substring(0, -(suffix.len() as i64)).to_string()
136        } else {
137            self.to_string()
138        }
139    }
140
141    #[inline]
142    fn join_path_segment(&self, segment: &str) -> String {
143        let mut url = self.ensure_suffix("/");
144
145        url.push_str(&segment.drop_prefix("/"));
146
147        url
148    }
149
150    fn join_path_segments(&self, segments: Vec<&str>) -> String {
151        let mut url: String = self.to_string();
152        for segment in segments {
153            url = url.join_path_segment(segment);
154        }
155        url
156    }
157}