#![cfg_attr(not(test), no_std)]
#[allow(unused_imports)]
use core::{fmt, str};
#[inline]
fn from_utf8(v: &[u8]) -> &str {
#[cfg(not(debug_assertions))]
return unsafe { str::from_utf8_unchecked(v) };
#[cfg(debug_assertions)]
return str::from_utf8(v).unwrap();
}
mod parse;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum Item<'a> {
Error(&'a str),
Section(&'a str),
SectionEnd,
Property(&'a str, Option<&'a str>),
Comment(&'a str),
Blank,
}
impl<'a> fmt::Display for Item<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
&Item::Error(error) => write!(f, "{}\n", error),
&Item::Section(section) => write!(f, "[{}]\n", section),
&Item::SectionEnd => Ok(()),
&Item::Property(key, Some(value)) => write!(f, "{}={}\n", key, value),
&Item::Property(key, None) => write!(f, "{}\n", key),
&Item::Comment(comment) => write!(f, ";{}\n", comment),
&Item::Blank => f.write_str("\n"),
}
}
}
#[inline(never)]
pub fn trim(s: &str) -> &str {
s.trim_matches(|chr: char| chr.is_ascii_whitespace())
}
#[derive(Clone, Debug)]
pub struct Parser<'a> {
line: u32,
comment_char: u8,
auto_trim: bool,
section_ended: bool,
state: &'a [u8],
}
impl<'a> Parser<'a> {
#[inline]
pub const fn new(s: &'a str) -> Parser<'a> {
let state = s.as_bytes();
Parser { line: 0, comment_char: b';', auto_trim: false, section_ended: false, state }
}
#[must_use]
#[inline]
pub const fn comment_char(self, chr: u8) -> Parser<'a> {
let comment_char = chr & 0x7f;
Parser { comment_char, ..self }
}
#[must_use]
#[inline]
pub const fn auto_trim(self, auto_trim: bool) -> Parser<'a> {
Parser { auto_trim, ..self }
}
#[inline]
pub const fn line(&self) -> u32 {
self.line
}
#[inline]
pub fn remainder(&self) -> &'a str {
from_utf8(self.state)
}
}
impl<'a> Iterator for Parser<'a> {
type Item = Item<'a>;
#[inline(never)]
fn next(&mut self) -> Option<Item<'a>> {
let mut s = self.state;
match s.first().cloned() {
None => {
if self.section_ended {
None
}
else {
self.section_ended = true;
Some(Item::SectionEnd)
}
},
Some(b'\r' | b'\n') => {
self.skip_ln(s);
Some(Item::Blank)
},
Some(chr) if chr == self.comment_char => {
s = &s[1..];
let i = parse::find_nl(s);
let comment = from_utf8(&s[..i]);
let comment = if self.auto_trim { trim(comment) } else { comment };
self.skip_ln(&s[i..]);
Some(Item::Comment(comment))
},
Some(b'[') => {
if self.section_ended {
self.section_ended = false;
let i = parse::find_nl(s);
if s[i - 1] != b']' {
let error = from_utf8(&s[..i]);
self.skip_ln(&s[i..]);
return Some(Item::Error(error));
}
let section = from_utf8(&s[1..i - 1]);
let section = if self.auto_trim { trim(section) } else { section };
self.skip_ln(&s[i..]);
Some(Item::Section(section))
}
else {
self.section_ended = true;
Some(Item::SectionEnd)
}
},
_ => {
let key = {
let i = parse::find_nl_chr(s, b'=');
let key = from_utf8(&s[..i]);
let key = if self.auto_trim { trim(key) } else { key };
if s.get(i) != Some(&b'=') {
self.skip_ln(&s[i..]);
if key.is_empty() {
return Some(Item::Blank);
}
return Some(Item::Property(key, None));
}
s = &s[i + 1..];
key
};
let value = {
let i = parse::find_nl(s);
let value = from_utf8(&s[..i]);
let value = if self.auto_trim { trim(value) } else { value };
self.skip_ln(&s[i..]);
value
};
Some(Item::Property(key, Some(value)))
},
}
}
}
impl<'a> core::iter::FusedIterator for Parser<'a> {}
impl<'a> Parser<'a> {
#[inline]
fn skip_ln(&mut self, mut s: &'a [u8]) {
if s.len() > 0 {
if s[0] == b'\r' {
s = &s[1..];
}
if s.len() > 0 {
if s[0] == b'\n' {
s = &s[1..];
}
}
self.line += 1;
}
self.state = s;
}
}
#[cfg(test)]
mod tests;