Интегрирован wesp в сайт
CI / quality (push) Canceled after 0s

This commit is contained in:
влад
2026-07-17 12:57:18 +03:00
parent 5dfa06ddbe
commit 355c0ef9f1
883 changed files with 194576 additions and 177 deletions
+290
View File
@@ -0,0 +1,290 @@
#!/usr/bin/env python3
"""Convert ROADMAP.md to PDF (Cyrillic-safe via reportlab)."""
from __future__ import annotations
import re
import sys
from pathlib import Path
from xml.sax.saxutils import escape
from reportlab.lib import colors
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
from reportlab.lib.units import mm
from reportlab.platypus import Paragraph, Preformatted, SimpleDocTemplate, Spacer, Table, TableStyle
ROOT = Path(__file__).resolve().parents[1]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from app.services.pdf_cyrillic_fonts import ( # noqa: E402
register_cyrillic_font,
register_cyrillic_font_bold,
)
def md_inline(text: str) -> str:
text = escape(text)
text = re.sub(r"\*\*(.+?)\*\*", r"<b>\1</b>", text)
text = re.sub(r"`([^`]+)`", r'<font face="Courier">\1</font>', text)
return text
def build_styles(font: str, bold_font: str):
styles = getSampleStyleSheet()
styles.add(
ParagraphStyle(
name="RoadmapTitle",
parent=styles["Title"],
fontName=bold_font,
fontSize=18,
leading=22,
spaceAfter=10,
)
)
styles.add(
ParagraphStyle(
name="RoadmapH2",
parent=styles["Heading2"],
fontName=bold_font,
fontSize=13,
leading=16,
spaceBefore=12,
spaceAfter=6,
)
)
styles.add(
ParagraphStyle(
name="RoadmapH3",
parent=styles["Heading3"],
fontName=bold_font,
fontSize=11,
leading=14,
spaceBefore=8,
spaceAfter=4,
)
)
styles.add(
ParagraphStyle(
name="RoadmapBody",
parent=styles["Normal"],
fontName=font,
fontSize=9,
leading=12,
spaceAfter=4,
)
)
styles.add(
ParagraphStyle(
name="RoadmapQuote",
parent=styles["Normal"],
fontName=font,
fontSize=9,
leading=12,
leftIndent=12,
textColor=colors.HexColor("#374151"),
spaceAfter=6,
)
)
styles.add(
ParagraphStyle(
name="RoadmapBullet",
parent=styles["Normal"],
fontName=font,
fontSize=9,
leading=12,
leftIndent=14,
bulletIndent=6,
spaceAfter=2,
)
)
styles.add(
ParagraphStyle(
name="RoadmapCode",
parent=styles["Code"],
fontName="Courier",
fontSize=7.5,
leading=9.5,
backColor=colors.HexColor("#f3f4f6"),
borderPadding=4,
spaceAfter=6,
)
)
styles.add(
ParagraphStyle(
name="RoadmapTableCell",
parent=styles["Normal"],
fontName=font,
fontSize=8,
leading=10,
)
)
styles.add(
ParagraphStyle(
name="RoadmapTableHeader",
parent=styles["Normal"],
fontName=bold_font,
fontSize=8,
leading=10,
)
)
return styles
def parse_table(lines: list[str], start: int) -> tuple[list[list[str]], int]:
rows: list[list[str]] = []
i = start
while i < len(lines):
line = lines[i].strip()
if not line.startswith("|"):
break
if re.match(r"^\|[-:\s|]+\|$", line):
i += 1
continue
cells = [c.strip() for c in line.strip("|").split("|")]
rows.append(cells)
i += 1
return rows, i
def table_flowable(rows: list[list[str]], styles, usable_width: float):
if not rows:
return None
ncols = max(len(r) for r in rows)
col_width = usable_width / ncols
data = []
for ri, row in enumerate(rows):
style = styles["RoadmapTableHeader"] if ri == 0 else styles["RoadmapTableCell"]
padded = row + [""] * (ncols - len(row))
data.append([Paragraph(md_inline(c), style) for c in padded])
tbl = Table(data, colWidths=[col_width] * ncols, repeatRows=1)
tbl.setStyle(
TableStyle(
[
("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#e5e7eb")),
("GRID", (0, 0), (-1, -1), 0.25, colors.HexColor("#d1d5db")),
("VALIGN", (0, 0), (-1, -1), "TOP"),
("LEFTPADDING", (0, 0), (-1, -1), 4),
("RIGHTPADDING", (0, 0), (-1, -1), 4),
("TOPPADDING", (0, 0), (-1, -1), 3),
("BOTTOMPADDING", (0, 0), (-1, -1), 3),
]
)
)
return tbl
def roadmap_to_pdf(md_path: Path, pdf_path: Path) -> None:
font = register_cyrillic_font()
bold_font = register_cyrillic_font_bold()
styles = build_styles(font, bold_font)
margin = 14 * mm
page_width, _ = A4
usable_width = page_width - 2 * margin
text = md_path.read_text(encoding="utf-8")
lines = text.splitlines()
story = []
i = 0
while i < len(lines):
line = lines[i]
stripped = line.strip()
if not stripped:
i += 1
continue
if stripped == "---":
story.append(Spacer(1, 4))
i += 1
continue
if stripped.startswith("```"):
code_lines = []
i += 1
while i < len(lines) and not lines[i].strip().startswith("```"):
code_lines.append(lines[i])
i += 1
if i < len(lines):
i += 1
code_text = escape("\n".join(code_lines))
story.append(Preformatted(code_text, styles["RoadmapCode"]))
continue
if stripped.startswith("|"):
rows, i = parse_table(lines, i)
tbl = table_flowable(rows, styles, usable_width)
if tbl:
story.append(tbl)
story.append(Spacer(1, 6))
continue
if stripped.startswith("# "):
story.append(Paragraph(md_inline(stripped[2:]), styles["RoadmapTitle"]))
i += 1
continue
if stripped.startswith("## "):
story.append(Paragraph(md_inline(stripped[3:]), styles["RoadmapH2"]))
i += 1
continue
if stripped.startswith("### "):
story.append(Paragraph(md_inline(stripped[4:]), styles["RoadmapH3"]))
i += 1
continue
if stripped.startswith("> "):
story.append(Paragraph(md_inline(stripped[2:]), styles["RoadmapQuote"]))
i += 1
continue
if re.match(r"^[-*]\s+\[[ x]\]", stripped):
content = re.sub(r"^[-*]\s+\[[ x]\]\s*", "", stripped)
story.append(Paragraph(f"{md_inline(content)}", styles["RoadmapBullet"]))
i += 1
continue
if stripped.startswith("- ") or stripped.startswith("* "):
story.append(Paragraph(f"{md_inline(stripped[2:])}", styles["RoadmapBullet"]))
i += 1
continue
if re.match(r"^\d+\.\s", stripped):
story.append(Paragraph(md_inline(stripped), styles["RoadmapBullet"]))
i += 1
continue
story.append(Paragraph(md_inline(stripped), styles["RoadmapBody"]))
i += 1
pdf_path.parent.mkdir(parents=True, exist_ok=True)
doc = SimpleDocTemplate(
str(pdf_path),
pagesize=A4,
leftMargin=margin,
rightMargin=margin,
topMargin=14 * mm,
bottomMargin=14 * mm,
title="WESP — дорожная карта разработки",
)
doc.build(story)
def main() -> int:
root = Path(__file__).resolve().parents[1]
md_path = root / "docs" / "ROADMAP.md"
out = Path(sys.argv[1]) if len(sys.argv) > 1 else Path.home() / "Downloads" / "WESP-ROADMAP.pdf"
if not md_path.is_file():
print(f"Not found: {md_path}", file=sys.stderr)
return 1
roadmap_to_pdf(md_path, out)
print(out)
return 0
if __name__ == "__main__":
raise SystemExit(main())