File size: 3,630 Bytes
eba4c06
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import re
import requests
import nbformat

RAW_BASE = "https://raw.githubusercontent.com/Qiskit/textbook/main/notebooks/intro/"
README_URL = RAW_BASE + "README.md"
FALLBACK = [
    "what-is-quantum.ipynb",
    "entangled-states.ipynb",
    "superdense-coding.ipynb",
    "teleportation.ipynb",
]


# ────────────────────────────────────────────────────────────────────
# Internal helpers
# ────────────────────────────────────────────────────────────────────
def _discover_notebooks() -> list[str]:
    """Scrape notebooks/intro/README.md for *.ipynb links; fallback if offline."""
    try:
        md = requests.get(README_URL, timeout=10).text
        found = re.findall(r"\(([^)]+?\.ipynb)\)", md)
        if found:
            return found
    except requests.RequestException:
        pass
    return FALLBACK


def _pretty(name: str) -> str:
    """'superdense-coding.ipynb' ➜ 'Superdense Coding'."""
    return name.replace("-", " ").replace(".ipynb", "").title()


# ────────────────────────────────────────────────────────────────────
# Public API
# ────────────────────────────────────────────────────────────────────
def get_theory_topics() -> dict[str, str]:
    """
    Return a mapping of *friendly topic name* β†’ *notebook filename*.

    Example
    -------
    {'What Is Quantum?': 'what-is-quantum.ipynb', ...}
    """
    return {_pretty(f): f for f in _discover_notebooks()}


def get_theory(
    topic: str,
    markdown_only: bool = True,
    include_headers: bool = True,
) -> str:
    """
    Download **one** intro notebook and return its content as text.

    Parameters
    ----------
    topic
        Accepts pretty title (β€œTeleportation”), slug (β€œteleportation”)
        or exact filename (β€œteleportation.ipynb”).
    markdown_only
        True (default) ➜ keep only Markdown cells;
        False          ➜ include code cells fenced as ```python.
    include_headers
        Prepend an H1 title for readability.

    Raises
    ------
    ValueError
        If *topic* cannot be resolved.

    Returns
    -------
    str
        Concatenated notebook text.
    """
    topics = get_theory_topics()

    # Build a lenient lookup table
    lookup: dict[str, str] = {}
    for pretty, fname in topics.items():
        slug = fname.removesuffix(".ipynb")
        lookup[pretty.lower()] = fname
        lookup[slug.lower()]   = fname
        lookup[fname.lower()]  = fname

    key = topic.lower()
    if key not in lookup:
        raise ValueError(
            f"Unknown topic '{topic}'. "
            f"Known: {', '.join(topics.keys())}."
        )

    fname = lookup[key]
    raw_json = requests.get(RAW_BASE + fname, timeout=20).text
    nb = nbformat.reads(raw_json, as_version=4)

    parts: list[str] = []
    if include_headers:
        parts.append(f"# {_pretty(fname)}\n")

    for cell in nb.cells:
        if cell.cell_type == "markdown":
            parts.append(cell.source)
        elif cell.cell_type == "code" and not markdown_only:
            parts.append(f"```python\n{cell.source}\n```")

    return "\n\n".join(parts)