Move contributor docs editor message structure tree generator from test to tools directory (#3663)

This commit is contained in:
Keavon Chambers 2026-01-20 23:53:15 -08:00 committed by GitHub
parent 7af60e02a3
commit 781fa7ae95
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 142 additions and 111 deletions

View File

@ -113,9 +113,11 @@ jobs:
- name: 📃 Generate code documentation info for website
if: github.ref == 'refs/heads/master'
run: |
cargo test --package graphite-editor --lib -- messages::message::test::generate_message_tree
cd tools/editor-message-tree
cargo run
cd ../..
mkdir -p artifacts-generated
mv hierarchical_message_system_tree.txt artifacts-generated/hierarchical_message_system_tree.txt
mv website/generated/hierarchical_message_system_tree.txt artifacts-generated/hierarchical_message_system_tree.txt
- name: 💿 Obtain cache of auto-generated code docs artifacts, to check if they've changed
if: github.ref == 'refs/heads/master'

View File

@ -67,20 +67,23 @@ jobs:
rustup update stable
echo "🦀 Latest updated version of Rust:"
rustc --version
cargo test --package graphite-editor --lib -- messages::message::test::generate_message_tree
cd tools/editor-message-tree
cargo run
cd ../..
mkdir artifacts
mv hierarchical_message_system_tree.txt artifacts/hierarchical_message_system_tree.txt
mv website/generated/hierarchical_message_system_tree.txt artifacts/hierarchical_message_system_tree.txt
- name: 🚚 Move `artifacts` contents to the project root
- name: 🚚 Move `artifacts` contents to website/generated
run: |
mv artifacts/* .
mkdir -p website/generated
mv artifacts/* website/generated/
- name: 🔧 Build auto-generated code docs artifacts into HTML
run: |
cd website
npm run generate-editor-structure
- name: Generate node catalog documentation
- name: 📃 Generate node catalog documentation
run: |
cd tools/node-docs
cargo run

2
.gitignore vendored
View File

@ -11,5 +11,3 @@ flamegraph.svg
.idea/
.direnv
.DS_Store
hierarchical_message_system_tree.txt
hierarchical_message_system_tree.html

7
Cargo.lock generated
View File

@ -1502,6 +1502,13 @@ dependencies = [
"syn 2.0.106",
]
[[package]]
name = "editor-message-tree"
version = "0.0.0"
dependencies = [
"graphite-editor",
]
[[package]]
name = "either"
version = "1.15.0"

View File

@ -40,7 +40,8 @@ members = [
"node-graph/preprocessor",
"proc-macros",
"tools/crate-hierarchy-viz",
"tools/node-docs"
"tools/editor-message-tree",
"tools/node-docs",
]
default-members = [
"editor",

View File

@ -53,97 +53,8 @@ impl specta::Type for MessageDiscriminant {
}
}
#[cfg(test)]
mod test {
use super::*;
use std::io::Write;
#[test]
fn generate_message_tree() {
let result = Message::build_message_tree();
let mut file = std::fs::File::create("../hierarchical_message_system_tree.txt").unwrap();
file.write_all(format!("{} `{}`\n", result.name(), result.path()).as_bytes()).unwrap();
if let Some(variants) = result.variants() {
for (i, variant) in variants.iter().enumerate() {
let is_last = i == variants.len() - 1;
print_tree_node(variant, "", is_last, &mut file);
}
}
}
fn print_tree_node(tree: &DebugMessageTree, prefix: &str, is_last: bool, file: &mut std::fs::File) {
// Print the current node
let (branch, child_prefix) = if tree.message_handler_data_fields().is_some() || tree.message_handler_fields().is_some() {
("├── ", format!("{prefix}"))
} else if is_last {
("└── ", format!("{prefix} "))
} else {
("├── ", format!("{prefix}"))
};
if tree.path().is_empty() {
file.write_all(format!("{}{}{}\n", prefix, branch, tree.name()).as_bytes()).unwrap();
} else {
file.write_all(format!("{}{}{} `{}`\n", prefix, branch, tree.name(), tree.path()).as_bytes()).unwrap();
}
// Print children if any
if let Some(variants) = tree.variants() {
let len = variants.len();
for (i, variant) in variants.iter().enumerate() {
let is_last_child = i == len - 1;
print_tree_node(variant, &child_prefix, is_last_child, file);
}
}
// Print message field if any
if let Some(fields) = tree.fields() {
let len = fields.len();
for (i, field) in fields.iter().enumerate() {
let is_last_field = i == len - 1;
let branch = if is_last_field { "└── " } else { "├── " };
file.write_all(format!("{child_prefix}{branch}{field}\n").as_bytes()).unwrap();
}
}
// Print handler field if any
if let Some(data) = tree.message_handler_fields() {
let len = data.fields().len();
let (branch, child_prefix) = if tree.message_handler_data_fields().is_some() {
("├── ", format!("{prefix}"))
} else {
("└── ", format!("{prefix} "))
};
const FRONTEND_MESSAGE_STR: &str = "FrontendMessage";
if data.name().is_empty() && tree.name() != FRONTEND_MESSAGE_STR {
panic!("{}'s MessageHandler is missing #[message_handler_data]", tree.name());
} else if tree.name() != FRONTEND_MESSAGE_STR {
file.write_all(format!("{}{}{} `{}`\n", prefix, branch, data.name(), data.path()).as_bytes()).unwrap();
for (i, field) in data.fields().iter().enumerate() {
let is_last_field = i == len - 1;
let branch = if is_last_field { "└── " } else { "├── " };
file.write_all(format!("{}{}{}\n", child_prefix, branch, field.0).as_bytes()).unwrap();
}
}
}
// Print data field if any
if let Some(data) = tree.message_handler_data_fields() {
let len = data.fields().len();
if data.path().is_empty() {
file.write_all(format!("{}{}{}\n", prefix, "└── ", data.name()).as_bytes()).unwrap();
} else {
file.write_all(format!("{}{}{} `{}`\n", prefix, "└── ", data.name(), data.path()).as_bytes()).unwrap();
}
for (i, field) in data.fields().iter().enumerate() {
let is_last_field = i == len - 1;
let branch = if is_last_field { "└── " } else { "├── " };
file.write_all(format!("{}{}{}\n", format!("{} ", prefix), branch, field.0).as_bytes()).unwrap();
}
}
impl Message {
pub fn message_tree() -> DebugMessageTree {
Self::build_message_tree()
}
}

View File

@ -0,0 +1,11 @@
[package]
name = "editor-message-tree"
description = "Tool to generate developer documentation for the editor message system structure"
edition.workspace = true
version.workspace = true
license.workspace = true
authors.workspace = true
[dependencies]
# Local dependencies
editor = { path = "../../editor", package = "graphite-editor" }

View File

@ -0,0 +1,93 @@
use editor::messages::message::Message;
use editor::utility_types::DebugMessageTree;
use std::io::Write;
fn main() {
let result = Message::message_tree();
std::fs::create_dir_all("../../website/generated").unwrap();
let mut file = std::fs::File::create("../../website/generated/hierarchical_message_system_tree.txt").unwrap();
file.write_all(format!("{} `{}`\n", result.name(), result.path()).as_bytes()).unwrap();
if let Some(variants) = result.variants() {
for (i, variant) in variants.iter().enumerate() {
let is_last = i == variants.len() - 1;
print_tree_node(variant, "", is_last, &mut file);
}
}
}
fn print_tree_node(tree: &DebugMessageTree, prefix: &str, is_last: bool, file: &mut std::fs::File) {
// Print the current node
let (branch, child_prefix) = if tree.message_handler_data_fields().is_some() || tree.message_handler_fields().is_some() {
("├── ", format!("{prefix}"))
} else if is_last {
("└── ", format!("{prefix} "))
} else {
("├── ", format!("{prefix}"))
};
if tree.path().is_empty() {
file.write_all(format!("{}{}{}\n", prefix, branch, tree.name()).as_bytes()).unwrap();
} else {
file.write_all(format!("{}{}{} `{}`\n", prefix, branch, tree.name(), tree.path()).as_bytes()).unwrap();
}
// Print children if any
if let Some(variants) = tree.variants() {
let len = variants.len();
for (i, variant) in variants.iter().enumerate() {
let is_last_child = i == len - 1;
print_tree_node(variant, &child_prefix, is_last_child, file);
}
}
// Print message field if any
if let Some(fields) = tree.fields() {
let len = fields.len();
for (i, field) in fields.iter().enumerate() {
let is_last_field = i == len - 1;
let branch = if is_last_field { "└── " } else { "├── " };
file.write_all(format!("{child_prefix}{branch}{field}\n").as_bytes()).unwrap();
}
}
// Print handler field if any
if let Some(data) = tree.message_handler_fields() {
let len = data.fields().len();
let (branch, child_prefix) = if tree.message_handler_data_fields().is_some() {
("├── ", format!("{prefix}"))
} else {
("└── ", format!("{prefix} "))
};
const FRONTEND_MESSAGE_STR: &str = "FrontendMessage";
if data.name().is_empty() && tree.name() != FRONTEND_MESSAGE_STR {
panic!("{}'s MessageHandler is missing #[message_handler_data]", tree.name());
} else if tree.name() != FRONTEND_MESSAGE_STR {
file.write_all(format!("{}{}{} `{}`\n", prefix, branch, data.name(), data.path()).as_bytes()).unwrap();
for (i, field) in data.fields().iter().enumerate() {
let is_last_field = i == len - 1;
let branch = if is_last_field { "└── " } else { "├── " };
file.write_all(format!("{}{}{}\n", child_prefix, branch, field.0).as_bytes()).unwrap();
}
}
}
// Print data field if any
if let Some(data) = tree.message_handler_data_fields() {
let len = data.fields().len();
if data.path().is_empty() {
file.write_all(format!("{}{}{}\n", prefix, "└── ", data.name()).as_bytes()).unwrap();
} else {
file.write_all(format!("{}{}{} `{}`\n", prefix, "└── ", data.name(), data.path()).as_bytes()).unwrap();
}
for (i, field) in data.fields().iter().enumerate() {
let is_last_field = i == len - 1;
let branch = if is_last_field { "└── " } else { "├── " };
let field = &field.0;
file.write_all(format!("{prefix} {branch}{field}\n").as_bytes()).unwrap();
}
}
}

View File

@ -1,3 +1,5 @@
// TODO: Port this script to Rust as part of `tools/editor-message-tree/src/main.rs`
/* eslint-disable no-console */
import fs from "fs";

1
website/.gitignore vendored
View File

@ -1,5 +1,6 @@
node_modules/
public/
generated/
static/*
!static/js/
content/learn/node-catalog

View File

@ -23,7 +23,7 @@
"eslint-plugin-prettier": "^5.5.5",
"prettier": "^3.8.0",
"sass": "1.97.2",
"tar": "^7.5.4",
"tar": "^7.5.6",
"typescript-eslint": "^8.53.1"
}
},
@ -4122,9 +4122,9 @@
}
},
"node_modules/tar": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.4.tgz",
"integrity": "sha512-AN04xbWGrSTDmVwlI4/GTlIIwMFk/XEv7uL8aa57zuvRy6s4hdBed+lVq2fAZ89XDa7Us3ANXcE3Tvqvja1kTA==",
"version": "7.5.6",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.6.tgz",
"integrity": "sha512-xqUeu2JAIJpXyvskvU3uvQW8PAmHrtXp2KDuMJwQqW8Sqq0CaZBAQ+dKS3RBXVhU4wC5NjAdKrmh84241gO9cA==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {

View File

@ -12,7 +12,7 @@
"type": "module",
"scripts": {
"postinstall": "node .build-scripts/install.ts",
"generate-editor-structure": "node .build-scripts/generate-editor-structure.ts ../hierarchical_message_system_tree.txt ../hierarchical_message_system_tree.html",
"generate-editor-structure": "node .build-scripts/generate-editor-structure.ts generated/hierarchical_message_system_tree.txt generated/hierarchical_message_system_tree.html",
"lint": "eslint . && tsc --noEmit",
"lint-fix": "eslint . --fix && tsc --noEmit"
},
@ -28,7 +28,7 @@
"eslint": "^9.39.2",
"prettier": "^3.8.0",
"sass": "1.97.2",
"tar": "^7.5.4",
"tar": "^7.5.6",
"typescript-eslint": "^8.53.1"
},
"dependencies": {

View File

@ -40,12 +40,14 @@
{% endmacro text_balancer %}
{% macro hierarchical_message_system_tree() %}
{%- set content = load_data(path = "../../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 `website` DIRECTORY, RUN:
TO TEST IT LOCALLY, FROM THE ROOT OF THE PROJECT, RUN:
cargo test --package graphite-editor --lib -- messages::message::test::generate_message_tree
cd tools/editor-message-tree
cargo run
cd ../../website
npm run generate-editor-structure</pre>" -%}
{{ content | default(value = fallback) | safe }}
{% endmacro hierarchical_message_system_tree %}