Add static documentation build API
This commit is contained in:
parent
e526df14dd
commit
738aa1e0b6
4 changed files with 93 additions and 5 deletions
30
README.md
30
README.md
|
|
@ -15,7 +15,7 @@ SharedDocsLib — небольшой Python-фреймворк для докум
|
|||
|
||||
UI не содержит языко-зависимых служебных подписей. Базовый шрифт — `system-ui 16px`; служебные UTF-8-пиктограммы используют Arial, а навигационные стрелки остаются в `system-ui`. AMOLED-тема включена по умолчанию и сохраняется локально после переключения.
|
||||
|
||||
На десктопе компактный header содержит полное название документации и search/theme/refresh, а слева постоянно видны header-кнопки и дерево страниц. Поисковый placeholder медленно печатает первые слова случайного названия, добавляет троеточие только в конце и сохраняет готовый текст 10 секунд; при вводе поле раскрывает ранжированные результаты вниз. Кнопка `⟳` вращается во время проверки manifest; при наведении она заменяет поиск и тему, показывая ISO-дату и короткий `revision`, а при нажатии синхронизирует manifest и перезагружает страницу.
|
||||
На десктопе компактный header содержит полное название документации и search/theme/refresh, а слева постоянно видны header-кнопки и дерево страниц. Поисковый placeholder медленно печатает первые слова случайного названия, добавляет троеточие только в конце и сохраняет готовый текст 5 секунд; при вводе поле раскрывает ранжированные результаты вниз. Кнопка `⟳` вращается во время проверки manifest; при наведении она заменяет поиск и тему, показывая ISO-дату и короткий `revision`, а при нажатии синхронизирует manifest и перезагружает страницу.
|
||||
|
||||
На мобильных устройствах используется viewport `100dvh` и нижний dock. Кнопка `☰` заменяет содержимое страницы копией desktop-sidebar. Ввод в нижний поиск заменяет содержимое результатами, расположенными снизу вверх по релевантности. Тема и обновление на мобильном скрыты.
|
||||
|
||||
|
|
@ -84,6 +84,34 @@ if __name__ == "__main__":
|
|||
|
||||
`manifest_path` принимает полный путь к JSON либо базовый URL, к которому будет добавлен `manifest.json`. Для совместимости также принят вариант `manifest_parh` из первоначального API, но в новом коде лучше использовать правильное имя.
|
||||
|
||||
## Статическая сборка
|
||||
|
||||
`build` принимает параметры компиляции из `run`, не связанные с сервером, и создаёт готовый статический комплект. По умолчанию используется каталог `./build`; отсутствующие родительские каталоги создаются автоматически.
|
||||
|
||||
```python
|
||||
from docslib import HeaderButton, build
|
||||
|
||||
build(
|
||||
build_directory="./dist/docs",
|
||||
title="My docs",
|
||||
assets_dir="./assets",
|
||||
custom_css="./assets/inject.css",
|
||||
manifest_path="./manifest.json",
|
||||
header_buttons=[
|
||||
HeaderButton("Знакомство", page1),
|
||||
HeaderButton("API reference", subpage1),
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
В `build_directory` записываются ровно три файла:
|
||||
|
||||
- `index.html` — полностью автономный клиент;
|
||||
- `manifest.json` — manifest для публикации отдельным файлом;
|
||||
- `index.zip` — архив, содержащий только `index.html`.
|
||||
|
||||
Функция возвращает `BuildResult` с теми же данными в памяти. Серверные параметры `host`, `port`, `live_preview`, `cors_origins` и `log_level` ей не требуются.
|
||||
|
||||
## Компоненты
|
||||
|
||||
Доступны `H1`, `H2`, `H3`, `P`, `MarkdownText`, `Image`, `Link`, `LocalLink` (`LocLink`), `Code`, `InlineCode`, `Quote`, `Callout`, `List`, `Table`, `Divider`, `Badge`, `Details`, `Json` и `RawHTML`.
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
"""Public API for SharedDocsLib."""
|
||||
|
||||
from .hooks import Component, Page
|
||||
from .server import create_app, run
|
||||
from .server import build, create_app, run
|
||||
from .ui import HeaderButton
|
||||
|
||||
__all__ = ["Component", "HeaderButton", "Page", "create_app", "run"]
|
||||
__all__ = ["Component", "HeaderButton", "Page", "build", "create_app", "run"]
|
||||
__version__ = "0.1.0"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
import json
|
||||
import runpy
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
|
@ -178,6 +179,39 @@ def create_app(
|
|||
return app
|
||||
|
||||
|
||||
def build(
|
||||
*,
|
||||
build_directory: str | Path = "./build",
|
||||
title: str = "Documentation",
|
||||
assets_dir: str | Path = "./assets",
|
||||
custom_css: str | Path | None = None,
|
||||
template_dir: str | Path | None = None,
|
||||
manifest_path: str | None = None,
|
||||
manifest_parh: str | None = None,
|
||||
header_buttons: Sequence[HeaderButton | Mapping[str, object]] | None = None,
|
||||
) -> BuildResult:
|
||||
"""Compile registered pages and write a static documentation bundle."""
|
||||
compiler = Compiler(
|
||||
title=title,
|
||||
assets_dir=assets_dir,
|
||||
custom_css=custom_css,
|
||||
template_dir=template_dir,
|
||||
manifest_url=_manifest_url(manifest_path, manifest_parh),
|
||||
live_preview=False,
|
||||
header_buttons=header_buttons,
|
||||
)
|
||||
result = compiler.build()
|
||||
output = Path(build_directory).expanduser().resolve()
|
||||
output.mkdir(parents=True, exist_ok=True)
|
||||
(output / "index.html").write_bytes(result.html)
|
||||
(output / "manifest.json").write_text(
|
||||
json.dumps(result.manifest, ensure_ascii=False, separators=(",", ":")),
|
||||
encoding="utf-8",
|
||||
)
|
||||
(output / "index.zip").write_bytes(result.zip_archive)
|
||||
return result
|
||||
|
||||
|
||||
def run(
|
||||
*,
|
||||
port: int = 8000,
|
||||
|
|
@ -208,4 +242,4 @@ def run(
|
|||
uvicorn.run(app, host=host, port=port, log_level=log_level)
|
||||
|
||||
|
||||
__all__ = ["DocumentationSite", "create_app", "run"]
|
||||
__all__ = ["DocumentationSite", "build", "create_app", "run"]
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import zipfile
|
|||
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from docslib import HeaderButton
|
||||
from docslib import HeaderButton, build
|
||||
from docslib.components import H1, Image, P
|
||||
from docslib.hooks import Page
|
||||
from docslib.server import create_app
|
||||
|
|
@ -121,6 +121,32 @@ def test_all_endpoints_and_self_contained_build(tmp_path):
|
|||
assert client.get("/live").status_code == 404
|
||||
|
||||
|
||||
def test_build_writes_static_bundle(tmp_path):
|
||||
@Page("1", "Static page")
|
||||
def static_page():
|
||||
return H1("Static documentation")
|
||||
|
||||
output = tmp_path / "nested" / "site"
|
||||
result = build(
|
||||
build_directory=output,
|
||||
title="Static docs",
|
||||
manifest_path="https://docs.example/manifest.json",
|
||||
header_buttons=[HeaderButton("Home", static_page)],
|
||||
)
|
||||
|
||||
assert sorted(path.name for path in output.iterdir()) == [
|
||||
"index.html",
|
||||
"index.zip",
|
||||
"manifest.json",
|
||||
]
|
||||
assert output.joinpath("index.html").read_bytes() == result.html
|
||||
assert json.loads(output.joinpath("manifest.json").read_text(encoding="utf-8")) == result.manifest
|
||||
assert "https://docs.example/manifest.json" in result.html.decode("utf-8")
|
||||
with zipfile.ZipFile(output / "index.zip") as archive:
|
||||
assert archive.namelist() == ["index.html"]
|
||||
assert archive.read("index.html") == result.html
|
||||
|
||||
|
||||
def test_live_preview_reloads_a_changed_main_source(tmp_path):
|
||||
source = tmp_path / "docs.py"
|
||||
source.write_text(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue