header-parsing 0.3.2

Simplifies parsing the headers of markdown inspired file formats
Documentation
# 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 */ }
    }
}
```