| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111 |
- """
- TOC directive
- ~~~~~~~~~~~~~
- The TOC directive syntax looks like::
- .. toc:: Title
- :min-level: 1
- :max-level: 3
- "Title", "min-level", and "max-level" option can be empty. "min-level"
- and "max-level" are integers >= 1 and <= 6, which define the allowed
- heading levels writers want to include in the table of contents.
- """
- from typing import TYPE_CHECKING, Any, Dict, Match
- from ..toc import normalize_toc_item, render_toc_ul
- from ._base import BaseDirective, DirectivePlugin
- if TYPE_CHECKING:
- from ..block_parser import BlockParser
- from ..core import BaseRenderer, BlockState
- from ..markdown import Markdown
- class TableOfContents(DirectivePlugin):
- def __init__(self, min_level: int = 1, max_level: int = 3) -> None:
- self.min_level = min_level
- self.max_level = max_level
- def generate_heading_id(self, token: Dict[str, Any], index: int) -> str:
- return "toc_" + str(index + 1)
- def parse(self, block: "BlockParser", m: Match[str], state: "BlockState") -> Dict[str, Any]:
- title = self.parse_title(m)
- options = self.parse_options(m)
- if options:
- d_options = dict(options)
- collapse = "collapse" in d_options
- min_level = _normalize_level(d_options, "min-level", self.min_level)
- max_level = _normalize_level(d_options, "max-level", self.max_level)
- if min_level < self.min_level:
- raise ValueError(f'"min-level" option MUST be >= {self.min_level}')
- if max_level > self.max_level:
- raise ValueError(f'"max-level" option MUST be <= {self.max_level}')
- if min_level > max_level:
- raise ValueError('"min-level" option MUST be less than "max-level" option')
- else:
- collapse = False
- min_level = self.min_level
- max_level = self.max_level
- attrs = {
- "min_level": min_level,
- "max_level": max_level,
- "collapse": collapse,
- }
- return {"type": "toc", "text": title or "", "attrs": attrs}
- def toc_hook(self, md: "Markdown", state: "BlockState") -> None:
- sections = []
- headings = []
- for tok in state.tokens:
- if tok["type"] == "toc":
- sections.append(tok)
- elif tok["type"] == "heading":
- headings.append(tok)
- if sections:
- toc_items = []
- # adding ID for each heading
- for i, tok in enumerate(headings):
- tok["attrs"]["id"] = self.generate_heading_id(tok, i)
- toc_items.append(normalize_toc_item(md, tok, parent=state))
- for sec in sections:
- _min = sec["attrs"]["min_level"]
- _max = sec["attrs"]["max_level"]
- toc = [item for item in toc_items if _min <= item[0] <= _max]
- sec["attrs"]["toc"] = toc
- def __call__(self, directive: BaseDirective, md: "Markdown") -> None:
- if md.renderer and md.renderer.NAME == "html":
- # only works with HTML renderer
- directive.register("toc", self.parse)
- md.before_render_hooks.append(self.toc_hook)
- md.renderer.register("toc", render_html_toc)
- def render_html_toc(renderer: "BaseRenderer", title: str, collapse: bool = False, **attrs: Any) -> str:
- if not title:
- title = "Table of Contents"
- content = render_toc_ul(attrs["toc"])
- html = '<details class="toc"'
- if not collapse:
- html += " open"
- html += ">\n<summary>" + title + "</summary>\n"
- return html + content + "</details>\n"
- def _normalize_level(options: Dict[str, Any], name: str, default: Any) -> Any:
- level = options.get(name)
- if not level:
- return default
- try:
- return int(level)
- except (ValueError, TypeError):
- raise ValueError(f'"{name}" option MUST be integer')
|