import { common, Enum, Root, util } from 'protobufjs'; import { basename, dirname, extname, isAbsolute, join, resolve } from 'path'; import { existsSync, lstatSync, outputFileSync, readdirSync, readFileSync } from 'fs-extra'; import { compile } from 'handlebars'; import * as chalk from 'chalk'; import './handlebars/var-helper'; import './handlebars/comment-helper'; import './handlebars/enum-comment-helper'; import './handlebars/uncapitalize-hepler'; import './handlebars/type-helper'; import './handlebars/default-value-helper'; import { IGenOptions } from '../types'; /** Set Compiller */ export class Compiller { constructor(private readonly options: IGenOptions) {} public compile(): void { this.options.path.forEach((pkg) => { if (this.options.path.length === 1) { this.getProtoFiles(pkg); } }); } private resolveRootPath(root: Root): void { const paths = this.options.path; const length = paths.length; // Search include paths when resolving imports root.resolvePath = (origin, target) => { let resolved = util.path.resolve(util.path.normalize(origin), util.path.normalize(target), false); const idx = resolved.lastIndexOf('google/protobuf/'); if (idx > -1) { const altname = resolved.substring(idx); if (resolved.match(/google\/protobuf\//g).length > 1) { resolved = resolved.split('google/protobuf')[0] + util.path.normalize(target); } if (altname in common) { resolved = altname; } } if (existsSync(resolved)) { return resolved; } for (let i = 0; i < length; ++i) { const iresolved = util.path.resolve(paths[i] + '/', target); if (existsSync(iresolved)) { return iresolved; } } return resolved; }; } private walkTree(item: Root | any): void { if (item.nested) { Object.keys(item.nested).forEach((key) => { this.walkTree(item.nested[key]); }); } if (item.fields) { Object.keys(item.fields).forEach((key) => { const field = item.fields[key]; if (field.resolvedType) { // Record the field's parent name if (field.resolvedType.parent) { // Abuse the options object! if (!field.options) { field.options = {}; } if (field.type.indexOf('.') === -1) { field.options.parent = field.resolvedType.parent.name; } } // Record if the field is an enum if (field.resolvedType instanceof Enum) { // Abuse the options object! if (!field.options) { field.options = {}; } field.options.enum = true; } } }); } } private output(file: string, pkg: string, tmpl: string): void { const root = new Root(); this.resolveRootPath(root); root.loadSync(file, { keepCase: this.options.keepCase, alternateCommentMode: this.options.comments }).resolveAll(); this.walkTree(root); const results = compile(tmpl)(root); const outputFile = this.options.output ? join(this.options.output, file.replace(/^.+?[/\\]/, '')) : file; const outputPath = join(dirname(outputFile), `${basename(file, extname(file))}.ts`); outputFileSync(outputPath, results, 'utf8'); } private generate(path: string, pkg: string): void { let hbTemplate = resolve(__dirname, '../..', this.options.template); //If absolute path we will know have custom template option if (isAbsolute(path)) { hbTemplate = path; } if (!existsSync(hbTemplate)) { throw new Error(`Template ${hbTemplate} is not found`); } const tmpl = readFileSync(hbTemplate, 'utf8'); if (this.options.verbose) { console.log(chalk.blueBright(`-- found: `) + chalk.gray(path)); } this.output(path, pkg, tmpl); } private getProtoFiles(pkg: string): void { if (!existsSync(pkg)) { throw new Error(`Directory ${pkg} is not exist`); } const files = readdirSync(pkg); for (let i = 0; i < files.length; i++) { const filename = join(pkg, files[i]); const stat = lstatSync(filename); if (filename.indexOf(this.options.ignore.join()) > -1) { continue; } if (stat.isDirectory()) { this.getProtoFiles(filename); } else if (filename.indexOf(this.options.target.join()) > -1) { this.generate(filename, pkg); } } } }