AISE502/tools/md2confluence.py

98 lines
4.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""Convert an AISE502 module description (Markdown) into Confluence wiki markup.
python3 tools/md2confluence.py <in.md> <out.txt>
Handles the constructs these documents use: ATX headings, pipe tables, horizontal rules,
bullet and numbered lists (one nesting level), block quotes, and inline bold/italic/code.
Hard-wrapped lines are joined first, so a paragraph or list item becomes one line -- the form
Confluence expects. The "Inhalte" section's `**N. Title** *(part)*` blocks with their bullets
become a numbered list with sub-bullets, matching the earlier hand-written export.
"""
import re, sys, pathlib
def inline(t):
t = t.replace("--", "–")
t = re.sub(r"\*\*(.+?)\*\*", lambda m: "\x01" + m.group(1) + "\x01", t) # bold -> *x*
t = re.sub(r"(?<!\w)\*(.+?)\*(?!\w)", lambda m: "_" + m.group(1) + "_", t) # italic -> _x_
t = t.replace("\x01", "*")
t = re.sub(r"`([^`]+)`", lambda m: "{{" + m.group(1) + "}}", t)
return t
def unwrap(lines):
"""join hard-wrapped continuation lines into their paragraph / list item / table row"""
out = []
for raw in lines:
line = raw.rstrip("\n")
stripped = line.strip()
starts_block = (not stripped or line.startswith("#") or line.startswith("|")
or re.match(r"^\s*([-*+]|\d+\.)\s", line) or stripped.startswith(">")
or re.match(r"^-{3,}$", stripped))
if out and out[-1].strip() and not starts_block:
out[-1] = out[-1].rstrip() + " " + stripped
else:
out.append(line)
return out
def convert(md):
lines = unwrap(md.splitlines())
res, i, section, in_item = [], 0, "", False
while i < len(lines):
line = lines[i]; s = line.strip()
if not s:
res.append(""); i += 1; continue
if re.match(r"^-{3,}$", s):
res.append("----"); i += 1; continue
m = re.match(r"^(#{1,6})\s+(.*)$", s)
if m:
level, text = len(m.group(1)), inline(m.group(2))
if level == 3 and section.startswith("Inhalte"):
res.append(f"h4. {text}")
else:
res.append(f"h{min(level,4)}. {text}")
if level == 2: section = m.group(2); in_item = False
i += 1; continue
if s.startswith("|"): # table
rows = []
while i < len(lines) and lines[i].strip().startswith("|"):
rows.append([c.strip() for c in lines[i].strip().strip("|").split("|")]); i += 1
body = [r for r in rows if not all(re.fullmatch(r":?-{2,}:?", c or "-") for c in r)]
head = body[0] if body and any(body[0]) and len(body) > 1 else None
if head and rows.index(head) == 0 and any(c for c in head):
res.append("||" + "||".join(inline(c) for c in head) + "||")
body = body[1:]
for r in body:
res.append("|" + "|".join(inline(c) if c else " " for c in r) + "|")
continue
if s.startswith(">"):
res.append("{quote}"); res.append(inline(s.lstrip("> ").strip())); res.append("{quote}")
i += 1; continue
m = re.match(r"^(\s*)(\d+)\.\s+(.*)$", line) # numbered item
if m:
res.append("# " + inline(m.group(3))); in_item = True; i += 1; continue
m = re.match(r"^(\s*)[-*+]\s+(.*)$", line) # bullet
if m:
indent, text = len(m.group(1)), inline(m.group(2))
res.append(("#* " if (indent >= 2 or in_item) else "* ") + text)
i += 1; continue
m = re.match(r"^\*\*(\d+)\.\s+(.*)$", s) # "**7. Title** *(part)*"
if m and section.startswith("Inhalte"):
res.append("# " + inline("**" + m.group(2))) # the list supplies the number
in_item = True; i += 1; continue
res.append(inline(s)); in_item = False; i += 1
# Confluence restarts a numbered list after a blank line: drop blanks between list items
tight, item = [], re.compile(r"^(#\*?|\*)\s")
for j, line in enumerate(res):
if not line.strip() and tight and item.match(tight[-1]):
nxt = next((x for x in res[j + 1:] if x.strip()), "")
if item.match(nxt):
continue
tight.append(line)
text = "\n".join(tight)
return re.sub(r"\n{3,}", "\n\n", text).strip() + "\n"
if __name__ == "__main__":
src, dst = pathlib.Path(sys.argv[1]), pathlib.Path(sys.argv[2])
dst.write_text(convert(src.read_text(encoding="utf-8")), encoding="utf-8")
print(f"wrote {dst} ({len(dst.read_text(encoding='utf-8').splitlines())} lines) from {src.name}")