1from __future__ import annotations
2
3import os
4from collections.abc import Iterator
5from pathlib import Path
6
7from plain.packages import packages_registry
8from plain.runtime import APP_PATH
9
10_APP_ASSETS_DIR = APP_PATH / "assets"
11
12_SKIP_ASSETS = (".DS_Store", ".gitignore")
13
14
15class Asset:
16 def __init__(self, *, url_path: str, absolute_path: str):
17 self.url_path = url_path
18 self.absolute_path = absolute_path
19
20 def __str__(self) -> str:
21 return self.url_path
22
23
24def _iter_assets() -> Iterator[Asset]:
25 """
26 Iterate all valid asset files found in the installed
27 packages and the app itself.
28 """
29
30 def __iter_assets_dir(path: str | Path) -> Iterator[tuple[str, str]]:
31 for root, _, files in os.walk(path):
32 for f in files:
33 if f in _SKIP_ASSETS:
34 continue
35 abs_path = os.path.join(root, f)
36 url_path = os.path.relpath(abs_path, path)
37 yield url_path, abs_path
38
39 for asset_dir in _iter_asset_dirs():
40 for url_path, abs_path in __iter_assets_dir(asset_dir):
41 yield Asset(url_path=url_path, absolute_path=abs_path)
42
43
44def _iter_asset_dirs() -> Iterator[str | Path]:
45 """
46 Iterate all directories containing assets, from installed
47 packages and from app/assets.
48 """
49 # Iterate the installed package assets, in order
50 for pkg in packages_registry.get_package_configs():
51 asset_dir = os.path.join(pkg.path, "assets")
52 if os.path.exists(asset_dir):
53 yield asset_dir
54
55 # The app/assets take priority over everything
56 if _APP_ASSETS_DIR.exists():
57 yield _APP_ASSETS_DIR