feat: mdgrep 0.1.0 – boolean full-text search for Markdown
- 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>
This commit is contained in:
commit
9c7b7e81f9
1 changed files with 2642 additions and 0 deletions
119
tests/test_markdown.py
Normal file
119
tests/test_markdown.py
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
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"]
|
||||
Loading…
Reference in a new issue