Files
Rpa_Skills/skills/rpa-bot-handoff/templates/to_docx.py
T
VasanthandClaude Opus 5.5 f4c42f2956 rpa-bot-handoff: end with HANDOVER.docx for the ops team
New last step: templates/to_docx.py turns the finished HANDOVER.md into a
Word document (python-docx), the skill checks it and gives that path to
the user as the deliverable. --selfcheck converts the Tebra example and
is run by tests/test_templates.py.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 15:01:42 +05:30

114 lines
4.3 KiB
Python

"""Turn the ops guide (HANDOVER.md) into a Word document ops can open, print and pass around.
python to_docx.py HANDOVER.md # writes HANDOVER.docx next to it
python to_docx.py HANDOVER.md -o guide.docx
python to_docx.py --selfcheck
Handles what the template uses: # headings, paragraphs, - bullets, - [ ] checkboxes, 1. steps,
| tables |, ``` blocks, **bold** and `code`. Needs python-docx (pip install python-docx).
"""
import argparse
import re
import sys
import tempfile
from pathlib import Path
from docx import Document
from docx.shared import Pt
INLINE = re.compile(r"(\*\*[^*]+\*\*|`[^`]+`)")
def add_runs(par, text):
for part in INLINE.split(text):
if part.startswith("**") and part.endswith("**"):
par.add_run(part[2:-2]).bold = True
elif part.startswith("`") and part.endswith("`"):
par.add_run(part[1:-1]).font.name = "Consolas"
elif part:
par.add_run(part)
def cells(line):
return [c.strip() for c in line.strip().strip("|").split("|")]
def convert(md_text, out_path):
doc = Document()
doc.styles["Normal"].font.name = "Calibri"
doc.styles["Normal"].font.size = Pt(10.5)
lines = md_text.splitlines()
i = 0
while i < len(lines):
line = lines[i]
if line.startswith("```"):
block = []
i += 1
while i < len(lines) and not lines[i].startswith("```"):
block.append(lines[i])
i += 1
run = doc.add_paragraph().add_run("\n".join(block))
run.font.name = "Consolas"
run.font.size = Pt(9.5)
elif line.startswith("|"):
rows = []
while i < len(lines) and lines[i].startswith("|"):
if not re.match(r"^\|[\s:|-]+\|\s*$", lines[i]): # skip the |---| separator
rows.append(cells(lines[i]))
i += 1
i -= 1
width = max(len(r) for r in rows)
table = doc.add_table(rows=len(rows), cols=width)
table.style = "Table Grid"
for r, row in enumerate(rows):
for c, text in enumerate(row):
par = table.cell(r, c).paragraphs[0]
add_runs(par, text)
if r == 0:
for run in par.runs:
run.bold = True
elif m := re.match(r"^(#{1,3})\s+(.*)", line):
doc.add_heading(m[2], level=len(m[1]) - 1 if len(m[1]) > 1 else 0)
elif m := re.match(r"^\s*- \[ \]\s+(.*)", line):
add_runs(doc.add_paragraph(style="List Bullet"), "☐ " + m[1])
elif m := re.match(r"^\s*[-*]\s+(.*)", line):
add_runs(doc.add_paragraph(style="List Bullet"), m[1])
elif m := re.match(r"^\s*\d+\.\s+(.*)", line):
add_runs(doc.add_paragraph(style="List Number"), m[1])
elif line.strip() and line.strip() != "---":
add_runs(doc.add_paragraph(), line.strip())
i += 1
doc.save(out_path)
return doc
def selfcheck():
example = Path(__file__).parent / "reference" / "tebra-auto-post.md"
guide = example.read_text(encoding="utf-8").split("# File 2:")[0].split("# File 1: `HANDOVER.md`")[1]
out = Path(tempfile.mkdtemp()) / "HANDOVER.docx"
convert(guide, out)
doc = Document(out)
heads = [p.text for p in doc.paragraphs if p.style.name.startswith("Heading")]
assert [h.split(".")[0] for h in heads if h[:1].isdigit()] == [str(n) for n in range(1, 12)], heads
assert len(doc.tables) >= 6, len(doc.tables)
assert doc.tables[0].cell(0, 0).text == "Where", doc.tables[0].cell(0, 0).text
assert not any("**" in p.text or "|---" in p.text for p in doc.paragraphs)
print(f"selfcheck ok: {len(heads)} headings, {len(doc.tables)} tables -> {out}")
if __name__ == "__main__":
ap = argparse.ArgumentParser()
ap.add_argument("md", nargs="?")
ap.add_argument("-o", "--out")
ap.add_argument("--selfcheck", action="store_true")
a = ap.parse_args()
if a.selfcheck:
selfcheck()
sys.exit(0)
if not a.md:
ap.error("give the HANDOVER.md path, or --selfcheck")
src = Path(a.md)
out = Path(a.out) if a.out else src.with_suffix(".docx")
convert(src.read_text(encoding="utf-8"), out)
print(f"wrote {out}")