Source browser
scripts/generate_sources.py
Read the complete file below or download its original bytes. Line numbers provide stable links to a declaration or processing step.
File fingerprint
1#!/usr/bin/env python3
2"""Render complete source files as versioned, linked HTML pages.
3
4Each page includes readable code, numbered lines and a file download.
5"""
6
7from __future__ import annotations # Allow readable type hints with Python 3.8.
8
9import base64 # Embed each file download in its source page.
10import hashlib # Identify the exact source shown on each page.
11import html # Escape code and filenames before adding them to HTML.
12import os # Calculate links relative to each nested source page.
13import re # Read the release label from the documented runtime header.
14from pathlib import Path # Discover published sources and write their HTML pages.
15from highlight_docs import add_stylesheet, highlight_lines, language_for_path # Render static syntax colors with the source file's actual language.
16
17ROOT = Path(__file__).resolve().parent.parent # Locate the checkout containing this script.
18DOCS = ROOT / "docs" # Keep all published pages under the documentation root.
19VERSION_MATCH = re.search(r'^\s*#define\s+SHDR_VERSION\s+"([^"]+)"', (ROOT / "shdr.h").read_text(encoding="utf-8"), re.MULTILINE) # Use the same version as the published runtime.
20if not VERSION_MATCH: # Refuse to publish a source browser for an unidentified release.
21 raise SystemExit("SHDR_VERSION is missing from shdr.h") # Explain which release definition is needed.
22VERSION = html.escape(VERSION_MATCH.group(1)) # Escape the release label before adding it to page chrome.
23
24
25def navigation(prefix: str) -> str: # Build the same navigation used by the guides.
26 """Return a keyboard-accessible page header with local documentation links."""
27 links = (("index.html", "Start"), ("examples.html", "Examples"), ("results.html", "Results"), ("corpus.html", "Public shaders"), ("api.html", "API")) # Keep the main destinations in reading order.
28 items = "".join('<a href="' + prefix + target + '">' + label + '</a>' for target, label in links) # Create links relative to the current page.
29 return '<a class="skip-link" href="#content">Skip to content</a><header class="site-header"><div class="header-inner"><a class="brand" href="' + prefix + 'index.html">shdr<span class="release-version">' + VERSION + '</span><span>CPU + GPU compute</span></a><nav class="site-nav" aria-label="Main navigation">' + items + '</nav></div></header>' # Include the documented release and a visible keyboard skip link.
30
31
32def page(title: str, body: str, target: Path) -> str: # Wrap one source document in the shared page shell.
33 """Create a complete source page with local documentation assets."""
34 prefix = Path(os.path.relpath(DOCS, target.parent)).as_posix().rstrip("/") + "/" # Find the documentation root from this page.
35 return '<!doctype html>\n<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="description" content="Read and download the documented shdr example and library sources."><title>' + html.escape(title) + ' · shdr ' + VERSION + '</title><link rel="stylesheet" href="' + prefix + 'site.css"></head><body>' + navigation(prefix) + '<div class="page-shell"><main class="page-main source-view" id="content">' + body + '</main></div><footer class="footer"><p>shdr ' + VERSION + ' · Source browser · <a href="' + prefix + 'examples.html">Choose an example</a> · <a href="' + prefix + 'portability.html">Portability and requirements</a></p></footer></body></html>\n' # Keep the entire shell local to this site.
36
37
38def published_sources() -> list[Path]: # Select readable files that are useful when learning the library.
39 """List example programs, shader bodies, wrappers and maintained build tools."""
40 files = [ROOT / "shdr.h", ROOT / "CMakeLists.txt", ROOT / "cmake" / "shdr.cmake"] # Include the single runtime header and build integration.
41 files.extend(sorted((ROOT / "detail").glob("*.hpp"))) # Explain where the amalgamated runtime is maintained.
42 files.extend(sorted((ROOT / "examples").glob("*.cpp"))) # Include every complete host application and plugin.
43 files.extend(sorted((ROOT / "examples").glob("*.hpp"))) # Include reusable pipeline wrappers and independent references.
44 files.extend(sorted((ROOT / "examples" / "shaders").glob("*.cpp"))) # Include all native shader sources.
45 files.extend(sorted((ROOT / "scripts").glob("*.py"))) # Include import, validation and dataset preparation tools.
46 files.extend(sorted((ROOT / "tests").glob("*.cpp"))) # Include independent runtime, math and GPU regression tests.
47 files.extend(sorted((ROOT / "tests").glob("*.py"))) # Include native shader import and compilation regressions.
48 data_readme = ROOT / "examples" / "data" / "readme.md" # Find the human-readable data provenance maintained with the fixtures.
49 if data_readme.exists(): # Include the provenance when it is available.
50 files.append(data_readme) # Make its content readable within the published site.
51 return sorted(set(path for path in files if path.is_file())) # Return a stable list of unique paths.
52
53
54def write_source(path: Path) -> Path: # Publish one exact source file as readable HTML.
55 """Write numbered source lines and a byte-exact embedded download."""
56 relative = path.relative_to(ROOT) # Show paths relative to the checkout.
57 target = DOCS / "source" / (relative.as_posix() + ".html") # Preserve the familiar source directory structure.
58 payload = path.read_bytes() # Keep the original bytes for downloads and checksums.
59 text = payload.decode("utf-8") # All selected source files use UTF-8 text.
60 digest = hashlib.sha256(payload).hexdigest() # Identify the exact version displayed here.
61 download = base64.b64encode(payload).decode("ascii") # Encode the original file as a browser download.
62 lines = [] # Collect the numbered source lines.
63 rendered = highlight_lines(text, language_for_path(path)) # Tokenize the full file so multiline comments and strings retain their state.
64 for number, line in enumerate(rendered[: len(text.splitlines())], 1): # Keep the same numbered lines as the original source browser.
65 lines.append(f'<span class="line" id="L{number}"><a class="line-number" href="#L{number}" aria-label="Line {number}">{number}</a>{line}</span>') # Keep balanced escaped token spans inside each source line.
66 body = '<p class="eyebrow">Source browser</p><h1><code>' + html.escape(relative.as_posix()) + '</code></h1><p>Read the complete file below or download its original bytes. Line numbers provide stable links to a declaration or processing step.</p><div class="actions"><a class="button-link" href="' + Path(os.path.relpath(DOCS / "source" / "index.html", target.parent)).as_posix() + '">All source files</a><a class="button-link" id="download" download="' + html.escape(path.name, quote=True) + '" href="data:application/octet-stream;base64,' + download + '">Download ' + html.escape(path.name) + '</a></div><details><summary>File fingerprint</summary><p class="metadata">SHA-256 <code>' + digest + '</code></p></details><pre class="source-code"><code>' + "\n".join(lines) + '</code></pre>' # Keep the source and its exact download together.
67 target.parent.mkdir(parents=True, exist_ok=True) # Create this source directory when needed.
68 css = Path(os.path.relpath(DOCS / "highlight.css", target.parent)).as_posix() # Find the local syntax stylesheet from this nested source page.
69 target.write_text(add_stylesheet(page(relative.as_posix(), body, target), css), encoding="utf-8") # Write a complete standalone page with static syntax colors.
70 return target # Return the generated page for the index.
71
72
73def main() -> None: # Generate every source page and its searchable index.
74 """Render the source collection from the maintained source files."""
75 files = published_sources() # Gather all current published text files.
76 generated = [write_source(path) for path in files] # Render each file with its exact contents.
77 expected = set(generated) | {DOCS / "source" / "index.html"} # Keep the current source pages and their generated index.
78 for previous in (DOCS / "source").rglob("*.html"): # Inspect only the source browser's generated pages.
79 if previous not in expected: # Find a page for a source removed from the published collection.
80 previous.unlink() # Remove the obsolete generated source page.
81 items = [] # Collect index links grouped by their normal source path.
82 for source, target in zip(files, generated): # Pair source paths with their generated browser pages.
83 relative = source.relative_to(ROOT).as_posix() # Use a machine-independent path as the label.
84 items.append('<li data-file="' + html.escape(relative.lower(), quote=True) + '"><a href="' + target.relative_to(DOCS / "source").as_posix() + '"><code>' + html.escape(relative) + '</code></a></li>') # Give search a plain lowercase filename.
85 body = '<p class="eyebrow">Complete files</p><h1>Read the source</h1><p class="lead">Follow a complete application, inspect one shader, or look up a reusable pipeline wrapper. Every file has line links and an exact download.</p><p>Start with <a href="../examples.html">the example catalog</a> for a guided choice. Native shader files are converted during the build. Their C++ adapters and portable GPU variants are embedded in the application.</p><label for="file-search">Find a source file</label><input type="search" id="file-search" placeholder="Try affine, fft, lenet or data_sources"><p id="file-count" class="muted" aria-live="polite">' + str(len(files)) + ' files</p><ul class="file-list">' + "\n".join(items) + '</ul><script>\n// Filter the existing file links without requesting any network resources.\nconst query = document.getElementById("file-search");\nconst files = Array.from(document.querySelectorAll("[data-file]"));\nquery.addEventListener("input", () => {\n const words = query.value.toLowerCase().trim().split(/\\s+/).filter(Boolean); // Match every search word.\n let count = 0; // Count the visible files.\n files.forEach(file => {\n file.hidden = !words.every(word => file.dataset.file.includes(word)); // Preserve the original file order.\n if (!file.hidden) count++; // Report the number of visible links.\n });\n document.getElementById("file-count").textContent = count + " files"; // Announce the updated count.\n});\n</script>' # Keep all files visible when JavaScript is unavailable.
86 index = DOCS / "source" / "index.html" # Keep the browser entry point beside its generated files.
87 index.write_text(page("Read the source", body, index), encoding="utf-8") # Publish the searchable file list.
88 print("Published " + str(len(files)) + " source files") # Report the completed source collection.
89
90
91if __name__ == "__main__": # Run only when invoked as a command.
92 main() # Rebuild the source browser from the current checkout.