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}