Adding scaffold option to generate contract from scratch

This commit is contained in:
Richard Ramos 2018-10-12 13:50:35 -04:00 committed by Pascal Precht
parent b815ea4d44
commit d6de374ce7
No known key found for this signature in database
GPG Key ID: 0EE28D8D6FD85D7D
6 changed files with 192 additions and 46 deletions

View File

@ -326,19 +326,29 @@ class Cmd {
scaffold() { scaffold() {
program program
.command('scaffold [contract] [environment]') .command('scaffold [contract] [fields...]')
.option('--framework <framework>', 'UI framework to use. (default: react)') .option('--framework <framework>', 'UI framework to use. (default: react)')
.option('--contract-language <language>', 'Language used for the smart contract generation (default: solidity)')
.option('--overwrite', 'Overwrite existing files. (default: false)') .option('--overwrite', 'Overwrite existing files. (default: false)')
.action(function(contract, env, options){ .action(function(contract, fields, options){
if(contract === undefined){ if(contract === undefined){
console.log("contract name is required"); console.log("contract name is required");
process.exit(0); process.exit(0);
} }
const fieldMapping = {};
if(fields.length > 0){
// TODO: validate fields
fields.forEach(curr => {
const c = curr.split(':');
fieldMapping[c[0]] = c[1];
});
}
checkDeps(); checkDeps();
i18n.setOrDetectLocale(options.locale); i18n.setOrDetectLocale(options.locale);
options.env = env || 'development'; options.env = 'development';
options.logFile = options.logfile; // fix casing options.logFile = options.logfile; // fix casing
options.logLevel = options.loglevel; // fix casing options.logLevel = options.loglevel; // fix casing
options.onlyCompile = options.contracts; options.onlyCompile = options.contracts;
@ -346,7 +356,9 @@ class Cmd {
options.webpackConfigName = options.pipeline || 'development'; options.webpackConfigName = options.pipeline || 'development';
options.contract = contract; options.contract = contract;
options.framework = options.framework || 'react'; options.framework = options.framework || 'react';
options.contractLanguage = options.contractLanguage || 'solidity';
options.overwrite = options.overwrite || false; options.overwrite = options.overwrite || false;
options.fields = fieldMapping;
embark.scaffold(options); embark.scaffold(options);
}); });

View File

@ -437,50 +437,67 @@ class EmbarkController {
} }
scaffold(options) { scaffold(options) {
this.context = options.context || [constants.contexts.scaffold]; this.context = options.context || [constants.contexts.scaffold];
options.onlyCompile = true;
const Engine = require('../lib/core/engine.js'); const Engine = require('../lib/core/engine.js');
const engine = new Engine({ const engine = new Engine({
env: options.env, env: options.env,
client: options.client,
locale: options.locale,
version: this.version, version: this.version,
embarkConfig: options.embarkConfig || 'embark.json', embarkConfig: 'embark.json',
interceptLogs: false,
logFile: options.logFile, logFile: options.logFile,
context: this.context logLevel: options.logLevel,
events: options.events,
logger: options.logger,
config: options.config,
plugins: options.plugins,
context: this.context,
webpackConfigName: options.webpackConfigName
}); });
async.waterfall([ async.waterfall([
function (callback) { function initEngine(callback) {
engine.init({}, callback); engine.init({}, callback);
}, },
function (callback) { function startServices(callback) {
engine.startService("scaffolding");
callback();
},
function generateContract(callback){
engine.events.request('scaffolding:generate:contract', options, function(){
// Engine is re-initiated to be able to see the new contract file
engine.init({}, callback);
});
},
function initEngineServices(callback){
let pluginList = engine.plugins.listPlugins(); let pluginList = engine.plugins.listPlugins();
if (pluginList.length > 0) { if (pluginList.length > 0) {
engine.logger.info(__("loaded plugins") + ": " + pluginList.join(", ")); engine.logger.info(__("loaded plugins") + ": " + pluginList.join(", "));
} }
engine.startService("processManager"); engine.startService("processManager");
engine.startService("serviceMonitor");
engine.startService("libraryManager"); engine.startService("libraryManager");
engine.startService("pipeline"); engine.startService("codeRunner");
engine.startService("deployment", {onlyCompile: true});
engine.startService("web3"); engine.startService("web3");
engine.startService("scaffolding"); engine.startService("deployment", {onlyCompile: true});
engine.events.request('deploy:contracts', callback); callback();
} },
], (err) => { function deploy(callback) {
if (err) { engine.events.request('deploy:contracts', function (err) {
engine.logger.error(err.message); callback(err);
engine.logger.info(err.stack); });
} else { },
function generateUI(callback){
engine.events.request("scaffolding:generate", options, () => { engine.events.request("scaffolding:generate", options, () => {
engine.logger.info(__("finished generating the UI").underline);
process.exit(); callback();
}); });
} }
], function (_err) {
engine.logger.info(__("finished generating the UI").underline);
process.exit();
}); });
} }

View File

@ -14,7 +14,7 @@ class {{capitalize name}}Form{{@index}} extends Component {
{{#if inputs.length}} {{#if inputs.length}}
input: { input: {
{{#each inputs}} {{#each inputs}}
{{name}}: {{#ifeq type 'bool'}}false{{else}}''{{/ifeq}}{{#unless @last}},{{/unless}} {{#ifeq name ''}}field{{else}}{{name}}{{/ifeq}}: {{#ifeq type 'bool'}}false{{else}}''{{/ifeq}}{{#unless @last}},{{/unless}}
{{/each}} {{/each}}
}, },
{{/if}} {{/if}}
@ -50,7 +50,7 @@ class {{capitalize name}}Form{{@index}} extends Component {
try { try {
{{#ifview stateMutability}} {{#ifview stateMutability}}
const result = await {{../contractName}}.methods{{methodname ../functions name inputs}}({{#each inputs}}input.{{name}}{{#unless @last}}, {{/unless}}{{/each}}).call() const result = await {{../contractName}}.methods{{methodname ../functions name inputs}}({{#each inputs}}input.{{#ifeq name ''}}field{{else}}{{name}}{{/ifeq}}{{#unless @last}}, {{/unless}}{{/each}}).call()
{{#iflengthgt outputs 1}} {{#iflengthgt outputs 1}}
this.setState({output: { this.setState({output: {
{{#each outputs}} {{#each outputs}}
@ -61,7 +61,7 @@ class {{capitalize name}}Form{{@index}} extends Component {
this.setState({output: result}); this.setState({output: result});
{{/iflengthgt}} {{/iflengthgt}}
{{else}} {{else}}
const toSend = {{../contractName}}.methods{{methodname ../functions name inputs}}({{#each inputs}}input.{{name}}{{#unless @last}}, {{/unless}}{{/each}}); const toSend = {{../contractName}}.methods{{methodname ../functions name inputs}}({{#each inputs}}input.{{#ifeq name ''}}field{{else}}{{name}}{{/ifeq}}{{#unless @last}}, {{/unless}}{{/each}});
const estimatedGas = await toSend.estimateGas({from: web3.eth.defaultAccount}); const estimatedGas = await toSend.estimateGas({from: web3.eth.defaultAccount});
@ -92,17 +92,17 @@ class {{capitalize name}}Form{{@index}} extends Component {
{{#if inputs.length}} {{#if inputs.length}}
{{#each inputs}} {{#each inputs}}
<FormGroup> <FormGroup>
<ControlLabel>{{name}}</ControlLabel> <ControlLabel>{{#ifeq name ''}}field{{else}}{{name}}{{/ifeq}}</ControlLabel>
{{#ifeq type 'bool'}} {{#ifeq type 'bool'}}
<Checkbox <Checkbox
onClick={(e) => this.handleCheckbox(e, '{{name}}')} onClick={(e) => this.handleCheckbox(e, '{{#ifeq name ''}}field{{else}}{{name}}{{/ifeq}}')}
/> />
{{else}} {{else}}
<FormControl <FormControl
type="text" type="text"
defaultValue={ input.{{name}} } defaultValue={ input.{{#ifeq name ''}}field{{else}}{{name}}{{/ifeq}} }
placeholder="{{type}}" placeholder="{{type}}"
onChange={(e) => this.handleChange(e, '{{name}}')} onChange={(e) => this.handleChange(e, '{{#ifeq name ''}}field{{else}}{{name}}{{/ifeq}}')}
/> />
{{/ifeq}} {{/ifeq}}
</FormGroup> </FormGroup>

View File

@ -0,0 +1,50 @@
const Handlebars = require('handlebars');
const fs = require('../../core/fs');
class ScaffoldingSolidity {
constructor(embark, options){
this.embark = embark;
this.options = options;
this.embark.registerDappGenerator('solidity', this.build.bind(this));
}
_generateFile(contract, templateFilename, extension, data, overwrite){
const filename = contract.className.toLowerCase() + '.' + extension;
const filePath = './contracts/' + filename;
if (!overwrite && fs.existsSync(filePath)){
throw new Error("file '" + filePath + "' already exists");
}
const templatePath = fs.embarkPath('lib/modules/scaffolding-solidity/templates/' + templateFilename);
const source = fs.readFileSync(templatePath).toString();
const template = Handlebars.compile(source);
// Write template
const result = template(data);
fs.writeFileSync(filePath, result);
}
build(contractDetails, overwrite, cb){
const {contract, fields} = contractDetails;
try {
const filename = contract.className.toLowerCase();
this._generateFile(contract, 'contract.sol.tpl', 'sol',
{
'contractName': contract.className,
'structName': contract.className + "Struct",
'fields': Object.keys(fields).map(f => { return {name:f, type:fields[f]}; })
}, overwrite);
this.embark.logger.info("contracts/" + filename + ".sol generated");
cb();
} catch(error){
this.embark.logger.error(error.message);
process.exit(1);
}
}
}
module.exports = ScaffoldingSolidity;

View File

@ -0,0 +1,45 @@
pragma solidity ^0.4.24;
contract {{contractName}} {
struct {{structName}} {
{{#each fields}}
{{type}} {{name}};
{{/each}}
}
{{structName}}[] public items;
event ItemCreated(uint id, address createdBy);
event ItemDeleted(uint id, address deletedBy);
event ItemUpdated(uint id, address updatedBy);
function add({{#each fields}}{{type}} _{{name}}{{#unless @last}}, {{/unless}}{{/each}}) public {
uint id = items.length++;
items[id] = {{structName}}({
{{#each fields}}
{{name}}: _{{name}}{{#unless @last}},{{/unless}}
{{/each}}
});
emit ItemCreated(id, msg.sender);
}
function edit(uint _id, {{#each fields}}{{type}} _{{name}}{{#unless @last}}, {{/unless}}{{/each}}) public {
require(_id < items.length, "Invalid {{structName}} id");
{{#each fields}}
items[_id].{{name}} = _{{name}};
{{/each}}
emit ItemUpdated(_id, msg.sender);
}
function remove(uint _id) public {
require(_id < items.length, "Invalid {{structName}} id");
delete items[_id];
emit ItemDeleted(_id, msg.sender);
}
}

View File

@ -6,7 +6,14 @@ class Scaffolding {
engine.events.setCommandHandler("scaffolding:generate", (options, cb) => { engine.events.setCommandHandler("scaffolding:generate", (options, cb) => {
this.framework = options.framework; this.framework = options.framework;
this.generate(options.contract, options.overwrite, cb); this.fields = options.fields;
this.generate(options.contract, options.overwrite, false, cb);
});
engine.events.setCommandHandler("scaffolding:generate:contract", (options, cb) => {
this.framework = options.contractLanguage;
this.fields = options.fields;
this.generate(options.contract, options.overwrite, true, cb);
}); });
} }
@ -14,32 +21,47 @@ class Scaffolding {
return this.engine.config.contractsConfig.contracts[contractName] !== undefined; return this.engine.config.contractsConfig.contracts[contractName] !== undefined;
} }
generate(contractName, overwrite, cb){ getScaffoldPlugin(framework){
if(this.framework === 'react'){
this.plugins.loadInternalPlugin('scaffolding-react', this.options);
}
let dappGenerators = this.plugins.getPluginsFor('dappGenerator'); let dappGenerators = this.plugins.getPluginsFor('dappGenerator');
let builder;
let build = null;
dappGenerators.forEach((plugin) => { dappGenerators.forEach((plugin) => {
plugin.dappGenerators.forEach((d) => { plugin.dappGenerators.forEach((d) => {
if(d.framework === this.framework){ if(d.framework === framework){
build = d.cb; builder = d.cb;
} }
}); });
}); });
return builder;
}
if(build === null){ generate(contractName, overwrite, preDeployment, cb){
switch(this.framework){
case 'react':
this.plugins.loadInternalPlugin('scaffolding-react', this.options);
break;
case 'solidity':
this.plugins.loadInternalPlugin('scaffolding-solidity', this.options);
break;
default:
}
const fields = this.fields;
let build = this.getScaffoldPlugin(this.framework);
if(!build){
this.engine.logger.error("Could not find plugin for framework '" + this.framework + "'"); this.engine.logger.error("Could not find plugin for framework '" + this.framework + "'");
process.exit();
cb(); cb();
} else if(!this.isContract(contractName)){ } else if(!this.isContract(contractName) && Object.getOwnPropertyNames(this.fields).length === 0){
this.engine.logger.error("contract '" + contractName + "' does not exist"); this.engine.logger.error("contract '" + contractName + "' does not exist");
cb(); cb();
} else if(preDeployment) {
build({contract: {className: contractName}, fields}, overwrite, cb);
} else { } else {
// Contract exists
this.engine.events.request("contracts:list", (_err, contractsList) => { this.engine.events.request("contracts:list", (_err, contractsList) => {
if(_err) throw new Error(_err); if(_err) throw new Error(_err);
const contract = contractsList.find(x => x.className === contractName); const contract = contractsList.find(x => x.className === contractName);
try { try {
build(contract, overwrite, cb); build(contract, overwrite, cb);