diff --git a/snooty/postprocess.py b/snooty/postprocess.py index fe2280b5..310b9a91 100644 --- a/snooty/postprocess.py +++ b/snooty/postprocess.py @@ -470,6 +470,7 @@ class HeadingData(NamedTuple): depth: int id: str title: Sequence[n.InlineNode] + selector_id: Optional[str] def __init__(self, context: Context) -> None: super().__init__(context) @@ -477,6 +478,7 @@ 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] = [] def enter_page(self, fileid_stack: FileIdStack, page: Page) -> None: self.contents_depth = sys.maxsize @@ -494,6 +496,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, } for h in self.headings if h.depth - 1 <= self.contents_depth @@ -506,6 +509,9 @@ 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) and node.name == "contents": if self.has_contents_directive: self.context.diagnostics[fileid_stack.current].append( @@ -520,10 +526,17 @@ def enter_node(self, fileid_stack: FileIdStack, node: n.Node) -> None: if self.current_depth - 1 > self.contents_depth: return + selector_id = None + if len(self.scanned_pattern) > 0: + for item in self.scanned_pattern: + selector_id = item + # 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) + ContentsHandler.HeadingData( + self.current_depth, node.id, node.children, selector_id + ) ) if isinstance(node, n.Directive) and node.name == "collapsible": @@ -533,10 +546,13 @@ 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, ) ) def exit_node(self, fileid_stack: FileIdStack, node: n.Node) -> None: + if isinstance(node, n.Directive) and node.name == "method-option": + 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 6a8d2293..380b1721 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 @@ -3512,6 +3512,7 @@ def test_collapsible_headings() -> None: "value": "Subsection heading", } ], + "selector_id": None, }, { "depth": 3, @@ -3523,6 +3524,7 @@ def test_collapsible_headings() -> None: "value": "Subsubsection heading", } ], + "selector_id": None, }, ] @@ -3574,6 +3576,7 @@ def test_collapsible_headings() -> None: "value": "Subsection heading", } ], + "selector_id": None, } ] @@ -3611,6 +3614,7 @@ def test_collapsible_headings() -> None: "value": "Collapsible heading", } ], + "selector_id": None, } ] @@ -3959,6 +3963,97 @@ def test_method_selector() -> None: ) +def test_method_selector_headings() -> None: + with make_test( + { + Path( + "source/index.txt" + ): """ +.. contents:: + :depth: 2 + +=================== +Heading of the page +=================== + +Subsection heading +------------------ + +.. method-selector:: + + .. method-option:: + :id: driver + + WHAT + ~~~~ + + .. method-description:: + + This is an optional description. Learn more about drivers at `MongoDB Documentation `__. + + This is content in the Driver method haha. + + .. method-option:: + :id: cli + + This is a heading + ~~~~~~~~~~~~~~~~~ + + .. method-description:: + + This is a description under the heading for cli. + + This is content in the CLI method haha. + + .. method-option:: + :id: mongosh + + Foo + +""", + } + ) as result: + page = result.pages[FileId("index.txt")] + assert (page.ast.options.get("headings")) == [ + { + "depth": 2, + "id": "subsection-heading", + "title": [ + { + "type": "text", + "position": {"start": {"line": 9}}, + "value": "Subsection heading", + } + ], + "selector_id": None, + }, + { + "depth": 3, + "id": "what", + "selector_id": "driver", + "title": [ + { + "position": {"start": {"line": 17}}, + "type": "text", + "value": "WHAT", + } + ], + }, + { + "depth": 3, + "id": "this-is-a-heading", + "selector_id": "cli", + "title": [ + { + "position": {"start": {"line": 29}}, + "type": "text", + "value": "This is a heading", + } + ], + }, + ] + + def test_multi_page_tutorials() -> None: test_page_template = """ .. multi-page-tutorial::