use crate::ParseError;
use std::io;
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum Line<'a> {
Nothing,
Section(&'a str),
Pair(&'a str, &'a str),
}
type LineReadResult<'a> = Result<Line<'a>, ParseError>;
pub fn parse_line(line: &str) -> LineReadResult<'_> {
let mut l = line.trim_start();
if l.starts_with(is_comment) {
return Ok(Line::Nothing);
}
let last_closing_bracket = l.rfind(']');
let last_comment = l.rfind(is_comment);
if let (Some(bracket), Some(comment)) = (last_closing_bracket, last_comment) {
if comment > bracket {
l = l[0..comment].as_ref();
}
}
l = l.trim_end();
if l.is_empty() {
Ok(Line::Nothing)
} else if let Some(s) = l.strip_prefix('[').and_then(|s| s.strip_suffix(']')) {
if s.is_empty() {
Err(ParseError::InvalidLine)
} else {
Ok(Line::Section(s))
}
} else if let Some((key_raw, val_raw)) = l.split_once('=') {
let key = key_raw.trim_end();
let val = val_raw.trim_start();
match (key.is_empty(), val.is_empty()) {
(true, _) => Err(ParseError::InvalidLine),
(false, true) => {
#[cfg(feature = "allow-empty-values")]
{
Ok(Line::Pair(key.trim_end(), val))
}
#[cfg(not(feature = "allow-empty-values"))]
{
Err(ParseError::InvalidLine)
}
}
(false, false) => Ok(Line::Pair(key.trim_end(), val.trim_start())),
}
} else {
Err(ParseError::InvalidLine)
}
}
pub struct LineReader<R: io::BufRead> {
ticker: usize,
line: String,
reader: R,
}
impl<R: io::BufRead> LineReader<R> {
pub fn new(r: R) -> LineReader<R> {
LineReader {
ticker: 0,
line: String::with_capacity(256),
reader: r,
}
}
pub fn line_no(&self) -> usize {
self.ticker
}
pub fn line(&self) -> &str {
self.line.as_str()
}
pub fn reparse(&self) -> LineReadResult<'_> {
parse_line(self.line())
}
pub fn next_line(&mut self) -> LineReadResult<'_> {
self.line.clear();
match self.reader.read_line(&mut self.line) {
Err(e) => Err(ParseError::Io(e)),
Ok(0) => Err(ParseError::Eof),
Ok(_) => {
self.ticker += 1;
if self.ticker == 1 {
parse_line(self.line.strip_prefix('\u{FEFF}').unwrap_or(&self.line))
} else {
self.reparse()
}
}
}
}
}
fn is_comment(c: char) -> bool {
c == ';' || c == '#'
}