Skip to main content

Rewrap Python # comments to a specified line length

Project description

octowrap

CI PyPI Python License

A CLI tool that rewraps octothorpe (#) Python comments to a specified line length while preserving commented-out code, section dividers, list items, and tool directives. TODO/FIXME markers are intelligently rewrapped with continuation indentation.

Features

  • Rewraps comment blocks to a configurable line length (default 88)
  • Keeps hyphenated words intact (never breaks command-line-interface at hyphens)
  • Keeps long words and URLs intact (they overflow the line length rather than being broken mid-word)
  • Heals previously broken hyphenated words on rewrap (e.g. re- / validate -> re-validate)
  • Preserves commented-out Python code (detected via 21 heuristic patterns with a prose disqualifier to avoid false positives on natural English)
  • Preserves section dividers (# --------, # ========, etc.)
  • Preserves list items (bullets, numbered items)
  • Rewraps TODO/FIXME markers with proper continuation indent, with configurable patterns, case sensitivity, and multi-line collection
  • Preserves tool directives (type: ignore, noqa, fmt: off, pragma: no cover, pylint: disable, etc.)
  • Supports # octowrap: off / # octowrap: on pragma comments to disable rewrapping for regions of a file
  • Applies changes automatically by default, or use -i for interactive per block approval with colorized diffs (a accept, A accept all, e exclude, f flag, s skip, q quit). Flagging inserts a FIXME marker above the block for later human attention. Quitting stops all processing, including remaining files.
  • Reads from stdin when - is passed as the path (like black/ruff/isort)
  • Auto-detects color support; respects --no-color, --color, and the NO_COLOR env var
  • Atomic file writes (temp file + rename) to protect against interruptions and power loss
  • Project-level configuration via [tool.octowrap] in pyproject.toml

Development Setup

git clone https://github.com/camUrban/octowrap.git
cd octowrap
uv venv            # uses .python-version (3.13)
uv pip install -e ".[dev]"

Note: The dev environment is pinned to Python 3.13 via .python-version because docformatter's untokenize dependency doesn't build on 3.14. The runtime itself supports 3.11+.

Usage

octowrap <files_or_dirs> [--line-length 88] [--config PATH] [--stdin-filename PATH] [--dry-run] [--diff] [--check] [--no-recursive] [-i] [--color | --no-color]

Stdin/stdout

Pass - as the path to read from stdin and write to stdout:

echo "# A very long comment that needs rewrapping to a shorter width." | octowrap -
cat file.py | octowrap - --diff          # show diff
cat file.py | octowrap - --check         # exit 1 if changes needed
cat file.py | octowrap - -l 79           # custom line length

Use --stdin-filename to provide the original file path for config discovery and diff labels (useful for editor integrations like VS Code and Vim that pipe buffers via stdin):

cat file.py | octowrap - --stdin-filename src/app.py --diff

Note: - cannot be mixed with other paths and is incompatible with -i (interactive mode). --stdin-filename requires -.

Example

Before:

# This is a long comment that has been written without much regard for line length and really should be wrapped to fit within a reasonable number of columns.

After (--line-length 88):

# This is a long comment that has been written without much regard for line
# length and really should be wrapped to fit within a reasonable number of
# columns.

TODO/FIXME Rewrapping

By default, TODO and FIXME markers are detected (case-insensitive, no colon required) and rewrapped with the marker on the first line and a one-space continuation indent on subsequent lines:

Before:

# TODO: Refactor this function to use the new async API instead of the old synchronous one, and update all callers.

After (--line-length 88):

# TODO: Refactor this function to use the new async API instead of the old
#  synchronous one, and update all callers.

Multi-line TODOs (continuation lines starting with exactly one space) are collected and rewrapped together:

# TODO: This is a long todo
#  that continues on the next line

Configure TODO handling via pyproject.toml:

[tool.octowrap]
todo-patterns = ["todo", "fixme", "hack"]    # replace default patterns
extend-todo-patterns = ["note"]              # add to effective patterns
todo-case-sensitive = true                   # match patterns literally
todo-multiline = false                       # don't collect continuations

Setting todo-patterns = [] disables TODO detection entirely, causing former TODO lines to be rewrapped as regular prose.

Disabling Rewrapping

Use pragma comments to protect regions of a file from rewrapping, similar to # fmt: off/on in black/ruff:

# octowrap: off
# This comment will not be rewrapped,
# no matter how long or short
# the lines are.
# octowrap: on

# This comment will be rewrapped normally.
  • Directives are case-insensitive (# OCTOWRAP: OFF works)
  • Must be a standalone comment line (inline x = 1 # octowrap: off is ignored)
  • # octowrap: off without a matching on disables rewrapping through end of file
  • Pragma lines themselves are always preserved as-is

Editor Integration

PyCharm

Settings -> Tools -> File Watchers -> Add:

  • File type: Python
  • Program: $ProjectFileDir$/.venv/Scripts/octowrap.exe (or .venv/bin/octowrap on Unix)
  • Arguments: $FilePath$
  • Output paths to refresh: $FilePath$
  • Working directory: $ProjectFileDir$

Pre-commit Hook

Add octowrap to your .pre-commit-config.yaml:

- repo: https://github.com/camUrban/octowrap
  rev: v0.3.1
  hooks:
    - id: octowrap
      # args: [-l, "79"]       # custom line length
      # args: [--check]        # fail without modifying (useful for CI)

GitHub Actions

Use --check in CI to fail if any comments would be rewrapped:

- name: Install octowrap
  run: pip install octowrap

- name: Check comment wrapping
  run: octowrap --check .

Configuration

Add a [tool.octowrap] section to your pyproject.toml to set project-level defaults:

[tool.octowrap]
line-length = 120
recursive = false
exclude = ["migrations", "generated"]
extend-exclude = ["vendor"]
Key Type Default CLI equivalent
line-length int 88 --line-length
recursive bool true --no-recursive
exclude list[str]
extend-exclude list[str]
todo-patterns list[str] ["todo", "fixme"]
extend-todo-patterns list[str]
todo-case-sensitive bool false
todo-multiline bool true

CLI flags always take precedence over config values. Use --config PATH to point to a specific pyproject.toml instead of relying on auto-discovery.

exclude replaces the built-in default exclude list entirely. extend-exclude adds patterns to the defaults (or to exclude if set). Default excludes: .git, .hg, .svn, .bzr, .venv, venv, .tox, .nox, .mypy_cache, .ruff_cache, .pytest_cache, __pycache__, __pypackages__, _build, build, dist, node_modules, .eggs. Patterns are matched against individual path components using fnmatch.

todo-patterns replaces the default TODO marker patterns (["todo", "fixme"]). extend-todo-patterns adds to the effective list. Both can be combined. Setting todo-patterns = [] disables TODO detection entirely.

License

MIT

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

octowrap-0.3.1.tar.gz (38.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

octowrap-0.3.1-py3-none-any.whl (16.7 kB view details)

Uploaded Python 3

File details

Details for the file octowrap-0.3.1.tar.gz.

File metadata

  • Download URL: octowrap-0.3.1.tar.gz
  • Upload date:
  • Size: 38.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for octowrap-0.3.1.tar.gz
Algorithm Hash digest
SHA256 5c89ca511c52ada6f7037ce0ff351c0366f7444bd60fecaafbee770692f5cc92
MD5 f9f6809b103fd6e53aa654737b9104cf
BLAKE2b-256 f0c005d308c836a5471fa1266d1b3dda97cba69453dbb298c4ce3617c306526a

See more details on using hashes here.

Provenance

The following attestation bundles were made for octowrap-0.3.1.tar.gz:

Publisher: publish.yml on camUrban/octowrap

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file octowrap-0.3.1-py3-none-any.whl.

File metadata

  • Download URL: octowrap-0.3.1-py3-none-any.whl
  • Upload date:
  • Size: 16.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for octowrap-0.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 c3cead9308e3f8e0ebe67c62c73d2925b6f95a921313e94e81893739114a4aff
MD5 0f38abac7804e774c9c0aadae1464698
BLAKE2b-256 5449f2c48f1ebc9340923520bb402be6639155ab638c2b68356d7373956a7be4

See more details on using hashes here.

Provenance

The following attestation bundles were made for octowrap-0.3.1-py3-none-any.whl:

Publisher: publish.yml on camUrban/octowrap

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page