573 lines
20 KiB
Python
573 lines
20 KiB
Python
#!/usr/bin/env python3
|
|
"""Render the project deliverables as polished DOCX/PDF files."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import subprocess
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
from docx import Document
|
|
from docx.enum.table import WD_CELL_VERTICAL_ALIGNMENT, WD_ROW_HEIGHT_RULE
|
|
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
|
from docx.oxml import OxmlElement
|
|
from docx.oxml.ns import qn
|
|
from docx.shared import Cm, Pt, RGBColor
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
OUT_DIR = ROOT / "rascunho" / "entregaveis"
|
|
PANDOC = Path.home() / ".local" / "bin" / "pandoc"
|
|
|
|
NAVY = "234D79"
|
|
BLUE = "2F6FAF"
|
|
LIGHT_BLUE = "EAF2F8"
|
|
MID_GRAY = "66717D"
|
|
LIGHT_GRAY = "E5E9ED"
|
|
TEXT = "20262E"
|
|
|
|
DOCUMENTS = {
|
|
"briefing": {
|
|
"kind": "brief",
|
|
"source": OUT_DIR / "briefing-dep-solla.md",
|
|
"docx": OUT_DIR / "briefing-dep-solla.docx",
|
|
"title": "Briefing legislativo: interoperabilidade no PL 4675/2025",
|
|
"break_before": {
|
|
"3. O que o Cade poderá exigir",
|
|
"5. O que a emenda de interoperabilidade acrescenta",
|
|
},
|
|
"font_size": 10.2,
|
|
"title_size": 17.5,
|
|
"margin": 1.75,
|
|
},
|
|
"memorando": {
|
|
"kind": "brief",
|
|
"source": OUT_DIR / "memorando-relator-aliel.md",
|
|
"docx": OUT_DIR / "memorando-relator-aliel.docx",
|
|
"title": "Memorando ao relator: interoperabilidade no PL 4675/2025",
|
|
"break_before": {"5. Competição e inovação"},
|
|
"font_size": 10.3,
|
|
"title_size": 16.5,
|
|
"margin": 1.85,
|
|
},
|
|
"emenda": {
|
|
"kind": "legislative",
|
|
"source": ROOT / "rascunho" / "emenda-pl-4675-interoperabilidade.md",
|
|
"justification": ROOT / "rascunho" / "justificativa-emenda.md",
|
|
"docx": OUT_DIR / "emenda-aditiva-pl4675-interoperabilidade.docx",
|
|
"title": "Emenda aditiva ao PL 4675/2025: interoperabilidade",
|
|
"break_before": {
|
|
"JUSTIFICAÇÃO",
|
|
"EMENDA DE PLENÁRIO A PROJETO COM URGÊNCIA",
|
|
},
|
|
"font_size": 11.5,
|
|
"title_size": 12,
|
|
"margin": 2.0,
|
|
},
|
|
}
|
|
|
|
|
|
def set_paragraph_border(paragraph, *, side: str, color: str, size: int = 8) -> None:
|
|
p_pr = paragraph._p.get_or_add_pPr()
|
|
borders = p_pr.find(qn("w:pBdr"))
|
|
if borders is None:
|
|
borders = OxmlElement("w:pBdr")
|
|
p_pr.append(borders)
|
|
border = borders.find(qn(f"w:{side}"))
|
|
if border is None:
|
|
border = OxmlElement(f"w:{side}")
|
|
borders.append(border)
|
|
border.set(qn("w:val"), "single")
|
|
border.set(qn("w:sz"), str(size))
|
|
border.set(qn("w:space"), "4")
|
|
border.set(qn("w:color"), color)
|
|
|
|
|
|
def set_run_font(run, name: str, size: float | None = None) -> None:
|
|
run.font.name = name
|
|
run._element.rPr.rFonts.set(qn("w:eastAsia"), name)
|
|
if size is not None:
|
|
run.font.size = Pt(size)
|
|
|
|
|
|
def add_field(run, instruction: str) -> None:
|
|
begin = OxmlElement("w:fldChar")
|
|
begin.set(qn("w:fldCharType"), "begin")
|
|
text = OxmlElement("w:instrText")
|
|
text.set(qn("xml:space"), "preserve")
|
|
text.text = instruction
|
|
separate = OxmlElement("w:fldChar")
|
|
separate.set(qn("w:fldCharType"), "separate")
|
|
end = OxmlElement("w:fldChar")
|
|
end.set(qn("w:fldCharType"), "end")
|
|
run._r.extend([begin, text, separate, end])
|
|
|
|
|
|
def configure_styles(doc: Document, font_size: float, title_size: float) -> None:
|
|
styles = doc.styles
|
|
|
|
normal = styles["Normal"]
|
|
normal.font.name = "Arial"
|
|
normal._element.rPr.rFonts.set(qn("w:eastAsia"), "Arial")
|
|
normal.font.size = Pt(font_size)
|
|
normal.font.color.rgb = RGBColor.from_string(TEXT)
|
|
normal.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY
|
|
normal.paragraph_format.line_spacing = 1.12
|
|
normal.paragraph_format.space_after = Pt(4.5)
|
|
normal.paragraph_format.widow_control = True
|
|
|
|
title = styles["Title"]
|
|
title.font.name = "Arial"
|
|
title._element.rPr.rFonts.set(qn("w:eastAsia"), "Arial")
|
|
title.font.size = Pt(title_size)
|
|
title.font.bold = True
|
|
title.font.color.rgb = RGBColor.from_string(NAVY)
|
|
title.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.LEFT
|
|
title.paragraph_format.space_before = Pt(2)
|
|
title.paragraph_format.space_after = Pt(10)
|
|
title.paragraph_format.keep_with_next = True
|
|
|
|
heading_specs = {
|
|
"Heading 1": (14, NAVY, 13, 5),
|
|
"Heading 2": (12.2, BLUE, 10, 4),
|
|
"Heading 3": (10.5, NAVY, 7, 2),
|
|
}
|
|
for style_name, (size, color, before, after) in heading_specs.items():
|
|
style = styles[style_name]
|
|
style.font.name = "Arial"
|
|
style._element.rPr.rFonts.set(qn("w:eastAsia"), "Arial")
|
|
style.font.size = Pt(size)
|
|
style.font.bold = True
|
|
style.font.color.rgb = RGBColor.from_string(color)
|
|
style.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.LEFT
|
|
style.paragraph_format.space_before = Pt(before)
|
|
style.paragraph_format.space_after = Pt(after)
|
|
style.paragraph_format.keep_with_next = True
|
|
style.paragraph_format.widow_control = True
|
|
|
|
for style_name in ("Compact", "List Bullet", "List Number"):
|
|
if style_name not in styles:
|
|
continue
|
|
style = styles[style_name]
|
|
style.font.name = "Arial"
|
|
style._element.rPr.rFonts.set(qn("w:eastAsia"), "Arial")
|
|
style.font.size = Pt(font_size)
|
|
style.paragraph_format.line_spacing = 1.08
|
|
style.paragraph_format.space_after = Pt(2.5)
|
|
style.paragraph_format.widow_control = True
|
|
|
|
if "Block Text" in styles:
|
|
block = styles["Block Text"]
|
|
block.font.name = "Arial"
|
|
block._element.rPr.rFonts.set(qn("w:eastAsia"), "Arial")
|
|
block.font.size = Pt(font_size)
|
|
block.font.color.rgb = RGBColor.from_string(NAVY)
|
|
block.paragraph_format.left_indent = Cm(0.35)
|
|
block.paragraph_format.right_indent = Cm(0.25)
|
|
block.paragraph_format.space_before = Pt(4)
|
|
block.paragraph_format.space_after = Pt(9)
|
|
block.paragraph_format.line_spacing = 1.08
|
|
|
|
|
|
def configure_page(doc: Document, margin: float) -> None:
|
|
for section in doc.sections:
|
|
section.page_width = Cm(21)
|
|
section.page_height = Cm(29.7)
|
|
section.top_margin = Cm(1.65)
|
|
section.bottom_margin = Cm(1.55)
|
|
section.left_margin = Cm(margin)
|
|
section.right_margin = Cm(margin)
|
|
section.header_distance = Cm(0.65)
|
|
section.footer_distance = Cm(0.65)
|
|
|
|
|
|
def add_header_footer(doc: Document, short_title: str) -> None:
|
|
for section in doc.sections:
|
|
header = section.header
|
|
paragraph = header.paragraphs[0]
|
|
paragraph.clear()
|
|
paragraph.alignment = WD_ALIGN_PARAGRAPH.LEFT
|
|
paragraph.paragraph_format.space_after = Pt(4)
|
|
run = paragraph.add_run(
|
|
"CÂMARA DOS DEPUTADOS | GABINETE DO DEPUTADO JORGE SOLLA"
|
|
)
|
|
set_run_font(run, "Arial", 8)
|
|
run.font.bold = True
|
|
run.font.color.rgb = RGBColor.from_string(NAVY)
|
|
set_paragraph_border(paragraph, side="bottom", color=BLUE, size=10)
|
|
|
|
footer = section.footer
|
|
table = footer.add_table(rows=1, cols=2, width=Cm(17))
|
|
table.autofit = False
|
|
table.columns[0].width = Cm(13.5)
|
|
table.columns[1].width = Cm(3.5)
|
|
left = table.cell(0, 0).paragraphs[0]
|
|
left.paragraph_format.space_before = Pt(3)
|
|
left_run = left.add_run(short_title)
|
|
set_run_font(left_run, "Arial", 7.5)
|
|
left_run.font.color.rgb = RGBColor.from_string(MID_GRAY)
|
|
right = table.cell(0, 1).paragraphs[0]
|
|
right.alignment = WD_ALIGN_PARAGRAPH.RIGHT
|
|
page_run = right.add_run("p. ")
|
|
set_run_font(page_run, "Arial", 7.5)
|
|
page_run.font.color.rgb = RGBColor.from_string(MID_GRAY)
|
|
number_run = right.add_run()
|
|
set_run_font(number_run, "Arial", 7.5)
|
|
number_run.font.color.rgb = RGBColor.from_string(MID_GRAY)
|
|
add_field(number_run, "PAGE")
|
|
|
|
|
|
def style_document(doc: Document, config: dict) -> None:
|
|
configure_styles(doc, config["font_size"], config["title_size"])
|
|
configure_page(doc, config["margin"])
|
|
add_header_footer(doc, config["title"])
|
|
|
|
source_mode = False
|
|
first_nonempty = True
|
|
|
|
for paragraph in doc.paragraphs:
|
|
text = paragraph.text.strip()
|
|
if not text:
|
|
continue
|
|
|
|
if first_nonempty:
|
|
paragraph.style = doc.styles["Title"]
|
|
set_paragraph_border(paragraph, side="bottom", color=LIGHT_GRAY, size=8)
|
|
first_nonempty = False
|
|
|
|
if text in config["break_before"]:
|
|
paragraph.paragraph_format.page_break_before = True
|
|
|
|
if text in {"Fontes primárias", "Fonte legislativa"}:
|
|
source_mode = True
|
|
|
|
if text.startswith(
|
|
("Destinatário:", "Finalidade:", "Texto analisado:", "Ao:", "De:", "Assunto:", "Data:")
|
|
):
|
|
paragraph.alignment = WD_ALIGN_PARAGRAPH.LEFT
|
|
paragraph.paragraph_format.line_spacing = 1.0
|
|
paragraph.paragraph_format.space_after = Pt(1)
|
|
|
|
if source_mode and paragraph.style.name not in {
|
|
"Heading 1",
|
|
"Heading 2",
|
|
"Heading 3",
|
|
"Title",
|
|
}:
|
|
paragraph.alignment = WD_ALIGN_PARAGRAPH.LEFT
|
|
paragraph.paragraph_format.line_spacing = 1.0
|
|
paragraph.paragraph_format.space_after = Pt(2)
|
|
for run in paragraph.runs:
|
|
set_run_font(run, "Arial", 8.3)
|
|
run.font.color.rgb = RGBColor.from_string(MID_GRAY)
|
|
|
|
if paragraph.style.name == "Block Text":
|
|
p_pr = paragraph._p.get_or_add_pPr()
|
|
shading = p_pr.find(qn("w:shd"))
|
|
if shading is None:
|
|
shading = OxmlElement("w:shd")
|
|
p_pr.append(shading)
|
|
shading.set(qn("w:fill"), LIGHT_BLUE)
|
|
set_paragraph_border(paragraph, side="left", color=BLUE, size=18)
|
|
paragraph.paragraph_format.keep_together = True
|
|
|
|
is_list = paragraph._p.pPr is not None and paragraph._p.pPr.numPr is not None
|
|
if is_list:
|
|
paragraph.paragraph_format.space_after = Pt(2.5)
|
|
paragraph.paragraph_format.line_spacing = 1.06
|
|
|
|
paragraph.paragraph_format.widow_control = True
|
|
for run in paragraph.runs:
|
|
set_run_font(run, "Arial")
|
|
|
|
for table in doc.tables:
|
|
for row in table.rows:
|
|
row.height_rule = WD_ROW_HEIGHT_RULE.AT_LEAST
|
|
row._tr.get_or_add_trPr().append(OxmlElement("w:cantSplit"))
|
|
for cell in row.cells:
|
|
cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
|
|
for paragraph in cell.paragraphs:
|
|
paragraph.paragraph_format.space_after = Pt(2)
|
|
paragraph.paragraph_format.line_spacing = 1.0
|
|
for run in paragraph.runs:
|
|
set_run_font(run, "Arial", 9)
|
|
|
|
|
|
def configure_legislative_styles(doc: Document, config: dict) -> None:
|
|
styles = doc.styles
|
|
font_size = config["font_size"]
|
|
|
|
normal = styles["Normal"]
|
|
normal.font.name = "Times New Roman"
|
|
normal._element.rPr.rFonts.set(qn("w:eastAsia"), "Times New Roman")
|
|
normal.font.size = Pt(font_size)
|
|
normal.font.color.rgb = RGBColor.from_string("000000")
|
|
normal.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY
|
|
normal.paragraph_format.line_spacing = 1.15
|
|
normal.paragraph_format.space_after = Pt(4)
|
|
normal.paragraph_format.widow_control = True
|
|
|
|
title = styles["Title"]
|
|
title.font.name = "Times New Roman"
|
|
title._element.rPr.rFonts.set(qn("w:eastAsia"), "Times New Roman")
|
|
title.font.size = Pt(config["title_size"])
|
|
title.font.bold = True
|
|
title.font.color.rgb = RGBColor.from_string("000000")
|
|
title.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
title.paragraph_format.space_before = Pt(4)
|
|
title.paragraph_format.space_after = Pt(8)
|
|
title.paragraph_format.keep_with_next = True
|
|
|
|
for style_name, size, alignment in (
|
|
("Heading 1", 12, WD_ALIGN_PARAGRAPH.CENTER),
|
|
("Heading 2", 11.5, WD_ALIGN_PARAGRAPH.LEFT),
|
|
("Heading 3", 11.5, WD_ALIGN_PARAGRAPH.LEFT),
|
|
):
|
|
style = styles[style_name]
|
|
style.font.name = "Times New Roman"
|
|
style._element.rPr.rFonts.set(qn("w:eastAsia"), "Times New Roman")
|
|
style.font.size = Pt(size)
|
|
style.font.bold = True
|
|
style.font.color.rgb = RGBColor.from_string("000000")
|
|
style.paragraph_format.alignment = alignment
|
|
style.paragraph_format.space_before = Pt(10)
|
|
style.paragraph_format.space_after = Pt(5)
|
|
style.paragraph_format.keep_with_next = True
|
|
|
|
for style_name in ("Compact", "List Bullet", "List Number"):
|
|
if style_name not in styles:
|
|
continue
|
|
style = styles[style_name]
|
|
style.font.name = "Times New Roman"
|
|
style._element.rPr.rFonts.set(qn("w:eastAsia"), "Times New Roman")
|
|
style.font.size = Pt(font_size)
|
|
style.paragraph_format.line_spacing = 1.08
|
|
style.paragraph_format.space_after = Pt(2)
|
|
|
|
|
|
def style_legislative_document(doc: Document, config: dict) -> None:
|
|
configure_legislative_styles(doc, config)
|
|
configure_page(doc, config["margin"])
|
|
add_header_footer(doc, config["title"])
|
|
|
|
first_nonempty = True
|
|
in_justification = False
|
|
in_sources = False
|
|
|
|
for paragraph in doc.paragraphs:
|
|
text = paragraph.text.strip()
|
|
if not text:
|
|
continue
|
|
|
|
if first_nonempty:
|
|
paragraph.style = doc.styles["Title"]
|
|
first_nonempty = False
|
|
|
|
if text in config["break_before"]:
|
|
paragraph.paragraph_format.page_break_before = True
|
|
|
|
if text == "JUSTIFICAÇÃO":
|
|
in_justification = True
|
|
if text == "Fontes":
|
|
in_sources = True
|
|
in_justification = False
|
|
|
|
if text == text.upper() and any(
|
|
text.startswith(prefix)
|
|
for prefix in ("TÍTULO", "CAPÍTULO", "DA ", "DAS ", "DO ", "DOS ")
|
|
):
|
|
paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
paragraph.paragraph_format.keep_with_next = True
|
|
paragraph.paragraph_format.space_before = Pt(7)
|
|
paragraph.paragraph_format.space_after = Pt(5)
|
|
for run in paragraph.runs:
|
|
run.font.bold = True
|
|
|
|
if text.startswith(
|
|
("(Do Sr.", "Sala das Sessões", "Deputado ", "PT/BA", "(Chancela")
|
|
):
|
|
paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
|
|
if text.startswith("Gabinete do Deputado"):
|
|
paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
|
|
if text.startswith("Altera a Lei nº 12.529"):
|
|
paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
paragraph.paragraph_format.left_indent = Cm(3.0)
|
|
paragraph.paragraph_format.right_indent = Cm(3.0)
|
|
|
|
heading_style_names = {"Heading 1", "Heading 2", "Heading 3", "Title"}
|
|
is_heading = paragraph.style.name in heading_style_names
|
|
is_list = paragraph._p.pPr is not None and paragraph._p.pPr.numPr is not None
|
|
is_article = text.startswith(("Art. ", "§ ", "Parágrafo único."))
|
|
is_incise = any(text.startswith(f"{roman} -") for roman in (
|
|
"I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X",
|
|
"XI", "XII", "XIII", "XIV", "XV",
|
|
))
|
|
|
|
if is_incise:
|
|
paragraph.paragraph_format.left_indent = Cm(0.75)
|
|
paragraph.paragraph_format.first_line_indent = Cm(-0.5)
|
|
elif in_justification and not is_heading and not is_list and not is_article:
|
|
paragraph.paragraph_format.first_line_indent = Cm(1.25)
|
|
|
|
if (
|
|
in_sources
|
|
and not is_heading
|
|
and not text.startswith(
|
|
("Sala das Sessões", "Deputado ", "PT/BA", "(Chancela", "(Do Sr.")
|
|
)
|
|
):
|
|
paragraph.alignment = WD_ALIGN_PARAGRAPH.LEFT
|
|
paragraph.paragraph_format.first_line_indent = Cm(0)
|
|
paragraph.paragraph_format.line_spacing = 1.0
|
|
for run in paragraph.runs:
|
|
set_run_font(run, "Times New Roman", 9)
|
|
|
|
paragraph.paragraph_format.widow_control = True
|
|
for run in paragraph.runs:
|
|
set_run_font(run, "Times New Roman")
|
|
|
|
|
|
def build_amendment_source(config: dict, destination: Path) -> Path:
|
|
amendment = config["source"].read_text(encoding="utf-8").strip()
|
|
justification = config["justification"].read_text(encoding="utf-8").strip()
|
|
content = f"""# CÂMARA DOS DEPUTADOS
|
|
|
|
**Gabinete do Deputado JORGE SOLLA - PT/BA**
|
|
|
|
# PROJETO DE LEI Nº 4.675, DE 2025
|
|
|
|
Altera a Lei nº 12.529, de 30 de novembro de 2011, dispõe sobre os processos
|
|
de designação de agentes econômicos de relevância sistêmica em mercados
|
|
digitais e de estabelecimento de obrigações especiais e cria unidade
|
|
especializada no Conselho Administrativo de Defesa Econômica.
|
|
|
|
{amendment}
|
|
|
|
{justification}
|
|
|
|
Sala das Sessões, em 19 de agosto de 2026.
|
|
|
|
**Deputado JORGE SOLLA**
|
|
|
|
**PT/BA**
|
|
|
|
# EMENDA DE PLENÁRIO A PROJETO COM URGÊNCIA
|
|
|
|
*(Do Sr. Jorge Solla)*
|
|
|
|
Assinaram eletronicamente o documento, nesta ordem:
|
|
|
|
1. Dep. Jorge Solla (PT/BA)
|
|
2. ............................................
|
|
3. ............................................
|
|
4. ............................................
|
|
5. ............................................
|
|
6. ............................................
|
|
7. ............................................
|
|
8. ............................................
|
|
9. ............................................
|
|
10. ............................................
|
|
|
|
*(Chancela eletrônica do parlamentar, nos termos do Ato da Mesa nº 25, de
|
|
2015.)*
|
|
"""
|
|
destination.write_text(content, encoding="utf-8")
|
|
return destination
|
|
|
|
|
|
def render(key: str) -> None:
|
|
config = DOCUMENTS[key]
|
|
source = config["source"]
|
|
target = config["docx"]
|
|
pdf = target.with_suffix(".pdf")
|
|
|
|
if not source.exists():
|
|
raise FileNotFoundError(source)
|
|
if not PANDOC.exists():
|
|
raise FileNotFoundError(PANDOC)
|
|
|
|
with tempfile.TemporaryDirectory(prefix="render-entregavel-") as temp_dir:
|
|
temp = Path(temp_dir)
|
|
raw_docx = temp / target.name
|
|
render_source = source
|
|
if config["kind"] == "legislative":
|
|
render_source = build_amendment_source(config, temp / "emenda-completa.md")
|
|
subprocess.run(
|
|
[
|
|
str(PANDOC),
|
|
str(render_source),
|
|
"--from=gfm",
|
|
"--to=docx",
|
|
"-o",
|
|
str(raw_docx),
|
|
],
|
|
check=True,
|
|
)
|
|
|
|
doc = Document(raw_docx)
|
|
doc.core_properties.title = config["title"]
|
|
doc.core_properties.author = "Gabinete do Deputado Jorge Solla"
|
|
doc.core_properties.subject = "PL 4675/2025"
|
|
if config["kind"] == "legislative":
|
|
style_legislative_document(doc, config)
|
|
else:
|
|
style_document(doc, config)
|
|
doc.save(target)
|
|
|
|
if pdf.exists():
|
|
pdf.unlink()
|
|
profile = temp / "libreoffice-profile"
|
|
subprocess.run(
|
|
[
|
|
"soffice",
|
|
"--headless",
|
|
f"-env:UserInstallation=file://{profile}",
|
|
"--convert-to",
|
|
"pdf",
|
|
"--outdir",
|
|
str(OUT_DIR),
|
|
str(target),
|
|
],
|
|
check=True,
|
|
)
|
|
|
|
if config["kind"] == "legislative":
|
|
legacy = target.with_suffix(".doc")
|
|
if legacy.exists():
|
|
legacy.unlink()
|
|
subprocess.run(
|
|
[
|
|
"soffice",
|
|
"--headless",
|
|
f"-env:UserInstallation=file://{profile}",
|
|
"--convert-to",
|
|
'doc:MS Word 97',
|
|
"--outdir",
|
|
str(OUT_DIR),
|
|
str(target),
|
|
],
|
|
check=True,
|
|
)
|
|
|
|
if not pdf.exists():
|
|
raise RuntimeError(f"PDF not generated: {pdf}")
|
|
print(f"Rendered {target.relative_to(ROOT)} and {pdf.relative_to(ROOT)}")
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument(
|
|
"documents",
|
|
nargs="*",
|
|
choices=sorted(DOCUMENTS),
|
|
)
|
|
args = parser.parse_args()
|
|
for key in args.documents or DOCUMENTS:
|
|
render(key)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|