Skip to content

Commit 897c1ae

Browse files
committed
✨ Initial implementation
1 parent 1d02ca5 commit 897c1ae

File tree

9 files changed

+408
-1
lines changed

9 files changed

+408
-1
lines changed

.github/dependabot.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# To get started with Dependabot version updates, you'll need to specify which
2+
# package ecosystems to update and where the package manifests are located.
3+
# Please see the documentation for all configuration options:
4+
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5+
6+
version: 2
7+
updates:
8+
- package-ecosystem: github-actions
9+
directory: /
10+
commit-message:
11+
prefix: ⬆️
12+
schedule:
13+
interval: weekly
14+
- package-ecosystem: cargo
15+
directory: /
16+
commit-message:
17+
prefix: ⬆️
18+
schedule:
19+
interval: weekly

.github/workflows/tests.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: tests
2+
3+
on:
4+
push:
5+
branches: [ "main" ]
6+
pull_request:
7+
8+
env:
9+
CARGO_TERM_COLOR: always
10+
11+
jobs:
12+
pre-commit:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v3
16+
- uses: actions/setup-python@v3
17+
- uses: pre-commit/[email protected]
18+
19+
build:
20+
runs-on: ubuntu-latest
21+
steps:
22+
- uses: actions/checkout@v3
23+
- name: Build
24+
run: cargo build --verbose
25+
- name: Run tests
26+
run: cargo test --verbose

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,9 @@ Cargo.lock
1212

1313
# MSVC Windows builds of rustc generate these, which store debugging information
1414
*.pdb
15+
16+
17+
# Added by cargo
18+
19+
/target
20+
/Cargo.lock

.pre-commit-config.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# See https://pre-commit.com for more information
2+
# See https://pre-commit.com/hooks.html for more hooks
3+
repos:
4+
- repo: https://github.com/pre-commit/pre-commit-hooks
5+
rev: v4.4.0
6+
hooks:
7+
- id: trailing-whitespace
8+
- id: end-of-file-fixer
9+
10+
- repo: local
11+
hooks:
12+
- id: fmt
13+
name: fmt
14+
description: Format files with cargo fmt.
15+
entry: cargo fmt
16+
language: rust
17+
types: [rust]
18+
args: ["--"]

Cargo.toml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[package]
2+
name = "markdown-it-front-matter"
3+
version = "0.1.0"
4+
authors = ["Chris Sewell <[email protected]>"]
5+
description = "A markdown-it plugin for parsing front matter"
6+
readme = "README.md"
7+
repository = "https://github.com/chrisjsewell/markdown-it-front-matter.rs"
8+
keywords = ["markdown", "markdown-it"]
9+
categories = ["text-processing", "parsing"]
10+
license = "Apache-2.0"
11+
edition = "2021"
12+
13+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
14+
15+
[dependencies]
16+
markdown-it = "0.5.0"
17+
18+
[dev-dependencies]
19+
indoc = "2.0.1"
20+
rstest = "0.17.0"

README.md

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,47 @@
11
# markdown-it-front-matter.rs
2-
A markdown-it.rs plugin to process front matter containers
2+
3+
A [markdown-it.rs](https://crates.io/crates/markdown-it) plugin to process front matter containers.
4+
5+
## Usage
6+
7+
```rust
8+
let parser = &mut markdown_it::MarkdownIt::new();
9+
markdown_it_front_matter::add(parser);
10+
let ast = parser.parse("---\nfoo: bar\n---\n");
11+
12+
print!("{:#?}", ast.children);
13+
// [
14+
// Node {
15+
// children: [],
16+
// srcmap: Some(
17+
// (
18+
// 0,
19+
// 16,
20+
// ),
21+
// ),
22+
// ext: NodeExtSet(
23+
// {},
24+
// ),
25+
// attrs: [],
26+
// node_type: markdown_it_front_matter::FrontMatter,
27+
// node_value: FrontMatter {
28+
// content: "foo: bar\n",
29+
// },
30+
// },
31+
// ]
32+
```
33+
34+
## Valid Front Matter
35+
36+
Essentially, valid front matter is a fenced block:
37+
38+
* Indicated by **three** or **more** dashes: `---`
39+
* Opening and closing fences must be the same number of *dash* characters
40+
* Opening fence must begin on the first line of the markdown string/file
41+
* Opening fence must not be indented
42+
43+
```yaml
44+
---
45+
valid-front-matter: true
46+
---
47+
```

src/lib.rs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
//! A [markdown_it] plugin for parsing front matter
2+
//!
3+
//! ```
4+
//! let parser = &mut markdown_it::MarkdownIt::new();
5+
//! markdown_it_front_matter::add(parser);
6+
//! let node = parser.parse("---\nfoo: bar\n---\n");
7+
//! ```
8+
9+
use markdown_it::parser::block::{BlockRule, BlockState};
10+
use markdown_it::parser::core::Root;
11+
use markdown_it::{MarkdownIt, Node, NodeValue, Renderer};
12+
13+
#[derive(Debug)]
14+
/// AST node for front-matter
15+
pub struct FrontMatter {
16+
pub content: String,
17+
}
18+
19+
impl NodeValue for FrontMatter {
20+
fn render(&self, _node: &Node, _fmt: &mut dyn Renderer) {
21+
// simply bypass the front-matter in HTML output
22+
}
23+
}
24+
25+
/// Add the front-matter extension to the markdown parser
26+
pub fn add(md: &mut MarkdownIt) {
27+
// insert this rule into block subparser
28+
md.block.add_rule::<FrontMatterBlockScanner>().before_all();
29+
}
30+
31+
/// An extension for the block subparser.
32+
struct FrontMatterBlockScanner;
33+
34+
impl BlockRule for FrontMatterBlockScanner {
35+
fn run(state: &mut BlockState) -> Option<(Node, usize)> {
36+
// check the parent is the document Root
37+
if !state.node.is::<Root>() {
38+
return None;
39+
}
40+
41+
// check we are on the first line of the document
42+
if state.line != 0 {
43+
return None;
44+
}
45+
46+
// check line starts with opening dashes
47+
let opening = state
48+
.get_line(state.line)
49+
.chars()
50+
.take_while(|c| *c == '-')
51+
.collect::<String>();
52+
if !opening.starts_with("---") {
53+
return None;
54+
}
55+
56+
// Search for the end of the block
57+
let mut next_line = state.line;
58+
loop {
59+
next_line += 1;
60+
if next_line >= state.line_max {
61+
return None;
62+
}
63+
64+
let line = state.get_line(next_line);
65+
if line.starts_with(&opening) {
66+
break;
67+
}
68+
}
69+
70+
// get the content of the block
71+
let (content, _) = state.get_lines(state.line + 1, next_line, 0, true);
72+
73+
// return new node and number of lines it occupies
74+
Some((Node::new(FrontMatter { content }), next_line + 1))
75+
}
76+
}
77+
78+
#[cfg(test)]
79+
mod tests {
80+
use super::*;
81+
82+
#[test]
83+
fn it_works() {
84+
let parser = &mut markdown_it::MarkdownIt::new();
85+
add(parser);
86+
let node = parser.parse("---\nfoo: bar\n---\nhallo\n");
87+
// println!("{:#?}", ast.children.first());
88+
assert!(node.children.first().unwrap().is::<FrontMatter>());
89+
90+
let text = node.render();
91+
assert_eq!(text, "hallo\n")
92+
}
93+
}

tests/fixtures.md

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
2+
should parse empty front matter:
3+
.
4+
---
5+
---
6+
# Head
7+
.
8+
9+
<h1>Head</h1>
10+
.
11+
12+
13+
should parse basic front matter:
14+
.
15+
---
16+
x: 1
17+
---
18+
# Head
19+
.
20+
21+
<h1>Head</h1>
22+
.
23+
24+
should parse until triple dots:
25+
.
26+
---
27+
x: 1
28+
...
29+
# Head
30+
.
31+
32+
<h1>Head</h1>
33+
.
34+
35+
should parse front matter with indentation:
36+
.
37+
---
38+
title: Associative arrays
39+
people:
40+
name: John Smith
41+
age: 33
42+
morePeople: { name: Grace Jones, age: 21 }
43+
---
44+
# Head
45+
.
46+
47+
<h1>Head</h1>
48+
.
49+
50+
should ignore spaces after front matter delimiters:
51+
.
52+
---
53+
title: Associative arrays
54+
people:
55+
name: John Smith
56+
age: 33
57+
morePeople: { name: Grace Jones, age: 21 }
58+
---
59+
# Head
60+
.
61+
62+
<h1>Head</h1>
63+
.
64+
65+
should ignore front matter with less than 3 opening dashes:
66+
.
67+
--
68+
x: 1
69+
--
70+
# Head
71+
.
72+
<h2>--
73+
x: 1</h2>
74+
<h1>Head</h1>
75+
.
76+
77+
should require front matter have matching number of opening and closing dashes:
78+
.
79+
----
80+
x: 1
81+
---
82+
# Head
83+
.
84+
<hr>
85+
<h2>x: 1</h2>
86+
<h1>Head</h1>
87+
.

0 commit comments

Comments
 (0)