mirror of
https://github.com/orbbec/OrbbecSDK_ROS2.git
synced 2026-10-04 12:07:46 +08:00
docs: implement versioning system and enhance documentation structure
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
# OrbbecSDK V2 ROS2 Documentation
|
||||
|
||||
This repository contains the documentation for OrbbecSDK V2 ROS2 Wrapper in both English and Chinese.
|
||||
|
||||
## Documentation Structure
|
||||
|
||||
- **English Version**: `docs/en/` - Full English documentation
|
||||
- **中文版本**: `docs/zh/` - 完整中文文档
|
||||
- **Published Versions**: `docs/version-manifest.json` - Version list for GitHub Pages
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Build English Documentation:
|
||||
```bash
|
||||
cd docs/en
|
||||
pip install -r requirements.txt
|
||||
make clean && make html
|
||||
```
|
||||
|
||||
### Build Chinese Documentation:
|
||||
```bash
|
||||
cd docs/zh
|
||||
pip install -r requirements.txt
|
||||
make clean && make html
|
||||
```
|
||||
|
||||
### For Full Versioned Site Build:
|
||||
```bash
|
||||
pip install -r docs/en/requirements.txt -r docs/zh/requirements.txt jieba
|
||||
python docs/build_versions.py
|
||||
```
|
||||
|
||||
## View Documentation
|
||||
|
||||
After building, open the following files in your browser:
|
||||
- English: `docs/en/_build/html/index.html`
|
||||
- Chinese: `docs/zh/_build/html/index.html`
|
||||
- Full GitHub Pages site: `site/index.html`
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
docs/
|
||||
├── en/ # English documentation
|
||||
│ ├── source/ # Source files
|
||||
│ ├── conf.py # Sphinx config
|
||||
│ └── requirements.txt # Python dependencies
|
||||
├── zh/ # Chinese documentation
|
||||
│ ├── source/ # Source files
|
||||
│ ├── conf.py # Sphinx config
|
||||
│ └── requirements.txt # Python dependencies
|
||||
├── _ext/ # Shared Sphinx extensions
|
||||
├── build_versions.py # Versioned site builder
|
||||
└── version-manifest.json # Published version definitions
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
1. Make changes to the English version first (`docs/en/`)
|
||||
2. Translate and adapt for the Chinese version (`docs/zh/`)
|
||||
3. Ensure both versions build successfully
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Sphinx
|
||||
- recommonmark
|
||||
- sphinx-rtd-theme
|
||||
- jieba (for Chinese search)
|
||||
- Required extensions (see requirements.txt in each language folder)
|
||||
@@ -15,22 +15,35 @@ def inject_language_switch(app, doctree, docname):
|
||||
other_label = '中文' if is_en else 'English'
|
||||
current_label = 'English' if is_en else '中文'
|
||||
|
||||
# JS now detects /en/ or /zh/ segment and swaps only the first occurrence
|
||||
html = (
|
||||
'<div class="lang-switch" style="margin:0 0 1em 0; font-size:0.9em;">'
|
||||
'<div class="lang-switch docs-floating-switches__item">'
|
||||
f'<a class="lang-other" href="#">{other_label}</a> | '
|
||||
f'<a class="lang-current" href="#">{current_label}</a>'
|
||||
'</div>'
|
||||
'<script>(function(){'
|
||||
"function ensureDock(){"
|
||||
" var dock=document.querySelector('[data-docs-switch-dock]');"
|
||||
" if(dock) return dock;"
|
||||
" dock=document.createElement('div');"
|
||||
" dock.className='docs-floating-switches';"
|
||||
" dock.setAttribute('data-docs-switch-dock','');"
|
||||
" document.body.appendChild(dock);"
|
||||
" return dock;"
|
||||
"}"
|
||||
"function mountSwitch(){"
|
||||
"var p=window.location.pathname;"
|
||||
"var isEn=p.indexOf('/en/')!==-1 && p.indexOf('/zh/')===-1;"
|
||||
"if(p.indexOf('/zh/')!==-1) isEn=false;"
|
||||
"var other = isEn ? p.replace(/\\/en\\//, '/zh/') : p.replace(/\\/zh\\//, '/en/');"
|
||||
"var current=p;"
|
||||
"var sw=document.querySelector('.lang-switch'); if(!sw) return;"
|
||||
"ensureDock().appendChild(sw);"
|
||||
"var o=sw.querySelector('.lang-other'); var c=sw.querySelector('.lang-current');"
|
||||
"if(isEn){ o.textContent='中文'; c.textContent='English'; } else { o.textContent='English'; c.textContent='中文'; }"
|
||||
"o.setAttribute('href', other); c.setAttribute('href', current);"
|
||||
"}"
|
||||
"if(document.body){ mountSwitch(); }"
|
||||
"else { document.addEventListener('DOMContentLoaded', mountSwitch, {once:true}); }"
|
||||
'})();</script>'
|
||||
)
|
||||
doctree.insert(0, nodes.raw('', html, format='html'))
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
.docs-floating-switches {
|
||||
position: fixed;
|
||||
left: 24px;
|
||||
bottom: 24px;
|
||||
z-index: 1100;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 0.75rem;
|
||||
max-width: calc(100vw - 48px);
|
||||
}
|
||||
|
||||
.docs-floating-switches__item {
|
||||
max-width: min(220px, calc(100vw - 48px));
|
||||
}
|
||||
|
||||
.docs-version-switch {
|
||||
display: grid;
|
||||
gap: 0.45rem;
|
||||
min-width: 192px;
|
||||
padding: 0.9rem 1rem;
|
||||
border: 1px solid rgba(15, 23, 42, 0.14);
|
||||
border-radius: 14px;
|
||||
background: rgba(255, 255, 255, 0.96);
|
||||
box-shadow: 0 18px 38px rgba(15, 23, 42, 0.16);
|
||||
backdrop-filter: blur(14px);
|
||||
}
|
||||
|
||||
.docs-version-switch__header {
|
||||
font-size: 0.74rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: #5b6472;
|
||||
}
|
||||
|
||||
.docs-version-switch__control {
|
||||
display: grid;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.docs-version-switch__label {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.docs-version-switch__select {
|
||||
width: 100%;
|
||||
min-height: 2.4rem;
|
||||
padding: 0 0.85rem;
|
||||
border: 1px solid #c7d2e0;
|
||||
border-radius: 10px;
|
||||
background: #f8fbff;
|
||||
color: #0f172a;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.docs-version-switch__select:focus {
|
||||
outline: 2px solid rgba(22, 120, 184, 0.22);
|
||||
border-color: #1678b8;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.docs-floating-switches {
|
||||
left: 12px;
|
||||
bottom: 12px;
|
||||
gap: 0.55rem;
|
||||
max-width: calc(100vw - 24px);
|
||||
}
|
||||
|
||||
.docs-floating-switches__item {
|
||||
max-width: calc(100vw - 24px);
|
||||
}
|
||||
|
||||
.docs-version-switch {
|
||||
min-width: 156px;
|
||||
padding: 0.75rem 0.85rem;
|
||||
}
|
||||
|
||||
.docs-version-switch__header {
|
||||
font-size: 0.68rem;
|
||||
}
|
||||
|
||||
.docs-version-switch__select {
|
||||
min-height: 2.2rem;
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
(function () {
|
||||
function ensureDock() {
|
||||
var dock = document.querySelector("[data-docs-switch-dock]");
|
||||
if (dock) {
|
||||
return dock;
|
||||
}
|
||||
|
||||
dock = document.createElement("div");
|
||||
dock.className = "docs-floating-switches";
|
||||
dock.setAttribute("data-docs-switch-dock", "");
|
||||
document.body.appendChild(dock);
|
||||
return dock;
|
||||
}
|
||||
|
||||
function parseConfig() {
|
||||
var element = document.getElementById("docs-version-data");
|
||||
if (!element) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(element.textContent);
|
||||
} catch (error) {
|
||||
console.error("Failed to parse docs version configuration.", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function parseLocation(config) {
|
||||
var segments = window.location.pathname.split("/").filter(Boolean);
|
||||
var langIndex = segments.findIndex(function (segment) {
|
||||
return segment === "en" || segment === "zh";
|
||||
});
|
||||
|
||||
if (langIndex === -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var language = segments[langIndex];
|
||||
var isVersioned = langIndex >= 2 && segments[langIndex - 2] === "versions";
|
||||
var versionSlug = isVersioned ? segments[langIndex - 1] : config.latest_slug;
|
||||
// Keep the project-pages prefix (for example /<repo>/...) when the
|
||||
// current page is served from the latest alias path /<repo>/<lang>/...
|
||||
var baseSegments = isVersioned ? segments.slice(0, langIndex - 2) : segments.slice(0, langIndex);
|
||||
var relativeSegments = segments.slice(langIndex + 1);
|
||||
|
||||
if (!relativeSegments.length) {
|
||||
relativeSegments = ["index.html"];
|
||||
}
|
||||
|
||||
return {
|
||||
baseSegments: baseSegments,
|
||||
language: language,
|
||||
relativeSegments: relativeSegments,
|
||||
versionSlug: versionSlug,
|
||||
};
|
||||
}
|
||||
|
||||
function buildPath(baseSegments, versionSlug, latestSlug, language, relativeSegments) {
|
||||
var segments = baseSegments.slice();
|
||||
if (versionSlug !== latestSlug) {
|
||||
segments.push("versions", versionSlug);
|
||||
}
|
||||
segments.push(language);
|
||||
segments = segments.concat(relativeSegments);
|
||||
return "/" + segments.join("/");
|
||||
}
|
||||
|
||||
function buildRootPath(baseSegments, versionSlug, latestSlug, language) {
|
||||
var segments = baseSegments.slice();
|
||||
if (versionSlug !== latestSlug) {
|
||||
segments.push("versions", versionSlug);
|
||||
}
|
||||
segments.push(language, "index.html");
|
||||
return "/" + segments.join("/");
|
||||
}
|
||||
|
||||
function renderOptions(select, config, currentSlug) {
|
||||
select.innerHTML = "";
|
||||
|
||||
config.versions.forEach(function (version) {
|
||||
var option = document.createElement("option");
|
||||
option.value = version.slug;
|
||||
option.textContent = version.label;
|
||||
option.selected = version.slug === currentSlug;
|
||||
select.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
function canUseHeadRequest() {
|
||||
return window.location.protocol === "http:" || window.location.protocol === "https:";
|
||||
}
|
||||
|
||||
function pageExists(url) {
|
||||
if (!canUseHeadRequest()) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
return fetch(url, {
|
||||
method: "HEAD",
|
||||
cache: "no-store",
|
||||
})
|
||||
.then(function (response) {
|
||||
return response.ok;
|
||||
})
|
||||
.catch(function () {
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
var config = parseConfig();
|
||||
var widget = document.querySelector("[data-docs-version-switch]");
|
||||
var select = document.getElementById("docs-version-select");
|
||||
|
||||
if (!config || !widget || !select || !config.versions || !config.versions.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
ensureDock().appendChild(widget);
|
||||
|
||||
var locationInfo = parseLocation(config);
|
||||
if (!locationInfo) {
|
||||
widget.hidden = true;
|
||||
return;
|
||||
}
|
||||
|
||||
renderOptions(select, config, locationInfo.versionSlug);
|
||||
|
||||
select.addEventListener("change", function (event) {
|
||||
var nextSlug = event.target.value;
|
||||
if (!nextSlug || nextSlug === locationInfo.versionSlug) {
|
||||
return;
|
||||
}
|
||||
|
||||
var targetUrl = buildPath(
|
||||
locationInfo.baseSegments,
|
||||
nextSlug,
|
||||
config.latest_slug,
|
||||
locationInfo.language,
|
||||
locationInfo.relativeSegments,
|
||||
);
|
||||
var fallbackUrl = buildRootPath(
|
||||
locationInfo.baseSegments,
|
||||
nextSlug,
|
||||
config.latest_slug,
|
||||
locationInfo.language,
|
||||
);
|
||||
|
||||
pageExists(targetUrl).then(function (exists) {
|
||||
window.location.href = exists ? targetUrl : fallbackUrl;
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,49 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from docutils import nodes
|
||||
|
||||
|
||||
STATIC_DIR = Path(__file__).resolve().parent / "static"
|
||||
|
||||
|
||||
def setup(app):
|
||||
app.connect("config-inited", configure_assets)
|
||||
app.connect("doctree-resolved", inject_version_switch)
|
||||
app.add_css_file("version_switch.css")
|
||||
app.add_js_file("version_switch.js")
|
||||
return {"version": "0.1", "parallel_read_safe": True}
|
||||
|
||||
|
||||
def configure_assets(app, config):
|
||||
static_dir = str(STATIC_DIR)
|
||||
if static_dir not in config.html_static_path:
|
||||
config.html_static_path.append(static_dir)
|
||||
|
||||
|
||||
def inject_version_switch(app, doctree, docname):
|
||||
if app.builder.format != "html":
|
||||
return
|
||||
|
||||
versioning = getattr(app.config, "html_context", {}).get("docs_versioning")
|
||||
if not versioning or not versioning.get("versions"):
|
||||
return
|
||||
|
||||
payload = json.dumps(versioning, ensure_ascii=False).replace("</", "<\\/")
|
||||
current_label = versioning.get("current", {}).get("label", "")
|
||||
|
||||
html = f"""
|
||||
<div class="docs-version-switch" data-docs-version-switch>
|
||||
<div class="docs-version-switch__header">Version</div>
|
||||
<div class="docs-version-switch__control">
|
||||
<label class="docs-version-switch__label" for="docs-version-select">Version</label>
|
||||
<select id="docs-version-select" class="docs-version-switch__select" aria-label="Select documentation version">
|
||||
<option value="">{current_label}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<script id="docs-version-data" type="application/json">{payload}</script>
|
||||
"""
|
||||
doctree.insert(0, nodes.raw("", html, format="html"))
|
||||
@@ -0,0 +1,102 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
EXTENSION_DIR = Path(__file__).resolve().parent
|
||||
DOCS_DIR = EXTENSION_DIR.parent
|
||||
DEFAULT_MANIFEST_PATH = DOCS_DIR / "version-manifest.json"
|
||||
|
||||
|
||||
def load_manifest() -> dict:
|
||||
manifest_path = Path(os.getenv("DOCS_VERSION_MANIFEST_JSON", DEFAULT_MANIFEST_PATH)).resolve()
|
||||
return json.loads(manifest_path.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def get_content_root(conf_file: str) -> Path:
|
||||
value = os.getenv("DOCS_CONTENT_ROOT")
|
||||
if value:
|
||||
return Path(value).resolve()
|
||||
return Path(conf_file).resolve().parent
|
||||
|
||||
|
||||
def get_conf_root(conf_file: str) -> Path:
|
||||
return Path(conf_file).resolve().parent
|
||||
|
||||
|
||||
def _unique_paths(paths: list[Path]) -> list[str]:
|
||||
unique: list[str] = []
|
||||
seen: set[str] = set()
|
||||
for path in paths:
|
||||
resolved = str(path.resolve())
|
||||
if resolved in seen or not path.exists():
|
||||
continue
|
||||
seen.add(resolved)
|
||||
unique.append(str(path))
|
||||
return unique
|
||||
|
||||
|
||||
def get_templates_paths(conf_root: Path) -> list[str]:
|
||||
candidates = [
|
||||
conf_root / "_templates",
|
||||
conf_root / "source" / "_templates",
|
||||
]
|
||||
return _unique_paths(candidates)
|
||||
|
||||
|
||||
def get_static_paths(conf_root: Path, content_root: Path) -> list[str]:
|
||||
candidates = [
|
||||
content_root / "source" / "_static",
|
||||
conf_root / "source" / "_static",
|
||||
content_root / "source" / "image",
|
||||
]
|
||||
return _unique_paths(candidates)
|
||||
|
||||
|
||||
def get_extra_paths(content_root: Path) -> list[str]:
|
||||
candidate = content_root / "source" / "image"
|
||||
return _unique_paths([candidate])
|
||||
|
||||
|
||||
def get_asset_path(content_root: Path, relative_path: str, fallback: str) -> str:
|
||||
candidate = content_root / relative_path
|
||||
if candidate.exists():
|
||||
return str(candidate)
|
||||
return fallback
|
||||
|
||||
|
||||
def build_version_context(language: str) -> dict:
|
||||
manifest = load_manifest()
|
||||
published_versions = [
|
||||
{
|
||||
"slug": version["slug"],
|
||||
"label": version.get("label", version["slug"]),
|
||||
"is_latest": bool(version.get("is_latest", False)),
|
||||
}
|
||||
for version in manifest.get("versions", [])
|
||||
if version.get("published", False)
|
||||
]
|
||||
|
||||
current_slug = os.getenv("DOCS_VERSION_SLUG", manifest.get("latest_slug", "latest"))
|
||||
matched = next((version for version in published_versions if version["slug"] == current_slug), None)
|
||||
current_label = os.getenv("DOCS_VERSION_LABEL") or (matched["label"] if matched else current_slug)
|
||||
|
||||
current_is_latest_env = os.getenv("DOCS_VERSION_IS_LATEST")
|
||||
if current_is_latest_env is None:
|
||||
current_is_latest = bool(matched["is_latest"]) if matched else current_slug == manifest.get("latest_slug")
|
||||
else:
|
||||
current_is_latest = current_is_latest_env.lower() in {"1", "true", "yes", "on"}
|
||||
|
||||
return {
|
||||
"default_language": manifest.get("default_language", "en"),
|
||||
"latest_slug": manifest.get("latest_slug", current_slug),
|
||||
"current": {
|
||||
"slug": current_slug,
|
||||
"label": current_label,
|
||||
"is_latest": current_is_latest,
|
||||
"language": language,
|
||||
},
|
||||
"versions": published_versions,
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
DOCS_DIR = REPO_ROOT / "docs"
|
||||
MANIFEST_PATH = DOCS_DIR / "version-manifest.json"
|
||||
SITE_DIR = REPO_ROOT / "site"
|
||||
WORKTREE_ROOT = REPO_ROOT / ".docs-worktrees"
|
||||
LANGUAGES = ("en", "zh")
|
||||
|
||||
|
||||
def run(command: list[str], *, cwd: Path | None = None, env: dict[str, str] | None = None) -> subprocess.CompletedProcess[str]:
|
||||
print("+", " ".join(command))
|
||||
return subprocess.run(
|
||||
command,
|
||||
cwd=str(cwd or REPO_ROOT),
|
||||
env=env,
|
||||
check=True,
|
||||
text=True,
|
||||
)
|
||||
|
||||
|
||||
def capture(command: list[str], *, cwd: Path | None = None) -> str:
|
||||
result = subprocess.run(
|
||||
command,
|
||||
cwd=str(cwd or REPO_ROOT),
|
||||
check=True,
|
||||
text=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
return result.stdout.strip()
|
||||
|
||||
|
||||
def load_manifest() -> dict:
|
||||
return json.loads(MANIFEST_PATH.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def ensure_ref_available(ref: str) -> None:
|
||||
if ref == "HEAD":
|
||||
return
|
||||
|
||||
if ref_resolves(ref) or resolve_remote_ref(ref) is not None:
|
||||
return
|
||||
|
||||
fetch_targets = [ref]
|
||||
if "/" not in ref and not ref.startswith("refs/"):
|
||||
fetch_targets.append(f"refs/heads/{ref}")
|
||||
|
||||
for remote in list_remotes():
|
||||
for target in fetch_targets:
|
||||
try:
|
||||
run(["git", "fetch", "--no-tags", remote, target])
|
||||
if ref_resolves(ref) or resolve_remote_ref(ref) is not None:
|
||||
return
|
||||
except subprocess.CalledProcessError:
|
||||
continue
|
||||
|
||||
raise RuntimeError(f"Unable to resolve git ref '{ref}'.")
|
||||
|
||||
|
||||
def list_remotes() -> list[str]:
|
||||
remotes = capture(["git", "remote"]).splitlines()
|
||||
ordered: list[str] = []
|
||||
for candidate in ("origin", "github"):
|
||||
if candidate in remotes:
|
||||
ordered.append(candidate)
|
||||
for remote in remotes:
|
||||
if remote not in ordered:
|
||||
ordered.append(remote)
|
||||
return ordered
|
||||
|
||||
|
||||
def ref_resolves(ref: str) -> bool:
|
||||
result = subprocess.run(
|
||||
["git", "rev-parse", "--verify", f"{ref}^{{commit}}"],
|
||||
cwd=str(REPO_ROOT),
|
||||
text=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
return result.returncode == 0
|
||||
|
||||
|
||||
def resolve_ref(ref: str) -> str:
|
||||
if ref_resolves(ref):
|
||||
return ref
|
||||
|
||||
remote_ref = resolve_remote_ref(ref)
|
||||
if remote_ref is not None:
|
||||
return remote_ref
|
||||
|
||||
raise RuntimeError(f"Unable to resolve git ref '{ref}'.")
|
||||
|
||||
|
||||
def resolve_remote_ref(ref: str) -> str | None:
|
||||
for remote in list_remotes():
|
||||
remote_ref = f"refs/remotes/{remote}/{ref}"
|
||||
if ref_resolves(remote_ref):
|
||||
return remote_ref
|
||||
return None
|
||||
|
||||
|
||||
def should_use_workspace(version: dict) -> bool:
|
||||
flag = os.getenv("DOCS_BUILD_LATEST_FROM_WORKSPACE", "0").lower()
|
||||
return version.get("is_latest", False) and flag in {"1", "true", "yes", "on"}
|
||||
|
||||
|
||||
def prepare_site_dir() -> None:
|
||||
if SITE_DIR.exists():
|
||||
shutil.rmtree(SITE_DIR)
|
||||
SITE_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
def create_root_redirect(default_language: str) -> None:
|
||||
target = f"{default_language}/index.html"
|
||||
(SITE_DIR / "index.html").write_text(
|
||||
(
|
||||
"<!DOCTYPE html><html><head>"
|
||||
f'<meta http-equiv="refresh" content="0; url={target}">'
|
||||
f'<link rel="canonical" href="{target}">'
|
||||
"</head><body>Redirecting...</body></html>"
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
(SITE_DIR / ".nojekyll").touch()
|
||||
|
||||
|
||||
def copy_tree(source_dir: Path, target_dir: Path) -> None:
|
||||
if target_dir.exists():
|
||||
shutil.rmtree(target_dir)
|
||||
shutil.copytree(source_dir, target_dir)
|
||||
|
||||
|
||||
def resolve_source_dir(content_root: Path, language: str) -> Path:
|
||||
candidates = [
|
||||
content_root / "docs" / language,
|
||||
content_root / "docs",
|
||||
]
|
||||
for candidate in candidates:
|
||||
if candidate.exists() and ((candidate / "index.rst").exists() or (candidate / "index.md").exists()):
|
||||
return candidate
|
||||
|
||||
fallback = REPO_ROOT / "docs" / language
|
||||
if content_root != REPO_ROOT and fallback.exists():
|
||||
print(
|
||||
"WARNING: "
|
||||
f"Falling back to current workspace docs for language '{language}' "
|
||||
f"because {content_root} does not contain a buildable multilingual docs layout."
|
||||
)
|
||||
return fallback
|
||||
|
||||
raise RuntimeError(f"Missing documentation source directory for language '{language}' in {content_root}")
|
||||
|
||||
|
||||
def build_language(version: dict, content_root: Path, language: str, output_dir: Path) -> None:
|
||||
source_dir = resolve_source_dir(content_root, language)
|
||||
conf_dir = DOCS_DIR / language
|
||||
|
||||
env = os.environ.copy()
|
||||
env.update(
|
||||
{
|
||||
"DOCS_VERSION_SLUG": version["slug"],
|
||||
"DOCS_VERSION_LABEL": version.get("label", version["slug"]),
|
||||
"DOCS_VERSION_IS_LATEST": "1" if version.get("is_latest") else "0",
|
||||
"DOCS_VERSION_MANIFEST_JSON": str(MANIFEST_PATH),
|
||||
"DOCS_CONTENT_ROOT": str(source_dir),
|
||||
}
|
||||
)
|
||||
|
||||
run(
|
||||
[
|
||||
"sphinx-build",
|
||||
"-b",
|
||||
"html",
|
||||
"-c",
|
||||
str(conf_dir),
|
||||
str(source_dir),
|
||||
str(output_dir),
|
||||
],
|
||||
env=env,
|
||||
)
|
||||
|
||||
|
||||
def build_version(version: dict) -> None:
|
||||
version_root = get_content_root(version)
|
||||
try:
|
||||
for language in LANGUAGES:
|
||||
temp_output_dir = WORKTREE_ROOT / ".build" / version["slug"] / language
|
||||
if temp_output_dir.exists():
|
||||
shutil.rmtree(temp_output_dir)
|
||||
temp_output_dir.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
build_language(version, version_root, language, temp_output_dir)
|
||||
|
||||
if version.get("is_latest"):
|
||||
target_dir = SITE_DIR / language
|
||||
else:
|
||||
target_dir = SITE_DIR / "versions" / version["slug"] / language
|
||||
target_dir.parent.mkdir(parents=True, exist_ok=True)
|
||||
copy_tree(temp_output_dir, target_dir)
|
||||
finally:
|
||||
cleanup_content_root(version_root)
|
||||
|
||||
|
||||
def get_content_root(version: dict) -> Path:
|
||||
if should_use_workspace(version):
|
||||
return REPO_ROOT
|
||||
|
||||
ref = version.get("ref", "HEAD")
|
||||
ensure_ref_available(ref)
|
||||
resolved_ref = resolve_ref(ref)
|
||||
|
||||
worktree_dir = WORKTREE_ROOT / version["slug"]
|
||||
if worktree_dir.exists():
|
||||
run(["git", "worktree", "remove", "--force", str(worktree_dir)])
|
||||
|
||||
worktree_dir.parent.mkdir(parents=True, exist_ok=True)
|
||||
run(["git", "worktree", "add", "--detach", str(worktree_dir), resolved_ref])
|
||||
return worktree_dir
|
||||
|
||||
|
||||
def cleanup_content_root(content_root: Path) -> None:
|
||||
if content_root == REPO_ROOT:
|
||||
return
|
||||
|
||||
if content_root.exists():
|
||||
run(["git", "worktree", "remove", "--force", str(content_root)])
|
||||
|
||||
|
||||
def validate_manifest(manifest: dict) -> list[dict]:
|
||||
published = [version for version in manifest.get("versions", []) if version.get("published")]
|
||||
if not published:
|
||||
raise RuntimeError("No published documentation versions found in manifest.")
|
||||
if sum(1 for version in published if version.get("is_latest")) != 1:
|
||||
raise RuntimeError("Manifest must contain exactly one published latest version.")
|
||||
return published
|
||||
|
||||
|
||||
def main() -> int:
|
||||
manifest = load_manifest()
|
||||
published_versions = validate_manifest(manifest)
|
||||
|
||||
prepare_site_dir()
|
||||
WORKTREE_ROOT.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
for version in published_versions:
|
||||
build_version(version)
|
||||
|
||||
create_root_redirect(manifest.get("default_language", "en"))
|
||||
print(f"Built documentation site in {SITE_DIR}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
raise SystemExit(main())
|
||||
except Exception as error: # noqa: BLE001
|
||||
print(f"ERROR: {error}", file=sys.stderr)
|
||||
raise
|
||||
+20
-4
@@ -24,6 +24,15 @@ sys.path.append(os.path.join(os.path.dirname(__file__), '..', '_ext'))
|
||||
|
||||
import recommonmark
|
||||
from recommonmark.transform import AutoStructify
|
||||
from versioning import (
|
||||
build_version_context,
|
||||
get_asset_path,
|
||||
get_conf_root,
|
||||
get_content_root,
|
||||
get_extra_paths,
|
||||
get_static_paths,
|
||||
get_templates_paths,
|
||||
)
|
||||
|
||||
# import sphinx_book_theme
|
||||
# import sphinx_rtd_theme
|
||||
@@ -35,6 +44,9 @@ from recommonmark.transform import AutoStructify
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
# html_theme = "furo"
|
||||
|
||||
content_root = get_content_root(__file__)
|
||||
conf_root = get_conf_root(__file__)
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = 'OrbbecSDK V2 ROS2 Wrapper'
|
||||
@@ -57,6 +69,7 @@ extensions = ['recommonmark',
|
||||
'sphinx_copybutton',
|
||||
'sphinx.ext.autosectionlabel',
|
||||
'language_switch',
|
||||
'version_switch',
|
||||
'section_search',
|
||||
# 'myst_parser',
|
||||
]
|
||||
@@ -67,7 +80,7 @@ source_suffix = ['.rst', 'rest', '.md', '.MD']
|
||||
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
templates_path = get_templates_paths(conf_root)
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
@@ -89,16 +102,16 @@ exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||
# html_favicon = "source/_static/orbbec_logo2.png"
|
||||
|
||||
#html_logo = "source/_static/orbbec-light_no_colour.svg"
|
||||
html_favicon = "source/_static/orbbec_footerlogo_3d_world.svg"
|
||||
html_favicon = get_asset_path(conf_root, "source/_static/orbbec_footerlogo_3d_world.svg", "source/_static/orbbec_footerlogo_3d_world.svg")
|
||||
# html_logo = "source/_static/orbbec_footerlogo_3d_world.svg"
|
||||
# html_favicon = "source/_static/orbbec_footerlogo_3d_world.svg"
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['source/_static','source/image']
|
||||
html_static_path = get_static_paths(conf_root, content_root)
|
||||
|
||||
html_extra_path = ['source/image']
|
||||
html_extra_path = get_extra_paths(content_root)
|
||||
|
||||
html_theme_options = {
|
||||
# 'analytics_id': 'G-EVD5Z6G6NH',
|
||||
@@ -137,6 +150,9 @@ html_theme_options = {
|
||||
|
||||
# #不需要添加_static目录,加了会不起作用
|
||||
html_css_files = ["custom.css"]
|
||||
html_context = {
|
||||
"docs_versioning": build_version_context(language),
|
||||
}
|
||||
|
||||
# 与文档无关的其他资源的路径,由于无需构建此文件夹的文件,会自动忽略
|
||||
# 默认情况下,此路径下的文件会输出到生成的html的根目录
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
|
||||
.lang-switch {
|
||||
position: fixed !important;
|
||||
top: 60px !important;
|
||||
right: 20px !important;
|
||||
z-index: 1000 !important;
|
||||
position: static !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
gap: 0.45rem !important;
|
||||
margin: 0 !important;
|
||||
background: rgba(255,255,255,0.95) !important;
|
||||
border: 1px solid #ddd !important;
|
||||
border-radius: 6px !important;
|
||||
border-radius: 10px !important;
|
||||
padding: 8px 12px !important;
|
||||
font-size: 0.9em !important;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.15) !important;
|
||||
box-shadow: 0 8px 22px rgba(15, 23, 42, 0.14) !important;
|
||||
backdrop-filter: blur(5px) !important;
|
||||
transition: all 0.3s ease !important;
|
||||
}
|
||||
@@ -26,7 +27,6 @@
|
||||
|
||||
.lang-switch .lang-other {
|
||||
color: #0078d4 !important;
|
||||
margin-right: 8px !important;
|
||||
}
|
||||
|
||||
.lang-switch .lang-other:hover {
|
||||
@@ -36,7 +36,6 @@
|
||||
.lang-switch .lang-current {
|
||||
color: #333 !important;
|
||||
font-weight: bold !important;
|
||||
margin-left: 8px !important;
|
||||
}
|
||||
|
||||
.wy-nav-side .wy-side-nav-search {
|
||||
@@ -121,10 +120,8 @@
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.lang-switch {
|
||||
top: 10px !important;
|
||||
right: 10px !important;
|
||||
font-size: 0.8em !important;
|
||||
padding: 6px 8px !important;
|
||||
font-size: 0.8em !important;
|
||||
padding: 6px 8px !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"default_language": "en",
|
||||
"latest_slug": "Latest",
|
||||
"versions": [
|
||||
{
|
||||
"slug": "Latest",
|
||||
"label": "Latest",
|
||||
"ref": "Document",
|
||||
"published": true,
|
||||
"is_latest": true
|
||||
},
|
||||
{
|
||||
"slug": "v2.8.4",
|
||||
"label": "v2.8.4",
|
||||
"ref": "docs/v2.8.4",
|
||||
"published": true,
|
||||
"is_latest": false
|
||||
},
|
||||
{
|
||||
"slug": "v2.8.1",
|
||||
"label": "v2.8.1",
|
||||
"ref": "docs/v2.8.1",
|
||||
"published": true,
|
||||
"is_latest": false
|
||||
}
|
||||
]
|
||||
}
|
||||
+20
-4
@@ -24,6 +24,15 @@ sys.path.append(os.path.join(os.path.dirname(__file__), '..', '_ext'))
|
||||
|
||||
import recommonmark
|
||||
from recommonmark.transform import AutoStructify
|
||||
from versioning import (
|
||||
build_version_context,
|
||||
get_asset_path,
|
||||
get_conf_root,
|
||||
get_content_root,
|
||||
get_extra_paths,
|
||||
get_static_paths,
|
||||
get_templates_paths,
|
||||
)
|
||||
|
||||
# import sphinx_book_theme
|
||||
# import sphinx_rtd_theme
|
||||
@@ -35,6 +44,9 @@ from recommonmark.transform import AutoStructify
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
# html_theme = "furo"
|
||||
|
||||
content_root = get_content_root(__file__)
|
||||
conf_root = get_conf_root(__file__)
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = 'OrbbecSDK V2 ROS2 封装'
|
||||
@@ -57,6 +69,7 @@ extensions = ['recommonmark',
|
||||
'sphinx_copybutton',
|
||||
'sphinx.ext.autosectionlabel',
|
||||
'language_switch',
|
||||
'version_switch',
|
||||
'section_search',
|
||||
# 'myst_parser',
|
||||
]
|
||||
@@ -67,7 +80,7 @@ source_suffix = ['.rst', 'rest', '.md', '.MD']
|
||||
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
templates_path = get_templates_paths(conf_root)
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
@@ -88,16 +101,16 @@ exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||
# html_favicon = "source/_static/orbbec_logo2.png"
|
||||
|
||||
#html_logo = "source/_static/orbbec-light_no_colour.svg"
|
||||
html_favicon = "source/_static/orbbec_footerlogo_3d_world.svg"
|
||||
html_favicon = get_asset_path(conf_root, "source/_static/orbbec_footerlogo_3d_world.svg", "source/_static/orbbec_footerlogo_3d_world.svg")
|
||||
# html_logo = "source/_static/orbbec_footerlogo_3d_world.svg"
|
||||
# html_favicon = "source/_static/orbbec_footerlogo_3d_world.svg"
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['source/_static','source/image']
|
||||
html_static_path = get_static_paths(conf_root, content_root)
|
||||
|
||||
html_extra_path = ['source/image']
|
||||
html_extra_path = get_extra_paths(content_root)
|
||||
|
||||
html_theme_options = {
|
||||
# 'analytics_id': 'G-EVD5Z6G6NH',
|
||||
@@ -135,6 +148,9 @@ html_theme_options = {
|
||||
|
||||
# #不需要添加_static目录,加了会不起作用
|
||||
html_css_files = ["custom.css"]
|
||||
html_context = {
|
||||
"docs_versioning": build_version_context(language),
|
||||
}
|
||||
|
||||
# 与文档无关的其他资源的路径,由于无需构建此文件夹的文件,会自动忽略
|
||||
# 默认情况下,此路径下的文件会输出到生成的html的根目录
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
|
||||
.lang-switch {
|
||||
position: fixed !important;
|
||||
top: 60px !important;
|
||||
right: 20px !important;
|
||||
z-index: 1000 !important;
|
||||
position: static !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
gap: 0.45rem !important;
|
||||
margin: 0 !important;
|
||||
background: rgba(255,255,255,0.95) !important;
|
||||
border: 1px solid #ddd !important;
|
||||
border-radius: 6px !important;
|
||||
border-radius: 10px !important;
|
||||
padding: 8px 12px !important;
|
||||
font-size: 0.9em !important;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.15) !important;
|
||||
box-shadow: 0 8px 22px rgba(15, 23, 42, 0.14) !important;
|
||||
backdrop-filter: blur(5px) !important;
|
||||
transition: all 0.3s ease !important;
|
||||
}
|
||||
@@ -26,7 +27,6 @@
|
||||
|
||||
.lang-switch .lang-other {
|
||||
color: #0078d4 !important;
|
||||
margin-right: 8px !important;
|
||||
}
|
||||
|
||||
.lang-switch .lang-other:hover {
|
||||
@@ -36,7 +36,6 @@
|
||||
.lang-switch .lang-current {
|
||||
color: #333 !important;
|
||||
font-weight: bold !important;
|
||||
margin-left: 8px !important;
|
||||
}
|
||||
|
||||
.wy-nav-side .wy-side-nav-search {
|
||||
@@ -121,10 +120,8 @@
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.lang-switch {
|
||||
top: 10px !important;
|
||||
right: 10px !important;
|
||||
font-size: 0.8em !important;
|
||||
padding: 6px 8px !important;
|
||||
font-size: 0.8em !important;
|
||||
padding: 6px 8px !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user