UNPKG

3.79 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 G: Object.assign({
50 overlap: false,
51 pad: 0.3,
52 rankdir: config.rankdir,
53 layout: config.layout,
54 bgcolor: config.backgroundColor
55 }, graphVizOptions.G),
56 E: Object.assign({
57 color: config.edgeColor
58 }, graphVizOptions.E),
59 N: Object.assign({
60 fontname: config.fontName,
61 fontsize: config.fontSize,
62 color: config.nodeColor,
63 shape: config.nodeShape,
64 style: config.nodeStyle,
65 height: 0,
66 fontcolor: config.nodeColor
67 }, graphVizOptions.N)
68 };
69}
70
71/**
72 * Creates the graphviz graph.
73 * @param {Object} modules
74 * @param {Array} circular
75 * @param {Object} config
76 * @param {Object} options
77 * @return {Promise}
78 */
79function createGraph(modules, circular, config, options) {
80 const g = graphviz.digraph('G');
81 const nodes = {};
82 const cyclicModules = circular.reduce((a, b) => a.concat(b), []);
83
84 if (config.graphVizPath) {
85 g.setGraphVizPath(config.graphVizPath);
86 }
87
88 Object.keys(modules).forEach((id) => {
89 nodes[id] = nodes[id] || g.addNode(id);
90
91 if (!modules[id].length) {
92 setNodeColor(nodes[id], config.noDependencyColor);
93 } else if (cyclicModules.indexOf(id) >= 0) {
94 setNodeColor(nodes[id], config.cyclicNodeColor);
95 }
96
97 modules[id].forEach((depId) => {
98 nodes[depId] = nodes[depId] || g.addNode(depId);
99
100 if (!modules[depId]) {
101 setNodeColor(nodes[depId], config.noDependencyColor);
102 }
103
104 g.addEdge(nodes[id], nodes[depId]);
105 });
106 });
107
108 return new Promise((resolve, reject) => {
109 g.output(options, resolve, (code, out, err) => {
110 reject(new Error(err));
111 });
112 });
113}
114
115/**
116 * Creates an image from the module dependency graph.
117 * @param {Object} modules
118 * @param {Array} circular
119 * @param {String} imagePath
120 * @param {Object} config
121 * @return {Promise}
122 */
123module.exports.image = function (modules, circular, imagePath, config) {
124 const options = createGraphvizOptions(config);
125
126 options.type = path.extname(imagePath).replace('.', '') || 'png';
127
128 return checkGraphvizInstalled(config)
129 .then(() => {
130 return createGraph(modules, circular, config, options)
131 .then((image) => writeFile(imagePath, image))
132 .then(() => path.resolve(imagePath));
133 });
134};
135
136/**
137 * Return the module dependency graph as DOT output.
138 * @param {Object} modules
139 * @param {Object} config
140 * @return {Promise}
141 */
142module.exports.dot = function (modules, config) {
143 const nodes = {};
144 const g = graphviz.digraph('G');
145
146 return checkGraphvizInstalled(config)
147 .then(() => {
148 Object.keys(modules).forEach((id) => {
149 nodes[id] = nodes[id] || g.addNode(id);
150
151 modules[id].forEach((depId) => {
152 nodes[depId] = nodes[depId] || g.addNode(depId);
153 g.addEdge(nodes[id], nodes[depId]);
154 });
155 });
156
157 return g.to_dot();
158 });
159};