Skip to content

Commit d11a3f5

Browse files
committed
Dependency graphs.
1 parent 37d7285 commit d11a3f5

File tree

4 files changed

+326
-44
lines changed

4 files changed

+326
-44
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ releases
33
build
44
demos/node/output
55
node_modules
6+
graphs
67
.eslintcache
78
.DS_Store
89
*.lock

src/publicpath.ts

Whitespace-only changes.

tools/dependency_graph.js

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
#!/usr/bin/env node
2+
3+
// VexFlow does NOT include the necessary dependencies. To run this script, install the following:
4+
// npm i -g dependency-cruiser
5+
// npm i -g graphviz-cli
6+
7+
// Set the NODE_PATH environment variable to point to your global node_modules directory.
8+
// Run this script from the vexflow/ directory:
9+
// NODE_PATH=/usr/local/lib/node_modules/ ./tools/dependency_graph.js
10+
11+
// If your global node_modules folder is /usr/local/lib/node_modules/
12+
// You can skip setting the NODE_PATH, because we do so in the line below:
13+
module.paths.push('/usr/local/lib/node_modules/');
14+
// Just run the script with no arguments, and we will output the graphs to the vexflow/graphs/ directory.
15+
// ./tools/dependency_graph.js
16+
17+
const fs = require('fs');
18+
const { cruise } = require('dependency-cruiser');
19+
const { renderGraphFromSource } = require('graphviz-cli');
20+
21+
fs.mkdirSync('graphs', { recursive: true });
22+
23+
function generateGraphForFolder(folderName, excludePattern, collapsePattern = undefined) {
24+
try {
25+
const options = {
26+
outputType: 'dot',
27+
exclude: { path: excludePattern },
28+
};
29+
if (collapsePattern) {
30+
options.collapse = collapsePattern;
31+
}
32+
const cruiseResult = cruise([folderName], options);
33+
renderGraphFromSource({ input: cruiseResult.output }, { format: 'svg' }).then((svg) => {
34+
const svgWithLinks = svg.replace(
35+
/xlink:href="(.*?)"/g,
36+
`href="https://github.com/0xfe/vexflow/tree/master/$1" target="_blank"`
37+
);
38+
const htmlOutput = getHTMLOutput(`VexFlow ${folderName}/`, svgWithLinks);
39+
fs.writeFileSync(`graphs/${folderName}.html`, htmlOutput);
40+
});
41+
} catch (error) {
42+
console.error(error);
43+
}
44+
}
45+
46+
function getHTMLOutput(title, svg) {
47+
return `
48+
<!DOCTYPE html>
49+
<html lang="en" dir="ltr">
50+
<head>
51+
<meta charset="utf-8" />
52+
<title>${title}</title>
53+
<style>
54+
.node:active path,
55+
.node:hover path,
56+
.node.current path,
57+
.node:active polygon,
58+
.node:hover polygon,
59+
.node.current polygon {
60+
stroke: fuchsia;
61+
stroke-width: 2;
62+
}
63+
.edge:active path,
64+
.edge:hover path,
65+
.edge.current path,
66+
.edge:active ellipse,
67+
.edge:hover ellipse,
68+
.edge.current ellipse {
69+
stroke: fuchsia;
70+
stroke-width: 3;
71+
stroke-opacity: 1;
72+
}
73+
.edge:active polygon,
74+
.edge:hover polygon,
75+
.edge.current polygon {
76+
stroke: fuchsia;
77+
stroke-width: 3;
78+
fill: fuchsia;
79+
stroke-opacity: 1;
80+
fill-opacity: 1;
81+
}
82+
.edge:active text,
83+
.edge:hover text {
84+
fill: fuchsia;
85+
}
86+
.cluster path {
87+
stroke-width: 3;
88+
}
89+
.cluster:active path,
90+
.cluster:hover path {
91+
fill: #ffff0011;
92+
}
93+
</style>
94+
</head>
95+
<body>
96+
${svg}
97+
<script>
98+
document.body.onmouseover = getHighlightHandler();
99+
document.body.onclick = getClickHandler();
100+
101+
function getHighlightHandler() {
102+
/** @type {string} */
103+
var currentHighlightedTitle;
104+
105+
/** @type {NodeListOf<SVGGElement>} */
106+
var nodes = document.querySelectorAll('.node');
107+
/** @type {NodeListOf<SVGGElement>} */
108+
var edges = document.querySelectorAll('.edge');
109+
var title2ElementMap = new Title2ElementMap(edges, nodes);
110+
111+
/** @param {MouseEvent} pMouseEvent */
112+
return function highlightHandler(pMouseEvent) {
113+
var closestNodeOrEdge = pMouseEvent.target.closest('.edge, .node');
114+
var closestTitleText = getTitleText(closestNodeOrEdge);
115+
116+
if (!(currentHighlightedTitle === closestTitleText)) {
117+
title2ElementMap.get(currentHighlightedTitle).forEach(removeHighlight);
118+
title2ElementMap.get(closestTitleText).forEach(addHighlight);
119+
currentHighlightedTitle = closestTitleText;
120+
}
121+
};
122+
}
123+
124+
function getClickHandler() {
125+
return function highlightHandler(pMouseEvent) {
126+
var closestNodeOrEdge = pMouseEvent.target.closest('.edge, .node');
127+
console.log('Clicked', closestNodeOrEdge);
128+
};
129+
}
130+
131+
/**
132+
*
133+
* @param {SVGGelement[]} pEdges
134+
* @param {SVGGElement[]} pNodes
135+
* @return {{get: (pTitleText:string) => SVGGElement[]}}
136+
*/
137+
function Title2ElementMap(pEdges, pNodes) {
138+
/* {{[key: string]: SVGGElement[]}} */
139+
var elementMap = buildMap(pEdges, pNodes);
140+
141+
/**
142+
* @param {NodeListOf<SVGGElement>} pEdges
143+
* @param {NodeListOf<SVGGElement>} pNodes
144+
* @return {{[key: string]: SVGGElement[]}}
145+
*/
146+
function buildMap(pEdges, pNodes) {
147+
var title2NodeMap = buildTitle2NodeMap(pNodes);
148+
149+
return nodeListToArray(pEdges).reduce(addEdgeToMap(title2NodeMap), {});
150+
}
151+
/**
152+
* @param {NodeListOf<SVGGElement>} pNodes
153+
* @return {{[key: string]: SVGGElement}}
154+
*/
155+
function buildTitle2NodeMap(pNodes) {
156+
return nodeListToArray(pNodes).reduce(addNodeToMap, {});
157+
}
158+
159+
function addNodeToMap(pMap, pNode) {
160+
var titleText = getTitleText(pNode);
161+
162+
if (titleText) {
163+
pMap[titleText] = pNode;
164+
}
165+
return pMap;
166+
}
167+
168+
function addEdgeToMap(pNodeMap) {
169+
return function (pEdgeMap, pEdge) {
170+
/** @type {string} */
171+
var titleText = getTitleText(pEdge);
172+
173+
if (titleText) {
174+
var edge = pryEdgeFromTitle(titleText);
175+
176+
pEdgeMap[titleText] = [pNodeMap[edge.from], pNodeMap[edge.to]];
177+
(pEdgeMap[edge.from] || (pEdgeMap[edge.from] = [])).push(pEdge);
178+
(pEdgeMap[edge.to] || (pEdgeMap[edge.to] = [])).push(pEdge);
179+
}
180+
return pEdgeMap;
181+
};
182+
}
183+
184+
/**
185+
*
186+
* @param {string} pString
187+
* @return {{from?: string; to?:string;}}
188+
*/
189+
function pryEdgeFromTitle(pString) {
190+
var nodeNames = pString.split(/\s*->\s*/);
191+
192+
return {
193+
from: nodeNames.shift(),
194+
to: nodeNames.shift(),
195+
};
196+
}
197+
/**
198+
*
199+
* @param {string} pTitleText
200+
* @return {SVGGElement[]}
201+
*/
202+
function get(pTitleText) {
203+
return (pTitleText && elementMap[pTitleText]) || [];
204+
}
205+
return {
206+
get: get,
207+
};
208+
}
209+
210+
/**
211+
* @param {SVGGElement} pGElement
212+
* @return {string?}
213+
*/
214+
function getTitleText(pGElement) {
215+
/** @type {SVGTitleElement} */
216+
var title = pGElement && pGElement.querySelector('title');
217+
/** @type {string} */
218+
var titleText = title && title.textContent;
219+
220+
if (titleText) {
221+
titleText = titleText.trim();
222+
}
223+
return titleText;
224+
}
225+
226+
/**
227+
* @param {NodeListOf<Element>} pNodeList
228+
* @return {Element[]}
229+
*/
230+
function nodeListToArray(pNodeList) {
231+
var lReturnValue = [];
232+
233+
pNodeList.forEach(function (pElement) {
234+
lReturnValue.push(pElement);
235+
});
236+
237+
return lReturnValue;
238+
}
239+
240+
/**
241+
* @param {SVGGElement} pGElement
242+
*/
243+
function removeHighlight(pGElement) {
244+
if (pGElement && pGElement.classList) {
245+
pGElement.classList.remove('current');
246+
}
247+
}
248+
249+
/**
250+
* @param {SVGGElement} pGroup
251+
*/
252+
function addHighlight(pGroup) {
253+
if (pGroup && pGroup.classList) {
254+
pGroup.classList.add('current');
255+
}
256+
}
257+
</script>
258+
</body>
259+
</html>
260+
`;
261+
}
262+
263+
const IGNORE = '(index|vex|flow|util|tables|typeguard|version).ts';
264+
265+
generateGraphForFolder('src', IGNORE);
266+
generateGraphForFolder('entry', IGNORE);
267+
generateGraphForFolder('tests', `(formatter|support|types|${IGNORE})`, '^src/');

0 commit comments

Comments
 (0)