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