1use std::{
36 convert::From,
37 error, fmt,
38 fs::File,
39 io::{self, BufRead, BufReader, Read},
40 path::Path,
41};
42
43#[derive(Debug)]
44pub struct IniHandlerError {}
46
47impl fmt::Display for IniHandlerError {
48 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
49 write!(fmt, "handler failure")
50 }
51}
52
53impl error::Error for IniHandlerError {}
54
55#[derive(Debug)]
56pub enum IniError<HandlerError: fmt::Debug + error::Error> {
58 InvalidLine(usize),
59 Handler(HandlerError),
60 Io(io::Error),
61}
62
63impl<HandlerError: fmt::Display + error::Error> fmt::Display for IniError<HandlerError> {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 match self {
66 IniError::InvalidLine(line) => write!(f, "invalid line: {}", line),
67 IniError::Handler(err) => write!(f, "handler error: {}", err),
68 IniError::Io(err) => write!(f, "input/output error: {}", err),
69 }
70 }
71}
72
73impl<HandlerError: fmt::Debug + error::Error> error::Error for IniError<HandlerError> {
74 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
75 match self {
76 IniError::InvalidLine(_) => None,
77 IniError::Handler(err) => err.source(),
78 IniError::Io(err) => err.source(),
79 }
80 }
81}
82
83impl<HandlerError: fmt::Debug + error::Error> From<HandlerError> for IniError<HandlerError> {
84 fn from(err: HandlerError) -> Self {
85 Self::Handler(err)
86 }
87}
88
89pub trait IniHandler {
91 type Error: fmt::Debug;
92
93 fn section(&mut self, name: &str) -> Result<(), Self::Error>;
95
96 fn option(&mut self, key: &str, value: &str) -> Result<(), Self::Error>;
98
99 fn comment(&mut self, _: &str) -> Result<(), Self::Error> {
101 Ok(())
102 }
103}
104
105pub struct IniParser<'a, Error: fmt::Debug + error::Error> {
107 handler: &'a mut dyn IniHandler<Error = Error>,
108 start_comment: String,
109}
110
111impl<'a, Error: fmt::Debug + error::Error> IniParser<'a, Error> {
112 pub fn new(handler: &'a mut dyn IniHandler<Error = Error>) -> IniParser<'a, Error> {
114 Self::with_start_comment(handler, ';')
115 }
116
117 pub fn with_start_comment(
119 handler: &'a mut dyn IniHandler<Error = Error>,
120 start_comment: char,
121 ) -> IniParser<'a, Error> {
122 let start_comment = format!("{}", start_comment);
123 Self {
124 handler,
125 start_comment,
126 }
127 }
128
129 fn parse_ini_line(&mut self, line: &str, lineno: usize) -> Result<(), IniError<Error>> {
131 let line = line.trim_start();
132 if line.is_empty() {
133 Ok(())
134 } else {
135 let (prefix, rest) = if line.is_char_boundary(1) {
136 line.split_at(1)
137 } else {
138 ("", line)
139 };
140 if prefix == "[" {
141 match rest.find(']') {
142 Some(pos) => {
143 let (name, _) = rest.split_at(pos);
144 self.handler.section(name.trim())?;
145 }
146 None => return Err(IniError::InvalidLine(lineno)),
147 }
148 } else if prefix == self.start_comment {
149 self.handler.comment(rest.trim_start())?;
150 } else {
151 match line.find('=') {
152 Some(pos) => {
153 let (name, rest) = line.split_at(pos);
154 let (_, value) = rest.split_at(1);
155 self.handler.option(name.trim(), value.trim())?;
156 }
157 None => return Err(IniError::InvalidLine(lineno)),
158 }
159 }
160 Ok(())
161 }
162 }
163
164 pub fn parse_buffered<B: BufRead>(&mut self, input: B) -> Result<(), IniError<Error>> {
166 let mut lineno = 0;
167 for res in input.lines() {
168 lineno += 1;
169 match res {
170 Ok(line) => self.parse_ini_line(line.trim_end(), lineno)?,
171 Err(err) => return Err(IniError::Io(err)),
172 }
173 }
174 Ok(())
175 }
176
177 pub fn parse<R: Read>(&mut self, input: R) -> Result<(), IniError<Error>> {
179 let mut reader = BufReader::new(input);
180 self.parse_buffered(&mut reader)
181 }
182
183 pub fn parse_file<P>(&mut self, path: P) -> Result<(), IniError<Error>>
185 where
186 P: AsRef<Path>,
187 {
188 let file = File::open(path).map_err(IniError::Io)?;
189 self.parse(file)
190 }
191}
192
193#[cfg(test)]
194mod tests {
195
196 use super::{IniError, IniHandler, IniParser};
197
198 use std::{
199 error, fmt,
200 io::{self, Seek, Write},
201 str,
202 };
203
204 #[derive(Debug)]
205 enum TestError {
206 InvalidSection,
207 InvalidOption,
208 Io(io::Error),
209 Utf8(str::Utf8Error),
210 }
211
212 impl fmt::Display for TestError {
213 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
214 match self {
215 TestError::InvalidSection => write!(fmt, "invalid section"),
216 TestError::InvalidOption => write!(fmt, "invalid option"),
217 TestError::Io(err) => write!(fmt, "i/o error: {}", err),
218 TestError::Utf8(err) => write!(fmt, "utf-8 error: {}", err),
219 }
220 }
221 }
222
223 impl error::Error for TestError {}
224
225 #[derive(Debug)]
226 struct Handler {
233 stream: io::Cursor<Vec<u8>>,
234 }
235
236 impl Handler {
237 fn new() -> Self {
238 Self {
239 stream: io::Cursor::new(Vec::<u8>::new()),
240 }
241 }
242
243 fn get(&self) -> Result<&str, TestError> {
244 str::from_utf8(self.stream.get_ref()).map_err(TestError::Utf8)
245 }
246 }
247
248 impl IniHandler for Handler {
249 type Error = TestError;
250
251 fn section(&mut self, name: &str) -> Result<(), Self::Error> {
252 if name == "invalid" {
253 Err(TestError::InvalidSection)
254 } else {
255 write!(self.stream, "<{}>", name).map_err(Self::Error::Io)
256 }
257 }
258
259 fn option(&mut self, name: &str, value: &str) -> Result<(), Self::Error> {
260 if name == "invalid" {
261 Err(TestError::InvalidOption)
262 } else {
263 write!(self.stream, "({}={})", name, value).map_err(Self::Error::Io)
264 }
265 }
266
267 fn comment(&mut self, comment: &str) -> Result<(), Self::Error> {
268 write!(self.stream, "/*{}*/", comment).map_err(Self::Error::Io)
269 }
270 }
271
272 type ParserError = IniError<TestError>;
273 type ParserResult<T> = Result<T, ParserError>;
274
275 fn new_input_stream(content: &str) -> io::Result<io::Cursor<Vec<u8>>> {
276 let mut buf = io::Cursor::new(Vec::<u8>::new());
277 writeln!(buf, "{}", content)?;
278 buf.seek(io::SeekFrom::Start(0))?;
279 Ok(buf)
280 }
281
282 fn read_ini(content: &str, start_comment: Option<char>) -> ParserResult<String> {
283 let mut handler = Handler::new();
284 let buf = new_input_stream(content).map_err(IniError::Io)?;
285 let mut parser = match start_comment {
286 Some(ch) => IniParser::with_start_comment(&mut handler, ch),
287 None => IniParser::new(&mut handler),
288 };
289 parser.parse(buf)?;
290 handler
291 .get()
292 .map(|s| s.to_string())
293 .map_err(ParserError::Handler)
294 }
295
296 const VALID_INI: &str = "name = test suite
297
298; logging section
299[logging]
300level = error
301";
302
303 #[test]
304 fn parse_valid_ini() -> ParserResult<()> {
305 let result = read_ini(VALID_INI, None)?;
306 assert_eq!(
307 "(name=test suite)/*logging section*/<logging>(level=error)",
308 result
309 );
310 Ok(())
311 }
312
313 const VALID_INI_ALT_COMMENT: &str = "# logging section
314[logging]
315level = error
316";
317
318 #[test]
319 fn parse_valid_ini_alt_comment() -> ParserResult<()> {
320 let result = read_ini(VALID_INI_ALT_COMMENT, Some('#'))?;
321 assert_eq!("/*logging section*/<logging>(level=error)", result);
322 Ok(())
323 }
324
325 const VALID_INI_UNICODE: &str = "[ŝipo]
326ĵurnalo = ĉirkaŭ";
327
328 #[test]
329 fn parse_unicode_ini() -> ParserResult<()> {
330 let result = read_ini(VALID_INI_UNICODE, None)?;
331 assert_eq!("<ŝipo>(ĵurnalo=ĉirkaŭ)", result);
332 Ok(())
333 }
334
335 const INVALID_SECTION: &str = "name = ok
336
337[logging";
338
339 #[test]
340 fn parse_invalid_section() {
341 let res = dbg!(read_ini(INVALID_SECTION, None));
342 assert!(matches!(res, Err(IniError::InvalidLine(3))));
343 }
344
345 const INVALID_OPTION: &str = "[logging]
346level error";
347
348 #[test]
349 fn parse_invalid_option() {
350 let res = dbg!(read_ini(INVALID_OPTION, None));
351 assert!(matches!(res, Err(IniError::InvalidLine(2))));
352 }
353
354 const UNEXPECTED_SECTION: &str = "name = test suite
355
356[invalid]
357level = error
358";
359
360 #[test]
361 fn parse_unexpected_section() {
363 let res = dbg!(read_ini(UNEXPECTED_SECTION, None));
364 assert!(matches!(
365 res,
366 Err(IniError::Handler(TestError::InvalidSection))
367 ));
368 }
369
370 const UNEXPECTED_OPTION: &str = "[logging]
371invalid = error
372";
373
374 #[test]
375 fn parse_unexpected_option() {
377 let res = dbg!(read_ini(UNEXPECTED_OPTION, None));
378 assert!(matches!(
379 res,
380 Err(IniError::Handler(TestError::InvalidOption))
381 ));
382 }
383}