pilcrow
Paste from Word, a PDF, an email client or a CMS and you get sentences broken by hard line breaks, runs of double spaces, non-breaking spaces where they do not belong, and smart quotes that survived three round trips and then did not. This library turns that into clean paragraphs. It does not do anything else, and that is the whole design.
Who This Is For
You maintain something that accepts text a human pasted in from somewhere else. A CMS field, a contact form, a note-taking app, an import pipeline. You have written the same three regexes at least twice. They were subtly wrong both times. The bug that made you write them the second time came from a French document, where the non-breaking space before a colon is correct and must be kept.
Install
pip install pilcrow
Python 3.9 or newer
No dependencies, and none planned. There is nothing else to install
Optional: pip install "pilcrow[cli]" if you want the pilcrow command
The One Thing It Does
normalise() takes a string and returns a string. It joins lines broken mid-sentence, keeps the breaks that were deliberate, collapses whitespace runs without touching indentation inside fenced blocks, and leaves typographic characters alone unless you ask otherwise.
How It Decides
A break is deliberate if the line before it ends in sentence punctuation, is shorter than the running median line length, or starts a list. Three rules, in that order. They are wrong for poetry, and poetry is out of scope in the README rather than in a heuristic.
Public functions
3
Runtime dependencies
0
Python versions in CI
3.9 to 3.13
Median first response on issues
4 days
Contributors
11
What It Will Not Do
It does not detect language, spellcheck, strip HTML, convert Markdown or guess what you meant. Each of those has been requested and declined, and the reasoning sits in the issue thread rather than behind a wontfix label. If you need HTML stripping, use something that actually parses HTML. A regex that half understands HTML is how the second bug on this page started.
"I deleted sixty lines of accumulated regex and replaced them with one function call. The French quotation marks finally survived the round trip.
"from issue 88
Your first run
Call normalise(text) and read the output before you configure anything
If a break you wanted was removed, pass keep_breaks=True
If a non-breaking space you needed was removed, pass locale="fr"
Run pilcrow --diff against a real file from your corpus, not a synthetic one
Only then decide whether you need the other two functions
Where To Go Next
The README is the documentation. There is no separate docs site, because there is not enough surface area to justify one and a docs site that lags the README is worse than none at all. The API reference fits on one page. If something reads as unclear, that is a documentation bug and it is worth an issue.

