3
3
import argparse as ap
4
4
import pathlib
5
5
import re
6
- from collections import namedtuple
6
+ from typing import NamedTuple
7
7
8
8
import nbformat
9
9
from nbformat import NotebookNode
10
10
11
11
12
- TocEntry = namedtuple ("TocEntry" , ["level" , "text" , "anchor" ])
12
+ class TocEntry (NamedTuple ):
13
+ """Table of contents entry"""
14
+
15
+ level : int
16
+ text : str
17
+ anchor : str
13
18
14
19
15
20
def extract_markdown_cells (notebook : NotebookNode ) -> str :
@@ -22,29 +27,32 @@ def extract_markdown_cells(notebook: NotebookNode) -> str:
22
27
def extract_toc (notebook : str ) -> list [TocEntry ]:
23
28
"""Extract the table of contents from a markdown string"""
24
29
toc = []
25
- line_re = re .compile (r"(#+)\s+(.+)" )
26
- for line in notebook .splitlines ():
27
- if groups := re .match (line_re , line ):
28
- heading , text , * _ = groups .groups ()
29
- level = len (heading )
30
+
31
+ # Regex trick: use a capture group to match the heading level discarding code blocks
32
+ line_re = re .compile (r"```py.*\n#|^(#{1,6})\s+(.+)" , re .MULTILINE )
33
+
34
+ for match in re .findall (line_re , notebook ):
35
+ if all (match ):
36
+ level , text = match
30
37
anchor = "-" .join (text .replace ("`" , "" ).split ())
31
- toc .append (TocEntry (level , text , anchor ))
38
+ toc .append (TocEntry (len (level ), text , anchor ))
39
+
32
40
return toc
33
41
34
42
35
43
def markdown_toc (toc : list [TocEntry ]) -> str :
36
44
"""Build a string representation of the toc as a nested markdown list"""
37
- lines = []
38
- for entry in toc :
39
- line = f"{ ' ' * entry .level } - [{ entry .text } ](#{ entry .anchor } )"
40
- lines .append (line )
41
- return "\n " .join (lines )
45
+ return "\n " .join (
46
+ f"{ ' ' * entry .level } - [{ entry .text } ](#{ entry .anchor } )" for entry in toc
47
+ )
42
48
43
49
44
50
def build_toc (nb_path : pathlib .Path , placeholder : str = "[TOC]" ) -> NotebookNode :
45
51
"""Build a table of contents for a notebook and insert it at the location of a placeholder"""
46
52
# Read the notebook
47
53
nb_obj : NotebookNode = nbformat .read (nb_path , nbformat .NO_CONVERT )
54
+
55
+ # Extract markdown cells
48
56
md_cells = extract_markdown_cells (nb_obj )
49
57
50
58
# Build tree
0 commit comments