preftool/
strings.rs

1use caseless::default_case_fold_str;
2use std::fmt;
3use std::hash::{Hash, Hasher};
4
5const KEY_DELIMITER: &str = ":";
6
7/// Represents a configuration key. Configuration keys are case-insensitive,
8/// and all instances are lower-cased.
9#[derive(Clone)]
10pub struct ConfigKey(String);
11
12impl ConfigKey {
13  fn new() -> Self {
14    Self(String::new())
15  }
16
17  /// Get an empty config key.
18  pub fn empty() -> Self {
19    Self::new()
20  }
21
22  pub fn is_empty(&self) -> bool {
23    self.0.is_empty()
24  }
25
26  /// Key length.
27  pub fn len(&self) -> usize {
28    self.0.len()
29  }
30
31  /// Configuration key seperator.
32  pub fn separator() -> &'static str {
33    KEY_DELIMITER
34  }
35
36  /// Get the section key given a config path.
37  ///
38  /// # Arguments
39  ///
40  /// * `path` - configuration path.
41  ///
42  /// # Example
43  ///
44  /// ```
45  /// use preftool::ConfigKey;
46  /// assert_eq!(ConfigKey::from("").section_key(), "");
47  /// assert_eq!(ConfigKey::from("foo").section_key(), "foo");
48  /// assert_eq!(ConfigKey::from("foo:bar").section_key(), "bar");
49  /// assert_eq!(ConfigKey::from("foo:bar:baz").section_key(), "baz");
50  /// ```
51  pub fn section_key(&self) -> Self {
52    if self == "" {
53      ConfigKey::new()
54    } else {
55      match self.0.rfind(KEY_DELIMITER) {
56        None => self.clone(),
57        Some(i) => Self(self.0[(i + 1)..].to_owned()),
58      }
59    }
60  }
61
62  /// Get the parent config path.
63  ///
64  /// # Arguments
65  ///
66  /// * `path` - configuration path.
67  ///
68  /// # Example
69  ///
70  /// ```
71  /// use preftool::ConfigKey;
72  /// assert_eq!(ConfigKey::from("").parent(), "");
73  /// assert_eq!(ConfigKey::from("foo").parent(), "");
74  /// assert_eq!(ConfigKey::from("foo:bar").parent(), "foo");
75  /// assert_eq!(ConfigKey::from("foo:bar:baz").parent(), "foo:bar");
76  /// ```
77  pub fn parent(&self) -> Self {
78    if self == "" {
79      ConfigKey::new()
80    } else {
81      match self.0.rfind(KEY_DELIMITER) {
82        None => ConfigKey::new(),
83        Some(i) => Self(self.0[..i].to_owned()),
84      }
85    }
86  }
87
88  /// Combine two config path segments.
89  ///
90  /// # Arguments
91  ///
92  /// * `path` - configuration path.
93  /// * `key` - configuration sub-key.
94  ///
95  /// # Example
96  ///
97  /// ```
98  /// use preftool::ConfigKey;
99  /// assert_eq!(ConfigKey::from("").combine(""), "");
100  /// assert_eq!(ConfigKey::from("").combine("foo"), "foo");
101  /// assert_eq!(ConfigKey::from("foo:bar").combine("baz"), "foo:bar:baz");
102  /// assert_eq!(ConfigKey::from("foo").combine("bar:baz"), "foo:bar:baz");
103  /// ```
104  pub fn combine<S2: Into<ConfigKey>>(&self, key: S2) -> Self {
105    let key = key.into();
106    if self == "" {
107      key
108    } else {
109      let mut s = String::with_capacity(self.len() + key.len() + KEY_DELIMITER.len());
110      s.push_str(self.as_ref());
111      s.push_str(KEY_DELIMITER);
112      s.push_str(key.as_ref());
113      Self(s)
114    }
115  }
116
117  /// Join a list of path segments into one config path.
118  ///
119  /// # Arguments
120  ///
121  /// * `paths` - configuration path segments.
122  ///
123  /// # Example
124  ///
125  /// ```
126  /// use preftool::ConfigKey;
127  /// assert_eq!(ConfigKey::join::<&'static str, Vec<_>>(vec!()), "");
128  /// assert_eq!(ConfigKey::join(vec!("foo")), "foo");
129  /// assert_eq!(ConfigKey::join(vec!("", "foo", "")), "foo");
130  /// assert_eq!(ConfigKey::join(vec!("", "foo:bar", "", "baz")), "foo:bar:baz");
131  /// ```
132  pub fn join<S: Into<ConfigKey>, I: IntoIterator<Item = S>>(parts: I) -> Self {
133    // default that's normally enough
134    let mut s = String::with_capacity(50);
135    let mut first = true;
136    for part in parts.into_iter().filter_map(|part| {
137      let part = part.into();
138      if part.as_ref() == "" {
139        None
140      } else {
141        Some(part)
142      }
143    }) {
144      if first {
145        first = false;
146      } else {
147        s.push_str(KEY_DELIMITER);
148      }
149      s.push_str(part.as_ref());
150    }
151
152    Self(s)
153  }
154
155  /// Construct a config key from a string. This does not do any
156  /// validation/lowercasing of the string, and should only be used
157  /// if the string is guaranteed to be safe. This method is typically
158  /// used by `derive` implementations.
159  ///
160  /// # Arguments
161  ///
162  /// * value - string value.
163  ///
164  /// # Example
165  ///
166  /// ```
167  /// use preftool::ConfigKey;
168  /// assert_eq!(ConfigKey::unsafe_from("foo:bar"), "foo:bar");
169  /// ```
170  pub fn unsafe_from<S: Into<String>>(value: S) -> Self {
171    Self(value.into())
172  }
173}
174
175impl fmt::Display for ConfigKey {
176  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
177    fmt::Display::fmt(&self.0, f)
178  }
179}
180
181impl fmt::Debug for ConfigKey {
182  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
183    fmt::Debug::fmt(&self.0, f)
184  }
185}
186
187impl AsRef<str> for ConfigKey {
188  fn as_ref(&self) -> &str {
189    &self.0
190  }
191}
192
193impl Hash for ConfigKey {
194  #[inline]
195  fn hash<H: Hasher>(&self, hasher: &mut H) {
196    self.0.hash(hasher)
197  }
198}
199
200impl PartialEq for ConfigKey {
201  #[inline]
202  fn eq(&self, other: &Self) -> bool {
203    self.0.eq(&other.0)
204  }
205}
206
207impl PartialEq<String> for ConfigKey {
208  fn eq(&self, other: &String) -> bool {
209    self.eq(&ConfigKey::from(other))
210  }
211}
212
213impl PartialEq<str> for ConfigKey {
214  fn eq(&self, other: &str) -> bool {
215    self.eq(&ConfigKey::from(other))
216  }
217}
218
219impl PartialEq<&String> for ConfigKey {
220  fn eq(&self, other: &&String) -> bool {
221    self.eq(&ConfigKey::from(*other))
222  }
223}
224
225impl PartialEq<&str> for ConfigKey {
226  fn eq(&self, other: &&str) -> bool {
227    self.eq(&ConfigKey::from(*other))
228  }
229}
230
231impl Eq for ConfigKey {}
232
233impl From<&str> for ConfigKey {
234  fn from(value: &str) -> Self {
235    ConfigKey(default_case_fold_str(value))
236  }
237}
238
239impl From<&String> for ConfigKey {
240  #[inline]
241  fn from(value: &String) -> Self {
242    let s: &str = &value;
243    ConfigKey::from(s)
244  }
245}
246
247impl From<String> for ConfigKey {
248  #[inline]
249  fn from(value: String) -> Self {
250    let s: &str = &value;
251    ConfigKey::from(s)
252  }
253}
254
255impl From<&ConfigKey> for ConfigKey {
256  #[inline]
257  fn from(value: &ConfigKey) -> Self {
258    value.clone()
259  }
260}
261
262impl Into<String> for ConfigKey {
263  fn into(self) -> String {
264    self.0
265  }
266}