1use std::collections::HashMap;
2use std::error::Error;
3use std::io::{BufRead, Write};
4use std::{fmt, io};
5
6#[derive(Debug)]
7pub struct PropertyNotFoundError<'a>(&'a str);
8
9impl<'a> Error for PropertyNotFoundError<'a> {}
10
11impl<'a> fmt::Display for PropertyNotFoundError<'a> {
12 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
13 writeln!(f, "Property {:?} not found", self.0)
14 }
15}
16
17#[derive(Debug, Clone, PartialEq, Eq, Default)]
18pub struct Properties(HashMap<String, String>);
19
20impl From<HashMap<String, String>> for Properties {
21 fn from(value: HashMap<String, String>) -> Self {
22 Self(value)
23 }
24}
25
26impl Properties {
27 pub fn get_property<'a>(&self, key: &'a str) -> Result<&'_ str, PropertyNotFoundError<'a>> {
29 self.0
30 .get(key)
31 .map(String::as_ref)
32 .ok_or(PropertyNotFoundError(key))
33 }
34
35 pub fn set_property(&mut self, key: String, value: String) -> Option<String> {
37 self.0.insert(key, value)
38 }
39
40 pub fn write_without_spaces<W: Write>(&self, writer: &mut W) -> Result<(), io::Error> {
41 for (key, value) in &self.0 {
42 writeln!(writer, "{}={}", key, value)?;
43 }
44
45 Ok(())
46 }
47
48 pub fn write_with_spaces<W: Write>(&self, writer: &mut W) -> Result<(), io::Error> {
49 for (key, value) in &self.0 {
50 writeln!(writer, "{} = {}", key, value)?;
51 }
52
53 Ok(())
54 }
55
56 pub fn write_aligned<W: Write>(&self, writer: &mut W) -> Result<(), io::Error> {
57 let max_length = self.0.keys().map(|k| k.len()).max().unwrap_or_default();
58
59 for (key, value) in &self.0 {
60 let padded_key = {
61 let pad = " ".repeat(max_length.saturating_sub(key.len()));
62 let mut padded = String::with_capacity(max_length);
63 padded += key;
64 padded += &pad;
65 padded
66 };
67
68 writeln!(writer, "{} = {}", padded_key, value)?;
69 }
70
71 Ok(())
72 }
73}
74
75#[derive(Debug)]
76pub struct PropertiesParseError {
77 pub line_number: usize,
78 pub kind: PropertiesParseErrorKind,
79}
80
81impl PropertiesParseError {
82 fn new_io(line_number: usize, error: io::Error) -> Self {
83 Self {
84 line_number,
85 kind: PropertiesParseErrorKind::Io(error),
86 }
87 }
88
89 fn new_invalid_kvp(line_number: usize, line: &str) -> Self {
90 Self {
91 line_number,
92 kind: PropertiesParseErrorKind::InvalidKeyValuePair(InvalidKeyValuePairError(
93 line.to_string(),
94 )),
95 }
96 }
97}
98
99#[derive(Debug)]
100pub struct InvalidKeyValuePairError(String);
101
102impl Error for InvalidKeyValuePairError {}
103
104impl fmt::Display for InvalidKeyValuePairError {
105 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106 write!(f, "cannot parse a key-value pair from {:?}", self.0)
107 }
108}
109
110#[derive(Debug)]
111pub enum PropertiesParseErrorKind {
112 Io(io::Error),
113 InvalidKeyValuePair(InvalidKeyValuePairError),
114}
115
116impl Error for PropertiesParseError {
117 fn source(&self) -> Option<&(dyn Error + 'static)> {
118 match &self.kind {
119 PropertiesParseErrorKind::Io(e) => Some(e),
120 PropertiesParseErrorKind::InvalidKeyValuePair(e) => Some(e),
121 }
122 }
123}
124
125impl fmt::Display for PropertiesParseError {
126 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127 write!(
128 f,
129 "Error while parsing properties file (line {})",
130 self.line_number
131 )
132 }
133}
134
135pub fn read_properties<R: BufRead>(reader: &mut R) -> Result<Properties, PropertiesParseError> {
136 let mut properties = Properties::default();
137
138 for (i, line) in reader.lines().enumerate() {
139 let line_number = i + 1;
140
141 let line = line.map_err(|e| PropertiesParseError::new_io(line_number, e))?;
142
143 let (field, value) = line
144 .split_once('=')
145 .ok_or_else(|| PropertiesParseError::new_invalid_kvp(line_number, &line))?;
146
147 let key = field.trim().to_string();
148 let value = value.trim().to_string();
149 properties.0.insert(key, value);
150 }
151
152 Ok(properties)
153}