rstwrap
A command-line tool to wrap prose paragraphs in reStructuredText (.rst) files to a maximum line width.
Only prose paragraphs and list items are wrapped. Everything else (directives, literal blocks, tables, section underlines, comments, indented blocks) is left unchanged.
- This is a very long paragraph that goes way beyond the standard seventy-nine characters and really should be wrapped for better readability in a terminal or text editor.
+ This is a very long paragraph that goes way beyond the standard seventy-nine
+ characters and really should be wrapped for better readability in a terminal or
+ text editor.
Primary workflows:
- Local: format
.rstfiles automatically on save in your editor. - CI: enforce consistent line width using the --check flag.
Installation
pip install rstwrap
Usage
Examples:
rstwrap docs/*.rst
rstwrap docs/ # whole dir, recursive
rstwrap --check docs/*.rst
rstwrap --width 120 foo.rst
rstwrap --no-join docs/*.rst # only wrap over-width lines
rstwrap --safe docs/*.rst # verify output with docutils
cat foo.rst | rstwrap - # read stdin, write to stdout
Options:
-w,--width: maximum line length (default: 79)--diff: print a unified diff instead of writing files--color: colorize diff output (auto,always,never; default:auto)--check: exit with code 1 if any file would be changed; don't write--join/--no-join: merge short consecutive lines within a paragraph into a single line (up to the target width).--safe: after wrapping, parse both the input and the output with docutils, and skip any file whose document tree would change (prints a diff to stderr and exits with code 1). Requirespip install 'rstwrap[safe]'.-q,--quiet: suppress informational output.--version: print the version and exit
Editor integration
Use - instead of a file path to read from stdin and write to stdout.
This lets you integrate it into any editor that can pipe the current buffer
through a shell command, and format .rst files on save.
Vim / Neovim
Add to ~/.vimrc:
autocmd BufWritePre *.rst silent! %!rstwrap -
VS Code
With the Custom Local Formatters extension:
"customLocalFormatters.formatters": [
{
"command": "rstwrap -",
"languages": ["restructuredtext"]
}
]
Sublime Text
With the Fmt plugin, add to
Preferences > Package Settings > Fmt > Settings:
{
"rules": [
{
"selector": "text.restructuredtext",
"cmd": ["rstwrap", "-"],
"format_on_save": true
}
]
}
Emacs
(defun rstwrap-buffer ()
(interactive)
(let ((p (point)))
(shell-command-on-region (point-min) (point-max)
"rstwrap -" nil t)
(goto-char p)))
GitHub Actions
Add the following workflow into .github/workflows/rstwrap.yml to fail CI for
any .rst file that isn't properly wrapped. Adjust docs/ to wherever your
.rst files live.
name: rstwrap
on: [push, pull_request]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v6
with:
python-version: '3.x'
- run: pip install 'rstwrap[safe]'
- run: rstwrap --check --diff --safe docs/
Configuration via pyproject.toml
Project-wide defaults can be set in a [tool.rstwrap] section of
pyproject.toml. The tool walks up from the current working directory
to find the nearest one. Supported keys:
[tool.rstwrap]
width = 120 # default: 79
join = false # default: true
safe = true # default: false
Command-line flags override anything set in pyproject.toml. To turn
off a setting from the CLI for a single run, use the negation flags
--no-join / --no-safe. Per-invocation flags (--check, --diff) are not
configurable in pyproject.toml — they're run modes, not project policy.
What gets wrapped
-
Prose paragraphs
- This is a very long paragraph that goes way beyond the standard seventy-nine characters and really should be wrapped. + This is a very long paragraph that goes way beyond the standard + seventy-nine characters and really should be wrapped.
-
Lists (bullet and enumerated), including nested sublists
- - This is a very long bullet item that exceeds the target width and needs to be re-wrapped to fit within the line limit. + - This is a very long bullet item that exceeds the target width and + needs to be re-wrapped to fit within the line limit.
-
Bodies of directives that contain prose (
.. note::,.. warning::,.. versionadded::,.. class::, etc.).. note:: - This is a very long note that exceeds the target width and needs to be re-wrapped to fit within the line limit. + This is a very long note that exceeds the target width and needs + to be re-wrapped to fit within the line limit.
-
Short consecutive lines within a paragraph (disable with
--no-join)- Some short - lines that - could fit on one. + Some short lines that could fit on one.
What gets formatted
Beyond wrapping, the tool also applies these normalizations everywhere (including lines that already fit within the target width):
-
Double or more spaces in prose are collapsed
- hello world + hello world
-
Consecutive blank lines between top-level paragraphs are collapsed into one. Blank lines inside indented content (literal blocks, directive bodies, simple tables) are preserved verbatim.
- Paragraph one. - - - - Paragraph two. + Paragraph one. + + Paragraph two.
-
Trailing whitespace is stripped from every line
- Some text with trailing spaces.··· + Some text with trailing spaces.
-
\r\n(Windows line endings) are converted to\n(UNIX)
What is left untouched
- Code blocks (
.. code-block::,::blocks) - Tables (grid and simple)
- Section titles and underlines
- Comments, hyperlink targets, substitution definitions
- Field lists (
:param foo:,:type bar:) - Option list items (
-x,--foo) - Block quotes
- Inline RST constructs (
:role:`display <target>`,*emphasis*,**bold**, etc.) are treated as atomic tokens: they are never split across lines, and their internal whitespace is preserved.
Tested against real-world docs
This tool targets a very specific niche: formatting reStructuredText (RST)
without breaking the semantic structure of the document. The integration
test suite runs against a large corpus of real-world .rst files (~7800 in
total) from several upstream projects:
- CPython (~550 files)
- Linux (~3900 files)
- Python PEPs (~740 files)
- Sphinx (~150 files)
- Salt (~1100 files)
- Ansible (~480 files)
- NumPy (~340 files)
- pytest (~260 files)
- SQLAlchemy (~200 files)
For every file the suite verifies:
- Idempotency: running the tool twice produces the same output as running it once.
- Width: no tool-produced line exceeds the target width (verbatim passthrough of already-long source lines is allowed).
- No double spaces: no tool-produced prose line contains a bare double space.
- Document tree invariant: parsing the original and the wrapped file with docutils produces identical document trees (after normalising intra-node whitespace). This confirms that wrapping prose never alters headings, directives, code blocks, hyperlinks, or any other structural element.
License
MIT
Release files for rstwrap 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| rstwrap-0.1.1.tar.gz | 50.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| rstwrap-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 69.1 kB
Release files / rstwrap-0.1.1.tar.gz
| Download URL | rstwrap-0.1.1.tar.gz |
|---|---|
| Size | 50.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
122fbb7b161806f3fc87ff2ab53ff89ddca5044424bea0a90c2374153737a6cc
|
|
BLAKE2b-256 checksum How to use checksums |
eb3ad6b73bded79d5022038271fc0130cf28fb6f5935d39db0963b8ca8f29e82
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Apr 17, 2026.
Transparency logRelease files / rstwrap-0.1.1-py3-none-any.whl
| Download URL | rstwrap-0.1.1-py3-none-any.whl |
|---|---|
| Size | 18.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
45552c3d8fd29892c5daee32f1482119a0cad085117f4495d19ff908cfe9d51b
|
|
BLAKE2b-256 checksum How to use checksums |
eae1b93a9e96a7e4b76312408638badb832cb60bcc751ba0b73e1f5b8b10a860
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Apr 17, 2026.
Transparency log