Files
Даниил Грабарь b7db2e991c Add documentation for SunriseWorkbench project setup and features
- Create libraries.md for library installation instructions in SunriseWorkbench.
- Create load-project.md for importing existing projects in SunriseWorkbench.
- Create new-project.md detailing the process of creating a new project in SunriseWorkbench.
- Add applications.md to describe the applications section in KUKA smartHMI.
- Add extra-menu.md for additional robot control options in KUKA smartHMI.
- Create io-group.md for managing input/output groups in KUKA smartHMI.
- Add robot-menu.md as a placeholder for the robot menu section in KUKA smartHMI.
- Create station.md to outline the main navigation and functionalities in KUKA smartHMI.
- Add LBRserver.md, robot-power-control.md, and teach-kuka.md as placeholders for respective control programs.
- Create config-error.md to provide troubleshooting steps for configuration errors in SunriseWorkbench.
- Add main.html to override base template with site root metadata.
2026-06-18 13:22:55 +03:00

249 lines
9.9 KiB
Python

from __future__ import annotations
import argparse
import os
import re
import shutil
import subprocess
import sys
from pathlib import Path
from cobot import process, ui
from cobot.ui import done
from cobot.process import StepProgress
_PROJECT_DIR = Path(__file__).parent.parent.parent
# The documentation source lives inside the project; it is mounted into the container
# so MkDocs picks up live edits without rebuilding the image.
# Исходники документации находятся внутри проекта; директория монтируется в контейнер,
# чтобы MkDocs подхватывал изменения вживую без пересборки образа.
_DOC_DIR = _PROJECT_DIR / "doc" / "lwc-doc"
_IMAGE_NAME = "lwc-docs"
_CONTAINER_NAME = "lwc-docs"
_DEFAULT_PORT = "8000"
def _docker(*args: str, capture: bool = False) -> subprocess.CompletedProcess:
"""Run a docker subcommand.
Запускает подкоманду docker.
"""
return subprocess.run(["docker", *args], capture_output=capture, text=True)
def _is_running() -> bool:
"""Return True if the lwc-docs container is currently running.
Возвращает True если контейнер lwc-docs запущен.
"""
r = _docker("ps", "--filter", f"name={_CONTAINER_NAME}", "--format", "{{.Names}}", capture=True)
return _CONTAINER_NAME in r.stdout
def _image_exists() -> bool:
"""Return True if the lwc-docs Docker image exists locally.
Возвращает True если Docker-образ lwc-docs существует локально.
"""
return bool(_docker("images", "-q", _IMAGE_NAME, capture=True).stdout.strip())
def _build_docs_image(p: StepProgress, lo: float, hi: float) -> bool:
"""Build the lwc-docs image, mapping "Step X/Y" to the lo..hi progress slice.
Собирает образ lwc-docs, отображая "Step X/Y" на участок lo..hi прогресса.
"""
p.raw("[cyan]▸[/cyan] Сборка образа документации (один раз)...")
env = {**os.environ, "DOCKER_BUILDKIT": "0"}
def on_line(s: str) -> None:
if s:
p.log(s)
m = re.match(r"Step (\d+)/(\d+) :", s)
if m:
step, total = int(m.group(1)), int(m.group(2))
p.set(lo + step / total * (hi - lo), f"шаг {step}/{total}")
rc = process.stream(["docker", "build", "-t", _IMAGE_NAME, str(_DOC_DIR)],
env=env, on_line=on_line)
if rc != 0:
p.raw("[red]Сборка образа не удалась.[/red]")
return False
p.raw("[green]✓[/green] Образ документации готов")
return True
def _start_container(p: StepProgress, port: str) -> bool:
"""Start the MkDocs container on the given port. Returns True on success.
Запускает контейнер MkDocs на заданном порту. Возвращает True при успехе.
"""
p.set(90, "Запуск сервера MkDocs...")
p.raw("[cyan]▸[/cyan] Запуск сервера MkDocs...")
result = _docker(
"run", "-d", "--name", _CONTAINER_NAME, "--rm",
"-p", f"{port}:8000",
"-v", f"{_DOC_DIR}:/docs",
_IMAGE_NAME, "serve", "--dev-addr=0.0.0.0:8000",
"--watch", "/docs", "--livereload",
capture=True,
)
if result.returncode != 0:
p.raw(f"[red]Не удалось запустить контейнер.[/red]\n{result.stderr}")
return False
return True
def _task_up(port: str) -> None:
"""Build the image if missing, then start the docs container.
Собирает образ если отсутствует, затем запускает контейнер документации.
"""
if _is_running():
ui.info(f"[green]Документация уже запущена:[/green] http://localhost:{port}")
ui.note("Остановить: cobot doc-setup down")
return
if not _DOC_DIR.exists():
ui.error(f"Директория документации не найдена: {_DOC_DIR}")
return
ok, fail_msg = True, ""
with StepProgress("Сервер документации") as p:
if not _image_exists():
p.set(0, "Сборка образа документации...")
if not _build_docs_image(p, 0, 85):
ok, fail_msg = False, "Сборка образа не удалась"
else:
p.log("Образ документации уже собран, пропускаем.")
if ok and not _start_container(p, port):
ok, fail_msg = False, "Не удалось запустить контейнер"
if ok:
p.set(100, "Сервер запущен")
if ok:
done(True, f"Документация доступна: http://localhost:{port}")
ui.note("Правьте файлы в doc/lwc-doc/docs/ — перезагрузка автоматическая.")
ui.note("Остановить: cobot doc-setup down")
else:
done(False, fail_msg)
def _task_build() -> None:
"""Run a one-shot mkdocs build with PDF generation enabled.
Запускает однократную сборку mkdocs с генерацией PDF.
"""
if not _DOC_DIR.exists():
ui.error(f"Директория документации не найдена: {_DOC_DIR}")
return
ok = True
with StepProgress("Сборка документации") as p:
if not _image_exists():
p.set(0, "Сборка образа документации...")
if not _build_docs_image(p, 0, 60):
ok = False
if ok:
p.set(65, "Запуск mkdocs build...")
p.raw("[cyan]▸[/cyan] Генерация сайта и PDF...")
rc = process.stream([
"docker", "run", "--rm",
"-v", f"{_DOC_DIR}:/docs",
"-e", "ENABLE_PDF_EXPORT=1",
_IMAGE_NAME, "build",
], on_line=lambda s: p.log(s) if s else None)
if rc != 0:
p.raw("[red]Сборка не удалась.[/red]")
ok = False
else:
p.set(100, "Готово")
if ok:
done(True, f"Сайт собран: {_DOC_DIR / 'site'} PDF: {_DOC_DIR / 'site' / 'pdf' / 'document.pdf'}")
else:
done(False, "Сборка завершилась с ошибкой")
def _task_down() -> None:
"""Stop the lwc-docs container if it is running.
Останавливает контейнер lwc-docs если он запущен.
"""
if not _is_running():
ui.info("[yellow]Контейнер документации не запущен.[/yellow]")
return
with StepProgress("Сервер документации") as p:
p.set(30, "Остановка контейнера...")
p.raw("[cyan]▸[/cyan] Остановка сервера документации...")
_docker("stop", _CONTAINER_NAME)
p.set(100, "Готово")
done(True, "Контейнер остановлен")
def _task_rebuild(port: str) -> None:
"""Stop the container, remove the old image, rebuild it, and start a fresh container.
Останавливает контейнер, удаляет старый образ, пересобирает и запускает новый контейнер.
"""
ok, fail_msg = True, ""
with StepProgress("Сервер документации — пересборка") as p:
if _is_running():
p.set(5, "Остановка контейнера...")
_docker("stop", _CONTAINER_NAME)
p.raw("[green]✓[/green] Остановлен.")
if _image_exists():
p.set(15, "Удаление старого образа...")
_docker("rmi", "-f", _IMAGE_NAME)
p.raw("[green]✓[/green] Образ удалён.")
p.set(20, "Сборка образа документации...")
if not _build_docs_image(p, 20, 88):
ok, fail_msg = False, "Сборка образа не удалась"
if ok and not _start_container(p, port):
ok, fail_msg = False, "Не удалось запустить контейнер"
if ok:
p.set(100, "Сервер запущен")
if ok:
done(True, f"Документация доступна: http://localhost:{port}")
ui.note("Остановить: cobot doc-setup down")
else:
done(False, fail_msg)
def _normalize_port(value: str) -> str:
"""Return a numeric port string, falling back to the default when invalid.
Возвращает числовой порт, откатываясь на значение по умолчанию при ошибке.
"""
value = (value or "").strip()
return value if value.isdigit() else _DEFAULT_PORT
def register(subparsers: argparse._SubParsersAction) -> None:
p = subparsers.add_parser("doc-setup", help="Deploy or stop the documentation server")
p.add_argument(
"action",
nargs="?",
choices=["up", "down", "rebuild", "build"],
default="up",
help="up — start (default), down — stop, rebuild — rebuild image and restart, build — generate static site + PDF",
)
p.set_defaults(func=run)
def run(args: argparse.Namespace) -> None:
if not shutil.which("docker"):
ui.error("Docker не установлен или отсутствует в PATH.")
sys.exit(1)
action = getattr(args, "action", "up")
if action == "down":
_task_down()
return
if action == "build":
_task_build()
return
port_v = ui.text("Порт для сервера документации:", _DEFAULT_PORT)
if port_v is None:
return
port = _normalize_port(port_v)
if action == "rebuild":
_task_rebuild(port)
else:
_task_up(port)