diff --git a/lib/cmd.js b/lib/cmd.js index bdd64ac85..e6e178484 100644 --- a/lib/cmd.js +++ b/lib/cmd.js @@ -18,6 +18,7 @@ class Cmd { this.simulator(); this.test(); this.reset(); + this.graph(); this.upload(); this.versionCmd(); this.otherCommands(); @@ -179,6 +180,18 @@ class Cmd { }); } + graph() { + program + .command('graph [environment]') + .description('generates documentation based on the smart contracts configured') + .action(function (env, options) { + embark.graph({ + env: env || 'development', + logfile: options.logfile + }); + }); + } + reset() { program .command('reset') @@ -214,6 +227,7 @@ class Cmd { process.exit(0); }); } + } diff --git a/lib/cmds/graph.js b/lib/cmds/graph.js new file mode 100644 index 000000000..7621fe655 --- /dev/null +++ b/lib/cmds/graph.js @@ -0,0 +1,110 @@ +const Viz = require('viz.js'); +const fs = require('fs'); + +class GraphGenerator { + constructor(engine) { + this.engine = engine; + } + + generate() { + let id = 0; + let contractString = ""; + let relationshipString = ""; + let idMapping = {}; + let contractInheritance = {}; + + + for (let contract in this.engine.contractsManager.contracts) { + id++; + + idMapping[contract] = id; + + let contractLabel = ""; + + contractLabel += `${contract}`; + let tooltip = contract; + + if(this.engine.contractsManager.contracts[contract].instanceOf !== undefined && + this.engine.contractsManager.contracts[this.engine.contractsManager.contracts[contract].instanceOf] !== undefined){ + contractInheritance[contract] = this.engine.contractsManager.contracts[contract].instanceOf; + contractLabel += ": " + this.engine.contractsManager.contracts[contract].instanceOf; + tooltip += " instance of " + this.engine.contractsManager.contracts[contract].instanceOf; + } else { + contractLabel += "|"; + + for(let i = 0; i < this.engine.contractsManager.contracts[contract].abiDefinition.length; i++){ + switch(this.engine.contractsManager.contracts[contract].abiDefinition[i].type){ + case 'fallback': + contractLabel += "«fallback»()\\l"; + break; + case 'constructor': + contractLabel += "«constructor»("; + this.engine.contractsManager.contracts[contract].abiDefinition[i].inputs.forEach(function(elem, index){ + contractLabel += (index == 0 ? "" : ", ") + elem.type; + }); + contractLabel += ")\\l"; + break; + case 'event': + contractLabel += "«event»" + this.engine.contractsManager.contracts[contract].abiDefinition[i].name + "("; + this.engine.contractsManager.contracts[contract].abiDefinition[i].inputs.forEach(function(elem, index){ + contractLabel += (index == 0 ? "" : ", ") + elem.type; + }); + contractLabel += ")\\l"; + break; + default: break; + } + } + + let fHashes = this.engine.contractsManager.contracts[contract].functionHashes; + if(fHashes != {} && fHashes != undefined){ + for(let method in this.engine.contractsManager.contracts[contract].functionHashes){ + contractLabel += method + '\\l'; + } + } + } + + let others = ''; + if(!this.engine.contractsManager.contracts[contract].deploy){ + others = 'fontcolor="#c3c3c3", color="#a0a0a0"'; + tooltip += " (not deployed)"; + } + + contractString += `${id}[label = "{${contractLabel}}", tooltip="${tooltip}", fillcolor=gray95, ${others}]\n`; + + } + + for (let c in this.engine.contractsManager.contractDependencies){ + let contractDependencies = Array.from(new Set(this.engine.contractsManager.contractDependencies[c])); + contractDependencies.forEach(function(d){ + if(idMapping[c] !== undefined && idMapping[d] !== undefined){ + relationshipString += `${idMapping[d]}->${idMapping[c]}[constraint=true, arrowtail=diamond, tooltip="${c} uses ${d}"]\n`; + } + }); + } + + for (let c in contractInheritance){ + relationshipString += `${idMapping[contractInheritance[c]]}->${idMapping[c]}[tooltip="${c} instance of ${contractInheritance[c]}"]\n`; + } + + + let dot = ` + digraph Contracts { + node[shape=record,style=filled] + edge[dir=back, arrowtail=empty] + ${contractString} + ${relationshipString} + }`; + + let svg = Viz(dot); + + let filename = "diagram.svg"; + + fs.writeFileSync(filename, svg, (err) => { + if (err) throw err; + }); + + + } +} + +module.exports = GraphGenerator; diff --git a/lib/contracts/deploy_manager.js b/lib/contracts/deploy_manager.js index 0562574eb..edcd49e71 100644 --- a/lib/contracts/deploy_manager.js +++ b/lib/contracts/deploy_manager.js @@ -17,6 +17,7 @@ class DeployManager { this.gasLimit = false; this.fatalErrors = false; this.deployOnlyOnConfig = false; + this.onlyCompile = options.onlyCompile !== undefined ? options.onlyCompile : false; } deployContracts(done) { @@ -33,6 +34,13 @@ class DeployManager { self.contractsManager.deployOnlyOnConfig = self.deployOnlyOnConfig; // temporary, should refactor self.contractsManager.build(callback); }, + function checkCompileOnly(contractsManager, callback){ + if(self.onlyCompile){ + self.events.emit('contractsDeployed', contractsManager); + return done(); + } + return callback(contractsManager, null); + }, function checkWeb3IsConnected(contractsManager, callback) { if (!self.web3) { return callback(Error("no web3 instance found")); diff --git a/lib/core/engine.js b/lib/core/engine.js index b68b5f57c..b4ebe9283 100644 --- a/lib/core/engine.js +++ b/lib/core/engine.js @@ -153,7 +153,8 @@ class Engine { logger: this.logger, plugins: this.plugins, events: this.events, - contractsManager: this.contractsManager + contractsManager: this.contractsManager, + onlyCompile: options.onlyCompile }); this.events.on('file-event', function (fileType, _path) { diff --git a/lib/index.js b/lib/index.js index 3f04d4aca..dcb5ac667 100644 --- a/lib/index.js +++ b/lib/index.js @@ -194,6 +194,53 @@ class Embark { return new Test(options); } + graph(options) { + options.onlyCompile = true; + + let engine = new Engine({ + env: options.env, + version: this.version, + embarkConfig: options.embarkConfig || 'embark.json', + logfile: options.logfile + }); + engine.init(); + + + async.parallel([ + + function (callback) { + let pluginList = engine.plugins.listPlugins(); + if (pluginList.length > 0) { + engine.logger.info("loaded plugins: " + pluginList.join(", ")); + } + + engine.startMonitor(); + engine.startService("libraryManager"); + engine.startService("pipeline"); + engine.startService("codeGenerator"); + engine.startService("deployment", {onlyCompile: true}); + + engine.deployManager.deployContracts(function (err) { + callback(err); + }); + } + ], function (err, _result) { + if (err) { + engine.logger.error(err.message); + engine.logger.info(err.stack); + } else { + + const GraphGenerator = require('./cmds/graph.js'); + let graphGen = new GraphGenerator(engine); + graphGen.generate(); + + engine.logger.info("Done".underline); + process.exit(); + } + }); + + } + reset() { let resetCmd = require('./cmds/reset.js'); resetCmd(); diff --git a/package-lock.json b/package-lock.json index e2e62dd00..188cefdf9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10063,6 +10063,11 @@ "extsprintf": "1.3.0" } }, + "viz.js": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/viz.js/-/viz.js-1.8.1.tgz", + "integrity": "sha512-KrSNgnIxec+JCAqDPliO6xYA69ToH2WTYB2Kbt8Bp/XRUvm23rTyfffFi4rvQLFkIRNUz/xCnnqhh/gChhsgGA==" + }, "vlq": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/vlq/-/vlq-0.2.3.tgz", diff --git a/package.json b/package.json index 4f10fae6a..a09577bed 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "underscore": "^1.8.3", "underscore.string": "^3.3.4", "url-loader": "^0.6.2", + "viz.js": "^1.8.1", "web3": "1.0.0-beta.27", "webpack": "^3.10.0", "window-size": "^1.1.0"