Plain is headed towards 1.0! Subscribe for development updates →

 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()