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    /// The header name that will be added.
13    pub header: Box<str>,
14    /// The path to which changes will be applied.
15    pub path: &'a mut Vec<Box<str>>,
16}
17
18impl PathChanges<'_> {
19    /// Applies the changes to the path.
20    pub fn apply(self) {
21        let Self { drop, header, path } = self;
22
23        for _ in 0..drop {
24            path.pop();
25        }
26
27        path.push(header);
28    }
29}
30
31/// Indicates that a subheader was found without a corresponding header.
32pub struct SubheaderWithoutHeader;
33
34/// Parses a header from a line of text.
35///
36/// # Arguments
37///
38/// * `path`: A list of headers including the current header.
39/// * `line`: The line of text to parse.
40///
41/// # Returns
42///
43/// If the line is not a header, returns `None`.
44/// If the line is a valid header, returns the `PathChanges` which have to be used to update the path.
45/// If the header is a subheader without a corresponding header, an error is returned.
46pub fn parse_header<'a>(
47    path: &'a mut Vec<Box<str>>,
48    line: &str,
49) -> Option<Result<PathChanges<'a>, SubheaderWithoutHeader>> {
50    let mut start = 0;
51
52    let mut chars = line.chars();
53    while Some('#') == chars.next() {
54        start += 1;
55    }
56
57    if start == 0 {
58        return None;
59    }
60
61    let level = start - 1;
62
63    let len = path.len();
64
65    Some(if len < level {
66        Err(SubheaderWithoutHeader)
67    } else {
68        Ok(PathChanges {
69            drop: len - level,
70            header: line[start..].trim().into(),
71            path,
72        })
73    })
74}