1use std::collections::HashSet;
2
3pub trait StringUtil {
5 fn is_quoted(&self) -> bool;
7
8 fn substring(&self, start: i64, end: i64) -> &str;
13
14 fn unquote(&self, unescape: bool, quote_set: Option<&HashSet<char>>) -> String;
18
19 fn url_to_nodes(&self) -> Vec<&str>;
21
22 fn ensure_prefix(&self, prefix: &str) -> String;
24
25 fn ensure_suffix(&self, suffix: &str) -> String;
27
28 fn drop_prefix(&self, prefix: &str) -> String;
30
31 fn drop_suffix(&self, suffix: &str) -> String;
33
34 fn join_path_segment(&self, segment: &str) -> String;
36
37 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 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}