- 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.
249 lines
9.9 KiB
Python
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)
|