137 lines
4.1 KiB
JavaScript
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}`); |