dot_properties/
lib.rs

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    /// Gets the corresponding value for a property key.
28    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    /// Sets a value for a property key.
36    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}