Spaces:
Running on Zero
Running on Zero
| """Export a forged case+note to downloadable files. | |
| Per the MVP cut-lines, PDF is optional and Markdown is the floor. We ship two | |
| zero-dependency formats: a clean **Markdown** file (the canonical artifact) and a | |
| self-contained **printable HTML** the instructor can open and Print-to-PDF — | |
| covering the PDF use case without a PDF toolchain in the Space. | |
| """ | |
| from __future__ import annotations | |
| import re | |
| import tempfile | |
| from pathlib import Path | |
| def _slug(title: str) -> str: | |
| s = re.sub(r"[^\w\s-]", "", (title or "case").lower()).strip() | |
| s = re.sub(r"[\s_-]+", "-", s) | |
| return (s or "case")[:60] | |
| def _combined_markdown(case_md: str, note_md: str) -> str: | |
| return f"{(case_md or '').strip()}\n\n---\n\n{(note_md or '').strip()}\n" | |
| def _md_inline(text: str) -> str: | |
| """Minimal inline Markdown → HTML: **bold** and escaping.""" | |
| import html | |
| text = html.escape(text) | |
| return re.sub(r"\*\*(.+?)\*\*", r"<strong>\1</strong>", text) | |
| def _md_to_html_body(md: str) -> str: | |
| """Tiny Markdown→HTML for our own controlled output (headings, lists, quotes).""" | |
| lines = md.splitlines() | |
| html_lines: list[str] = [] | |
| list_type: str | None = None # 'ul' | 'ol' | |
| def close_list(): | |
| nonlocal list_type | |
| if list_type: | |
| html_lines.append(f"</{list_type}>") | |
| list_type = None | |
| for raw in lines: | |
| line = raw.rstrip() | |
| if not line.strip(): | |
| close_list() | |
| continue | |
| if line.startswith("# "): | |
| close_list(); html_lines.append(f"<h1>{_md_inline(line[2:])}</h1>") | |
| elif line.startswith("## "): | |
| close_list(); html_lines.append(f"<h2>{_md_inline(line[3:])}</h2>") | |
| elif line.startswith("> "): | |
| close_list(); html_lines.append(f"<blockquote>{_md_inline(line[2:])}</blockquote>") | |
| elif line.strip() == "---": | |
| close_list(); html_lines.append("<hr>") | |
| elif line.startswith("- "): | |
| if list_type != "ul": | |
| close_list(); html_lines.append("<ul>"); list_type = "ul" | |
| html_lines.append(f"<li>{_md_inline(line[2:])}</li>") | |
| elif re.match(r"^\d+\.\s", line): | |
| if list_type != "ol": | |
| close_list(); html_lines.append("<ol>"); list_type = "ol" | |
| item = re.sub(r"^\d+\.\s", "", line) # backslash kept out of the f-string (py3.10) | |
| html_lines.append(f"<li>{_md_inline(item)}</li>") | |
| else: | |
| close_list(); html_lines.append(f"<p>{_md_inline(line)}</p>") | |
| close_list() | |
| return "\n".join(html_lines) | |
| _HTML_SHELL = """<!doctype html> | |
| <html lang="{lang}"><head><meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <title>{title}</title> | |
| <style> | |
| body {{ font-family: Georgia, "Times New Roman", serif; max-width: 760px; | |
| margin: 40px auto; padding: 0 24px; color: #1a1a2e; line-height: 1.55; }} | |
| h1 {{ font-size: 1.7rem; border-bottom: 2px solid #6d5dfc; padding-bottom: 6px; }} | |
| h2 {{ font-size: 1.15rem; color: #4a3fb0; margin-top: 1.6em; }} | |
| blockquote {{ border-left: 4px solid #b16cea; margin: 1em 0; padding: .4em 1em; | |
| background: #f5f2ff; font-style: italic; }} | |
| hr {{ border: none; border-top: 1px dashed #ccc; margin: 2.5em 0; }} | |
| ul, ol {{ padding-left: 1.4em; }} | |
| li {{ margin: .25em 0; }} | |
| footer {{ margin-top: 3em; font-size: .8rem; color: #888; text-align: center; | |
| font-family: sans-serif; }} | |
| @media print {{ body {{ margin: 0; max-width: none; }} }} | |
| </style></head> | |
| <body> | |
| {body} | |
| <footer>Forged with Case Forge · Build Small Hackathon</footer> | |
| </body></html> | |
| """ | |
| def to_markdown_file(case_md: str, note_md: str, title: str = "case") -> str: | |
| """Write the (possibly edited) case+note Markdown to a downloadable file.""" | |
| path = Path(tempfile.gettempdir()) / f"{_slug(title)}.md" | |
| path.write_text(_combined_markdown(case_md, note_md), encoding="utf-8") | |
| return str(path) | |
| def to_html_file(case_md: str, note_md: str, title: str = "case", | |
| lang: str = "pt") -> str: | |
| """Render the (possibly edited) Markdown into a self-contained printable HTML.""" | |
| body = _md_to_html_body(_combined_markdown(case_md, note_md)) | |
| doc = _HTML_SHELL.format(lang=lang, title=title or "Case", body=body) | |
| path = Path(tempfile.gettempdir()) / f"{_slug(title)}.html" | |
| path.write_text(doc, encoding="utf-8") | |
| return str(path) | |
| __all__ = ["to_markdown_file", "to_html_file"] | |