1from __future__ import annotations
2
3import os
4
5from plain.internal import internalcode
6from plain.urls import URLPattern
7
8from .exceptions import PageNotFoundError
9from .pages import Page
10
11
12@internalcode
13class PagesRegistry:
14 """
15 The registry loads up all the pages at once, so we only have to do a
16 dict key lookup at runtime to get a page.
17 """
18
19 def __init__(self):
20 self._url_name_mappings = {} # url_name -> relative_path
21 self._path_mappings = {} # relative_path -> absolute_path
22
23 def get_page_urls(self) -> list[URLPattern]:
24 """
25 Generate a list of real urls based on the files that exist.
26 This way, you get a concrete url reversingerror if you try
27 to refer to a page/url that isn't going to work.
28 """
29 paths = []
30
31 for relative_path in self._path_mappings.keys():
32 page = self.get_page_from_path(relative_path)
33
34 # Get all URL path objects from the page
35 paths.extend(page.get_urls())
36
37 return paths
38
39 def discover_pages(self, pages_dir: str) -> None:
40 for root, _, files in os.walk(pages_dir, followlinks=True):
41 for file in files:
42 relative_path = str(
43 os.path.relpath(os.path.join(root, file), pages_dir)
44 )
45 absolute_path = str(os.path.join(root, file))
46
47 page = Page(relative_path=relative_path, absolute_path=absolute_path)
48 urls = page.get_urls()
49
50 # Some pages don't get any urls (like templates)
51 if not urls:
52 continue
53
54 # Register the page by its file path
55 self._path_mappings[relative_path] = absolute_path
56
57 # Register all URL names to point back to this file path
58 for url_path_obj in urls:
59 url_name = url_path_obj.name
60 self._url_name_mappings[url_name] = relative_path
61
62 def get_page_from_name(self, url_name: str) -> Page:
63 """Get a page by its URL name."""
64 try:
65 relative_path = self._url_name_mappings[url_name]
66 return self.get_page_from_path(relative_path)
67 except KeyError:
68 raise PageNotFoundError(f"Could not find a page for URL name {url_name}")
69
70 def get_page_from_path(self, relative_path: str) -> Page:
71 """Get a page by its relative file path."""
72 try:
73 absolute_path = self._path_mappings[relative_path]
74 # Instantiate the page here, so we don't store a ton of cached data over time
75 # as we render all the pages
76 return Page(relative_path=relative_path, absolute_path=absolute_path)
77 except KeyError:
78 raise PageNotFoundError(f"Could not find a page for path {relative_path}")
79
80
81pages_registry = PagesRegistry()