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}