diff options
Diffstat (limited to 'packages/typed-contracts/src')
-rw-r--r-- | packages/typed-contracts/src/globals.d.ts | 1 | ||||
-rw-r--r-- | packages/typed-contracts/src/index.ts | 85 | ||||
-rw-r--r-- | packages/typed-contracts/src/types.ts | 4 | ||||
-rw-r--r-- | packages/typed-contracts/src/utils.ts | 56 |
4 files changed, 146 insertions, 0 deletions
diff --git a/packages/typed-contracts/src/globals.d.ts b/packages/typed-contracts/src/globals.d.ts new file mode 100644 index 000000000..80c6a8b8e --- /dev/null +++ b/packages/typed-contracts/src/globals.d.ts @@ -0,0 +1 @@ +declare module 'to-snake-case'; diff --git a/packages/typed-contracts/src/index.ts b/packages/typed-contracts/src/index.ts new file mode 100644 index 000000000..baa3ebc91 --- /dev/null +++ b/packages/typed-contracts/src/index.ts @@ -0,0 +1,85 @@ +#!/usr/bin/env node + +import chalk from 'chalk'; +import * as fs from 'fs'; +import {sync as globSync} from 'glob'; +import * as Handlebars from 'handlebars'; +import * as _ from 'lodash'; +import * as mkdirp from 'mkdirp'; +import * as yargs from 'yargs'; + +import * as toSnakeCase from 'to-snake-case'; +import * as Web3 from 'web3'; + +import {ParamKind} from './types'; +import {utils} from './utils'; + +const args = yargs + .option('abiGlob', { + describe: 'Glob pattern to search for ABI JSON files', + type: 'string', + demand: true, + }) + .option('templates', { + describe: 'Folder where to search for templates', + type: 'string', + demand: true, + }) + .option('output', { + describe: 'Folder where to put the output files', + type: 'string', + demand: true, + }) + .argv; + +function writeOutputFile(name: string, renderedTsCode: string): void { + const fileName = toSnakeCase(name); + const filePath = `${args.output}/${fileName}.ts`; + fs.writeFileSync(filePath, renderedTsCode); + utils.log(`Created: ${chalk.bold(filePath)}`); +} + +Handlebars.registerHelper('parameterType', utils.solTypeToTsType.bind(utils, ParamKind.Input)); +Handlebars.registerHelper('returnType', utils.solTypeToTsType.bind(utils, ParamKind.Output)); +const partialTemplateFileNames = globSync(`${args.templates}/partials/**/*.mustache`); +for (const partialTemplateFileName of partialTemplateFileNames) { + const namedContent = utils.getNamedContent(partialTemplateFileName); + Handlebars.registerPartial(namedContent.name, namedContent.content); +} + +const mainTemplate = utils.getNamedContent(`${args.templates}/contract.mustache`); +const template = Handlebars.compile(mainTemplate.content); +const abiFileNames = globSync(args.abiGlob); +if (_.isEmpty(abiFileNames)) { + utils.log(`${chalk.red(`No ABI files found.`)}`); + utils.log(`Please make sure you've passed a correct folder and they have ${chalk.bold('*.json')} extensions`); + process.exit(1); +} else { + utils.log(`Found ${chalk.green(`${abiFileNames.length}`)} ${chalk.bold('ABI')} files`); + mkdirp.sync(args.output); +} +for (const abiFileName of abiFileNames) { + const namedContent = utils.getNamedContent(abiFileName); + utils.log(`Processing: ${chalk.bold(namedContent.name)}...`); + const parsedContent = JSON.parse(namedContent.content); + const ABI = _.isArray(parsedContent) ? + parsedContent : // ABI file + parsedContent.abi; // Truffle contracts file + const methodAbis = ABI.filter((abi: Web3.AbiDefinition) => abi.type === 'function') as Web3.MethodAbi[]; + methodAbis.map(methodAbi => { + methodAbi.inputs.map(input => { + if (_.isEmpty(input.name)) { + // Auto-generated getters don't have parameter names + input.name = 'index'; + } + }); + // This will make temlates simpler + (methodAbi.outputs as any).singleReturnValue = methodAbi.outputs.length === 1; + }); + const templateData = { + contractName: namedContent.name, + methodAbis, + }; + const renderedTsCode = template(templateData); + writeOutputFile(namedContent.name, renderedTsCode); +} diff --git a/packages/typed-contracts/src/types.ts b/packages/typed-contracts/src/types.ts new file mode 100644 index 000000000..3331588da --- /dev/null +++ b/packages/typed-contracts/src/types.ts @@ -0,0 +1,4 @@ +export enum ParamKind { + Input = 'input', + Output = 'output', +} diff --git a/packages/typed-contracts/src/utils.ts b/packages/typed-contracts/src/utils.ts new file mode 100644 index 000000000..0600188a1 --- /dev/null +++ b/packages/typed-contracts/src/utils.ts @@ -0,0 +1,56 @@ +import * as fs from 'fs'; +import * as _ from 'lodash'; +import * as path from 'path'; + +import {ParamKind} from './types'; + +export const utils = { + solTypeToTsType(paramKind: ParamKind, solType: string): string { + const trailingArrayRegex = /\[\d*\]$/; + if (solType.match(trailingArrayRegex)) { + const arrayItemSolType = solType.replace(trailingArrayRegex, ''); + const arrayItemTsType = utils.solTypeToTsType(paramKind, arrayItemSolType); + const arrayTsType = `${arrayItemTsType}[]`; + return arrayTsType; + } else { + const solTypeRegexToTsType = [ + {regex: '^string$', tsType: 'string'}, + {regex: '^address$', tsType: 'string'}, + {regex: '^bool$', tsType: 'boolean'}, + {regex: '^u?int\\d*$', tsType: 'BigNumber'}, + {regex: '^bytes\\d*$', tsType: 'string'}, + ]; + if (paramKind === ParamKind.Input) { + // web3 allows to pass those an non-bignumbers and that's nice + // but it always returns stuff as BigNumbers + solTypeRegexToTsType.unshift({regex: '^u?int(8|16|32)?$', tsType: 'number|BigNumber'}); + } + for (const regexAndTxType of solTypeRegexToTsType) { + const {regex, tsType} = regexAndTxType; + if (solType.match(regex)) { + return tsType; + } + } + throw new Error(`Unknown type ${solType}`); + } + }, + log(...args: any[]): void { + console.log(...args); // tslint:disable-line:no-console + }, + getPartialNameFromFileName(filename: string): string { + const name = path.parse(filename).name; + return name; + }, + getNamedContent(filename: string): {name: string; content: string} { + const name = path.parse(filename).name; + try { + const content = fs.readFileSync(filename).toString(); + return { + name, + content, + }; + } catch (err) { + throw new Error(`Failed to read ${filename}: ${err}`); + } + }, +}; |