Document model for powerful text parsing and processing in Markdown
Project description
FlexDoc
A Markdown parser gives you a block AST but no sentences, sizes, or exact source offsets. An NLP toolkit gives you sentences but no Markdown structure. If you need to know which sentence is in which section, how many words (or LLM tokens) a section holds, or exactly where a link sits in the original text, you end up gluing tools together; the glue breaks on the first edit.
FlexDoc builds a single source-grounded model and exposes its structure as layers over
one shared coordinate space: exact [start, end) offsets into one retained, normalized
source string. The Markdown layer (blocks, inline elements, typed attributes), the
textual layer (paragraphs, sentences, word tokens), and the document layer
(heading hierarchy, table of contents) are independent parses of the same text, so
cross-cutting questions are simple offset queries.
One query primitive (collect()) spans all layers; DocGraph serializes any slice
as language-neutral JSON; SpanRef anchors spans by quoted text so references
survive edits and reparses.
FlexDoc is a standalone library. chopdiff builds its diff-filtering and windowed-transform layer on top of flexdoc.
Installation
uv add flexdoc
# or: pip install flexdoc
Status
Beta (0.3.x). The core model is established, but the later-stage mechanisms in the stabilization roadmap remain open. Breaking changes before 1.0 bump the minor version, so pin a minor version. See the changelog.
Usage
The primary entry point is FlexDoc:
from flexdoc import FlexDoc, NodeKind, TextUnit
markdown_text = "# Introduction\n\nSee [docs](https://example.com/docs).\n"
doc = FlexDoc.from_text(markdown_text)
# Section hierarchy with rolled-up sizes:
print(doc.section_size_tree(units=(TextUnit.words, TextUnit.sentences)))
# # Introduction (4 words, 2 sentences)
# Sizes at every grain, including approximate LLM tokens:
print(doc.size_summary())
# 53 bytes (3 lines, 2 paras, 2 sents, 4 words, ~14 tok)
# One query primitive across all layers:
link = doc.collect(kinds={NodeKind.link})[0]
print(link.attrs["url"], link.source_span)
# https://example.com/docs (20, 52)
# Round-trips back to normalized Markdown:
print(doc.reassemble())
Every located source-backed unit carries an exact [start, end) span into normalized
source_text; paragraphs and sentences also expose that slice as original_text. Some
parser-derived identities can be unlocatable and therefore carry span=None.
FlexDoc delegates Markdown parsing to marko (CommonMark with GFM tables and footnotes) via flowmark, and adds sentence segmentation, the section hierarchy, the flat node table, offset-grounded queries, serialization, and span anchoring on top. Parsing any input never raises: malformed Markdown degrades deterministically and visibly (see the spec and the golden-test corpus that pins this behavior).
The full public surfaces live in the submodules:
flexdoc.docs:FlexDoc,Paragraph,Sentence,Section,Block,BlockType, the node table,collect(),DocGraph,SpanRef, and source-linked render/report helpers.flexdoc.docs.wordtoks,flexdoc.docs.search_tokens,flexdoc.docs.token_diffs, andflexdoc.docs.token_mapping: lower-level token, search, diff, and mapping utilities that are not promoted byflexdoc.docs.flexdoc.html: html-in-md, html/plaintext conversion, HTML tag helpers, the content extractor, and timestamp extraction.flexdoc.util: read-time and token-count estimation.
See usage.md for the main
workflows, and the worked examples (run with uv run python examples/<name>.py from a
checkout):
doc_structure.pycovers section hierarchy, size rollups, offset lookups, and the block tree.normalized_form.pycovers block-type tallies, list-density invariance, and per-section links.backfill_timestamps.pyaligns an edited transcript to its timestamped source via token mapping.
Project Docs
For users:
- Design of record (the full spec): flexdoc-spec.md.
- Installing uv and Python: installation.md.
For contributors:
- Development workflows: development.md.
- Publishing to PyPI: publishing.md.
- Dependency policy: SUPPLY-CHAIN-SECURITY.md.
- Design history and plans: docs/project/specs/active/.
This project was built from simple-modern-uv.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file flexdoc-0.3.0.tar.gz.
File metadata
- Download URL: flexdoc-0.3.0.tar.gz
- Upload date:
- Size: 418.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.12 {"installer":{"name":"uv","version":"0.11.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
95cdad1551376de53c191c0694a7a8d4a1eb972fc1a2e16b4b4e2452ecc0abaa
|
|
| MD5 |
7a25d70f59afd9d2d47e53553c0bca77
|
|
| BLAKE2b-256 |
fb9e689c7e6a82ac47fb36035081c89d629c412f07d68a74f2e8d4c8ffc5134c
|
File details
Details for the file flexdoc-0.3.0-py3-none-any.whl.
File metadata
- Download URL: flexdoc-0.3.0-py3-none-any.whl
- Upload date:
- Size: 90.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.12 {"installer":{"name":"uv","version":"0.11.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a9688cbd74265982b961c85b43b509c89a033ec2a5dfadf6f5ee66841a6e5073
|
|
| MD5 |
2f8791da022627942598fc29647ddf66
|
|
| BLAKE2b-256 |
a0d09ae7b445da66b2c26a3a138377a0ed0dd973defe38696361081a97459307
|