httk-web 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- httk/web/__init__.py +3 -0
- httk/web/api.py +71 -0
- httk/web/compat/__init__.py +1 -0
- httk/web/engine/__init__.py +3 -0
- httk/web/engine/discovery.py +51 -0
- httk/web/engine/site_engine.py +694 -0
- httk/web/functions/__init__.py +3 -0
- httk/web/functions/python_module.py +110 -0
- httk/web/model/__init__.py +15 -0
- httk/web/model/config.py +60 -0
- httk/web/model/errors.py +14 -0
- httk/web/model/page.py +25 -0
- httk/web/model/request.py +9 -0
- httk/web/publishing/__init__.py +3 -0
- httk/web/publishing/static.py +44 -0
- httk/web/py.typed +0 -0
- httk/web/renderers/__init__.py +22 -0
- httk/web/renderers/_frontmatter.py +51 -0
- httk/web/renderers/base.py +13 -0
- httk/web/renderers/html.py +10 -0
- httk/web/renderers/httkweb_compat.py +29 -0
- httk/web/renderers/markdown.py +20 -0
- httk/web/renderers/rst.py +76 -0
- httk/web/runtime/__init__.py +3 -0
- httk/web/runtime/asgi.py +101 -0
- httk/web/runtime/devserver.py +6 -0
- httk/web/templating/__init__.py +5 -0
- httk/web/templating/_legacy_formatter.py +187 -0
- httk/web/templating/base.py +16 -0
- httk/web/templating/httk_compat.py +66 -0
- httk/web/templating/jinja2_engine.py +149 -0
- httk_web-0.1.0.dist-info/METADATA +54 -0
- httk_web-0.1.0.dist-info/RECORD +36 -0
- httk_web-0.1.0.dist-info/WHEEL +5 -0
- httk_web-0.1.0.dist-info/licenses/LICENSE +661 -0
- httk_web-0.1.0.dist-info/top_level.txt +1 -0
httk/web/__init__.py
ADDED
httk/web/api.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from starlette.applications import Starlette
|
|
4
|
+
|
|
5
|
+
from .engine.site_engine import SiteEngine
|
|
6
|
+
from .model.config import SiteConfig
|
|
7
|
+
from .model.page import PublishReport
|
|
8
|
+
from .publishing.static import publish_site
|
|
9
|
+
from .runtime.asgi import create_app
|
|
10
|
+
from .runtime.devserver import run_dev_server
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def create_asgi_app(
|
|
14
|
+
srcdir: str | Path,
|
|
15
|
+
*,
|
|
16
|
+
baseurl: str | None = None,
|
|
17
|
+
compatibility_mode: bool = False,
|
|
18
|
+
config_name: str = "config",
|
|
19
|
+
debug: bool = False,
|
|
20
|
+
) -> Starlette:
|
|
21
|
+
config = SiteConfig.from_srcdir(
|
|
22
|
+
srcdir=srcdir,
|
|
23
|
+
baseurl=baseurl,
|
|
24
|
+
compatibility_mode=compatibility_mode,
|
|
25
|
+
config_name=config_name,
|
|
26
|
+
)
|
|
27
|
+
engine = SiteEngine(config)
|
|
28
|
+
return create_app(engine=engine, debug=debug)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def serve(
|
|
32
|
+
srcdir: str | Path,
|
|
33
|
+
*,
|
|
34
|
+
host: str = "127.0.0.1",
|
|
35
|
+
port: int = 8080,
|
|
36
|
+
baseurl: str | None = None,
|
|
37
|
+
compatibility_mode: bool = False,
|
|
38
|
+
config_name: str = "config",
|
|
39
|
+
debug: bool = False,
|
|
40
|
+
) -> None:
|
|
41
|
+
app = create_asgi_app(
|
|
42
|
+
srcdir=srcdir,
|
|
43
|
+
baseurl=baseurl,
|
|
44
|
+
compatibility_mode=compatibility_mode,
|
|
45
|
+
config_name=config_name,
|
|
46
|
+
debug=debug,
|
|
47
|
+
)
|
|
48
|
+
run_dev_server(app=app, host=host, port=port)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def publish(
|
|
52
|
+
srcdir: str | Path,
|
|
53
|
+
outdir: str | Path,
|
|
54
|
+
baseurl: str,
|
|
55
|
+
*,
|
|
56
|
+
host_static: str | None = None,
|
|
57
|
+
compatibility_mode: bool = False,
|
|
58
|
+
config_name: str = "config",
|
|
59
|
+
use_urls_without_ext: bool | None = None,
|
|
60
|
+
) -> PublishReport:
|
|
61
|
+
publish_use_urls_without_ext = use_urls_without_ext if use_urls_without_ext is not None else not compatibility_mode
|
|
62
|
+
config = SiteConfig.from_srcdir(
|
|
63
|
+
srcdir=srcdir,
|
|
64
|
+
baseurl=baseurl,
|
|
65
|
+
host_static=host_static,
|
|
66
|
+
compatibility_mode=compatibility_mode,
|
|
67
|
+
config_name=config_name,
|
|
68
|
+
publish_use_urls_without_ext=publish_use_urls_without_ext,
|
|
69
|
+
)
|
|
70
|
+
engine = SiteEngine(config)
|
|
71
|
+
return publish_site(engine=engine, outdir=outdir)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Placeholder package for compatibility helpers.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from httk.web.model.config import SiteConfig
|
|
4
|
+
from httk.web.model.page import ResolvedRoute
|
|
5
|
+
|
|
6
|
+
DEFAULT_CONTENT_EXTENSIONS: tuple[str, ...] = (".md", ".rst", ".html", ".httkweb")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def normalize_route(route: str) -> str:
|
|
10
|
+
normalized = route.lstrip("/").strip()
|
|
11
|
+
if normalized in {"", "."}:
|
|
12
|
+
return "index"
|
|
13
|
+
return normalized
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _is_within(path: Path, root: Path) -> bool:
|
|
17
|
+
try:
|
|
18
|
+
path.resolve().relative_to(root.resolve())
|
|
19
|
+
return True
|
|
20
|
+
except ValueError:
|
|
21
|
+
return False
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def resolve_route(config: SiteConfig, route: str) -> ResolvedRoute:
|
|
25
|
+
normalized = normalize_route(route)
|
|
26
|
+
|
|
27
|
+
static_candidate = (config.static_dir / normalized).resolve()
|
|
28
|
+
if static_candidate.exists() and static_candidate.is_file() and _is_within(static_candidate, config.static_dir):
|
|
29
|
+
return ResolvedRoute(kind="static", route=normalized, source_path=static_candidate)
|
|
30
|
+
|
|
31
|
+
content = _resolve_content_route(content_dir=config.content_dir, route=normalized)
|
|
32
|
+
if content is not None:
|
|
33
|
+
return ResolvedRoute(kind="content", route=normalized, source_path=content)
|
|
34
|
+
|
|
35
|
+
return ResolvedRoute(kind="missing", route=normalized)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _resolve_content_route(content_dir: Path, route: str) -> Path | None:
|
|
39
|
+
route_path = Path(route)
|
|
40
|
+
|
|
41
|
+
if route_path.suffix:
|
|
42
|
+
candidate = (content_dir / route_path).resolve()
|
|
43
|
+
if candidate.exists() and candidate.is_file() and _is_within(candidate, content_dir):
|
|
44
|
+
return candidate
|
|
45
|
+
|
|
46
|
+
for ext in DEFAULT_CONTENT_EXTENSIONS:
|
|
47
|
+
candidate = (content_dir / route_path).with_suffix(ext).resolve()
|
|
48
|
+
if candidate.exists() and candidate.is_file() and _is_within(candidate, content_dir):
|
|
49
|
+
return candidate
|
|
50
|
+
|
|
51
|
+
return None
|