header_parsing/
lib.rs

1#![deny(missing_docs)]
2
3//! This crate provides functionality for parsing markdown style headers.
4
5/// Represents the changes to be made to a path.
6///
7/// This is generated when a header is found.
8/// It's still possible to access the previous path of headers before applying the changes.
9#[must_use]
10pub struct PathChanges<'a> {
11    drop: usize,
12    add: Box<str>,
13    /// The path to which changes will be applied.
14    pub path: &'a mut Vec<Box<str>>,
15}
16
17impl PathChanges<'_> {
18    /// Applies the changes to the path.
19    pub fn apply(self) {
20        let Self { drop, add, path } = self;
21
22        for _ in 0..drop {
23            path.pop();
24        }
25
26        path.push(add);
27    }
28}
29
30/// Indicates that a subheader was found without a corresponding header.
31pub struct SubheaderWithoutHeader;
32
33/// Parses a header from a line of text.
34///
35/// # Arguments
36///
37/// * `path`: A list of headers including the current header.
38/// * `line`: The line of text to parse.
39///
40/// # Returns
41///
42/// If the line is not a header, returns `None`.
43/// If the line is a valid header, returns the `PathChanges` which have to be used to update the path.
44/// If the header is a subheader without a corresponding header, an error is returned.
45pub fn parse_header<'a>(
46    path: &'a mut Vec<Box<str>>,
47    line: &str,
48) -> Option<Result<PathChanges<'a>, SubheaderWithoutHeader>> {
49    let mut start = 0;
50
51    let mut chars = line.chars();
52    while Some('#') == chars.next() {
53        start += 1;
54    }
55
56    if start == 0 {
57        return None;
58    }
59
60    let level = start - 1;
61
62    let len = path.len();
63
64    Some(if len < level {
65        Err(SubheaderWithoutHeader)
66    } else {
67        Ok(PathChanges {
68            drop: len - level,
69            add: line[start..].trim().into(),
70            path,
71        })
72    })
73}