build_manuals_pdf.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. # -*- coding: utf-8 -*-
  2. """Build 分册 + 合订版 Word/PDF for 大洋 MES manuals 9.19."""
  3. import os, re, html as htmlmod
  4. from pathlib import Path
  5. import fitz
  6. from docx import Document
  7. from docx.shared import Pt, Cm, RGBColor, Emu
  8. from docx.oxml.ns import qn
  9. from docx.enum.table import WD_TABLE_ALIGNMENT
  10. from docx.enum.text import WD_ALIGN_PARAGRAPH
  11. ROOT = Path(r"D:\产线\大洋\大洋操作手册汇总9.19")
  12. FONT_FILE = r"C:\Windows\Fonts\simhei.ttf"
  13. FONT_FILE_REG = r"C:\Windows\Fonts\msyh.ttc"
  14. DATE = "2026年9月19日"
  15. A4 = fitz.paper_rect("a4")
  16. FASCICLES = [
  17. ("1、系统管理操作手册.md", "系统管理操作手册", "分册一"),
  18. ("2、设备管理操作手册.md", "设备管理操作手册", "分册二"),
  19. ("3、物料管理操作手册.md", "物料管理操作手册", "分册三"),
  20. ("4、质量管理操作手册.md", "质量管理操作手册", "分册四"),
  21. ("5、生产管理操作手册.md", "生产管理操作手册", "分册五"),
  22. ("6、ERP对接操作手册.md", "ERP对接操作手册", "分册六"),
  23. ("7、生产线管理操作手册.md", "生产线管理操作手册", "分册七"),
  24. ("8、PDA操作说明操作手册.md", "PDA操作说明操作手册", "分册八"),
  25. ("9、MES系统客户端员工扫码操作手册.md", "MES系统客户端员工扫码操作手册", "分册九"),
  26. ]
  27. def esc(s):
  28. return htmlmod.escape(s, quote=True)
  29. def md_inline_html(text):
  30. text = esc(text)
  31. text = re.sub(r"\*\*([^*]+)\*\*", r"<b>\1</b>", text)
  32. text = re.sub(r"`([^`]+)`", r"<code>\1</code>", text)
  33. return text
  34. def is_sep_row(cells):
  35. return all(re.match(r"^:?-+:?$", c.replace(" ", "")) for c in cells if c != "")
  36. def md_to_html_body(md_text, skip_first_h1=False):
  37. lines = md_text.splitlines()
  38. out = []
  39. i = 0
  40. skipped = False
  41. while i < len(lines):
  42. line = lines[i].rstrip()
  43. if not line:
  44. i += 1
  45. continue
  46. if line.strip() == "---":
  47. i += 1
  48. continue
  49. if line.startswith("# "):
  50. title = line[2:].strip()
  51. if skip_first_h1 and not skipped:
  52. skipped = True
  53. i += 1
  54. continue
  55. out.append("<h1>%s</h1>" % md_inline_html(title))
  56. i += 1
  57. continue
  58. m = re.match(r"^(#{2,6})\s+(.*)$", line)
  59. if m:
  60. lv = len(m.group(1))
  61. out.append("<h%d>%s</h%d>" % (lv, md_inline_html(m.group(2).strip()), lv))
  62. i += 1
  63. continue
  64. if line.startswith("|"):
  65. rows = []
  66. while i < len(lines) and lines[i].startswith("|"):
  67. cells = [c.strip() for c in lines[i].strip().strip("|").split("|")]
  68. if not is_sep_row(cells):
  69. rows.append(cells)
  70. i += 1
  71. if rows:
  72. out.append("<table>")
  73. for ri, row in enumerate(rows):
  74. out.append("<tr>")
  75. tag = "th" if ri == 0 else "td"
  76. for c in row:
  77. out.append("<%s>%s</%s>" % (tag, md_inline_html(c), tag))
  78. out.append("</tr>")
  79. out.append("</table>")
  80. continue
  81. if line.startswith("- "):
  82. out.append("<ul>")
  83. while i < len(lines) and lines[i].startswith("- "):
  84. out.append("<li>%s</li>" % md_inline_html(lines[i][2:].strip()))
  85. i += 1
  86. out.append("</ul>")
  87. continue
  88. if re.match(r"^\d+\.\s", line):
  89. out.append("<ol>")
  90. while i < len(lines) and re.match(r"^\d+\.\s", lines[i]):
  91. out.append("<li>%s</li>" % md_inline_html(re.sub(r"^\d+\.\s+", "", lines[i])))
  92. i += 1
  93. out.append("</ol>")
  94. continue
  95. img = re.match(r"^!\[(.*?)\]\((.*?)\)$", line.strip())
  96. if img:
  97. cap, src = img.group(1), img.group(2).replace("\\", "/")
  98. rel = src.split("images/")[-1] if "images/" in src else src.split("/")[-1]
  99. member = rel.replace("/", "__")
  100. out.append('<p class="shot"><img src="%s" alt="%s"/></p>' % (esc(member), esc(cap)))
  101. out.append('<p class="imgcap">%s</p>' % md_inline_html(cap))
  102. i += 1
  103. continue
  104. if line.strip().startswith("*(界面截图") and "为准" in line:
  105. i += 1
  106. continue
  107. out.append("<p>%s</p>" % md_inline_html(line))
  108. i += 1
  109. return "\n".join(out)
  110. CSS = r"""
  111. @font-face { font-family: bodyfont; src: url("simhei.ttf"); }
  112. body { font-family: bodyfont, sans-serif; font-size: 10.5pt; line-height: 1.5; color: #222; }
  113. h1 { font-size: 18pt; color: #1F4E79; margin: 18pt 0 8pt 0; page-break-before: auto; }
  114. h2 { font-size: 14pt; color: #2E75B6; margin: 14pt 0 6pt 0; }
  115. h3 { font-size: 12.5pt; color: #2E75B6; margin: 12pt 0 5pt 0; }
  116. h4 { font-size: 11.5pt; color: #1F4E79; margin: 10pt 0 4pt 0; }
  117. h5, h6 { font-size: 11pt; margin: 8pt 0 4pt 0; }
  118. p { margin: 0 0 6pt 0; }
  119. ul, ol { margin: 0 0 8pt 18pt; padding: 0; }
  120. li { margin: 0 0 3pt 0; }
  121. table { border-collapse: collapse; width: 100%; margin: 6pt 0 10pt 0; }
  122. th { background: #1F4E79; color: #fff; font-weight: bold; }
  123. th, td { border: 1px solid #8aa; padding: 4pt 6pt; font-size: 9.5pt; vertical-align: top; }
  124. code { font-size: 9.5pt; }
  125. p.shot { text-align: center; margin: 8pt 0 2pt 0; }
  126. p.shot img { max-width: 100%; max-height: 380pt; }
  127. p.imgcap { text-align: center; font-size: 9pt; color: #555; margin: 0 0 10pt 0; }
  128. """
  129. def make_cover(title, subtitle, extra=""):
  130. doc = fitz.open()
  131. page = doc.new_page(width=A4.width, height=A4.height)
  132. page.insert_font(fontname="hei", fontfile=FONT_FILE)
  133. cx = A4.width / 2
  134. page.draw_rect(fitz.Rect(40, 40, A4.width - 40, A4.height - 40), color=(0.12, 0.31, 0.47), width=1.5)
  135. page.draw_rect(fitz.Rect(48, 48, A4.width - 48, A4.height - 48), color=(0.18, 0.46, 0.71), width=0.6)
  136. page.insert_text((cx - 120, 180), "安徽大洋 · MES", fontname="hei", fontsize=14, fill=(0.18, 0.46, 0.71))
  137. # wrap title
  138. y = 250
  139. for line in title.split("\n"):
  140. w = fitz.get_text_length(line, fontname="hei", fontsize=26, fontfile=FONT_FILE) if False else None
  141. tw = fitz.Font(fontfile=FONT_FILE).text_length(line, 26)
  142. page.insert_text((cx - tw / 2, y), line, fontname="hei", fontsize=26, fill=(0.12, 0.31, 0.47))
  143. y += 40
  144. if subtitle:
  145. tw = fitz.Font(fontfile=FONT_FILE).text_length(subtitle, 16)
  146. page.insert_text((cx - tw / 2, y + 10), subtitle, fontname="hei", fontsize=16, fill=(0.3, 0.3, 0.3))
  147. y += 40
  148. if extra:
  149. tw = fitz.Font(fontfile=FONT_FILE).text_length(extra, 13)
  150. page.insert_text((cx - tw / 2, y + 20), extra, fontname="hei", fontsize=13, fill=(0.35, 0.35, 0.35))
  151. tw = fitz.Font(fontfile=FONT_FILE).text_length(DATE, 12)
  152. page.insert_text((cx - tw / 2, A4.height - 120), DATE, fontname="hei", fontsize=12, fill=(0.2, 0.2, 0.2))
  153. return doc
  154. def html_to_pdf_pages(html_body, header_text):
  155. css = CSS
  156. html = "<html><body>%s</body></html>" % html_body
  157. import tempfile, shutil
  158. img_tmp = tempfile.mkdtemp(prefix="mesimg_")
  159. img_root = str(ROOT / "images")
  160. if os.path.isdir(img_root):
  161. for dirpath, _, files in os.walk(img_root):
  162. for fn in files:
  163. if fn.lower().endswith((".png", ".jpg", ".jpeg")):
  164. full = os.path.join(dirpath, fn)
  165. rel = os.path.relpath(full, img_root).replace("\\", "/")
  166. member = rel.replace("/", "__")
  167. shutil.copy2(full, os.path.join(img_tmp, member))
  168. arch = fitz.Archive(os.path.dirname(FONT_FILE))
  169. arch.add(img_tmp)
  170. story = fitz.Story(html=html, user_css=css, archive=arch)
  171. where = fitz.Rect(48, 52, A4.width - 48, A4.height - 48)
  172. import tempfile
  173. tmp = tempfile.mktemp(suffix=".pdf")
  174. writer = fitz.DocumentWriter(tmp)
  175. more = 1
  176. while more:
  177. dev = writer.begin_page(A4)
  178. more, _ = story.place(where)
  179. story.draw(dev)
  180. writer.end_page()
  181. writer.close()
  182. try:
  183. shutil.rmtree(img_tmp, ignore_errors=True)
  184. except Exception:
  185. pass
  186. body = fitz.open(tmp)
  187. try:
  188. os.remove(tmp)
  189. except Exception:
  190. pass
  191. font = fitz.Font(fontfile=FONT_FILE)
  192. for i, page in enumerate(body):
  193. page.insert_font(fontname="hei", fontfile=FONT_FILE)
  194. page.insert_text((48, 32), header_text, fontname="hei", fontsize=8, fill=(0.4, 0.4, 0.4))
  195. pn = str(i + 1)
  196. tw = font.text_length(pn, 9)
  197. page.insert_text((A4.width - 48 - tw, A4.height - 32), pn, fontname="hei", fontsize=9, fill=(0.3, 0.3, 0.3))
  198. page.draw_line(fitz.Point(48, 40), fitz.Point(A4.width - 48, 40), color=(0.75, 0.75, 0.75), width=0.4)
  199. page.draw_line(fitz.Point(48, A4.height - 40), fitz.Point(A4.width - 48, A4.height - 40), color=(0.75, 0.75, 0.75), width=0.4)
  200. return body
  201. def stamp_page_numbers(doc, header_text, start=1):
  202. font = fitz.Font(fontfile=FONT_FILE)
  203. for i, page in enumerate(doc):
  204. # skip cover (first page) header already different
  205. page.insert_font(fontname="hei", fontfile=FONT_FILE)
  206. if i == 0:
  207. continue
  208. page.insert_text((48, 32), header_text, fontname="hei", fontsize=8, fill=(0.4, 0.4, 0.4))
  209. pn = str(start + i - 1)
  210. tw = font.text_length(pn, 9)
  211. page.insert_text((A4.width - 48 - tw, A4.height - 32), pn, fontname="hei", fontsize=9, fill=(0.3, 0.3, 0.3))
  212. page.draw_line(fitz.Point(48, 40), fitz.Point(A4.width - 48, 40), color=(0.75, 0.75, 0.75), width=0.4)
  213. page.draw_line(fitz.Point(48, A4.height - 40), fitz.Point(A4.width - 48, A4.height - 40), color=(0.75, 0.75, 0.75), width=0.4)
  214. def build_pdf(cover_title, cover_sub, extra, html_body, header, out_path):
  215. cover = make_cover(cover_title, cover_sub, extra)
  216. body = html_to_pdf_pages(html_body, header)
  217. final = fitz.open()
  218. final.insert_pdf(cover)
  219. final.insert_pdf(body)
  220. # restamp body pages only
  221. font = fitz.Font(fontfile=FONT_FILE)
  222. for i, page in enumerate(final):
  223. if i == 0:
  224. continue
  225. page.insert_font(fontname="hei", fontfile=FONT_FILE)
  226. # html_to_pdf_pages already stamped; cover inserted shifts numbers.
  227. final.save(out_path, deflate=True, garbage=4)
  228. cover.close()
  229. body.close()
  230. final.close()
  231. print("PDF", out_path.name, "pages ok")
  232. # ---- docx (reuse simplified converter) ----
  233. from importlib.machinery import SourceFileLoader
  234. # inline converter to avoid path issues
  235. def set_run_font(run, size=11, bold=False, color=None):
  236. run.bold = bold
  237. run.font.size = Pt(size)
  238. run.font.name = "微软雅黑"
  239. rPr = run._element.get_or_add_rPr()
  240. rFonts = rPr.find(qn("w:rFonts"))
  241. if rFonts is None:
  242. rFonts = rPr.makeelement(qn("w:rFonts"), {})
  243. rPr.append(rFonts)
  244. rFonts.set(qn("w:eastAsia"), "微软雅黑")
  245. rFonts.set(qn("w:ascii"), "微软雅黑")
  246. rFonts.set(qn("w:hAnsi"), "微软雅黑")
  247. if color:
  248. run.font.color.rgb = RGBColor(*color)
  249. def add_runs(p, text, size=11, bold=False):
  250. parts = re.split(r"(\*\*[^*]+\*\*)", text)
  251. for part in parts:
  252. if part.startswith("**") and part.endswith("**"):
  253. run = p.add_run(part[2:-2])
  254. set_run_font(run, size=size, bold=True)
  255. else:
  256. run = p.add_run(part)
  257. set_run_font(run, size=size, bold=bold)
  258. def md_to_docx(md_text, docx_path, heading_shift=0):
  259. doc = Document()
  260. section = doc.sections[0]
  261. section.top_margin = Cm(2)
  262. section.bottom_margin = Cm(2)
  263. section.left_margin = Cm(2.2)
  264. section.right_margin = Cm(2.2)
  265. style = doc.styles["Normal"]
  266. style.font.name = "微软雅黑"
  267. style.font.size = Pt(11)
  268. style.element.rPr.rFonts.set(qn("w:eastAsia"), "微软雅黑")
  269. lines = md_text.splitlines()
  270. i = 0
  271. while i < len(lines):
  272. line = lines[i].rstrip()
  273. if not line or line.strip() == "---":
  274. i += 1
  275. continue
  276. if line.startswith("#"):
  277. hashes = len(line) - len(line.lstrip("#"))
  278. title = line[hashes:].strip()
  279. level = min(max(hashes - 1 + heading_shift, 0), 4)
  280. p = doc.add_heading("", level=level)
  281. p.clear()
  282. run = p.add_run(title)
  283. sizes = {0: 18, 1: 16, 2: 14, 3: 12, 4: 12}
  284. set_run_font(run, size=sizes.get(level, 12), bold=True, color=(0x1F, 0x4E, 0x79))
  285. i += 1
  286. continue
  287. if line.startswith("|"):
  288. rows = []
  289. while i < len(lines) and lines[i].startswith("|"):
  290. cells = [c.strip() for c in lines[i].strip().strip("|").split("|")]
  291. if not is_sep_row(cells):
  292. rows.append(cells)
  293. i += 1
  294. if rows:
  295. cols = max(len(r) for r in rows)
  296. table = doc.add_table(rows=len(rows), cols=cols)
  297. table.style = "Table Grid"
  298. table.alignment = WD_TABLE_ALIGNMENT.CENTER
  299. for r_idx, row in enumerate(rows):
  300. for c_idx in range(cols):
  301. cell = table.cell(r_idx, c_idx)
  302. cell.text = ""
  303. p = cell.paragraphs[0]
  304. val = row[c_idx] if c_idx < len(row) else ""
  305. add_runs(p, val, size=10, bold=(r_idx == 0))
  306. continue
  307. if line.startswith("- "):
  308. p = doc.add_paragraph(style="List Bullet")
  309. p.clear()
  310. add_runs(p, line[2:].strip())
  311. i += 1
  312. continue
  313. if re.match(r"^\d+\.\s", line):
  314. p = doc.add_paragraph(style="List Number")
  315. p.clear()
  316. add_runs(p, re.sub(r"^\d+\.\s+", "", line))
  317. i += 1
  318. continue
  319. img = re.match(r"^!\[(.*?)\]\((.*?)\)$", line.strip())
  320. if img:
  321. cap, src = img.group(1), img.group(2).replace("/", os.sep)
  322. img_path = src if os.path.isabs(src) else str(ROOT / src)
  323. p = doc.add_paragraph()
  324. p.alignment = WD_ALIGN_PARAGRAPH.CENTER
  325. if os.path.exists(img_path):
  326. run = p.add_run()
  327. try:
  328. run.add_picture(img_path, width=Cm(14.5))
  329. except Exception:
  330. add_runs(p, "[图片无法插入] " + cap)
  331. cap_p = doc.add_paragraph()
  332. cap_p.alignment = WD_ALIGN_PARAGRAPH.CENTER
  333. add_runs(cap_p, cap, size=9)
  334. i += 1
  335. continue
  336. if line.strip().startswith("*(界面截图"):
  337. i += 1
  338. continue
  339. p = doc.add_paragraph()
  340. add_runs(p, line)
  341. i += 1
  342. try:
  343. doc.save(str(docx_path))
  344. except PermissionError:
  345. alt = str(docx_path).replace(".docx", "_新.docx")
  346. doc.save(alt)
  347. print("docx locked, wrote", alt)
  348. def strip_toc(md):
  349. # drop ## 目录 ... until next ##
  350. return re.sub(r"\n## 目录\n[\s\S]*?(?=\n## )", "\n", md, count=1)
  351. def remap_headings(md, delta):
  352. def repl(m):
  353. hashes = m.group(1)
  354. rest = m.group(2)
  355. n = min(len(hashes) + delta, 6)
  356. return "#" * n + " " + rest
  357. return re.sub(r"^(#{1,6})\s+(.*)$", repl, md, flags=re.M)
  358. def build_combined_md():
  359. intro = (ROOT / "_合订版_导言.md").read_text(encoding="utf-8")
  360. parts = [intro, ""]
  361. mapping = [
  362. (FASCICLES[0][0], "#### 3.1.3 系统管理", 3),
  363. (FASCICLES[1][0], "#### 3.1.4 设备管理", 3),
  364. (FASCICLES[2][0], "#### 3.1.5 物料管理", 3),
  365. (FASCICLES[3][0], "#### 3.1.6 质量管理", 3),
  366. (FASCICLES[4][0], "#### 3.1.7 生产管理", 3),
  367. (FASCICLES[5][0], "#### 3.1.8 ERP 对接", 3),
  368. (FASCICLES[6][0], "#### 3.1.9 生产线管理", 3),
  369. (FASCICLES[7][0], "### 3.2 PDA 软件操作说明", 2),
  370. (FASCICLES[8][0], "### 3.3 工控机软件操作说明", 2),
  371. ]
  372. for fname, section_title, delta in mapping:
  373. raw = (ROOT / fname).read_text(encoding="utf-8")
  374. raw = strip_toc(raw)
  375. # drop first h1
  376. raw = re.sub(r"^# .*\n+", "", raw)
  377. raw = re.sub(r"^20\d{2} 年.*\n+", "", raw)
  378. raw = remap_headings(raw, delta)
  379. parts.append(section_title)
  380. parts.append("")
  381. parts.append(raw.strip())
  382. parts.append("")
  383. changelog = (ROOT / "00-修订对照说明.md").read_text(encoding="utf-8")
  384. changelog = remap_headings(re.sub(r"^# .*\n+", "## 附录 本次修订对照说明\n\n", changelog, count=1), 0)
  385. parts.append(changelog)
  386. text = "\n".join(parts)
  387. out = ROOT / "大洋交付-操作手册合订版.md"
  388. out.write_text(text, encoding="utf-8")
  389. return text
  390. def fix_toc_numbers(md):
  391. """Avoid '4. 常见问题' in TOC being parsed as a new ordered list."""
  392. def repl(m):
  393. block = m.group(0)
  394. block = re.sub(r"^(\d+)\.\s+", r"\1、", block, flags=re.M)
  395. return block
  396. return re.sub(r"(## 目录\n)([\s\S]*?)(?=\n## )", lambda m: m.group(1) + re.sub(r"^(\d+)\.\s+", r"\1、", m.group(2), flags=re.M), md, count=1)
  397. def main():
  398. import shutil
  399. # 分册 PDF + 刷新 docx
  400. for fname, short, extra in FASCICLES:
  401. md_path = ROOT / fname
  402. md = fix_toc_numbers(md_path.read_text(encoding="utf-8"))
  403. md_path.write_text(md, encoding="utf-8")
  404. stem = fname[:-3]
  405. html_body = md_to_html_body(md, skip_first_h1=False)
  406. build_pdf(
  407. "大洋MES 生产管理系统",
  408. "——" + short + "——",
  409. extra,
  410. html_body,
  411. "大洋MES · " + short + " · 分册",
  412. ROOT / (stem + ".pdf"),
  413. )
  414. md_to_docx(md, ROOT / (stem + ".docx"))
  415. # 对照说明
  416. ch = (ROOT / "00-修订对照说明.md").read_text(encoding="utf-8")
  417. build_pdf("大洋MES 操作手册", "修订对照说明", "相对 8.28 版本", md_to_html_body(ch), "大洋MES · 修订对照说明", ROOT / "00-修订对照说明.pdf")
  418. md_to_docx(ch, ROOT / "00-修订对照说明.docx")
  419. combined = build_combined_md()
  420. build_pdf(
  421. "大洋MES 生产管理系统\n操作手册合订版",
  422. "——(MES 后台 + PDA + 工控机)——",
  423. "分册合订 · 现行菜单与 PDA 1.0.6",
  424. md_to_html_body(combined, skip_first_h1=True),
  425. "大洋MES 生产管理系统 · 操作手册合订版",
  426. ROOT / "大洋交付-操作手册合订版.pdf",
  427. )
  428. md_to_docx(combined, ROOT / "大洋交付-操作手册合订版.docx")
  429. fen = ROOT / "分册"
  430. he = ROOT / "合订版"
  431. fen.mkdir(exist_ok=True)
  432. he.mkdir(exist_ok=True)
  433. for fname, short, extra in FASCICLES:
  434. stem = fname[:-3]
  435. for ext in (".pdf", ".docx", ".md"):
  436. src = ROOT / (stem + ext)
  437. if src.exists():
  438. shutil.copy2(src, fen / src.name)
  439. for name in ("大洋交付-操作手册合订版.pdf", "大洋交付-操作手册合订版.docx", "大洋交付-操作手册合订版.md"):
  440. src = ROOT / name
  441. if src.exists():
  442. shutil.copy2(src, he / name)
  443. for name in ("00-修订对照说明.pdf", "00-修订对照说明.docx", "00-修订对照说明.md"):
  444. src = ROOT / name
  445. if src.exists():
  446. shutil.copy2(src, fen / name)
  447. shutil.copy2(src, he / name)
  448. chk = ROOT / "_pdf_check.txt"
  449. if chk.exists():
  450. chk.unlink()
  451. print("done")
  452. if __name__ == "__main__":
  453. main()