Wrap prose paragraphs in reStructuredText files to a max line width
Project description
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
Project details
Release history Release notifications | RSS feed
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 rstwrap-0.1.1.tar.gz.
File metadata
- Download URL: rstwrap-0.1.1.tar.gz
- Upload date:
- Size: 50.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
122fbb7b161806f3fc87ff2ab53ff89ddca5044424bea0a90c2374153737a6cc
|
|
| MD5 |
36a4ed8628842922fe7013c680afee71
|
|
| BLAKE2b-256 |
eb3ad6b73bded79d5022038271fc0130cf28fb6f5935d39db0963b8ca8f29e82
|
Provenance
The following attestation bundles were made for rstwrap-0.1.1.tar.gz:
Publisher:
publish.yml on giampaolo/rstwrap
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rstwrap-0.1.1.tar.gz -
Subject digest:
122fbb7b161806f3fc87ff2ab53ff89ddca5044424bea0a90c2374153737a6cc - Sigstore transparency entry: 1328073566
- Sigstore integration time:
-
Permalink:
giampaolo/rstwrap@75dd947b49be60444b0b9fe18fbd2d3891af77ce -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/giampaolo
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@75dd947b49be60444b0b9fe18fbd2d3891af77ce -
Trigger Event:
push
-
Statement type:
File details
Details for the file rstwrap-0.1.1-py3-none-any.whl.
File metadata
- Download URL: rstwrap-0.1.1-py3-none-any.whl
- Upload date:
- Size: 18.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
45552c3d8fd29892c5daee32f1482119a0cad085117f4495d19ff908cfe9d51b
|
|
| MD5 |
48ddfeeaad37039a2a1d6bfd4f961edc
|
|
| BLAKE2b-256 |
eae1b93a9e96a7e4b76312408638badb832cb60bcc751ba0b73e1f5b8b10a860
|
Provenance
The following attestation bundles were made for rstwrap-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on giampaolo/rstwrap
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rstwrap-0.1.1-py3-none-any.whl -
Subject digest:
45552c3d8fd29892c5daee32f1482119a0cad085117f4495d19ff908cfe9d51b - Sigstore transparency entry: 1328073580
- Sigstore integration time:
-
Permalink:
giampaolo/rstwrap@75dd947b49be60444b0b9fe18fbd2d3891af77ce -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/giampaolo
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@75dd947b49be60444b0b9fe18fbd2d3891af77ce -
Trigger Event:
push
-
Statement type: