cesium-native/tools/dep-graph-gen/index.js

137 lines
4.1 KiB
JavaScript

const parse = require("dotparser");
const fs = require("fs");
const palette = require("google-palette");
const yargs = require("yargs");
const path = require("path");
const argv = yargs.options({
input: {
description: "The GraphViz .dot file generated by CMake.",
demandOption: true,
type: "string",
},
output: {
description: "The directory to write the output files to.",
demandOption: true,
type: "string"
},
targets: {
description: "A regular expression to filter the targets that will be included. Only these targets and their direct dependencies will be listed on the graph.",
demandOption: true,
type: "string"
}
}).argv;
if (!fs.existsSync(argv.input)) {
console.error(`Missing input file ${argv.input}`);
process.exit(1);
}
const ast = parse(fs.readFileSync(argv.input, "utf-8"));
const nodes = ast[0].children;
// node id -> dependency name
const dependencies = {};
// dependency name -> [dependencies]
const dependencyLinks = {};
nodes.forEach(n => {
if (n.type == "node_stmt") {
const id = n.node_id.id;
const label = n.attr_list.filter(a => a.id == "label")[0].eq;
dependencies[id] = label;
}
});
nodes.forEach(n => {
if (n.type == "edge_stmt") {
const from = n.edge_list[0].id;
const to = n.edge_list[1].id;
const arr = dependencyLinks[dependencies[from]] || [];
arr.push(dependencies[to]);
dependencyLinks[dependencies[from]] = arr;
}
});
function formatLibraryNameAsId(libraryName) {
return libraryName.replace(/(::|\+\+)/g, "_");
}
const includedRegex = new RegExp(argv.targets);
function generateChart(includedSet, colorLinks, useElk, title) {
let output = `---
comment: This file was generated by dep-graph-gen. DO NOT EDIT THIS FILE!
${useElk ? `config:
layout: elk` : ``}
${title ? `title: ${title}` : ``}
---
graph TD
classDef dependencyNode fill:#fff,stroke:#ccc,color:#666,font-weight:bold,font-size:28px
classDef libraryNode fill:#9f9,font-weight:bold,font-size:28px\n`;
// Keep track of which nodes are in which class, so we can style them appropriately
const classes = {
"dependencyNode": {},
"libraryNode": {}
};
// dependency name -> [index of link]
// We can only style links based on the order they were declared
const nodeLinks = {};
let linkNum = 0;
Object.keys(includedSet).forEach(n => {
const fromNodeId = formatLibraryNameAsId(n);
(dependencyLinks[n] || []).forEach(l => {
const toNodeId = formatLibraryNameAsId(l);
const isImportant = includedRegex.test(l);
const toLabel = isImportant ? `[${l}]` : `{{${l}}}`;
output += ` ${fromNodeId}[${n}] --> ${toNodeId}${toLabel}\n`;
if (isImportant) {
classes["libraryNode"][toNodeId] = true;
} else {
classes["dependencyNode"][toNodeId] = true;
}
const arr = nodeLinks[l] || [];
arr.push(linkNum++);
nodeLinks[l] = arr;
});
classes["libraryNode"][fromNodeId] = true;
});
Object.keys(classes).forEach(cl => {
if (Object.keys(classes[cl]).length > 0) {
output += ` class ${Object.keys(classes[cl]).join(",")} ${cl}\n`;
}
});
if (colorLinks) {
// Use a palette for link colors to assist in readability
const linkColors = palette("mpn65", Object.keys(nodeLinks).length);
let linkColorIndex = 0;
Object.keys(nodeLinks).forEach(l => {
output += ` linkStyle ${nodeLinks[l].join(",")} stroke:#${linkColors[linkColorIndex++]},stroke-width:8px\n`;
});
}
return output;
}
// dependency name -> true
const includedNodes = {};
// We're only interested in the Cesium libraries and their direct dependencies
Object.keys(dependencyLinks).filter(d => includedRegex.test(d)).forEach(d => {
includedNodes[d] = true;
});
fs.writeFileSync(path.join(argv.output, "all.mmd"), generateChart(includedNodes, true, true));
// Per-target graphs
Object.keys(includedNodes).forEach(n => {
const filename = formatLibraryNameAsId(n) + ".mmd";
fs.writeFileSync(path.join(argv.output, filename), generateChart({ [n]: true }, false, false, `${n} Dependency Graph`));
});
console.log(`Wrote generated Mermaid graphs to ${argv.output}`);