bare_config/
properties.rs1use core::fmt;
37use core::str::FromStr;
38
39use crate::ConfigContent;
40use crate::error::{ConfigError, ConfigResult};
41use crate::key::{Key, KeySegment};
42use crate::value::Value;
43use std::collections::HashMap;
44
45#[derive(Debug, Clone)]
49pub struct PropertiesContent {
50 data: HashMap<String, String>,
51}
52
53impl PartialEq for PropertiesContent {
54 fn eq(&self, other: &Self) -> bool {
55 self.data == other.data
56 }
57}
58
59#[allow(dead_code)]
60impl PropertiesContent {
61 pub fn from_map(map: HashMap<String, String>) -> Self {
63 Self { data: map }
64 }
65
66 pub fn as_map(&self) -> &HashMap<String, String> {
68 &self.data
69 }
70
71 pub fn into_map(self) -> HashMap<String, String> {
73 self.data
74 }
75
76 fn parse_key(key: &Key) -> ConfigResult<String> {
77 let segments = key.segments();
78
79 if segments.is_empty() {
80 return Err(ConfigError::InvalidKey("Empty key".to_string()));
81 }
82
83 let parts: Vec<String> = segments
84 .iter()
85 .filter_map(|s| match s {
86 KeySegment::Key(s) => Some(s.clone()),
87 KeySegment::Index(_) => None,
88 KeySegment::Attribute(_) => None,
89 })
90 .collect();
91
92 if parts.is_empty() {
93 return Err(ConfigError::InvalidKey("Invalid key format".to_string()));
94 }
95
96 Ok(parts.join("."))
97 }
98
99 fn get_value_at_key(&self, key: &Key) -> ConfigResult<String> {
100 let key_str = Self::parse_key(key)?;
101
102 self.data
103 .get(&key_str)
104 .cloned()
105 .ok_or_else(|| ConfigError::KeyNotFound(key.to_key_string()))
106 }
107
108 fn contains_key_at(&self, key: &Key) -> bool {
109 if let Ok(key_str) = Self::parse_key(key) {
110 self.data.contains_key(&key_str)
111 } else {
112 false
113 }
114 }
115
116 fn keys_at(&self) -> Vec<Key> {
117 self.data
118 .keys()
119 .filter_map(|k| Key::from_str(&format!(".{}", k)).ok())
120 .collect()
121 }
122
123 fn len_at(&self) -> usize {
124 self.data.len()
125 }
126
127 fn delete_at(&mut self, key: &Key) -> ConfigResult<()> {
128 let key_str = Self::parse_key(key)?;
129
130 if self.data.remove(&key_str).is_some() {
131 Ok(())
132 } else {
133 Err(ConfigError::KeyNotFound(key.to_key_string()))
134 }
135 }
136
137 fn upsert_at(&mut self, key: &Key, value: &Value) -> ConfigResult<()> {
138 let key_str = Self::parse_key(key)?;
139 let value_str = value.to_string_repr();
140
141 drop(self.data.insert(key_str, value_str));
142 Ok(())
143 }
144}
145
146impl ConfigContent for PropertiesContent {
147 fn select(&self, key: &Key) -> ConfigResult<Value> {
148 let value = self.get_value_at_key(key)?;
149 Ok(Value::String(value))
150 }
151
152 fn insert(&mut self, key: &Key, value: &Value) -> ConfigResult<()> {
153 if self.contains_key_at(key) {
154 return Err(ConfigError::KeyAlreadyExists(key.to_key_string()));
155 }
156 self.upsert_at(key, value)
157 }
158
159 fn update(&mut self, key: &Key, value: &Value) -> ConfigResult<()> {
160 if !self.contains_key_at(key) {
161 return Err(ConfigError::KeyDoesNotExist(key.to_key_string()));
162 }
163 self.upsert_at(key, value)
164 }
165
166 fn delete(&mut self, key: &Key) -> ConfigResult<()> {
167 self.delete_at(key)
168 }
169
170 fn upsert(&mut self, key: &Key, value: &Value) -> ConfigResult<()> {
171 self.upsert_at(key, value)
172 }
173
174 fn keys(&self) -> Vec<Key> {
175 self.keys_at()
176 }
177}
178
179impl FromStr for PropertiesContent {
180 type Err = ConfigError;
181
182 fn from_str(s: &str) -> Result<Self, Self::Err> {
183 let estimated = s
185 .lines()
186 .map(|l| l.trim())
187 .filter(|l| !l.is_empty() && !l.starts_with('#') && !l.starts_with('!'))
188 .count();
189 let mut map = HashMap::with_capacity(estimated);
190
191 let mut current_key: Option<String> = None;
193 let mut current_value = String::new();
194
195 let processed: String = {
197 let mut result = String::new();
198 let mut previous_backslash = false;
199
200 for line in s.lines() {
201 if previous_backslash {
202 result.push_str(line);
204 previous_backslash = false;
205 } else {
206 result.push('\n');
208 result.push_str(line);
209 }
210
211 let trimmed = line.trim_end();
213 if trimmed.ends_with('\\') {
214 previous_backslash = true;
215 let _ = result.pop();
217 }
218 }
219 result
220 };
221
222 for line in processed.lines() {
223 let line = line.trim();
224
225 if line.is_empty() || line.starts_with('#') || line.starts_with('!') {
226 continue;
227 }
228
229 if let Some(key) = current_key.take() {
231 if !current_value.is_empty() || !map.contains_key(&key) {
232 drop(map.insert(key, current_value.clone()));
233 }
234 current_value.clear();
235 }
236
237 if let Some(pos) = line.find('=') {
238 let key = line[..pos].trim().to_string();
239 let value = line[pos + 1..].trim().to_string();
240 drop(map.insert(key, value));
241 } else if let Some(pos) = line.find(':') {
242 let key = line[..pos].trim().to_string();
243 let value = line[pos + 1..].trim().to_string();
244 drop(map.insert(key, value));
245 }
246 }
247
248 if let Some(key) = current_key {
250 if !current_value.is_empty() || !map.contains_key(&key) {
251 drop(map.insert(key, current_value));
252 }
253 }
254
255 Ok(Self { data: map })
256 }
257}
258
259impl fmt::Display for PropertiesContent {
260 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
261 let mut keys: Vec<_> = self.data.keys().collect();
262 keys.sort();
263
264 for key in keys {
265 if let Some(value) = self.data.get(key) {
266 writeln!(f, "{}={}", key, value)?;
267 }
268 }
269
270 Ok(())
271 }
272}
273
274#[cfg(test)]
275mod tests {
276 use super::*;
277
278 #[test]
279 fn test_properties_from_str() {
280 let content = PropertiesContent::from_str("key=value").expect("test should succeed");
281 assert!(
282 content
283 .select(&Key::from_str(".key").expect("test should succeed"))
284 .is_ok()
285 );
286 }
287
288 #[test]
289 fn test_properties_select() {
290 let content = PropertiesContent::from_str("name=test").expect("test should succeed");
291 let value = content
292 .select(&Key::from_str(".name").expect("test should succeed"))
293 .expect("test should succeed");
294 assert_eq!(value.as_str(), Some("test"));
295 }
296
297 #[test]
298 fn test_properties_nested() {
299 let content =
300 PropertiesContent::from_str("database.url=localhost").expect("test should succeed");
301 let value = content
302 .select(&Key::from_str(".database.url").expect("test should succeed"))
303 .expect("test should succeed");
304 assert_eq!(value.as_str(), Some("localhost"));
305 }
306
307 #[test]
308 fn test_properties_insert() {
309 let mut content = PropertiesContent::from_str("").expect("test should succeed");
310 content
311 .insert(
312 &Key::from_str(".name").expect("test should succeed"),
313 &Value::string("test"),
314 )
315 .expect("test should succeed");
316 let value = content
317 .select(&Key::from_str(".name").expect("test should succeed"))
318 .expect("test should succeed");
319 assert_eq!(value.as_str(), Some("test"));
320 }
321
322 #[test]
323 fn test_properties_update() {
324 let mut content = PropertiesContent::from_str("name=old").expect("test should succeed");
325 content
326 .update(
327 &Key::from_str(".name").expect("test should succeed"),
328 &Value::string("new"),
329 )
330 .expect("test should succeed");
331 let value = content
332 .select(&Key::from_str(".name").expect("test should succeed"))
333 .expect("test should succeed");
334 assert_eq!(value.as_str(), Some("new"));
335 }
336
337 #[test]
338 fn test_properties_delete() {
339 let mut content = PropertiesContent::from_str("name=test").expect("test should succeed");
340 content
341 .delete(&Key::from_str(".name").expect("test should succeed"))
342 .expect("test should succeed");
343 assert!(
344 content
345 .select(&Key::from_str(".name").expect("test should succeed"))
346 .is_err()
347 );
348 }
349
350 #[test]
351 fn test_properties_upsert() {
352 let mut content = PropertiesContent::from_str("").expect("test should succeed");
353 content
354 .upsert(
355 &Key::from_str(".name").expect("test should succeed"),
356 &Value::string("test"),
357 )
358 .expect("test should succeed");
359 let value = content
360 .select(&Key::from_str(".name").expect("test should succeed"))
361 .expect("test should succeed");
362 assert_eq!(value.as_str(), Some("test"));
363 }
364
365 #[test]
366 fn test_properties_display_is_stable_and_sorted() {
367 let content = PropertiesContent::from_str("z=1\na=2").expect("test should succeed");
368 let first = content.to_string();
369 let second = content.to_string();
370 assert_eq!(first, second);
371 assert_eq!(first.lines().next(), Some("a=2"));
372 }
373
374 #[test]
375 fn test_properties_comment() {
376 let content = PropertiesContent::from_str("# This is a comment\nkey=value")
377 .expect("test should succeed");
378 assert!(
379 content
380 .select(&Key::from_str(".key").expect("test should succeed"))
381 .is_ok()
382 );
383 }
384
385 #[test]
386 fn test_properties_whitespace() {
387 let content = PropertiesContent::from_str("key = value").expect("test should succeed");
388 let value = content
389 .select(&Key::from_str(".key").expect("test should succeed"))
390 .expect("test should succeed");
391 assert_eq!(value.as_str(), Some("value"));
392 }
393
394 #[test]
395 fn test_properties_continuation() {
396 let content =
397 PropertiesContent::from_str("key=value1\\\nvalue2").expect("test should succeed");
398 let value = content
399 .select(&Key::from_str(".key").expect("test should succeed"))
400 .expect("test should succeed");
401 assert!(
402 value
403 .as_str()
404 .expect("test should succeed")
405 .contains("value1")
406 );
407 }
408
409 #[test]
410 fn test_properties_colon_separator() {
411 let content = PropertiesContent::from_str("key:value").expect("test should succeed");
412 let value = content
413 .select(&Key::from_str(".key").expect("test should succeed"))
414 .expect("test should succeed");
415 assert_eq!(value.as_str(), Some("value"));
416 }
417
418 #[test]
419 fn test_properties_update_existing() {
420 let mut content = PropertiesContent::from_str("key=old").expect("test should succeed");
421 content
422 .update(
423 &Key::from_str(".key").expect("test should succeed"),
424 &Value::string("new"),
425 )
426 .expect("test should succeed");
427 let value = content
428 .select(&Key::from_str(".key").expect("test should succeed"))
429 .expect("test should succeed");
430 assert_eq!(value.as_str(), Some("new"));
431 }
432
433 #[test]
434 fn test_properties_delete_existing() {
435 let mut content = PropertiesContent::from_str("key=value").expect("test should succeed");
436 content
437 .delete(&Key::from_str(".key").expect("test should succeed"))
438 .expect("test should succeed");
439 assert!(
440 content
441 .select(&Key::from_str(".key").expect("test should succeed"))
442 .is_err()
443 );
444 }
445
446 #[test]
447 fn test_properties_upsert_new() {
448 let mut content = PropertiesContent::from_str("").expect("test should succeed");
449 content
450 .upsert(
451 &Key::from_str(".key").expect("test should succeed"),
452 &Value::string("value"),
453 )
454 .expect("test should succeed");
455 let value = content
456 .select(&Key::from_str(".key").expect("test should succeed"))
457 .expect("test should succeed");
458 assert_eq!(value.as_str(), Some("value"));
459 }
460
461 #[test]
462 fn test_properties_special_characters() {
463 let content =
464 PropertiesContent::from_str(r#"path=C\:\\Windows"#).expect("test should succeed");
465 let value = content
466 .select(&Key::from_str(".path").expect("test should succeed"))
467 .expect("test should succeed");
468 assert!(value.as_str().is_some());
469 }
470}