# Header parsing
This library is meant to help with parsing markdown inspired files, where headers are marked by a sequence of "#".
The only function `parse_header` is meant to be used when parsing a file line by line.
If the line starts with a "#", the function will return `Some` result to indicate if it's a valid format, else it will return `None`.
The only invalid format is when a header starts with more than one "#" more than the previous header.
This makes using markdown like formats viable for config files.
## Example config
Headers and subheaders are stored as array.
For example you could have a file like this:
```md
A
# Header 1
B
## Subheader 1
C
## Subheader 2
D
# Header 2
E
## Subheader 1
F
## Subheader 2
G
```
The letters would belong to different headers:
- `A` belongs to no subheader (`[]`)
- `B` belongs to "Header 1" (`["Header 1"]`)
- `C` belongs to "Subheader 1" of "Header 1" (`["Header 1", "Subheader 1"]`)
- `D` belongs to "Subheader 2" of "Header 1" (`["Header 1", "Subheader 2"]`)
- `E` belongs to "Header 2" (`["Header 2"]`)
- `F` belongs to "Subheader 1" of "Header 2" (`["Header 2", "Subheader 1"]`)
- `G` belongs to "Subheader 2" of "Header 2" (`["Header 2", "Subheader 2"]`)
## Usage
You have to store the path of headers and subheader yourself.
This way, you are allowed to handle the sections inbetween the headers as you want.
```rust
use header_parsing::parse_header;
use std::io::{BufRead, BufReader, Read};
enum Error {
SubheaderWithoutHeader,
... // Custom error types
}
fn parse<R: Read>(reader: R) -> Result<Map<Vec<Box<str>>, Value>, Error> {
// a `Vec<Box<str>>`
let mut current_path = Vec::new();
// the result is likely a map type, like a `HashMap`
let mut result = Map::new();
// the content inbetween the headers parsed into the wanted format
let mut value = Value::new();
for line in BufReader::new(reader).lines() {
let Ok(line) = line else {
return Err(...);
};
// if current line is a header
if let Some(success) = parse_header(&mut current_path, &line) {
if let Ok(path_changes) = success {
// add parsed value to previous subheader
result.insert(path_changes.path.clone(), value);
// start parsing next subsection
value = Value::new();
// apply path changes
path_changes.apply();
} else {
return Err(Error::SubheaderWithoutHeader);
}
} else {
// parse the content inbetween headers
value.modify(parse_line(&line)?);
}
}
result.insert(current_path, value);
Ok(result)
}
```
## Simpler usage with `Parser`
If you don't need to access the path before applying the changes, `Parser` tracks the path for you and applies the changes automatically.
Every line is classified as either a `Header` (with its nesting `level`, `0` for `#`) or `Content`:
```rust
use header_parsing::{Line, Parser};
let mut parser = Parser::new();
for line in content.lines() {
match parser.line(line) {
Ok(Line::Header { level }) => {
// `parser.path()` now contains the full path of headers
}
Ok(Line::Content(line)) => {
// the content inbetween headers
}
Err(_) => { /* subheader without a header */ }
}
}
```