@firefoxic/beautypography
v0.1.1
Published
Binds function words, numbers and em dashes in Markdown prose with non-breaking spaces
Maintainers
Readme
@firefoxic/beautypography
Binds function words, numbers and em dashes in Markdown prose with non-breaking spaces, so that a line break never leaves an article, a preposition or a dash stranded at the end of a line.
It reads Markdown as Markdown: inline code spans and fenced code blocks are left exactly as their author wrote them, down to the spaces inside them.
The package has no runtime dependencies, and is not going to have any.
Installation
Globally
pnpm add -g @firefoxic/beautypographyLocally
pnpm add -D @firefoxic/beautypographyNode 24.0 or newer.
Usage
On the command line
beautypography [options] [file...]With no file given, every Markdown file below the current directory is bound, skipping the directories that belong to a tool rather than to the prose — .claude, .git, coverage, dist, node_modules and tmp, wherever in the tree they stand — and leaving the LICENSE.md of the root alone, since a license is quoted verbatim.
A file given by name is bound whatever it is called and wherever it lies: naming a file is asking for that file.
| Option | What it does |
|--------|--------------|
| -c, --check | Report the files that need binding instead of writing them |
| -h, --help | Print the usage |
| -v, --version | Print the version |
The command answers with 0 when there was nothing to do or everything was written, 1 when --check found prose that needs binding or an option was not understood, and 2 when a file could not be read or written. What went wrong is written to the error stream; what was bound, to the standard one.
As a library
import { bindProse } from "@firefoxic/beautypography"
let bound = bindProse("Read the docs of a kind — and more.")bindProse(source, language?) applies the convention to a whole document and is what the command uses. It is idempotent: binding a bound document changes nothing.
For a single line, bindLine(line, patterns) takes a language already compiled by createPatterns(language) — compiling once and binding many lines is why the command is fast on a large document.
ENGLISH is the only language that ships. toWords splits a list of words written as indented prose, which is how the language file holds them. NBSP is the character everything above puts in.
The types Language, Patterns and MaskedLine are exported for anyone writing a language of their own.
What gets bound
- Function words bind forward to the word that follows: articles, prepositions, coordinating and subordinating conjunctions, relative pronouns, particles, and numerals spelled out. Emphasis markers stay glued to the word, so
**not** readyand_not_ readybind as the word does. - Phrases and names claim their spaces before any single word can, longest first, and only where they stand whole:
Keep a Changelogis bound, while the opening ofKeep a Changelogsis not. A name is matched as it is written, since the case of a name carries meaning. - Numbers are bound on both sides, and never to a function word:
cost 0.05 mskeeps its measurement whole,version 2 of itkeeps its version and lets theofgo, and the5ofthe 5 filesgoes to the files rather than to the article. - Em dashes bind to the word before them, never to the line below.
- Exceptions are unbound again at the end, because the rules bind them but the meaning does not —
that isand its like.
Keeping prose bound
In a pre-commit hook
beautypography --check $(git diff --cached --name-only --diff-filter=ACM | grep -E '\.md$')In CI
- name: Check the prose
run: pnpm dlx @firefoxic/beautypography --checkKnown limits
- English only. A second language is planned, and the matching logic is ready for one, but nothing else ships yet.
- The word list is incomplete. It covers the function words that break lines most visibly, not every one English has.
- A code block indented by four spaces is prose to this tool. Fenced blocks are recognised the way CommonMark writes them — backticks or tildes, any length, under an indent of up to three spaces — but telling an indented code block from a nested list item needs a full block parser.
- Any word before a number takes it.
see 5 filesbinds the5back toseeas well as forward to the files it counts. Telling a verb from aversionneeds to know parts of speech.
