We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Allow plugins to override `Page` when populating navigation
Following up on https://github.com/squidfunk/mkdocs-material/issues/5909#issuecomment-1696958973, I'm suggesting to adjust the logic that MkDocs implies to create `Page` objects from/in files, i.e., `File` objects. Currently, [`_data_to_navigation`](https://github.com/mkdocs/mkdocs/blob/master/mkdocs/structure/nav.py#L179) will always create (and thus overwrite) `Page` on everything that doesn't look like a `Link` or `Section`. Excerpt of the comment linked:

> MkDocs will always generate either a [`Link` or a `Page` for a file that is not excluded](https://github.com/mkdocs/mkdocs/blob/94e9f17cd7a69e70c18ae282d55ba2f34f93c542/mkdocs/structure/nav.py#L203-L204). As you've probably noticed, the blog plugin makes use of subclasses for pages of specific types, e.g. [`Post`](https://github.com/squidfunk/mkdocs-material/blob/de0c5b7719deff6d092dc673e82af701c6f36001/src/plugins/blog/structure/__init__.py#L48), [`View`](https://github.com/squidfunk/mkdocs-material/blob/de0c5b7719deff6d092dc673e82af701c6f36001/src/plugins/blog/structure/__init__.py#L204), etc., and relations among different concepts in the plugin are managed inside the instances of those subclasses. The problem is, that __MkDocs will always generate a plain old `Page` instance, overwriting the more specific instance that has been generated by the plugin before__ [after `on_files` and before `on_nav`](https://github.com/mkdocs/mkdocs/blob/94e9f17cd7a69e70c18ae282d55ba2f34f93c542/mkdocs/commands/build.py#L303-L311). We could still solve it by splitting file generation and page instantiation, but this would make things much more complicated and non-linear, because during generation, [we already assign categories to pages and vice versa](https://github.com/squidfunk/mkdocs-material/blob/de0c5b7719deff6d092dc673e82af701c6f36001/src/plugins/blog/plugin.py#L598-L599), etc. We would need to split this into two stages, blowing up the code base of the plugin and also the runtime, as we're doing two passes. This is clearly not favorable from our perspective.
>
> __Moving forward__: I believe that this problem can be mitigated with a very small but important change in MkDocs: if a `File` already defines a dedicated `Page` in `file.page`, MkDocs should just use that instead of creating a new one:
> 
> ``` py
> return file.page if isinstance(file.page, Page) else Page(title, file, config)
> ```
>
> It's essentially a single line change in MkDocs that would make things much, much simpler for plugin developers. I've written several plugins now, and I repeatedly had to work around the oddities of this behavior.

Thus, I'm proposing to change [the following line](https://github.com/mkdocs/mkdocs/blob/94e9f17cd7a69e70c18ae282d55ba2f34f93c542/mkdocs/structure/nav.py#L203):

``` py
# Now: always create a page
return Page(title, file, config)

# Better: only create a page when one was not created before
return file.page if isinstance(file.page, Page) else Page(title, file, config)
```

Currently, the only way to work around this limitation is to overwrite `Page.__class__` on the resulting instance in `on_nav`, e.g., like done in [section-index](https://github.com/oprypin/mkdocs-section-index/blob/f5414c0775848cec96f1181f74c622fe3e14985e/mkdocs_section_index/plugin.py#L33-L34), which, AFAIK, does not call `__init__` or initializes instance variables:

``` py
page.__class__ = CustomPage
assert isinstance(page, CustomPage)
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `mkdocs/structure/nav.py`
Content:
```
1 from __future__ import annotations
2 
3 import logging
4 from typing import TYPE_CHECKING, Iterator, TypeVar
5 from urllib.parse import urlsplit
6 
7 from mkdocs.structure import StructureItem
8 from mkdocs.structure.pages import Page
9 from mkdocs.utils import nest_paths
10 
11 if TYPE_CHECKING:
12     from mkdocs.config.defaults import MkDocsConfig
13     from mkdocs.structure.files import Files
14 
15 
16 log = logging.getLogger(__name__)
17 
18 
19 class Navigation:
20     def __init__(self, items: list, pages: list[Page]) -> None:
21         self.items = items  # Nested List with full navigation of Sections, Pages, and Links.
22         self.pages = pages  # Flat List of subset of Pages in nav, in order.
23 
24         self.homepage = None
25         for page in pages:
26             if page.is_homepage:
27                 self.homepage = page
28                 break
29 
30     homepage: Page | None
31     """The [page][mkdocs.structure.pages.Page] object for the homepage of the site."""
32 
33     pages: list[Page]
34     """A flat list of all [page][mkdocs.structure.pages.Page] objects contained in the navigation."""
35 
36     def __str__(self) -> str:
37         return '\n'.join(item._indent_print() for item in self)
38 
39     def __iter__(self) -> Iterator:
40         return iter(self.items)
41 
42     def __len__(self) -> int:
43         return len(self.items)
44 
45 
46 class Section(StructureItem):
47     def __init__(self, title: str, children: list[StructureItem]) -> None:
48         self.title = title
49         self.children = children
50 
51         self.active = False
52 
53     def __repr__(self):
54         name = self.__class__.__name__
55         return f"{name}(title={self.title!r})"
56 
57     title: str
58     """The title of the section."""
59 
60     children: list[StructureItem]
61     """An iterable of all child navigation objects. Children may include nested sections, pages and links."""
62 
63     @property
64     def active(self) -> bool:
65         """
66         When `True`, indicates that a child page of this section is the current page and
67         can be used to highlight the section as the currently viewed section. Defaults
68         to `False`.
69         """
70         return self.__active
71 
72     @active.setter
73     def active(self, value: bool):
74         """Set active status of section and ancestors."""
75         self.__active = bool(value)
76         if self.parent is not None:
77             self.parent.active = bool(value)
78 
79     is_section: bool = True
80     """Indicates that the navigation object is a "section" object. Always `True` for section objects."""
81 
82     is_page: bool = False
83     """Indicates that the navigation object is a "page" object. Always `False` for section objects."""
84 
85     is_link: bool = False
86     """Indicates that the navigation object is a "link" object. Always `False` for section objects."""
87 
88     def _indent_print(self, depth: int = 0) -> str:
89         ret = [super()._indent_print(depth)]
90         for item in self.children:
91             ret.append(item._indent_print(depth + 1))
92         return '\n'.join(ret)
93 
94 
95 class Link(StructureItem):
96     def __init__(self, title: str, url: str):
97         self.title = title
98         self.url = url
99 
100     def __repr__(self):
101         name = self.__class__.__name__
102         title = f"{self.title!r}" if self.title is not None else '[blank]'
103         return f"{name}(title={title}, url={self.url!r})"
104 
105     title: str
106     """The title of the link. This would generally be used as the label of the link."""
107 
108     url: str
109     """The URL that the link points to. The URL should always be an absolute URLs and
110     should not need to have `base_url` prepended."""
111 
112     children: None = None
113     """Links do not contain children and the attribute is always `None`."""
114 
115     active: bool = False
116     """External links cannot be "active" and the attribute is always `False`."""
117 
118     is_section: bool = False
119     """Indicates that the navigation object is a "section" object. Always `False` for link objects."""
120 
121     is_page: bool = False
122     """Indicates that the navigation object is a "page" object. Always `False` for link objects."""
123 
124     is_link: bool = True
125     """Indicates that the navigation object is a "link" object. Always `True` for link objects."""
126 
127 
128 def get_navigation(files: Files, config: MkDocsConfig) -> Navigation:
129     """Build site navigation from config and files."""
130     documentation_pages = files.documentation_pages()
131     nav_config = config['nav'] or nest_paths(f.src_uri for f in documentation_pages)
132     items = _data_to_navigation(nav_config, files, config)
133     if not isinstance(items, list):
134         items = [items]
135 
136     # Get only the pages from the navigation, ignoring any sections and links.
137     pages = _get_by_type(items, Page)
138 
139     # Include next, previous and parent links.
140     _add_previous_and_next_links(pages)
141     _add_parent_links(items)
142 
143     missing_from_config = []
144     for file in documentation_pages:
145         if file.page is None:
146             # Any documentation files not found in the nav should still have an associated page, so we
147             # create them here. The Page object will automatically be assigned to `file.page` during
148             # its creation (and this is the only way in which these page objects are accessible).
149             Page(None, file, config)
150             if file.inclusion.is_in_nav():
151                 missing_from_config.append(file.src_path)
152     if missing_from_config:
153         log.log(
154             config.validation.nav.omitted_files,
155             'The following pages exist in the docs directory, but are not '
156             'included in the "nav" configuration:\n  - ' + '\n  - '.join(missing_from_config),
157         )
158 
159     links = _get_by_type(items, Link)
160     for link in links:
161         scheme, netloc, path, query, fragment = urlsplit(link.url)
162         if scheme or netloc:
163             log.debug(f"An external link to '{link.url}' is included in the 'nav' configuration.")
164         elif link.url.startswith('/'):
165             log.log(
166                 config.validation.nav.absolute_links,
167                 f"An absolute path to '{link.url}' is included in the 'nav' "
168                 "configuration, which presumably points to an external resource.",
169             )
170         else:
171             log.log(
172                 config.validation.nav.not_found,
173                 f"A relative path to '{link.url}' is included in the 'nav' "
174                 "configuration, which is not found in the documentation files.",
175             )
176     return Navigation(items, pages)
177 
178 
179 def _data_to_navigation(data, files: Files, config: MkDocsConfig):
180     if isinstance(data, dict):
181         return [
182             _data_to_navigation((key, value), files, config)
183             if isinstance(value, str)
184             else Section(title=key, children=_data_to_navigation(value, files, config))
185             for key, value in data.items()
186         ]
187     elif isinstance(data, list):
188         return [
189             _data_to_navigation(item, files, config)[0]
190             if isinstance(item, dict) and len(item) == 1
191             else _data_to_navigation(item, files, config)
192             for item in data
193         ]
194     title, path = data if isinstance(data, tuple) else (None, data)
195     file = files.get_file_from_path(path)
196     if file:
197         if file.inclusion.is_excluded():
198             log.log(
199                 min(logging.INFO, config.validation.nav.not_found),
200                 f"A reference to '{file.src_path}' is included in the 'nav' "
201                 "configuration, but this file is excluded from the built site.",
202             )
203         return Page(title, file, config)
204     return Link(title, path)
205 
206 
207 T = TypeVar('T')
208 
209 
210 def _get_by_type(nav, t: type[T]) -> list[T]:
211     ret = []
212     for item in nav:
213         if isinstance(item, t):
214             ret.append(item)
215         if item.children:
216             ret.extend(_get_by_type(item.children, t))
217     return ret
218 
219 
220 def _add_parent_links(nav) -> None:
221     for item in nav:
222         if item.is_section:
223             for child in item.children:
224                 child.parent = item
225             _add_parent_links(item.children)
226 
227 
228 def _add_previous_and_next_links(pages: list[Page]) -> None:
229     bookended = [None, *pages, None]
230     zipped = zip(bookended[:-2], pages, bookended[2:])
231     for page0, page1, page2 in zipped:
232         page1.previous_page, page1.next_page = page0, page2
233
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/mkdocs/structure/nav.py b/mkdocs/structure/nav.py
--- a/mkdocs/structure/nav.py
+++ b/mkdocs/structure/nav.py
@@ -1,6 +1,7 @@
 from __future__ import annotations
 
 import logging
+import warnings
 from typing import TYPE_CHECKING, Iterator, TypeVar
 from urllib.parse import urlsplit
 
@@ -200,6 +201,12 @@
                 f"A reference to '{file.src_path}' is included in the 'nav' "
                 "configuration, but this file is excluded from the built site.",
             )
+        if file.page is not None:
+            if not isinstance(file.page, Page):
+                warnings.warn(  # type: ignore[unreachable]
+                    "File.page should not be set to any type other than Page", DeprecationWarning
+                )
+            return file.page
         return Page(title, file, config)
     return Link(title, path)

{"golden_diff": "diff --git a/mkdocs/structure/nav.py b/mkdocs/structure/nav.py\n--- a/mkdocs/structure/nav.py\n+++ b/mkdocs/structure/nav.py\n@@ -1,6 +1,7 @@\n from __future__ import annotations\n \n import logging\n+import warnings\n from typing import TYPE_CHECKING, Iterator, TypeVar\n from urllib.parse import urlsplit\n \n@@ -200,6 +201,12 @@\n                 f\"A reference to '{file.src_path}' is included in the 'nav' \"\n                 \"configuration, but this file is excluded from the built site.\",\n             )\n+        if file.page is not None:\n+            if not isinstance(file.page, Page):\n+                warnings.warn(  # type: ignore[unreachable]\n+                    \"File.page should not be set to any type other than Page\", DeprecationWarning\n+                )\n+            return file.page\n         return Page(title, file, config)\n     return Link(title, path)\n", "issue": "Allow plugins to override `Page` when populating navigation\nFollowing up on https://github.com/squidfunk/mkdocs-material/issues/5909#issuecomment-1696958973, I'm suggesting to adjust the logic that MkDocs implies to create `Page` objects from/in files, i.e., `File` objects. Currently, [`_data_to_navigation`](https://github.com/mkdocs/mkdocs/blob/master/mkdocs/structure/nav.py#L179) will always create (and thus overwrite) `Page` on everything that doesn't look like a `Link` or `Section`. Excerpt of the comment linked:\r\n\r\n> MkDocs will always generate either a [`Link` or a `Page` for a file that is not excluded](https://github.com/mkdocs/mkdocs/blob/94e9f17cd7a69e70c18ae282d55ba2f34f93c542/mkdocs/structure/nav.py#L203-L204). As you've probably noticed, the blog plugin makes use of subclasses for pages of specific types, e.g. [`Post`](https://github.com/squidfunk/mkdocs-material/blob/de0c5b7719deff6d092dc673e82af701c6f36001/src/plugins/blog/structure/__init__.py#L48), [`View`](https://github.com/squidfunk/mkdocs-material/blob/de0c5b7719deff6d092dc673e82af701c6f36001/src/plugins/blog/structure/__init__.py#L204), etc., and relations among different concepts in the plugin are managed inside the instances of those subclasses. The problem is, that __MkDocs will always generate a plain old `Page` instance, overwriting the more specific instance that has been generated by the plugin before__ [after `on_files` and before `on_nav`](https://github.com/mkdocs/mkdocs/blob/94e9f17cd7a69e70c18ae282d55ba2f34f93c542/mkdocs/commands/build.py#L303-L311). We could still solve it by splitting file generation and page instantiation, but this would make things much more complicated and non-linear, because during generation, [we already assign categories to pages and vice versa](https://github.com/squidfunk/mkdocs-material/blob/de0c5b7719deff6d092dc673e82af701c6f36001/src/plugins/blog/plugin.py#L598-L599), etc. We would need to split this into two stages, blowing up the code base of the plugin and also the runtime, as we're doing two passes. This is clearly not favorable from our perspective.\r\n>\r\n> __Moving forward__: I believe that this problem can be mitigated with a very small but important change in MkDocs: if a `File` already defines a dedicated `Page` in `file.page`, MkDocs should just use that instead of creating a new one:\r\n> \r\n> ``` py\r\n> return file.page if isinstance(file.page, Page) else Page(title, file, config)\r\n> ```\r\n>\r\n> It's essentially a single line change in MkDocs that would make things much, much simpler for plugin developers. I've written several plugins now, and I repeatedly had to work around the oddities of this behavior.\r\n\r\nThus, I'm proposing to change [the following line](https://github.com/mkdocs/mkdocs/blob/94e9f17cd7a69e70c18ae282d55ba2f34f93c542/mkdocs/structure/nav.py#L203):\r\n\r\n``` py\r\n# Now: always create a page\r\nreturn Page(title, file, config)\r\n\r\n# Better: only create a page when one was not created before\r\nreturn file.page if isinstance(file.page, Page) else Page(title, file, config)\r\n```\r\n\r\nCurrently, the only way to work around this limitation is to overwrite `Page.__class__` on the resulting instance in `on_nav`, e.g., like done in [section-index](https://github.com/oprypin/mkdocs-section-index/blob/f5414c0775848cec96f1181f74c622fe3e14985e/mkdocs_section_index/plugin.py#L33-L34), which, AFAIK, does not call `__init__` or initializes instance variables:\r\n\r\n``` py\r\npage.__class__ = CustomPage\r\nassert isinstance(page, CustomPage)\r\n```\n", "before_files": [{"content": "from __future__ import annotations\n\nimport logging\nfrom typing import TYPE_CHECKING, Iterator, TypeVar\nfrom urllib.parse import urlsplit\n\nfrom mkdocs.structure import StructureItem\nfrom mkdocs.structure.pages import Page\nfrom mkdocs.utils import nest_paths\n\nif TYPE_CHECKING:\n    from mkdocs.config.defaults import MkDocsConfig\n    from mkdocs.structure.files import Files\n\n\nlog = logging.getLogger(__name__)\n\n\nclass Navigation:\n    def __init__(self, items: list, pages: list[Page]) -> None:\n        self.items = items  # Nested List with full navigation of Sections, Pages, and Links.\n        self.pages = pages  # Flat List of subset of Pages in nav, in order.\n\n        self.homepage = None\n        for page in pages:\n            if page.is_homepage:\n                self.homepage = page\n                break\n\n    homepage: Page | None\n    \"\"\"The [page][mkdocs.structure.pages.Page] object for the homepage of the site.\"\"\"\n\n    pages: list[Page]\n    \"\"\"A flat list of all [page][mkdocs.structure.pages.Page] objects contained in the navigation.\"\"\"\n\n    def __str__(self) -> str:\n        return '\\n'.join(item._indent_print() for item in self)\n\n    def __iter__(self) -> Iterator:\n        return iter(self.items)\n\n    def __len__(self) -> int:\n        return len(self.items)\n\n\nclass Section(StructureItem):\n    def __init__(self, title: str, children: list[StructureItem]) -> None:\n        self.title = title\n        self.children = children\n\n        self.active = False\n\n    def __repr__(self):\n        name = self.__class__.__name__\n        return f\"{name}(title={self.title!r})\"\n\n    title: str\n    \"\"\"The title of the section.\"\"\"\n\n    children: list[StructureItem]\n    \"\"\"An iterable of all child navigation objects. Children may include nested sections, pages and links.\"\"\"\n\n    @property\n    def active(self) -> bool:\n        \"\"\"\n        When `True`, indicates that a child page of this section is the current page and\n        can be used to highlight the section as the currently viewed section. Defaults\n        to `False`.\n        \"\"\"\n        return self.__active\n\n    @active.setter\n    def active(self, value: bool):\n        \"\"\"Set active status of section and ancestors.\"\"\"\n        self.__active = bool(value)\n        if self.parent is not None:\n            self.parent.active = bool(value)\n\n    is_section: bool = True\n    \"\"\"Indicates that the navigation object is a \"section\" object. Always `True` for section objects.\"\"\"\n\n    is_page: bool = False\n    \"\"\"Indicates that the navigation object is a \"page\" object. Always `False` for section objects.\"\"\"\n\n    is_link: bool = False\n    \"\"\"Indicates that the navigation object is a \"link\" object. Always `False` for section objects.\"\"\"\n\n    def _indent_print(self, depth: int = 0) -> str:\n        ret = [super()._indent_print(depth)]\n        for item in self.children:\n            ret.append(item._indent_print(depth + 1))\n        return '\\n'.join(ret)\n\n\nclass Link(StructureItem):\n    def __init__(self, title: str, url: str):\n        self.title = title\n        self.url = url\n\n    def __repr__(self):\n        name = self.__class__.__name__\n        title = f\"{self.title!r}\" if self.title is not None else '[blank]'\n        return f\"{name}(title={title}, url={self.url!r})\"\n\n    title: str\n    \"\"\"The title of the link. This would generally be used as the label of the link.\"\"\"\n\n    url: str\n    \"\"\"The URL that the link points to. The URL should always be an absolute URLs and\n    should not need to have `base_url` prepended.\"\"\"\n\n    children: None = None\n    \"\"\"Links do not contain children and the attribute is always `None`.\"\"\"\n\n    active: bool = False\n    \"\"\"External links cannot be \"active\" and the attribute is always `False`.\"\"\"\n\n    is_section: bool = False\n    \"\"\"Indicates that the navigation object is a \"section\" object. Always `False` for link objects.\"\"\"\n\n    is_page: bool = False\n    \"\"\"Indicates that the navigation object is a \"page\" object. Always `False` for link objects.\"\"\"\n\n    is_link: bool = True\n    \"\"\"Indicates that the navigation object is a \"link\" object. Always `True` for link objects.\"\"\"\n\n\ndef get_navigation(files: Files, config: MkDocsConfig) -> Navigation:\n    \"\"\"Build site navigation from config and files.\"\"\"\n    documentation_pages = files.documentation_pages()\n    nav_config = config['nav'] or nest_paths(f.src_uri for f in documentation_pages)\n    items = _data_to_navigation(nav_config, files, config)\n    if not isinstance(items, list):\n        items = [items]\n\n    # Get only the pages from the navigation, ignoring any sections and links.\n    pages = _get_by_type(items, Page)\n\n    # Include next, previous and parent links.\n    _add_previous_and_next_links(pages)\n    _add_parent_links(items)\n\n    missing_from_config = []\n    for file in documentation_pages:\n        if file.page is None:\n            # Any documentation files not found in the nav should still have an associated page, so we\n            # create them here. The Page object will automatically be assigned to `file.page` during\n            # its creation (and this is the only way in which these page objects are accessible).\n            Page(None, file, config)\n            if file.inclusion.is_in_nav():\n                missing_from_config.append(file.src_path)\n    if missing_from_config:\n        log.log(\n            config.validation.nav.omitted_files,\n            'The following pages exist in the docs directory, but are not '\n            'included in the \"nav\" configuration:\\n  - ' + '\\n  - '.join(missing_from_config),\n        )\n\n    links = _get_by_type(items, Link)\n    for link in links:\n        scheme, netloc, path, query, fragment = urlsplit(link.url)\n        if scheme or netloc:\n            log.debug(f\"An external link to '{link.url}' is included in the 'nav' configuration.\")\n        elif link.url.startswith('/'):\n            log.log(\n                config.validation.nav.absolute_links,\n                f\"An absolute path to '{link.url}' is included in the 'nav' \"\n                \"configuration, which presumably points to an external resource.\",\n            )\n        else:\n            log.log(\n                config.validation.nav.not_found,\n                f\"A relative path to '{link.url}' is included in the 'nav' \"\n                \"configuration, which is not found in the documentation files.\",\n            )\n    return Navigation(items, pages)\n\n\ndef _data_to_navigation(data, files: Files, config: MkDocsConfig):\n    if isinstance(data, dict):\n        return [\n            _data_to_navigation((key, value), files, config)\n            if isinstance(value, str)\n            else Section(title=key, children=_data_to_navigation(value, files, config))\n            for key, value in data.items()\n        ]\n    elif isinstance(data, list):\n        return [\n            _data_to_navigation(item, files, config)[0]\n            if isinstance(item, dict) and len(item) == 1\n            else _data_to_navigation(item, files, config)\n            for item in data\n        ]\n    title, path = data if isinstance(data, tuple) else (None, data)\n    file = files.get_file_from_path(path)\n    if file:\n        if file.inclusion.is_excluded():\n            log.log(\n                min(logging.INFO, config.validation.nav.not_found),\n                f\"A reference to '{file.src_path}' is included in the 'nav' \"\n                \"configuration, but this file is excluded from the built site.\",\n            )\n        return Page(title, file, config)\n    return Link(title, path)\n\n\nT = TypeVar('T')\n\n\ndef _get_by_type(nav, t: type[T]) -> list[T]:\n    ret = []\n    for item in nav:\n        if isinstance(item, t):\n            ret.append(item)\n        if item.children:\n            ret.extend(_get_by_type(item.children, t))\n    return ret\n\n\ndef _add_parent_links(nav) -> None:\n    for item in nav:\n        if item.is_section:\n            for child in item.children:\n                child.parent = item\n            _add_parent_links(item.children)\n\n\ndef _add_previous_and_next_links(pages: list[Page]) -> None:\n    bookended = [None, *pages, None]\n    zipped = zip(bookended[:-2], pages, bookended[2:])\n    for page0, page1, page2 in zipped:\n        page1.previous_page, page1.next_page = page0, page2\n", "path": "mkdocs/structure/nav.py"}], "after_files": [{"content": "from __future__ import annotations\n\nimport logging\nimport warnings\nfrom typing import TYPE_CHECKING, Iterator, TypeVar\nfrom urllib.parse import urlsplit\n\nfrom mkdocs.structure import StructureItem\nfrom mkdocs.structure.pages import Page\nfrom mkdocs.utils import nest_paths\n\nif TYPE_CHECKING:\n    from mkdocs.config.defaults import MkDocsConfig\n    from mkdocs.structure.files import Files\n\n\nlog = logging.getLogger(__name__)\n\n\nclass Navigation:\n    def __init__(self, items: list, pages: list[Page]) -> None:\n        self.items = items  # Nested List with full navigation of Sections, Pages, and Links.\n        self.pages = pages  # Flat List of subset of Pages in nav, in order.\n\n        self.homepage = None\n        for page in pages:\n            if page.is_homepage:\n                self.homepage = page\n                break\n\n    homepage: Page | None\n    \"\"\"The [page][mkdocs.structure.pages.Page] object for the homepage of the site.\"\"\"\n\n    pages: list[Page]\n    \"\"\"A flat list of all [page][mkdocs.structure.pages.Page] objects contained in the navigation.\"\"\"\n\n    def __str__(self) -> str:\n        return '\\n'.join(item._indent_print() for item in self)\n\n    def __iter__(self) -> Iterator:\n        return iter(self.items)\n\n    def __len__(self) -> int:\n        return len(self.items)\n\n\nclass Section(StructureItem):\n    def __init__(self, title: str, children: list[StructureItem]) -> None:\n        self.title = title\n        self.children = children\n\n        self.active = False\n\n    def __repr__(self):\n        name = self.__class__.__name__\n        return f\"{name}(title={self.title!r})\"\n\n    title: str\n    \"\"\"The title of the section.\"\"\"\n\n    children: list[StructureItem]\n    \"\"\"An iterable of all child navigation objects. Children may include nested sections, pages and links.\"\"\"\n\n    @property\n    def active(self) -> bool:\n        \"\"\"\n        When `True`, indicates that a child page of this section is the current page and\n        can be used to highlight the section as the currently viewed section. Defaults\n        to `False`.\n        \"\"\"\n        return self.__active\n\n    @active.setter\n    def active(self, value: bool):\n        \"\"\"Set active status of section and ancestors.\"\"\"\n        self.__active = bool(value)\n        if self.parent is not None:\n            self.parent.active = bool(value)\n\n    is_section: bool = True\n    \"\"\"Indicates that the navigation object is a \"section\" object. Always `True` for section objects.\"\"\"\n\n    is_page: bool = False\n    \"\"\"Indicates that the navigation object is a \"page\" object. Always `False` for section objects.\"\"\"\n\n    is_link: bool = False\n    \"\"\"Indicates that the navigation object is a \"link\" object. Always `False` for section objects.\"\"\"\n\n    def _indent_print(self, depth: int = 0) -> str:\n        ret = [super()._indent_print(depth)]\n        for item in self.children:\n            ret.append(item._indent_print(depth + 1))\n        return '\\n'.join(ret)\n\n\nclass Link(StructureItem):\n    def __init__(self, title: str, url: str):\n        self.title = title\n        self.url = url\n\n    def __repr__(self):\n        name = self.__class__.__name__\n        title = f\"{self.title!r}\" if self.title is not None else '[blank]'\n        return f\"{name}(title={title}, url={self.url!r})\"\n\n    title: str\n    \"\"\"The title of the link. This would generally be used as the label of the link.\"\"\"\n\n    url: str\n    \"\"\"The URL that the link points to. The URL should always be an absolute URLs and\n    should not need to have `base_url` prepended.\"\"\"\n\n    children: None = None\n    \"\"\"Links do not contain children and the attribute is always `None`.\"\"\"\n\n    active: bool = False\n    \"\"\"External links cannot be \"active\" and the attribute is always `False`.\"\"\"\n\n    is_section: bool = False\n    \"\"\"Indicates that the navigation object is a \"section\" object. Always `False` for link objects.\"\"\"\n\n    is_page: bool = False\n    \"\"\"Indicates that the navigation object is a \"page\" object. Always `False` for link objects.\"\"\"\n\n    is_link: bool = True\n    \"\"\"Indicates that the navigation object is a \"link\" object. Always `True` for link objects.\"\"\"\n\n\ndef get_navigation(files: Files, config: MkDocsConfig) -> Navigation:\n    \"\"\"Build site navigation from config and files.\"\"\"\n    documentation_pages = files.documentation_pages()\n    nav_config = config['nav'] or nest_paths(f.src_uri for f in documentation_pages)\n    items = _data_to_navigation(nav_config, files, config)\n    if not isinstance(items, list):\n        items = [items]\n\n    # Get only the pages from the navigation, ignoring any sections and links.\n    pages = _get_by_type(items, Page)\n\n    # Include next, previous and parent links.\n    _add_previous_and_next_links(pages)\n    _add_parent_links(items)\n\n    missing_from_config = []\n    for file in documentation_pages:\n        if file.page is None:\n            # Any documentation files not found in the nav should still have an associated page, so we\n            # create them here. The Page object will automatically be assigned to `file.page` during\n            # its creation (and this is the only way in which these page objects are accessible).\n            Page(None, file, config)\n            if file.inclusion.is_in_nav():\n                missing_from_config.append(file.src_path)\n    if missing_from_config:\n        log.log(\n            config.validation.nav.omitted_files,\n            'The following pages exist in the docs directory, but are not '\n            'included in the \"nav\" configuration:\\n  - ' + '\\n  - '.join(missing_from_config),\n        )\n\n    links = _get_by_type(items, Link)\n    for link in links:\n        scheme, netloc, path, query, fragment = urlsplit(link.url)\n        if scheme or netloc:\n            log.debug(f\"An external link to '{link.url}' is included in the 'nav' configuration.\")\n        elif link.url.startswith('/'):\n            log.log(\n                config.validation.nav.absolute_links,\n                f\"An absolute path to '{link.url}' is included in the 'nav' \"\n                \"configuration, which presumably points to an external resource.\",\n            )\n        else:\n            log.log(\n                config.validation.nav.not_found,\n                f\"A relative path to '{link.url}' is included in the 'nav' \"\n                \"configuration, which is not found in the documentation files.\",\n            )\n    return Navigation(items, pages)\n\n\ndef _data_to_navigation(data, files: Files, config: MkDocsConfig):\n    if isinstance(data, dict):\n        return [\n            _data_to_navigation((key, value), files, config)\n            if isinstance(value, str)\n            else Section(title=key, children=_data_to_navigation(value, files, config))\n            for key, value in data.items()\n        ]\n    elif isinstance(data, list):\n        return [\n            _data_to_navigation(item, files, config)[0]\n            if isinstance(item, dict) and len(item) == 1\n            else _data_to_navigation(item, files, config)\n            for item in data\n        ]\n    title, path = data if isinstance(data, tuple) else (None, data)\n    file = files.get_file_from_path(path)\n    if file:\n        if file.inclusion.is_excluded():\n            log.log(\n                min(logging.INFO, config.validation.nav.not_found),\n                f\"A reference to '{file.src_path}' is included in the 'nav' \"\n                \"configuration, but this file is excluded from the built site.\",\n            )\n        if file.page is not None:\n            if not isinstance(file.page, Page):\n                warnings.warn(  # type: ignore[unreachable]\n                    \"File.page should not be set to any type other than Page\", DeprecationWarning\n                )\n            return file.page\n        return Page(title, file, config)\n    return Link(title, path)\n\n\nT = TypeVar('T')\n\n\ndef _get_by_type(nav, t: type[T]) -> list[T]:\n    ret = []\n    for item in nav:\n        if isinstance(item, t):\n            ret.append(item)\n        if item.children:\n            ret.extend(_get_by_type(item.children, t))\n    return ret\n\n\ndef _add_parent_links(nav) -> None:\n    for item in nav:\n        if item.is_section:\n            for child in item.children:\n                child.parent = item\n            _add_parent_links(item.children)\n\n\ndef _add_previous_and_next_links(pages: list[Page]) -> None:\n    bookended = [None, *pages, None]\n    zipped = zip(bookended[:-2], pages, bookended[2:])\n    for page0, page1, page2 in zipped:\n        page1.previous_page, page1.next_page = page0, page2\n", "path": "mkdocs/structure/nav.py"}]}