add tools/rewrap-comments.py
This commit is contained in:
Executable
+152
@@ -0,0 +1,152 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Rewrap over-long Zig comment lines at a column limit (default 100).
|
||||
|
||||
Rules:
|
||||
- Only comment-only lines are touched; trailing comments after code are left alone.
|
||||
- Consecutive comment lines with the same indentation and marker (`//`, `///`, `//!`)
|
||||
form a block. Blank comment lines separate paragraphs within a block.
|
||||
- A line whose text starts with `- ` begins a bullet paragraph; its continuation
|
||||
lines are the ones indented to align under the bullet's text (bullet lead + 2).
|
||||
- Plain paragraphs join consecutive lines with the same text indentation;
|
||||
wrapped lines align where the first line's text begins.
|
||||
- A paragraph is rewrapped only if at least one of its lines exceeds the limit,
|
||||
so deliberate short line breaks elsewhere are preserved.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import difflib
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import textwrap
|
||||
|
||||
LIMIT = 100
|
||||
COMMENT_RE = re.compile(r"^(\s*)(//[/!]?)(?:\s(.*))?$")
|
||||
BULLET_RE = re.compile(r"^(\s*)- (.*)$")
|
||||
|
||||
|
||||
def split_paragraphs(texts):
|
||||
"""texts: list of comment text (None for a bare marker line).
|
||||
Returns paragraphs: dicts with lead/hang/bullet/texts/lines(indices)."""
|
||||
paragraphs = []
|
||||
current = None
|
||||
for index, text in enumerate(texts):
|
||||
if text is None or text.strip() == "":
|
||||
paragraphs.append({"literal": True, "lines": [index]})
|
||||
current = None
|
||||
continue
|
||||
lead = len(text) - len(text.lstrip(" "))
|
||||
bullet = BULLET_RE.match(text)
|
||||
if bullet:
|
||||
current = {
|
||||
"lead": len(bullet.group(1)),
|
||||
"hang": len(bullet.group(1)) + 2,
|
||||
"bullet": True,
|
||||
"texts": [bullet.group(2)],
|
||||
"lines": [index],
|
||||
}
|
||||
paragraphs.append(current)
|
||||
elif current is not None and lead == current["hang"]:
|
||||
current["texts"].append(text)
|
||||
current["lines"].append(index)
|
||||
else:
|
||||
current = {
|
||||
"lead": lead,
|
||||
"hang": lead,
|
||||
"bullet": False,
|
||||
"texts": [text],
|
||||
"lines": [index],
|
||||
}
|
||||
paragraphs.append(current)
|
||||
return paragraphs
|
||||
|
||||
|
||||
def wrap_paragraph(paragraph, prefix):
|
||||
joined = re.sub(r"\s+", " ", " ".join(t.strip() for t in paragraph["texts"]))
|
||||
if paragraph["bullet"]:
|
||||
initial = prefix + " " * paragraph["lead"] + "- "
|
||||
else:
|
||||
initial = prefix + " " * paragraph["lead"]
|
||||
subsequent = prefix + " " * paragraph["hang"]
|
||||
return textwrap.wrap(
|
||||
joined,
|
||||
width=LIMIT,
|
||||
initial_indent=initial,
|
||||
subsequent_indent=subsequent,
|
||||
break_long_words=False,
|
||||
break_on_hyphens=False,
|
||||
)
|
||||
|
||||
|
||||
def rewrap_block(original_lines, indent, marker, texts):
|
||||
prefix = indent + marker + " "
|
||||
output = []
|
||||
for paragraph in split_paragraphs(texts):
|
||||
block_originals = [original_lines[i] for i in paragraph["lines"]]
|
||||
if paragraph.get("literal") or all(len(l) <= LIMIT for l in block_originals):
|
||||
output.extend(block_originals)
|
||||
else:
|
||||
output.extend(wrap_paragraph(paragraph, prefix))
|
||||
return output
|
||||
|
||||
|
||||
def process(source):
|
||||
lines = source.split("\n")
|
||||
result = []
|
||||
i = 0
|
||||
while i < len(lines):
|
||||
match = COMMENT_RE.match(lines[i])
|
||||
if not match:
|
||||
result.append(lines[i])
|
||||
i += 1
|
||||
continue
|
||||
indent, marker = match.group(1), match.group(2)
|
||||
block_lines, texts = [], []
|
||||
while i < len(lines):
|
||||
m = COMMENT_RE.match(lines[i])
|
||||
if not m or m.group(1) != indent or m.group(2) != marker:
|
||||
break
|
||||
block_lines.append(lines[i])
|
||||
texts.append(m.group(3))
|
||||
i += 1
|
||||
result.extend(rewrap_block(block_lines, indent, marker, texts))
|
||||
return "\n".join(result)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--write", action="store_true", help="apply changes (default: diff only)")
|
||||
parser.add_argument("files", nargs="*", help="files to process (default: git ls-files '*.zig')")
|
||||
args = parser.parse_args()
|
||||
|
||||
files = args.files or subprocess.run(
|
||||
["git", "ls-files", "*.zig"], capture_output=True, text=True, check=True
|
||||
).stdout.split()
|
||||
|
||||
changed = 0
|
||||
for path in files:
|
||||
with open(path, encoding="utf-8") as f:
|
||||
source = f.read()
|
||||
rewrapped = process(source)
|
||||
if rewrapped == source:
|
||||
continue
|
||||
changed += 1
|
||||
if args.write:
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
f.write(rewrapped)
|
||||
print(f"rewrapped {path}")
|
||||
else:
|
||||
sys.stdout.writelines(
|
||||
difflib.unified_diff(
|
||||
source.splitlines(keepends=True),
|
||||
rewrapped.splitlines(keepends=True),
|
||||
fromfile=path,
|
||||
tofile=path,
|
||||
)
|
||||
)
|
||||
if not changed:
|
||||
print("no comments over the limit")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user