"""Generate the app registry website."""
import logging
import os
import os.path
import shutil
from collections.abc import Generator
from itertools import chain
from pathlib import Path
from typing import Optional
import pkg_resources
from ..utils import parse_app_repo
from . import api, yaml
from .apps_index import generate_apps_index
from .core import AppRegistryData, AppRegistrySchemas
from .html import build_html
logger = logging.getLogger(__name__)
[docs]def copy_static_tree_from_path(base_path: Path, static_path: Path) -> Generator[Path]:
for root, _, files in os.walk(static_path):
# Create directory
base_path.joinpath(Path(root).relative_to(static_path)).mkdir(
parents=True, exist_ok=True
)
# Copy all files
for filename in files:
src = Path(root).joinpath(filename)
dst = base_path.joinpath(Path(root).relative_to(static_path), filename)
dst.write_bytes(src.read_bytes())
yield dst
[docs]def _walk_pkg_resources(package: str, root: str) -> Generator[tuple[str, list]]:
paths = pkg_resources.resource_listdir(package, root)
for path in paths:
dir_paths = [
path
for path in paths
if pkg_resources.resource_isdir(package, os.path.join(root, path))
]
yield root, list(set(paths).difference(dir_paths))
for dir_path in dir_paths:
yield from _walk_pkg_resources(package, os.path.join(root, dir_path))
[docs]def copy_static_tree_from_package(
html_path: Path, root: str = "static"
) -> Generator[Path]:
assert __package__ is not None
for directory, files in _walk_pkg_resources(__package__, root):
stem = html_path.joinpath(Path(directory).relative_to(root))
stem.mkdir(parents=True, exist_ok=True)
for fn in files:
src = pkg_resources.resource_stream(
__package__, os.path.join(directory, fn)
)
dst = stem.joinpath(fn)
dst.write_bytes(src.read())
yield dst
[docs]def build(
apps_path: Path,
categories_path: Path,
base_path: Path,
html_path: Optional[Path] = None,
api_path: Optional[Path] = None,
static_path: Optional[Path] = None,
templates_path: Optional[Path] = None,
validate_output: bool = True,
validate_input: bool = False,
) -> None:
"""Build the app registry website (including schema files)."""
# Parse the schemas shipped with the package.
schemas = AppRegistrySchemas.from_package()
# Parse the apps and categories data from the given paths.
data = AppRegistryData(
apps=yaml.load(apps_path),
categories=yaml.load(categories_path),
)
if validate_input:
data.validate(schemas)
# Remove previous build (if present) and re-create root directory.
shutil.rmtree(base_path, ignore_errors=True)
base_path.mkdir(parents=True, exist_ok=True)
apps_index, apps_data = generate_apps_index(
data=data, scan_app_repository=parse_app_repo
)
# Build the website and API endpoints.
logger.info(f"Building registry at: {base_path.resolve()}")
for outfile in chain(
# Build the html pages if the html path is specified
(
chain(
# Copy static files from package
copy_static_tree_from_package(html_path=base_path / html_path),
# Copy static files (if specified)
(
copy_static_tree_from_path(base_path / html_path, static_path)
if static_path
else ()
),
# Build the html pages.
build_html(
base_path=base_path / html_path,
apps_index=apps_index,
apps_data=apps_data,
templates_path=templates_path,
),
)
if html_path is not None
else ()
),
# Build the API endpoints if the api path is specified
(
api.build_api_v1(
api_path=base_path / api_path,
apps_index=apps_index,
apps_data=apps_data,
)
if api_path
else ()
),
):
logger.info(f" - {outfile.relative_to(base_path)}")
if validate_output:
if api_path:
api.validate_api_v1(api_path=base_path / api_path, schemas=schemas)