2 Commits

Author SHA1 Message Date
Arthur Sonzogni
fd37efb131 Update .github/workflows/documentation.yaml
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-21 17:43:21 +02:00
ArthurSonzogni
3765249e87 Add versioned documentation
They are added under /en/<version>/

This might be helpful, before adding new supported languages.
https://github.com/ArthurSonzogni/FTXUI/issues/1105
2025-09-21 17:39:42 +02:00
4 changed files with 235 additions and 11 deletions

View File

@@ -12,6 +12,10 @@ jobs:
steps: steps:
- name: "Checkout repository" - name: "Checkout repository"
uses: actions/checkout@v3 uses: actions/checkout@v3
with:
fetch-depth: 0 # Need full history.
fetch-tags: true # Need tags.
- name: "Install cmake" - name: "Install cmake"
uses: lukka/get-cmake@latest uses: lukka/get-cmake@latest
@@ -30,19 +34,23 @@ jobs:
sudo apt-get install graphviz; sudo apt-get install graphviz;
- name: "Build documentation" - name: "Build documentation"
run: |
python3 ./tools/build_multiversion_doc.py
- name: "Build examples"
run: > run: >
mkdir -p multiversion_docs/main/examples;
mkdir build; mkdir build;
cd build; cd build;
emcmake cmake .. emcmake cmake ..
-DCMAKE_BUILD_TYPE=Release -DCMAKE_BUILD_TYPE=Release
-DFTXUI_BUILD_DOCS=ON -DFTXUI_BUILD_DOCS=OFF
-DFTXUI_BUILD_EXAMPLES=ON -DFTXUI_BUILD_EXAMPLES=ON
-DFTXUI_BUILD_TESTS=OFF -DFTXUI_BUILD_TESTS=OFF
-DFTXUI_BUILD_TESTS_FUZZER=OFF -DFTXUI_BUILD_TESTS_FUZZER=OFF
-DFTXUI_ENABLE_INSTALL=OFF -DFTXUI_ENABLE_INSTALL=OFF
-DFTXUI_DEV_WARNINGS=OFF; -DFTXUI_DEV_WARNINGS=OFF;
cmake --build . --target doc; cmake --build . --target doc;
cmake --build . ;
rsync -amv rsync -amv
--include='*/' --include='*/'
--include='*.html' --include='*.html'
@@ -52,13 +60,13 @@ jobs:
--include='*.wasm' --include='*.wasm'
--exclude='*' --exclude='*'
examples examples
doc/doxygen/html; ../multiversion_docs/main/examples;
- name: "Deploy" - name: "Deploy"
uses: peaceiris/actions-gh-pages@v3 uses: peaceiris/actions-gh-pages@v3
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: build/doc/doxygen/html/ publish_dir: multiversion_docs
enable_jekyll: false enable_jekyll: false
allow_empty_commit: false allow_empty_commit: false
force_orphan: true force_orphan: true

1
.gitignore vendored
View File

@@ -70,5 +70,6 @@ out/
# tools directory: # tools directory:
!tools/**/*.sh !tools/**/*.sh
!tools/**/*.py
!tools/**/*.cpp !tools/**/*.cpp
build/ build/

View File

@@ -2,16 +2,9 @@
<!-- start footer part --> <!-- start footer part -->
<!--BEGIN GENERATE_TREEVIEW--> <!--BEGIN GENERATE_TREEVIEW-->
<div id="nav-path" class="navpath"><!-- id is needed for treeview function! --> <div id="nav-path" class="navpath"><!-- id is needed for treeview function! -->
<ul>
$navpath
<li class="footer">$generatedby <a href="https://www.doxygen.org/index.html"><img class="footer" src="$relpath^doxygen.svg" width="104" height="31" alt="doxygen"/></a> $doxygenversion </li>
</ul>
</div> </div>
<!--END GENERATE_TREEVIEW--> <!--END GENERATE_TREEVIEW-->
<!--BEGIN !GENERATE_TREEVIEW--> <!--BEGIN !GENERATE_TREEVIEW-->
<hr class="footer"/><address class="footer"><small>
$generatedby&#160;<a href="https://www.doxygen.org/index.html"><img class="footer" src="$relpath^doxygen.svg" width="104" height="31" alt="doxygen"/></a> $doxygenversion
</small></address>
<!--END !GENERATE_TREEVIEW--> <!--END !GENERATE_TREEVIEW-->
</body> </body>
</html> </html>

222
tools/build_multiversion_doc.py Executable file
View File

@@ -0,0 +1,222 @@
#!/usr/bin/env python3
import os
import subprocess
import shutil
import tempfile
import json
from pathlib import Path
from typing import List, Dict
class VersionInfo:
"""A structure to hold all information about a single documentation version."""
def __init__(self, name: str, is_main: bool, output_root: Path):
self.name = name
self.is_main = is_main
# Destination directory for the built docs, relative to the output root.
self.dest_dir = output_root if is_main else output_root / "en" / name
# The path to this version's index.html, relative to the output root.
self.index_path_from_root = self.dest_dir / "index.html"
def __repr__(self) -> str:
return f"VersionInfo(name='{self.name}', dest_dir='{self.dest_dir}')"
def run_command(command: List[str], check: bool = True, cwd: Path = None):
"""
Runs a command, prints its output, and handles errors.
"""
command_str = ' '.join(command)
print(f"Executing: {command_str} in {cwd or Path.cwd()}")
try:
# Using capture_output=True to get stdout/stderr
result = subprocess.run(
command,
capture_output=True,
text=True,
check=check,
cwd=cwd
)
if result.stdout:
print(result.stdout)
if result.stderr:
print(result.stderr)
return result
except subprocess.CalledProcessError as e:
print(f"ERROR: Command failed with exit code {e.returncode}")
print(f"Command: {command_str}")
if e.stdout:
print("--- STDOUT ---")
print(e.stdout)
if e.stderr:
print("--- STDERR ---")
print(e.stderr)
raise # Re-raise the exception to halt the script
def get_version_switcher_js(
current_version: VersionInfo,
all_versions: List[VersionInfo],
current_html_file: Path
) -> str:
"""
Generates the JavaScript for the version switcher dropdown.
This version pre-calculates the relative path from the current HTML file
to the index.html of every other version, simplifying the JS logic.
"""
version_names = [v.name for v in all_versions]
# Create a dictionary mapping version names to their relative URLs.
relative_paths: Dict[str, str] = {}
for version in all_versions:
# Calculate the relative path from the *parent directory* of the current HTML file
# to the target version's index.html.
path = os.path.relpath(version.index_path_from_root, current_html_file.parent)
relative_paths[version.name] = path
# Use json.dumps for safe serialization of data into JavaScript.
versions_json = json.dumps(version_names)
paths_json = json.dumps(relative_paths)
current_version_json = json.dumps(current_version.name)
return f"""
document.addEventListener('DOMContentLoaded', function() {{
const projectNumber = document.getElementById('projectnumber');
if (!projectNumber) {{
console.warn('Doxygen element with ID "projectnumber" not found. Cannot add version switcher.');
return;
}}
const versions = {versions_json};
const version_paths = {paths_json};
const currentVersion = {current_version_json};
// Sort versions: 'main' first, then others numerically descending.
versions.sort((a, b) => {{
if (a === 'main') return -1;
if (b === 'main') return 1;
return b.localeCompare(a, undefined, {{ numeric: true, sensitivity: 'base' }});
}});
const select = document.createElement('select');
select.onchange = function() {{
const selectedVersion = this.value;
// Navigate directly to the pre-calculated relative path.
if (selectedVersion !== currentVersion) {{
window.location.href = version_paths[selectedVersion];
}}
}};
versions.forEach(v => {{
const option = document.createElement('option');
option.value = v;
option.textContent = v;
if (v === currentVersion) {{
option.selected = true;
}}
select.appendChild(option);
}});
// Replace the Doxygen project number element with our dropdown.
projectNumber.replaceWith(select);
// Apply some styling to make it look good.
Object.assign(select.style, {{
backgroundColor: 'rgba(0, 0, 0, 0.8)',
color: 'white',
border: '1px solid rgba(255, 255, 255, 0.2)',
padding: '5px',
borderRadius: '5px',
fontSize: '14px',
fontFamily: 'inherit',
marginLeft: '10px',
cursor: 'pointer'
}});
}});
"""
def main():
"""Main function to build multi-version documentation."""
root_dir = Path.cwd()
output_dir = root_dir / "multiversion_docs"
print("--- 1. Cleaning up old documentation ---")
if output_dir.exists():
shutil.rmtree(output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
print("--- 2. Getting versions from git ---")
git_tags_result = run_command(["git", "tag", "--list", "v*"])
# Create a list of version names, starting with 'main'.
version_names = ["main"] + sorted(
git_tags_result.stdout.splitlines(),
reverse=True
)
# For demonstration, limit the number of versions. Remove this in production.
version_names = version_names[:4]
print(f"Versions to build: {', '.join(version_names)}")
# Pre-compute all version information and paths.
versions = [
VersionInfo(name, name == "main", output_dir)
for name in version_names
]
with tempfile.TemporaryDirectory() as build_dir_str:
build_dir = Path(build_dir_str)
# --- 3. Build documentation for each version ---
for version in versions:
print(f"\n--- Building docs for version: {version.name} ---")
# Create a temporary directory for this version's source code.
version_src_dir = build_dir / f"src_{version.name}"
version_src_dir.mkdir()
# Check out the version's source code from git.
archive_path = version_src_dir / "source.tar"
run_command([
"git", "archive", version.name,
"--format=tar", f"--output={archive_path}"
])
run_command(["tar", "-xf", str(archive_path)], cwd=version_src_dir)
archive_path.unlink()
# Configure and build the docs using CMake.
version_build_dir = build_dir / f"build_{version.name}"
version_build_dir.mkdir()
run_command([
"cmake", str(version_src_dir), "-DFTXUI_BUILD_DOCS=ON"
], cwd=version_build_dir)
run_command(["make", "doc"], cwd=version_build_dir)
# Copy the generated HTML files to the final destination.
doxygen_html_dir = version_build_dir / "doc" / "doxygen" / "html"
if not doxygen_html_dir.is_dir():
print(f"FATAL: Doxygen HTML output not found for version {version.name}")
exit(1)
print(f"Copying files to: {version.dest_dir}")
shutil.copytree(doxygen_html_dir, version.dest_dir, dirs_exist_ok=True)
# --- 4. Inject version switcher into all HTML files ---
print("\n--- Injecting version switcher JavaScript ---")
for version in versions:
if not version.dest_dir.exists():
print(f"Warning: Destination directory for {version.name} does not exist. Skipping JS injection.")
continue
print(f"Processing HTML files in: {version.dest_dir}")
for html_file in version.dest_dir.rglob("*.html"):
js_script = get_version_switcher_js(version, versions, html_file)
script_tag = f'<script>{js_script}</script>'
content = html_file.read_text(encoding='utf-8')
# Inject the script right before the closing body tag.
new_content = content.replace("</body>", f"{script_tag}\n</body>")
html_file.write_text(new_content, encoding='utf-8')
print("\n--- 5. Finalizing ---")
print("Multi-version documentation generated successfully!")
print(f"Output located in: {output_dir.resolve()}")
if __name__ == "__main__":
main()