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() {
program
.command('scaffold [contract] [environment]')
.command('scaffold [contract] [fields...]')
.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)')
.action(function(contract, env, options){
.action(function(contract, fields, options){
if(contract === undefined){
console.log("contract name is required");
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();
i18n.setOrDetectLocale(options.locale);
options.env = env || 'development';
options.env = 'development';
options.logFile = options.logfile; // fix casing
options.logLevel = options.loglevel; // fix casing
options.onlyCompile = options.contracts;
@ -346,7 +356,9 @@ class Cmd {
options.webpackConfigName = options.pipeline || 'development';
options.contract = contract;
options.framework = options.framework || 'react';
options.contractLanguage = options.contractLanguage || 'solidity';
options.overwrite = options.overwrite || false;
options.fields = fieldMapping;
embark.scaffold(options);
});

View File

@ -437,51 +437,68 @@ class EmbarkController {
}
scaffold(options) {
this.context = options.context || [constants.contexts.scaffold];
options.onlyCompile = true;
const Engine = require('../lib/core/engine.js');
const engine = new Engine({
env: options.env,
client: options.client,
locale: options.locale,
version: this.version,
embarkConfig: options.embarkConfig || 'embark.json',
embarkConfig: 'embark.json',
interceptLogs: false,
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([
function (callback) {
function initEngine(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();
if (pluginList.length > 0) {
engine.logger.info(__("loaded plugins") + ": " + pluginList.join(", "));
}
engine.startService("processManager");
engine.startService("serviceMonitor");
engine.startService("libraryManager");
engine.startService("pipeline");
engine.startService("deployment", {onlyCompile: true});
engine.startService("codeRunner");
engine.startService("web3");
engine.startService("scaffolding");
engine.events.request('deploy:contracts', callback);
}
], (err) => {
if (err) {
engine.logger.error(err.message);
engine.logger.info(err.stack);
} else {
engine.startService("deployment", {onlyCompile: true});
callback();
},
function deploy(callback) {
engine.events.request('deploy:contracts', function (err) {
callback(err);
});
},
function generateUI(callback){
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();
});
}
upload(options) {

View File

@ -14,7 +14,7 @@ class {{capitalize name}}Form{{@index}} extends Component {
{{#if inputs.length}}
input: {
{{#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}}
},
{{/if}}
@ -50,7 +50,7 @@ class {{capitalize name}}Form{{@index}} extends Component {
try {
{{#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}}
this.setState({output: {
{{#each outputs}}
@ -61,7 +61,7 @@ class {{capitalize name}}Form{{@index}} extends Component {
this.setState({output: result});
{{/iflengthgt}}
{{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});
@ -92,17 +92,17 @@ class {{capitalize name}}Form{{@index}} extends Component {
{{#if inputs.length}}
{{#each inputs}}
<FormGroup>
<ControlLabel>{{name}}</ControlLabel>
<ControlLabel>{{#ifeq name ''}}field{{else}}{{name}}{{/ifeq}}</ControlLabel>
{{#ifeq type 'bool'}}
<Checkbox
onClick={(e) => this.handleCheckbox(e, '{{name}}')}
onClick={(e) => this.handleCheckbox(e, '{{#ifeq name ''}}field{{else}}{{name}}{{/ifeq}}')}
/>
{{else}}
<FormControl
type="text"
defaultValue={ input.{{name}} }
defaultValue={ input.{{#ifeq name ''}}field{{else}}{{name}}{{/ifeq}} }
placeholder="{{type}}"
onChange={(e) => this.handleChange(e, '{{name}}')}
onChange={(e) => this.handleChange(e, '{{#ifeq name ''}}field{{else}}{{name}}{{/ifeq}}')}
/>
{{/ifeq}}
</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) => {
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;
}
generate(contractName, overwrite, cb){
if(this.framework === 'react'){
this.plugins.loadInternalPlugin('scaffolding-react', this.options);
}
getScaffoldPlugin(framework){
let dappGenerators = this.plugins.getPluginsFor('dappGenerator');
let build = null;
let builder;
dappGenerators.forEach((plugin) => {
plugin.dappGenerators.forEach((d) => {
if(d.framework === this.framework){
build = d.cb;
if(d.framework === framework){
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 + "'");
process.exit();
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");
cb();
} else if(preDeployment) {
build({contract: {className: contractName}, fields}, overwrite, cb);
} else {
// Contract exists
this.engine.events.request("contracts:list", (_err, contractsList) => {
if(_err) throw new Error(_err);
const contract = contractsList.find(x => x.className === contractName);
try {
build(contract, overwrite, cb);