From c3fd35a05e7e65a0c366ace7c5c68262cfb3c407 Mon Sep 17 00:00:00 2001 From: Maya Raman Date: Tue, 15 Oct 2024 13:05:12 -0400 Subject: [PATCH] DOP-3424: "On this page" subnav incorrectly displays headings for all language tabs (#625) * checkpoint * working * tests + formatting * format * remove comment * typing * addressing comments --- snooty/postprocess.py | 40 +++++++++++++------ snooty/test_postprocess.py | 79 ++++++++++++++++++++++++++++++++++---- 2 files changed, 100 insertions(+), 19 deletions(-) diff --git a/snooty/postprocess.py b/snooty/postprocess.py index 88623cee..29b55e89 100644 --- a/snooty/postprocess.py +++ b/snooty/postprocess.py @@ -463,6 +463,9 @@ def enter_node(self, fileid_stack: FileIdStack, node: n.Node) -> None: node.refuri = refuri +SelectorId = Dict[str, Union[str, "SelectorId"]] + + class ContentsHandler(Handler): """Identify all headings on a given page. If a contents directive appears on the page, save list of headings as a page-level option.""" @@ -470,7 +473,7 @@ class HeadingData(NamedTuple): depth: int id: str title: Sequence[n.InlineNode] - selector_id: Optional[str] + selector_ids: SelectorId def __init__(self, context: Context) -> None: super().__init__(context) @@ -478,7 +481,18 @@ def __init__(self, context: Context) -> None: self.current_depth = 0 self.has_contents_directive = False self.headings: List[ContentsHandler.HeadingData] = [] - self.scanned_pattern: List[str] = [] + self.scanned_pattern: List[Tuple[str, str]] = [] + + def scan_pattern(self, arr: List[Tuple[str, str]]) -> SelectorId: + if not arr: + return {} + if len(arr) == 1: + return {arr[0][0]: arr[0][1]} + scanned_pattern: SelectorId = { + arr[0][0]: arr[0][1], + "children": self.scan_pattern(arr[1:]), + } + return scanned_pattern def enter_page(self, fileid_stack: FileIdStack, page: Page) -> None: self.contents_depth = sys.maxsize @@ -496,7 +510,7 @@ def exit_page(self, fileid_stack: FileIdStack, page: Page) -> None: "depth": h.depth, "id": h.id, "title": [node.serialize() for node in h.title], - "selector_id": h.selector_id, + "selector_ids": h.selector_ids, } for h in self.headings if h.depth - 1 <= self.contents_depth @@ -509,8 +523,11 @@ def enter_node(self, fileid_stack: FileIdStack, node: n.Node) -> None: self.current_depth += 1 return - if isinstance(node, n.Directive) and node.name == "method-option": - self.scanned_pattern.append(node.options["id"]) + if isinstance(node, n.Directive): + if node.name == "method-option": + self.scanned_pattern.append((node.name, node.options["id"])) + elif node.name == "tab": + self.scanned_pattern.append((node.name, node.options["tabid"])) if isinstance(node, n.Directive) and node.name == "contents": if self.has_contents_directive: @@ -526,16 +543,15 @@ def enter_node(self, fileid_stack: FileIdStack, node: n.Node) -> None: if self.current_depth - 1 > self.contents_depth: return - selector_id = None + selector_ids = {} if len(self.scanned_pattern) > 0: - for item in self.scanned_pattern: - selector_id = item + selector_ids = self.scan_pattern(self.scanned_pattern) # Omit title headings (depth = 1) from heading list if isinstance(node, n.Heading) and self.current_depth > 1: self.headings.append( ContentsHandler.HeadingData( - self.current_depth, node.id, node.children, selector_id + self.current_depth, node.id, node.children, selector_ids ) ) @@ -546,12 +562,14 @@ def enter_node(self, fileid_stack: FileIdStack, node: n.Node) -> None: self.current_depth + 1, node.options["id"], [n.Text(node.span, node.options["heading"])], - selector_id, + selector_ids, ) ) def exit_node(self, fileid_stack: FileIdStack, node: n.Node) -> None: - if isinstance(node, n.Directive) and node.name == "method-option": + if isinstance(node, n.Directive) and ( + node.name == "method-option" or node.name == "tab" + ): self.scanned_pattern.pop() if isinstance(node, n.Section): self.current_depth -= 1 diff --git a/snooty/test_postprocess.py b/snooty/test_postprocess.py index 71fb4fcf..3fc4faa8 100644 --- a/snooty/test_postprocess.py +++ b/snooty/test_postprocess.py @@ -2347,7 +2347,7 @@ def test_contents_directive() -> None: check_ast_testing_string( page.ast, """ - +
Title @@ -3580,7 +3580,7 @@ def test_collapsible_headings() -> None: "value": "Subsection heading", } ], - "selector_id": None, + "selector_ids": {}, }, { "depth": 3, @@ -3592,7 +3592,7 @@ def test_collapsible_headings() -> None: "value": "Subsubsection heading", } ], - "selector_id": None, + "selector_ids": {}, }, ] @@ -3644,7 +3644,7 @@ def test_collapsible_headings() -> None: "value": "Subsection heading", } ], - "selector_id": None, + "selector_ids": {}, } ] @@ -3682,7 +3682,7 @@ def test_collapsible_headings() -> None: "value": "Collapsible heading", } ], - "selector_id": None, + "selector_ids": {}, } ] @@ -4093,12 +4093,12 @@ def test_method_selector_headings() -> None: "value": "Subsection heading", } ], - "selector_id": None, + "selector_ids": {}, }, { "depth": 3, "id": "what", - "selector_id": "driver", + "selector_ids": {"method-option": "driver"}, "title": [ { "position": {"start": {"line": 17}}, @@ -4110,7 +4110,7 @@ def test_method_selector_headings() -> None: { "depth": 3, "id": "this-is-a-heading", - "selector_id": "cli", + "selector_ids": {"method-option": "cli"}, "title": [ { "position": {"start": {"line": 29}}, @@ -4122,6 +4122,69 @@ def test_method_selector_headings() -> None: ] +def test_tab_headings() -> None: + with make_test( + { + Path( + "source/index.txt" + ): """ +.. contents:: On this page + :depth: 3 + +Title here +========== + +.. tabs:: + + .. tab:: tabs1 + :tabid: tabs1 + + Heading here + ------------ + This is content in tab1. + + .. tabs:: + + .. tab:: tabby + :tabid: tabby + + This is another headinge! + ~~~~~~~~~~~~~~~~~~~~~~~~~ + + Text ext text + +""", + } + ) as result: + page = result.pages[FileId("index.txt")] + assert (page.ast.options.get("headings")) == [ + { + "depth": 2, + "id": "heading-here", + "title": [ + { + "type": "text", + "position": {"start": {"line": 13}}, + "value": "Heading here", + } + ], + "selector_ids": {"tab": "tabs1"}, + }, + { + "depth": 3, + "id": "this-is-another-headinge-", + "title": [ + { + "type": "text", + "position": {"start": {"line": 22}}, + "value": "This is another headinge!", + } + ], + "selector_ids": {"tab": "tabs1", "children": {"tab": "tabby"}}, + }, + ] + + def test_multi_page_tutorials() -> None: test_page_template = """ .. multi-page-tutorial::