UNPKG

4.13 kBJavaScriptView Raw
1'use strict';
2
3const path = require('path');
4const pify = require('pify');
5const graphviz = require('graphviz');
6
7const exec = pify(require('child_process').exec);
8const writeFile = pify(require('fs').writeFile);
9
10/**
11 * Set color on a node.
12 * @param {Object} node
13 * @param {String} color
14 */
15function setNodeColor(node, color) {
16 node.set('color', color);
17 node.set('fontcolor', color);
18}
19
20/**
21 * Check if Graphviz is installed on the system.
22 * @param {Object} config
23 * @return {Promise}
24 */
25function checkGraphvizInstalled(config) {
26 if (config.graphVizPath) {
27 const cmd = path.join(config.graphVizPath, 'gvpr -V');
28 return exec(cmd)
29 .catch(() => {
30 throw new Error('Could not execute ' + cmd);
31 });
32 }
33
34 return exec('gvpr -V')
35 .catch((error) => {
36 throw new Error('Graphviz could not be found. Ensure that "gvpr" is in your $PATH.\n' + error);
37 });
38}
39
40/**
41 * Return options to use with graphviz digraph.
42 * @param {Object} config
43 * @return {Object}
44 */
45function createGraphvizOptions(config) {
46 const graphVizOptions = config.graphVizOptions || {};
47
48 return {
49 // Graph
50 G: Object.assign({
51 overlap: false,
52 pad: 0.3,
53 rankdir: config.rankdir,
54 layout: config.layout,
55 bgcolor: config.backgroundColor
56 }, graphVizOptions.G),
57 // Edge
58 E: Object.assign({
59 color: config.edgeColor
60 }, graphVizOptions.E),
61 // Node
62 N: Object.assign({
63 fontname: config.fontName,
64 fontsize: config.fontSize,
65 color: config.nodeColor,
66 shape: config.nodeShape,
67 style: config.nodeStyle,
68 height: 0,
69 fontcolor: config.nodeColor
70 }, graphVizOptions.N)
71 };
72}
73
74/**
75 * Creates the graphviz graph.
76 * @param {Object} modules
77 * @param {Array} circular
78 * @param {Object} config
79 * @param {Object} options
80 * @return {Promise}
81 */
82function createGraph(modules, circular, config, options) {
83 const g = graphviz.digraph('G');
84 const nodes = {};
85 const cyclicModules = circular.reduce((a, b) => a.concat(b), []);
86
87 if (config.graphVizPath) {
88 g.setGraphVizPath(config.graphVizPath);
89 }
90
91 Object.keys(modules).forEach((id) => {
92 nodes[id] = nodes[id] || g.addNode(id);
93
94 if (!modules[id].length) {
95 setNodeColor(nodes[id], config.noDependencyColor);
96 } else if (cyclicModules.indexOf(id) >= 0) {
97 setNodeColor(nodes[id], config.cyclicNodeColor);
98 }
99
100 modules[id].forEach((depId) => {
101 nodes[depId] = nodes[depId] || g.addNode(depId);
102
103 if (!modules[depId]) {
104 setNodeColor(nodes[depId], config.noDependencyColor);
105 }
106
107 g.addEdge(nodes[id], nodes[depId]);
108 });
109 });
110
111 return new Promise((resolve, reject) => {
112 g.output(options, resolve, (code, out, err) => {
113 reject(new Error(err));
114 });
115 });
116}
117
118/**
119 * Return the module dependency graph XML SVG representation as a Buffer.
120 * @param {Object} modules
121 * @param {Array} circular
122 * @param {Object} config
123 * @return {Promise}
124 */
125module.exports.svg = function (modules, circular, config) {
126 const options = createGraphvizOptions(config);
127
128 options.type = 'svg';
129
130 return checkGraphvizInstalled(config)
131 .then(() => createGraph(modules, circular, config, options));
132};
133
134/**
135 * Creates an image from the module dependency graph.
136 * @param {Object} modules
137 * @param {Array} circular
138 * @param {String} imagePath
139 * @param {Object} config
140 * @return {Promise}
141 */
142module.exports.image = function (modules, circular, imagePath, config) {
143 const options = createGraphvizOptions(config);
144
145 options.type = path.extname(imagePath).replace('.', '') || 'png';
146
147 return checkGraphvizInstalled(config)
148 .then(() => {
149 return createGraph(modules, circular, config, options)
150 .then((image) => writeFile(imagePath, image))
151 .then(() => path.resolve(imagePath));
152 });
153};
154
155/**
156 * Return the module dependency graph as DOT output.
157 * @param {Object} modules
158 * @param {Array} circular
159 * @param {Object} config
160 * @return {Promise}
161 */
162module.exports.dot = function (modules, circular, config) {
163 const options = createGraphvizOptions(config);
164
165 options.type = 'dot';
166
167 return checkGraphvizInstalled(config)
168 .then(() => createGraph(modules, circular, config, options))
169 .then((output) => output.toString('utf8'));
170};