File size: 4,480 Bytes
f8d4986
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d5999d0
 
f8d4986
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
"""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"]