Skip to content

Commit 4f4c879

Browse files
committed
Add documentation
1 parent 338919b commit 4f4c879

17 files changed

Lines changed: 1219 additions & 0 deletions

‎.github/workflows/docs.yml‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Eggs.Stacktrace
2+
#
3+
# Copyright (c) 2026 Agustin Berge
4+
#
5+
# Distributed under the Boost Software License, Version 1.0.
6+
# See accompanying file LICENSE.txt or copy at
7+
# http://www.boost.org/LICENSE_1_0.txt
8+
9+
name: docs
10+
11+
on:
12+
push:
13+
branches: [ main, feature/** ]
14+
paths:
15+
- 'docs/**'
16+
- 'CMakeLists.txt'
17+
- '.github/workflows/docs.yml'
18+
pull_request:
19+
types: [opened, synchronize, reopened]
20+
paths:
21+
- 'docs/**'
22+
- 'CMakeLists.txt'
23+
- '.github/workflows/docs.yml'
24+
25+
jobs:
26+
build:
27+
runs-on: ubuntu-latest
28+
timeout-minutes: 10
29+
30+
steps:
31+
- uses: actions/checkout@v5
32+
33+
- name: Install CMake
34+
uses: lukka/get-cmake@latest
35+
with:
36+
cmakeVersion: "4.4.0"
37+
38+
- name: Configure
39+
run: cmake --preset dev-gcc -B build/docs -DBUILD_DOCS=ON
40+
41+
- name: Build docs
42+
run: cmake --build build/docs --target docs
43+
44+
- name: Upload docs
45+
uses: actions/upload-artifact@v6
46+
with:
47+
name: docs
48+
path: build/docs/docs/html/
49+
if-no-files-found: error
50+
retention-days: 7

‎.github/workflows/release.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ jobs:
108108

109109
- name: Configure (static)
110110
run: cmake --preset ${{ matrix.preset }} -B build/${{ matrix.preset }}-static
111+
-DBUILD_DOCS=ON
111112

112113
- name: Build (static, Release)
113114
run: cmake --build build/${{ matrix.preset }}-static --config Release

‎CMakeLists.txt‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,19 @@ if(PROJECT_IS_TOP_LEVEL)
3030
string(APPEND CMAKE_SHARED_LINKER_FLAGS " /DEBUG")
3131
endif()
3232

33+
option(BUILD_DOCS "Build the documentation" ON)
3334
option(BUILD_EXAMPLES "Build the examples" ON)
3435
include(CTest)
3536
option(ENABLE_INSTALL "Generate install rules" ON)
3637
else()
3738
set_property(DIRECTORY PROPERTY SYSTEM ON)
3839

40+
option(EGGS_STACKTRACE_BUILD_DOCS "Build the documentation" OFF)
3941
option(EGGS_STACKTRACE_BUILD_EXAMPLES "Build the examples" OFF)
4042
option(EGGS_STACKTRACE_BUILD_TESTING "Build the test suite" OFF)
4143
option(EGGS_STACKTRACE_ENABLE_INSTALL "Generate install rules" OFF)
4244

45+
set(BUILD_DOCS ${EGGS_STACKTRACE_BUILD_DOCS})
4346
set(BUILD_EXAMPLES ${EGGS_STACKTRACE_BUILD_EXAMPLES})
4447
set(BUILD_TESTING ${EGGS_STACKTRACE_BUILD_TESTING})
4548
set(ENABLE_INSTALL ${EGGS_STACKTRACE_ENABLE_INSTALL})
@@ -269,6 +272,13 @@ add_library(
269272
ALIAS _eggs_stacktrace_${EGGS_STACKTRACE_DEFAULT_BACKEND}
270273
)
271274

275+
#
276+
# Documentation
277+
#
278+
if(BUILD_DOCS)
279+
add_subdirectory(docs)
280+
endif()
281+
272282
#
273283
# Examples
274284
#

‎CMakePresets.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"CMAKE_CXX_EXTENSIONS": "OFF",
3030
"CMAKE_COMPILE_WARNING_AS_ERROR": "ON",
3131
"CMAKE_LINK_WARNING_AS_ERROR": "ON",
32+
"BUILD_DOCS": "OFF",
3233
"BUILD_EXAMPLES": "ON",
3334
"BUILD_TESTING": "ON",
3435
"ENABLE_INSTALL": "ON"

‎docs/CMakeLists.txt‎

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Eggs.Stacktrace
2+
#
3+
# Copyright (c) 2026 Agustin Berge
4+
#
5+
# Distributed under the Boost Software License, Version 1.0. (See accompanying
6+
# file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7+
8+
find_package(Python3 COMPONENTS Interpreter)
9+
if(NOT Python3_Interpreter_FOUND)
10+
message(
11+
FATAL_ERROR
12+
"BUILD_DOCS requires a Python 3 interpreter.\n"
13+
"Install Python 3 and re-run CMake, or set -DBUILD_DOCS=OFF to skip."
14+
)
15+
endif()
16+
17+
set(_python_packages_dir "${CMAKE_CURRENT_BINARY_DIR}/python-packages")
18+
19+
# Install pinned doc dependencies into the build tree (runs once per build dir).
20+
add_custom_command(
21+
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/pip.stamp
22+
COMMAND
23+
${Python3_EXECUTABLE} -m pip install --target "${_python_packages_dir}"
24+
--upgrade --quiet "markdown==3.10.2" "pygments==2.20.0"
25+
COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/pip.stamp
26+
COMMENT "Installing documentation Python dependencies"
27+
VERBATIM
28+
)
29+
30+
# Convert Markdown sources to HTML in the binary directory.
31+
add_custom_command(
32+
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/docs.stamp
33+
COMMAND
34+
${CMAKE_COMMAND} -E env "PYTHONPATH=${_python_packages_dir}"
35+
${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/build.py
36+
${CMAKE_CURRENT_BINARY_DIR}/html ${PROJECT_VERSION}
37+
COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/docs.stamp
38+
DEPENDS
39+
${CMAKE_CURRENT_BINARY_DIR}/pip.stamp
40+
${CMAKE_CURRENT_SOURCE_DIR}/build.py
41+
${CMAKE_CURRENT_SOURCE_DIR}/index.md
42+
${CMAKE_CURRENT_SOURCE_DIR}/intro.md
43+
${CMAKE_CURRENT_SOURCE_DIR}/design.md
44+
${CMAKE_CURRENT_SOURCE_DIR}/usage.md
45+
${CMAKE_CURRENT_SOURCE_DIR}/cmake.md
46+
${CMAKE_CURRENT_SOURCE_DIR}/reference.md
47+
${CMAKE_CURRENT_SOURCE_DIR}/html/css/main.css
48+
${CMAKE_CURRENT_SOURCE_DIR}/html/css/normalize.css
49+
${CMAKE_CURRENT_SOURCE_DIR}/html/css/highlight.css
50+
${CMAKE_CURRENT_SOURCE_DIR}/html/css/img/favicon.png
51+
${CMAKE_CURRENT_SOURCE_DIR}/html/logo.png
52+
COMMENT "Building documentation"
53+
VERBATIM
54+
)
55+
add_custom_target(docs ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/docs.stamp)
56+
57+
if(ENABLE_INSTALL)
58+
install(
59+
DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html/
60+
DESTINATION ${CMAKE_INSTALL_DOCDIR}
61+
)
62+
endif()

‎docs/build.py‎

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
"""Convert docs/*.md to docs/*.html."""
2+
3+
import pathlib
4+
import re
5+
import shutil
6+
import sys
7+
import markdown
8+
9+
DOCS = pathlib.Path(__file__).parent
10+
ASSETS = DOCS / 'html'
11+
OUTPUT = pathlib.Path(sys.argv[1]) if len(sys.argv) > 1 else ASSETS
12+
VERSION = sys.argv[2] if len(sys.argv) > 2 else ''
13+
14+
PAGES = [
15+
('index.html', 'Index'),
16+
('intro.html', 'Introduction'),
17+
('design.html', 'Design'),
18+
('usage.html', 'Usage'),
19+
('cmake.html', 'CMake'),
20+
('reference.html', 'Reference'),
21+
]
22+
23+
TEMPLATE = """\
24+
<!DOCTYPE html>
25+
<html>
26+
<head>
27+
<meta charset="utf-8">
28+
<title>{title}</title>
29+
<link rel="stylesheet" type="text/css" href="css/normalize.css">
30+
<link rel="stylesheet" type="text/css" href="css/highlight.css">
31+
<link rel="stylesheet" type="text/css" href="css/main.css">
32+
<link rel="shortcut icon" href="css/img/favicon.png"
33+
type="image/png">
34+
</head>
35+
<body>
36+
<div id="container">
37+
<header id="head">
38+
<div class="top">
39+
<img src="logo.png" class="logo" alt="">
40+
<a href="index.html">Eggs.Stacktrace</a>
41+
</div>
42+
</header>
43+
<div id="head_post">
44+
{text_nav}
45+
</div>
46+
<div class="{page_class}">
47+
<section>
48+
{body}
49+
</section>
50+
</div>
51+
<hr>
52+
<p>
53+
Copyright &copy; 2026 Agustin Berge
54+
</p>
55+
<p>
56+
Distributed under the Boost Software License, Version 1.0.<br>
57+
See accompanying file LICENSE.txt or copy at
58+
<a href="http://www.boost.org/LICENSE_1_0.txt">http://www.boost.org/LICENSE_1_0.txt</a>
59+
</p>
60+
</div>
61+
</body>
62+
</html>
63+
"""
64+
65+
def build_text_nav(current_href):
66+
items = []
67+
for href, label in PAGES[1:]: # skip index (already the logo link)
68+
cls = 'name title' if href == current_href else 'name'
69+
items.append(
70+
f' <a class="{cls}" href="{href}">{label}</a>')
71+
return ' &middot;\n'.join(items)
72+
73+
def strip_html_comment(text):
74+
return re.sub(r'^\s*<!--.*?-->\s*', '', text, flags=re.DOTALL)
75+
76+
def add_heading_anchors(html):
77+
"""Wrap heading text in a self-link so headings are blue/clickable."""
78+
def replace(m):
79+
tag, attrs, content = m.group(1), m.group(2), m.group(3)
80+
id_match = re.search(r'\bid="([^"]+)"', attrs)
81+
if not id_match:
82+
return m.group(0)
83+
hid = id_match.group(1)
84+
return (f'<{tag}{attrs}>'
85+
f'<a href="#{hid}">{content}</a>'
86+
f'</{tag}>')
87+
return re.sub(
88+
r'<(h[1-6])( [^>]+)>(.*?)</\1>',
89+
replace, html, flags=re.DOTALL)
90+
91+
def md_to_html(src):
92+
src = strip_html_comment(src)
93+
src = re.sub(
94+
r'\[([^\]]+)\]\(([^)]+)\.md\)', r'[\1](\2.html)', src)
95+
md = markdown.Markdown(
96+
extensions=['tables', 'fenced_code', 'codehilite', 'toc'],
97+
extension_configs={
98+
'codehilite': {'guess_lang': False},
99+
})
100+
return add_heading_anchors(md.convert(src))
101+
102+
def build():
103+
OUTPUT.mkdir(parents=True, exist_ok=True)
104+
if OUTPUT != ASSETS:
105+
shutil.copytree(ASSETS / 'css', OUTPUT / 'css', dirs_exist_ok=True)
106+
shutil.copy2(ASSETS / 'logo.png', OUTPUT / 'logo.png')
107+
by_stem = {pathlib.Path(h).stem: l for h, l in PAGES}
108+
for md_path in sorted(DOCS.glob('*.md')):
109+
stem = md_path.stem
110+
html_name = stem + '.html'
111+
src = md_path.read_text(encoding='utf-8-sig')
112+
if VERSION:
113+
src = src.replace('@VERSION@', VERSION)
114+
body = md_to_html(src)
115+
label = by_stem.get(stem, stem)
116+
title = f'{label} - Eggs.Stacktrace'
117+
html = TEMPLATE.format(
118+
title=title,
119+
text_nav=build_text_nav(html_name),
120+
page_class=stem,
121+
body=body,
122+
)
123+
(OUTPUT / html_name).write_text(html, encoding='utf-8')
124+
print(f' {html_name}')
125+
126+
if __name__ == '__main__':
127+
build()
128+
print('done')

0 commit comments

Comments
 (0)