Port website dev docs generated content scripts from JS to Rust to avoid intermediate parsing (#3909)

* Remove the crate dependency graph website generation intermediate generation step

* Remove the message system tree website generation intermediate generation step

* Code cleanup

* Remove cache system

* Fix Windows comment URL error

* Fix incorrect artifact download URLs

* Add Flatpak to comment

* Make Flatpak use debug/release mode choice instead of always release mode
This commit is contained in:
Keavon Chambers
2026-03-17 15:02:15 -07:00
committed by GitHub
parent d9214c7292
commit 187b4c38b4
12 changed files with 345 additions and 268 deletions

View File

@@ -1,19 +0,0 @@
/* eslint-disable no-console */
import fs from "fs";
import { instance } from "@viz-js/viz";
const [inputFile, outputFile] = process.argv.slice(2);
if (!inputFile || !outputFile) {
console.error("Usage: node generate-crate-hierarchy.ts <input.dot> <output.svg>");
process.exit(1);
}
const dot = fs.readFileSync(inputFile, "utf-8");
const viz = await instance();
const svg = viz.renderString(dot, { format: "svg" });
fs.writeFileSync(outputFile, svg);
console.log(`SVG output written to: ${outputFile}`);

View File

@@ -1,122 +0,0 @@
// TODO: Port this script to Rust as part of `tools/editor-message-tree/src/main.rs`
/* eslint-disable no-console */
import fs from "fs";
import path from "path";
type Entry = { level: number; text: string; link: string | undefined };
/// Parses a single line of the input text.
function parseLine(line: string) {
const linkRegex = /`([^`]+)`$/;
const linkMatch = line.match(linkRegex);
let link = undefined;
if (linkMatch) {
const filePath = linkMatch[1].replace(/\\/g, "/");
link = `https://github.com/GraphiteEditor/Graphite/blob/master/${filePath}`;
}
const textContent = line
.replace(/^[\s│├└─]*/, "")
.replace(linkRegex, "")
.trim();
const indentation = line.indexOf(textContent);
// Each level of indentation is 4 characters.
const level = Math.floor(indentation / 4);
return { level, text: textContent, link };
}
/// Recursively builds the HTML list from the parsed nodes.
function buildHtmlList(nodes: Entry[], currentIndex: number, currentLevel: number) {
if (currentIndex >= nodes.length) {
return { html: "", nextIndex: currentIndex };
}
let html = "<ul>\n";
let i = currentIndex;
while (i < nodes.length && nodes[i].level >= currentLevel) {
const node = nodes[i];
if (node.level > currentLevel) {
// This case handles malformed input, skip to next valid line
i++;
continue;
}
const hasDirectChildren = i + 1 < nodes.length && nodes[i + 1].level > node.level;
const hasDeeperChildren = hasDirectChildren && i + 2 < nodes.length && nodes[i + 2].level > nodes[i + 1].level;
const linkHtml = node.link ? `<a href="${node.link}" target="_blank">${path.basename(node.link.split("#L").join(":"))}</a>` : "";
const fieldPieces = node.text.match(/([^:]*):(.*)/);
let escapedText;
if (fieldPieces && fieldPieces.length === 3) {
escapedText = [escapeHtml(fieldPieces[1].trim()), escapeHtml(fieldPieces[2].trim())];
} else {
escapedText = [escapeHtml(node.text)];
}
let role = "message";
if (node.link) role = "subsystem";
else if (hasDeeperChildren) role = "submessage";
else if (escapedText.length === 2) role = "field";
const partOfMessageFromNamingConvention = ["Message", "MessageHandler", "MessageContext"].some((suffix) => node.text.replace(/(.*)<.*>/g, "$1").endsWith(suffix));
const partOfMessageViolatesNamingConvention = node.link && !partOfMessageFromNamingConvention;
const violatesNamingConvention = partOfMessageViolatesNamingConvention
? "<span class=\"warn\">(violates naming convention — should end with 'Message', 'MessageHandler', or 'MessageContext')</span>"
: "";
if (hasDirectChildren) {
html += `<li><span class="tree-node"><span class="${role}">${escapedText}</span>${linkHtml}${violatesNamingConvention}</span>`;
const childResult = buildHtmlList(nodes, i + 1, node.level + 1);
html += `<div class="nested">${childResult.html}</div></li>\n`;
i = childResult.nextIndex;
} else if (role === "field") {
html += `<li><span class="tree-leaf field">${escapedText[0]}</span>: <span>${escapedText[1]}</span>${linkHtml}</li>\n`;
i++;
} else {
html += `<li><span class="tree-leaf ${role}">${escapedText[0]}</span>${linkHtml}${violatesNamingConvention}</li>\n`;
i++;
}
}
html += "</ul>\n";
return { html, nextIndex: i };
}
function escapeHtml(text: string) {
return text.replace(/</g, "&lt;").replace(/>/g, "&gt;");
}
const inputFile = process.argv[2];
const outputFile = process.argv[3];
if (!inputFile || !outputFile) {
console.error("Error: Please provide the input text and output HTML file paths as arguments.");
console.log("Usage: node generate-editor-structure.ts <input txt> <output html>");
process.exit(1);
}
if (!fs.existsSync(inputFile)) {
console.error(`Error: File not found at "${inputFile}"`);
process.exit(1);
}
try {
const fileContent = fs.readFileSync(inputFile, "utf-8");
const lines = fileContent.split(/\r?\n/).filter((line) => line.trim() !== "" && !line.startsWith("// filepath:"));
const parsedNodes = lines.map(parseLine);
const { html } = buildHtmlList(parsedNodes, 0, 0);
fs.writeFileSync(outputFile, html, "utf-8");
console.log(`Successfully generated HTML outline at: ${outputFile}`);
} catch (error) {
console.error("An error occurred during processing:", error);
process.exit(1);
}

View File

@@ -22,7 +22,7 @@ The dispatcher lives at the root of the editor hierarchy and acts as the owner o
cargo run explore editor
```
Click to explore the outline of the editor subsystem hierarchy which forms the structure of the editor's subsystems, state, and interactions.
Click to explore the outline of the editor subsystem hierarchy which forms the structure of the editor's subsystems, state, and interactions. Also available as a searchable <a href="/volunteer/guide/codebase-overview/hierarchical-message-system-tree.txt">plain text file</a>.
<div class="structure-outline">
<!-- replacements::hierarchical_message_system_tree() -->

View File

@@ -16,7 +16,6 @@
"@eslint/eslintrc": "^3.3.3",
"@eslint/js": "^9.39.2",
"@types/node": "^25.0.9",
"@viz-js/viz": "^3.25.0",
"eslint": "^9.39.2",
"eslint-config-prettier": "^10.1.8",
"eslint-import-resolver-typescript": "^4.4.4",
@@ -1233,13 +1232,6 @@
"win32"
]
},
"node_modules/@viz-js/viz": {
"version": "3.25.0",
"resolved": "https://registry.npmjs.org/@viz-js/viz/-/viz-3.25.0.tgz",
"integrity": "sha512-dM7zAYMdf7mcRz5Kdb+YJb6+qv5Rjk0rPZ18gROdpMrP/3S7RFOp8uxybeiz5RypHrE1zo1vccA8Twh4mIcLZw==",
"dev": true,
"license": "MIT"
},
"node_modules/acorn": {
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",

View File

@@ -12,8 +12,6 @@
"type": "module",
"scripts": {
"postinstall": "node .build-scripts/install.ts",
"generate-editor-structure": "node .build-scripts/generate-editor-structure.ts generated/hierarchical_message_system_tree.txt generated/hierarchical_message_system_tree.html",
"generate-crate-hierarchy": "node .build-scripts/generate-crate-hierarchy.ts generated/crate_hierarchy.dot generated/crate_hierarchy.svg",
"check": "tsc --noEmit && eslint",
"fix": "eslint --fix"
},
@@ -22,7 +20,6 @@
"@eslint/eslintrc": "^3.3.3",
"@eslint/js": "^9.39.2",
"@types/node": "^25.0.9",
"@viz-js/viz": "^3.25.0",
"eslint": "^9.39.2",
"eslint-config-prettier": "^10.1.8",
"eslint-import-resolver-typescript": "^4.4.4",

View File

@@ -40,25 +40,21 @@
{% endmacro text_balancer %}
{% macro hierarchical_message_system_tree() %}
{%- set content = load_data(path = "../generated/hierarchical_message_system_tree.html", format = "plain", required = false) -%}
{%- set content = load_data(path = "../generated/hierarchical-message-system-tree.html", format = "plain", required = false) -%}
{%- set fallback = "<pre>THIS CONTENT IS FILLED IN WHEN CI BUILDS THE WEBSITE.
TO TEST IT LOCALLY, FROM THE ROOT OF THE PROJECT, RUN:
cargo run -p editor-message-tree -- website/generated/hierarchical_message_system_tree.txt
cd website
npm run generate-editor-structure</pre>" -%}
cargo run -p editor-message-tree -- website/generated</pre>" -%}
{{ content | default(value = fallback) | safe }}
{% endmacro hierarchical_message_system_tree %}
{% macro crate_hierarchy() %}
{%- set content = load_data(path = "../generated/crate_hierarchy.svg", format = "plain", required = false) -%}
{%- set content = load_data(path = "../generated/crate-hierarchy.svg", format = "plain", required = false) -%}
{%- set fallback = "<pre>THIS CONTENT IS FILLED IN WHEN CI BUILDS THE WEBSITE.
TO TEST IT LOCALLY, FROM THE ROOT OF THE PROJECT, RUN:
cargo run -p crate-hierarchy-viz -- website/generated/crate_hierarchy.dot
cd website
npm run generate-crate-hierarchy</pre>" -%}
cargo run -p crate-hierarchy-viz -- website/generated</pre>" -%}
{{ content | default(value = fallback) | safe }}
{% endmacro crate_hierarchy %}