embark-area-51/lib/modules/graph/index.js

151 lines
5.0 KiB
JavaScript

const async = require('async');
const Viz = require('viz.js');
const fs = require('fs');
class GraphGenerator {
constructor(embark, _options) {
const self = this;
this.events = embark.events;
this.contracts = [];
this.events.setCommandHandler("graph:create", function(options, cb) {
self.generate(options);
cb();
});
}
generate(options) {
const self = this;
let id = 0;
let contractString = "";
let relationshipString = "";
let idMapping = {};
let contractInheritance = {};
let contractsDependencies = {};
async.waterfall([
function getContractList(next) {
self.events.request('contracts:list', (err, contracts) => {
self.contracts = contracts;
next();
});
},
function getContractsDependencies(next) {
self.events.request('contracts:dependencies', (err, _contractsDependencies) => {
contractsDependencies = _contractsDependencies;
next();
});
},
function (next) {
for (let contract of self.contracts) {
if (options.skipUndeployed && !contract.deploy) continue;
id++;
idMapping[contract.className] = id;
let contractLabel = "";
contractLabel += `${contract.className}`;
let tooltip = contract.className;
if (contract.instanceOf !== undefined && self.getContract(contract.instanceOf) !== undefined) {
contractInheritance[contract.className] = contract.instanceOf;
contractLabel += ": " + contract.instanceOf;
tooltip += " instance of " + contract.instanceOf;
} else {
if (!(options.skipFunctions === true && options.skipEvents === true)) contractLabel += "|";
for (let i = 0; i < contract.abiDefinition.length; i++) {
let abiDef = contract.abiDefinition[i];
if (abiDef.type === 'event' && options.skipEvents) continue;
if (['constructor', 'fallback'].indexOf(abiDef.type) > -1 && options.skipFunctions) continue;
switch(abiDef.type){
case 'fallback':
contractLabel += "«fallback»()\\l";
break;
case 'constructor':
contractLabel += "«constructor»(";
abiDef.inputs.forEach(function(elem, index){
contractLabel += (index === 0 ? "" : ", ") + elem.type;
});
contractLabel += ")\\l";
break;
case 'event':
contractLabel += "«event»" + abiDef.name + "(";
abiDef.inputs.forEach(function(elem, index){
contractLabel += (index === 0 ? "" : ", ") + elem.type;
});
contractLabel += ")\\l";
break;
default: break;
}
}
let fHashes = contract.functionHashes;
if (fHashes !== {} && fHashes !== undefined && !options.skipFunctions){
for (let method in contract.functionHashes){
contractLabel += method + '\\l';
}
}
}
let others = '';
if (!contract.deploy){
others = 'fontcolor="#c3c3c3", color="#a0a0a0"';
tooltip += " (not deployed)";
}
contractString += `${id}[label = "{${contractLabel}}", tooltip="${tooltip}", fillcolor=gray95, ${others}]\n`;
}
next();
},
function (next) {
for (let c in contractsDependencies) {
let contractDependencies = Array.from(new Set(contractsDependencies[c]));
contractDependencies.forEach((d) => {
if (idMapping[c] !== undefined && idMapping[d] !== undefined) {
if ((options.skipUndeployed && self.getContract(c).deploy && self.getContract(d).deploy) || !options.skipUndeployed) {
relationshipString += `${idMapping[d]}->${idMapping[c]}[constraint=true, arrowtail=diamond, tooltip="${c} uses ${d}"]\n`;
}
}
});
}
next();
},
function (next) {
for (let c in contractInheritance){
if(options.skipUndeployed && !self.getContract(contractInheritance[c]).deploy) continue;
relationshipString += `${idMapping[contractInheritance[c]]}->${idMapping[c]}[tooltip="${c} instance of ${contractInheritance[c]}"]\n`;
}
next();
},
function (next) {
let dot = `
digraph Contracts {
node[shape=record,style=filled]
edge[dir=back, arrowtail=empty]
${contractString}
${relationshipString}
}`;
let svg = Viz(dot);
fs.writeFileSync(options.output, svg, (err) => {
if (err) throw err;
next();
});
}
], function(_err, _result) {
});
}
getContract(contractName) {
return this.contracts.find((contract) => { return contract.className === contractName; });
}
}
module.exports = GraphGenerator;