Skip to content

Commit 01be507

Browse files
committed
Define a base class for all navigation item classes
1 parent c459cd2 commit 01be507

File tree

4 files changed

+59
-65
lines changed

4 files changed

+59
-65
lines changed

docs/dev-guide/themes.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,7 @@ on the homepage:
408408
show_root_full_path: false
409409
heading_level: 5
410410

411-
::: mkdocs.structure.pages.Page.parent
411+
::: mkdocs.structure.StructureItem.parent
412412
options:
413413
show_root_full_path: false
414414
heading_level: 5
@@ -479,7 +479,7 @@ The following attributes are available on `section` objects:
479479
show_root_full_path: false
480480
heading_level: 5
481481

482-
::: mkdocs.structure.nav.Section.parent
482+
::: mkdocs.structure.StructureItem.parent
483483
options:
484484
show_root_full_path: false
485485
heading_level: 5
@@ -533,7 +533,7 @@ The following attributes are available on `link` objects:
533533
show_root_full_path: false
534534
heading_level: 5
535535

536-
::: mkdocs.structure.nav.Link.parent
536+
::: mkdocs.structure.StructureItem.parent
537537
options:
538538
show_root_full_path: false
539539
heading_level: 5

mkdocs/structure/__init__.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from __future__ import annotations
2+
3+
import abc
4+
from typing import TYPE_CHECKING, Iterable
5+
6+
if TYPE_CHECKING:
7+
from mkdocs.structure.nav import Section
8+
9+
10+
class StructureItem(metaclass=abc.ABCMeta):
11+
"""An item in MkDocs structure - see concrete subclasses Section, Page or Link."""
12+
13+
@abc.abstractmethod
14+
def __init__(self):
15+
...
16+
17+
parent: Section | None = None
18+
"""The immediate parent of the item in the site navigation. `None` if it's at the top level."""
19+
20+
@property
21+
def is_top_level(self) -> bool:
22+
return self.parent is None
23+
24+
title: str | None
25+
is_section: bool = False
26+
is_page: bool = False
27+
is_link: bool = False
28+
29+
@property
30+
def ancestors(self) -> Iterable[StructureItem]:
31+
if self.parent is None:
32+
return []
33+
return [self.parent, *self.parent.ancestors]
34+
35+
def _indent_print(self, depth=0):
36+
return (' ' * depth) + repr(self)

mkdocs/structure/nav.py

Lines changed: 13 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from typing import TYPE_CHECKING, Iterator, TypeVar
55
from urllib.parse import urlsplit
66

7+
from mkdocs.structure import StructureItem
78
from mkdocs.structure.pages import Page
89
from mkdocs.utils import nest_paths
910

@@ -16,7 +17,7 @@
1617

1718

1819
class Navigation:
19-
def __init__(self, items: list[Page | Section | Link], pages: list[Page]) -> None:
20+
def __init__(self, items: list, pages: list[Page]) -> None:
2021
self.items = items # Nested List with full navigation of Sections, Pages, and Links.
2122
self.pages = pages # Flat List of subset of Pages in nav, in order.
2223

@@ -32,34 +33,30 @@ def __init__(self, items: list[Page | Section | Link], pages: list[Page]) -> Non
3233
pages: list[Page]
3334
"""A flat list of all [page][mkdocs.structure.pages.Page] objects contained in the navigation."""
3435

35-
def __repr__(self):
36+
def __str__(self) -> str:
3637
return '\n'.join(item._indent_print() for item in self)
3738

38-
def __iter__(self) -> Iterator[Page | Section | Link]:
39+
def __iter__(self) -> Iterator:
3940
return iter(self.items)
4041

4142
def __len__(self) -> int:
4243
return len(self.items)
4344

4445

45-
class Section:
46-
def __init__(self, title: str, children: list[Page | Section | Link]) -> None:
46+
class Section(StructureItem):
47+
def __init__(self, title: str, children: list[StructureItem]) -> None:
4748
self.title = title
4849
self.children = children
4950

50-
self.parent = None
5151
self.active = False
5252

5353
def __repr__(self):
54-
return f"Section(title='{self.title}')"
54+
return f"Section(title={self.title!r})"
5555

5656
title: str
5757
"""The title of the section."""
5858

59-
parent: Section | None
60-
"""The immediate parent of the section or `None` if the section is at the top level."""
61-
62-
children: list[Page | Section | Link]
59+
children: list[StructureItem]
6360
"""An iterable of all child navigation objects. Children may include nested sections, pages and links."""
6461

6562
@property
@@ -87,28 +84,21 @@ def active(self, value: bool):
8784
is_link: bool = False
8885
"""Indicates that the navigation object is a "link" object. Always `False` for section objects."""
8986

90-
@property
91-
def ancestors(self):
92-
if self.parent is None:
93-
return []
94-
return [self.parent] + self.parent.ancestors
95-
96-
def _indent_print(self, depth=0):
97-
ret = ['{}{}'.format(' ' * depth, repr(self))]
87+
def _indent_print(self, depth: int = 0):
88+
ret = [super()._indent_print(depth)]
9889
for item in self.children:
9990
ret.append(item._indent_print(depth + 1))
10091
return '\n'.join(ret)
10192

10293

103-
class Link:
94+
class Link(StructureItem):
10495
def __init__(self, title: str, url: str):
10596
self.title = title
10697
self.url = url
107-
self.parent = None
10898

10999
def __repr__(self):
110-
title = f"'{self.title}'" if (self.title is not None) else '[blank]'
111-
return f"Link(title={title}, url='{self.url}')"
100+
title = f"{self.title!r}" if self.title is not None else '[blank]'
101+
return f"Link(title={title}, url={self.url!r})"
112102

113103
title: str
114104
"""The title of the link. This would generally be used as the label of the link."""
@@ -117,9 +107,6 @@ def __repr__(self):
117107
"""The URL that the link points to. The URL should always be an absolute URLs and
118108
should not need to have `base_url` prepended."""
119109

120-
parent: Section | None
121-
"""The immediate parent of the link. `None` if the link is at the top level."""
122-
123110
children: None = None
124111
"""Links do not contain children and the attribute is always `None`."""
125112

@@ -135,15 +122,6 @@ def __repr__(self):
135122
is_link: bool = True
136123
"""Indicates that the navigation object is a "link" object. Always `True` for link objects."""
137124

138-
@property
139-
def ancestors(self):
140-
if self.parent is None:
141-
return []
142-
return [self.parent] + self.parent.ancestors
143-
144-
def _indent_print(self, depth=0):
145-
return '{}{}'.format(' ' * depth, repr(self))
146-
147125

148126
def get_navigation(files: Files, config: MkDocsConfig) -> Navigation:
149127
"""Build site navigation from config and files."""

mkdocs/structure/pages.py

Lines changed: 7 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import os
66
import posixpath
77
import warnings
8-
from typing import TYPE_CHECKING, Any, Callable, Mapping, MutableMapping
8+
from typing import TYPE_CHECKING, Any, Callable, MutableMapping
99
from urllib.parse import unquote as urlunquote
1010
from urllib.parse import urljoin, urlsplit, urlunsplit
1111

@@ -15,6 +15,7 @@
1515
import markdown.treeprocessors
1616
from markdown.util import AMP_SUBSTITUTE
1717

18+
from mkdocs.structure import StructureItem
1819
from mkdocs.structure.toc import get_toc
1920
from mkdocs.utils import get_build_date, get_markdown_title, meta, weak_property
2021

@@ -23,7 +24,6 @@
2324

2425
from mkdocs.config.defaults import MkDocsConfig
2526
from mkdocs.structure.files import File, Files
26-
from mkdocs.structure.nav import Section
2727
from mkdocs.structure.toc import TableOfContents
2828

2929
_unescape: Callable[[str], str]
@@ -36,17 +36,14 @@
3636
log = logging.getLogger(__name__)
3737

3838

39-
class Page:
40-
def __init__(
41-
self, title: str | None, file: File, config: MkDocsConfig | Mapping[str, Any]
42-
) -> None:
39+
class Page(StructureItem):
40+
def __init__(self, title: str | None, file: File, config: MkDocsConfig) -> None:
4341
file.page = self
4442
self.file = file
4543
if title is not None:
4644
self.title = title
4745

4846
# Navigation attributes
49-
self.parent = None
5047
self.children = None
5148
self.previous_page = None
5249
self.next_page = None
@@ -74,12 +71,9 @@ def __eq__(self, other) -> bool:
7471
)
7572

7673
def __repr__(self):
77-
title = f"'{self.title}'" if (self.title is not None) else '[blank]'
74+
title = f"{self.title!r}" if self.title is not None else '[blank]'
7875
url = self.abs_url or self.file.url
79-
return f"Page(title={title}, url='{url}')"
80-
81-
def _indent_print(self, depth=0):
82-
return '{}{}'.format(' ' * depth, repr(self))
76+
return f"Page(title={title}, url={url!r})"
8377

8478
markdown: str | None
8579
"""The original Markdown content from the file."""
@@ -133,10 +127,6 @@ def active(self, value: bool):
133127
def is_index(self) -> bool:
134128
return self.file.name == 'index'
135129

136-
@property
137-
def is_top_level(self) -> bool:
138-
return self.parent is None
139-
140130
edit_url: str | None
141131
"""The full URL to the source page in the source repository. Typically used to
142132
provide a link to edit the source page. [base_url][] should not be used with this
@@ -157,10 +147,6 @@ def is_homepage(self) -> bool:
157147
The value will be `None` if the current page is the last item in the site navigation
158148
or if the current page is not included in the navigation at all."""
159149

160-
parent: Section | None
161-
"""The immediate parent of the page in the site navigation. `None` if the
162-
page is at the top level."""
163-
164150
children: None = None
165151
"""Pages do not contain children and the attribute is always `None`."""
166152

@@ -173,12 +159,6 @@ def is_homepage(self) -> bool:
173159
is_link: bool = False
174160
"""Indicates that the navigation object is a "link" object. Always `False` for page objects."""
175161

176-
@property
177-
def ancestors(self):
178-
if self.parent is None:
179-
return []
180-
return [self.parent] + self.parent.ancestors
181-
182162
def _set_canonical_url(self, base: str | None) -> None:
183163
if base:
184164
if not base.endswith('/'):
@@ -242,7 +222,7 @@ def _set_title(self) -> None:
242222
)
243223

244224
@weak_property
245-
def title(self) -> str | None:
225+
def title(self) -> str | None: # type: ignore[override]
246226
"""
247227
Returns the title for the current page.
248228

0 commit comments

Comments
 (0)