- query language: substrings, phrases, /regex/, AND/OR/NOT (+ UND/ODER/NICHT), parentheses; case-insensitive; ReDoS protection via regex timeouts; syntax errors with stable code and position - SearchEngine over in-memory blocks: filters, predicate, grouping, sorting, paging, time budget, automatic reload, highlighted snippets - Markdown loader with split modes file/heading/list/paragraph - grep-like CLI (mdgrep), JSON output, grep exit codes - Flask blueprint/app with JSON API and widget assets, dev server - framework-free JS widget: live search, URL state, filters, paging, syntax help, en/de labels, CSS custom properties for theming Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
119 lines
3.8 KiB
Python
119 lines
3.8 KiB
Python
import pytest
|
|
|
|
from mdgrep.markdown import load_markdown, markdown_to_text, slugify, split_markdown
|
|
|
|
DOC = """# Report
|
|
## Edition
|
|
|
|
1. **[Rust 2.0 released](https://example.com/rust)** (100 points by alice)
|
|
|
|
The Rust team announced a *major* release with `async` traits.
|
|
|
|
2. **[Go generics](https://example.com/go)** (50 points by bob)
|
|
|
|
Generics in Go are now __fast__.
|
|
|
|
## Insights
|
|
|
|
1. **Memory safety wins**
|
|
|
|
- **Trend:** Rust adoption grows.
|
|
- **Why:** fewer bugs.
|
|
|
|
2. **Typed languages**
|
|
lazy continuation line
|
|
|
|
A closing paragraph
|
|
spanning two lines.
|
|
|
|
---
|
|
|
|
*Footer text*
|
|
|
|
```
|
|
# not a heading
|
|
- not an item
|
|
```
|
|
"""
|
|
|
|
|
|
def kinds(blocks):
|
|
return [(b.meta["kind"], b.meta["section"], b.line) for b in blocks]
|
|
|
|
|
|
def test_split_list():
|
|
blocks = split_markdown(DOC, source="doc.md", split="list")
|
|
assert kinds(blocks) == [
|
|
("item", "Edition", 4),
|
|
("item", "Edition", 8),
|
|
("item", "Insights", 14),
|
|
("item", "Insights", 19),
|
|
("paragraph", "Insights", 22),
|
|
("paragraph", "Insights", 27),
|
|
("paragraph", "Insights", 29),
|
|
]
|
|
first = blocks[0]
|
|
assert first.text == ("Rust 2.0 released (100 points by alice)\n\n"
|
|
"The Rust team announced a major release with async traits.")
|
|
assert first.title == ""
|
|
assert first.id == "doc.md:4" and first.source == "doc.md"
|
|
assert first.meta["markdown"].startswith("1. **[Rust 2.0 released]")
|
|
assert "Trend: Rust adoption grows." in blocks[2].text
|
|
assert "lazy continuation line" in blocks[3].text
|
|
assert "not a heading" in blocks[6].text
|
|
|
|
|
|
def test_split_heading():
|
|
blocks = split_markdown(DOC, split="heading")
|
|
assert [(b.title, b.anchor) for b in blocks] == [
|
|
("Report", "report"), ("Edition", "edition"), ("Insights", "insights")]
|
|
assert "Go generics" in blocks[1].text
|
|
assert not blocks[1].text.startswith("Edition")
|
|
|
|
|
|
def test_split_paragraph():
|
|
blocks = split_markdown(DOC, split="paragraph")
|
|
texts = [b.text.split("\n")[0] for b in blocks]
|
|
assert "Rust 2.0 released (100 points by alice)" in texts
|
|
assert "The Rust team announced a major release with async traits." in texts
|
|
assert "A closing paragraph" in texts
|
|
fence = [b for b in blocks if "not a heading" in b.text]
|
|
assert len(fence) == 1 and "not an item" in fence[0].text
|
|
|
|
|
|
def test_split_file():
|
|
blocks = split_markdown(DOC, source="x.md", split="file")
|
|
assert len(blocks) == 1 and blocks[0].title == "Report" and blocks[0].line == 1
|
|
|
|
|
|
def test_invalid_split_mode():
|
|
with pytest.raises(ValueError):
|
|
split_markdown("x", split="bogus")
|
|
|
|
|
|
def test_markdown_to_text():
|
|
md = ("- **Bold** and *italic* and __b2__ and `code` and ~~gone~~\n"
|
|
" [link](https://x.y/a_(b))  <https://auto.link>\n"
|
|
"> quote with snake_case_name")
|
|
assert markdown_to_text(md) == (
|
|
"Bold and italic and b2 and code and gone\n"
|
|
"link alt https://auto.link\n"
|
|
"quote with snake_case_name")
|
|
|
|
|
|
def test_slugify():
|
|
assert slugify("AI/ML Insights & Trends") == "aiml-insights--trends"
|
|
assert slugify("Über **uns**") == "über-uns"
|
|
|
|
|
|
def test_load_markdown_directory_and_meta(tmp_path):
|
|
(tmp_path / "sub").mkdir()
|
|
(tmp_path / "a.md").write_text("first para\n\nsecond para\n", encoding="utf-8")
|
|
(tmp_path / "sub" / "b.md").write_text("- item one\n- item two\n", encoding="utf-8")
|
|
(tmp_path / "ignore.txt").write_text("nope", encoding="utf-8")
|
|
blocks = load_markdown(tmp_path, split="list", meta=lambda p: {"file": p.name})
|
|
assert [(b.meta["file"], b.text) for b in blocks] == [
|
|
("a.md", "first para"), ("a.md", "second para"),
|
|
("b.md", "item one"), ("b.md", "item two")]
|
|
rel = load_markdown(tmp_path, relative_to=tmp_path)
|
|
assert [b.id for b in rel] == ["a.md:1", "a.md:3", "sub/b.md:1"]
|