#![deny(missing_docs)]
#[must_use]
pub struct PathChanges<'a> {
drop: usize,
pub header: Box<str>,
pub path: &'a mut Vec<Box<str>>,
}
impl PathChanges<'_> {
pub fn level(&self) -> usize {
self.path.len() - self.drop
}
pub fn apply(self) -> usize {
let Self { drop, header, path } = self;
for _ in 0..drop {
path.pop();
}
path.push(header);
path.len() - 1
}
}
pub struct SubheaderWithoutHeader;
pub fn parse_header<'a>(
path: &'a mut Vec<Box<str>>,
line: &str,
) -> Option<Result<PathChanges<'a>, SubheaderWithoutHeader>> {
let mut start = 0;
let mut chars = line.chars();
while Some('#') == chars.next() {
start += 1;
}
if start == 0 {
return None;
}
let level = start - 1;
let len = path.len();
Some(if len < level {
Err(SubheaderWithoutHeader)
} else {
Ok(PathChanges {
drop: len - level,
header: line[start..].trim().into(),
path,
})
})
}
pub enum Line<'a> {
Header {
level: usize,
},
Content(&'a str),
}
#[derive(Default)]
pub struct Parser {
path: Vec<Box<str>>,
}
impl Parser {
pub fn new() -> Self {
Self::default()
}
pub fn path(&self) -> &[Box<str>] {
&self.path
}
pub fn line<'a>(&mut self, line: &'a str) -> Result<Line<'a>, SubheaderWithoutHeader> {
match parse_header(&mut self.path, line) {
None => Ok(Line::Content(line)),
Some(changes) => changes.map(|changes| Line::Header {
level: changes.apply(),
}),
}
}
}
#[cfg(test)]
mod tests {
use super::{Line, Parser};
#[test]
fn dedent_reports_correct_level() {
let mut parser = Parser::new();
let levels: Vec<usize> = ["# A", "## B", "# C"]
.into_iter()
.map(|line| match parser.line(line) {
Ok(Line::Header { level }) => level,
_ => panic!("expected header"),
})
.collect();
assert_eq!(levels, [0, 1, 0]);
assert_eq!(parser.path(), &["C".into()]);
}
}