From 2dbb218c41787ae15ce0adbac793b4d6594f8542 Mon Sep 17 00:00:00 2001 From: Jeremy Robertson Date: Fri, 25 Oct 2019 00:34:47 -0600 Subject: [PATCH 1/2] Can generate subTemplates within templates --- src/model/Variable.ts | 23 +++++- src/model/VariableTable.ts | 14 ++++ src/model/types.ts | 11 +++ src/worker/CodesGenerator.ts | 147 +++++++++++++++++++++++++++++++---- src/worker/getUserInput.ts | 8 +- 5 files changed, 185 insertions(+), 18 deletions(-) diff --git a/src/model/Variable.ts b/src/model/Variable.ts index f16d965..4bf706c 100644 --- a/src/model/Variable.ts +++ b/src/model/Variable.ts @@ -1,7 +1,12 @@ import config from '../utils/config'; import { duplicate } from '../utils/string'; import { AUTO, toCamelCase } from '../utils/identifier'; -import { IVariable, IVariableConfigDTO, IIdentifierStyleDTO } from './types'; +import { + IVariable, + IVariableConfigDTO, + IIdentifierStyleDTO, + ISubTemplateVariableConfigDTO, +} from './types'; /** * A variable will be uniquely identified by it's `name` property. @@ -15,6 +20,7 @@ export default class Variable implements IVariable { case: variableCase = AUTO, prefixUnderscore = 0, suffixUnderscore = 0, + subTemplates, } = variableConfigDTO; const { noTransformation = void 0, @@ -33,6 +39,12 @@ export default class Variable implements IVariable { prefix: prefix || duplicate('_', prefixUnderscore), suffix: suffix || duplicate('_', suffixUnderscore), }; + + if (subTemplates && subTemplates.length > 0) { + subTemplates.forEach(template => this._subTemplates.set(template.as, template)); + } + + this.getSubTemplates = this.getSubTemplates.bind(this); } public get name(): string { @@ -51,6 +63,14 @@ export default class Variable implements IVariable { this._value = val; } + public get hasSubTemplates() { + return this._subTemplates.size > 0; + } + + public getSubTemplates() { + return this._subTemplates; + } + private normalizeName(name: string): string { return toCamelCase(name); } @@ -58,4 +78,5 @@ export default class Variable implements IVariable { private _name: string; private _value: string | undefined; private _style: IIdentifierStyleDTO; + private _subTemplates = new Map(); } diff --git a/src/model/VariableTable.ts b/src/model/VariableTable.ts index bca9e32..5f5ea9b 100644 --- a/src/model/VariableTable.ts +++ b/src/model/VariableTable.ts @@ -2,15 +2,28 @@ import { toCamelCase } from '../utils/identifier'; import { IVariableTable, IVariableValueDTO, IVariable } from './types'; export default class VariableTable implements IVariableTable { + constructor() { + this.get = this.get.bind(this); + this.getAliases = this.getAliases.bind(this); + } public get(variableName: string): IVariable | undefined { return this._table.get(this.normalizeName(variableName)); } + public getAliases(alias: string): IVariable | undefined { + return this._aliases.get(this.normalizeName(alias)); + } + /** * Add a variable into the variable table. * If the variable table already has the same variable, cover it with the new one. */ public add(variable: IVariable): void { + if (variable.hasSubTemplates) { + variable.getSubTemplates().forEach(subTemplate => { + this._aliases.set(this.normalizeName(subTemplate.as), variable); + }); + } this._table.set(this.normalizeName(variable.name), variable); } @@ -36,4 +49,5 @@ export default class VariableTable implements IVariableTable { } private _table = new Map(); + private _aliases = new Map(); } diff --git a/src/model/types.ts b/src/model/types.ts index 169fe0a..9eb93c1 100644 --- a/src/model/types.ts +++ b/src/model/types.ts @@ -6,6 +6,13 @@ export interface IIdentifierStyleDTO { suffix: string; } +export interface ISubTemplateVariableConfigDTO { + file: string; + as: string; + variableAlias: string; + joinChars: string; +} + export interface IVariableConfigDTO { name: string; defaultValue?: string; @@ -15,6 +22,7 @@ export interface IVariableConfigDTO { case?: string; prefixUnderscore?: number; suffixUnderscore?: number; + subTemplates?: ISubTemplateVariableConfigDTO[]; } export interface ITemplateConfigDTO { @@ -32,6 +40,8 @@ export interface IVariableValueDTO { export interface IVariableDTO extends IVariableValueDTO { style: IIdentifierStyleDTO; + hasSubTemplates: boolean; + getSubTemplates: () => Map; } export interface IUserInputRequestDTO { @@ -57,6 +67,7 @@ export interface IVariable extends IVariableDTO {} export interface IVariableTable { get(variableName: string): IVariable | undefined; + getAliases(alias: string): IVariable | undefined; add(variable: IVariable): void; delete(variableName: string): void; variables(): IVariable[]; diff --git a/src/worker/CodesGenerator.ts b/src/worker/CodesGenerator.ts index 99bfc03..eeb3ab0 100644 --- a/src/worker/CodesGenerator.ts +++ b/src/worker/CodesGenerator.ts @@ -6,24 +6,33 @@ import { convertIdentifierStyle } from '../utils/identifier'; import { FileAlreadyExistsError } from '../utils/error'; import { trimStart, trimEnd, escapeRegExpSpecialChars } from '../utils/string'; import config from '../utils/config'; -import { ITemplate, IVariableTable } from '../model/types'; +import { ITemplate, IVariableTable, IVariable, IIdentifierStyleDTO } from '../model/types'; +import Variable from '../model/Variable'; +const subTemplatesDir = 'subTemplates'; export default class CodesGenerator { private _template: ITemplate; private _destDirPath: string; + private _subTemplates: Map; public constructor(template: ITemplate, destDirPath: string) { this._template = template; this._destDirPath = destDirPath; + this._subTemplates = new Map(); + + this.resolveSubTemplate = this.resolveSubTemplate.bind(this); } public async execute(): Promise { const template = this._template; const baseNames = await this.globDir(template.rootPath); const srcBaseNames = baseNames.filter( - (fileName: string): boolean => fileName !== config.configFile + (fileName: string): boolean => + fileName !== config.configFile && fileName.indexOf(subTemplatesDir) < 0 ); + await this.loadSubTemplates(); + await Promise.all( srcBaseNames.map((srcBaseName: string) => { const srcPath = resolvePath(template.rootPath, srcBaseName); @@ -34,6 +43,24 @@ export default class CodesGenerator { ); } + private async loadSubTemplates() { + const encoding = config.encoding; + const template = this._template; + const subTemplatesPath = resolvePath(template.rootPath, subTemplatesDir); + const subTemplateNames = await this.globDir(subTemplatesPath); + if (subTemplateNames && subTemplateNames.length > 0) { + await Promise.all( + subTemplateNames.map(async fileName => { + const content = (await readFile(resolvePath(subTemplatesPath, fileName), { + encoding, + })) as string; + this._subTemplates.set(fileName, content); + return content; + }) + ); + } + } + private async generate(srcPath: string, destPath: string): Promise { const exist = existsSync(destPath); if (exist && !this._template.allowExistingFolder) { @@ -70,6 +97,7 @@ export default class CodesGenerator { } private globDir(dir: string): Promise { + //JRM: Add subtemplate as ignored const ignore = [...config.ignore, ...this._template.ignore]; return new Promise((resolve, reject): void => { glob('*', { dot: true, cwd: dir, ignore }, (err, matches) => { @@ -85,28 +113,115 @@ export default class CodesGenerator { const varLeft = config.variableLeftBoundary; const varRight = config.variableRightBoundary; const varStyle = config.variableStyleBoundary; - const varStylePatternStr = escapeRegExpSpecialChars(varStyle); - let pattern; - if (varLeft === '___' && varRight === '___') { - pattern = new RegExp(`___([a-zA-Z\d-${varStylePatternStr}]|[a-zA-Z][_a-zA-Z\d-${varStylePatternStr}]*[a-zA-Z\d-${varStylePatternStr}])___`, "g"); - } else { - const varLeftPatternStr = escapeRegExpSpecialChars(varLeft); - const varRightPatternStr = escapeRegExpSpecialChars(varRight); - const patternStr = `${varLeftPatternStr}[_a-zA-Z\\d\\-${varStylePatternStr}]+${varRightPatternStr}`; - pattern = new RegExp(patternStr, 'g'); + let pattern = this.buildPattern(varStyle, varLeft, varRight); + + const contentWithSubTemplates = this.replaceVariables( + content, + pattern, + variableTable.getAliases, + this.resolveSubTemplate + ); + + return this.replaceVariables( + contentWithSubTemplates, + pattern, + variableTable.get, + this.convertStyleHelper + ); + } + + private convertStyleHelper(variable: IVariable, key: string, style: IIdentifierStyleDTO) { + if (!variable) { + return ''; + } + if (!variable.value) { + return variable.toString() || ''; } + return convertIdentifierStyle(variable.value, style, key); + } - return content.replace(pattern, (m: string) => { + private replaceVariables( + content: string, + pattern: RegExp, + variableGetter: (key: string) => IVariable | undefined, + resolver: (variable: IVariable, key: string, style: IIdentifierStyleDTO) => string + ): string { + const varLeft = config.variableLeftBoundary; + const varRight = config.variableRightBoundary; + const varStyle = config.variableStyleBoundary; + const newContent = content.replace(pattern, (m: string) => { const key = trimStart(trimEnd(m, varRight), varLeft); const keyStyle = key.split(varStyle); - const variable = variableTable.get(keyStyle[0]); + const variable = variableGetter(keyStyle[0]) || ''; + if (!variable || !variable.value) { return m; } + var style = variable.style; - if (keyStyle.length > 1) - style.case = keyStyle[1]; - return convertIdentifierStyle(variable.value, style, key); + if (keyStyle.length > 1) style.case = keyStyle[1]; + + return resolver(variable, key, style); }); + return newContent; + } + + private buildPattern(varStyle: string, varLeft: string, varRight: string) { + // const varStylePatternStr = escapeRegExpSpecialChars(varStyle); + let pattern; + if (varLeft === '___' && varRight === '___') { + // /___([a-zA-Zd-:]|[a-zA-Z][_a-zA-Zd-:]*[a-zA-Zd-:])___/; + pattern = /___([a-zA-Z\d-]|[a-zA-Z][_a-zA-Z\d-]*[a-zA-Z\d-])___/g; + // pattern = new RegExp( + // `___([a-zA-Z\d-${varStylePatternStr}]|[a-zA-Z][_a-zA-Z\d-${varStylePatternStr}]*[a-zA-Z\d-${varStylePatternStr}])___`, + // 'g' + // ); + } else { + const varLeftPatternStr = escapeRegExpSpecialChars(varLeft); + const varRightPatternStr = escapeRegExpSpecialChars(varRight); + const patternStr = `${varLeftPatternStr}[_a-zA-Z\\d\\-]+${varRightPatternStr}`; + // const patternStr = `${varLeftPatternStr}[_a-zA-Z\\d\\-${varStylePatternStr}]+${varRightPatternStr}`; + pattern = new RegExp(patternStr, 'g'); + } + return pattern; + } + + private resolveSubTemplate(variable: IVariable, key: string, style: IIdentifierStyleDTO): string { + if (!variable || !variable.value) { + return ''; + } + const subTemplateConfig = variable.getSubTemplates().get(key); + if (subTemplateConfig) { + const subTemplateContent = + this._subTemplates.get(subTemplateConfig.file) || 'Bad subtemplate map'; + const variableSplits = variable.value.split(','); + const content = variableSplits + .map(v => { + const varName = v.trim(); + let pattern = this.buildPattern( + config.variableStyleBoundary, + config.variableLeftBoundary, + config.variableRightBoundary + ); + + return this.replaceVariables( + subTemplateContent, + pattern, + key => + (key === subTemplateConfig.variableAlias && + new Variable({ + name: subTemplateConfig.variableAlias, + defaultValue: varName, + style: variable.style, + })) || + undefined, + this.convertStyleHelper + ); + }) + .join(subTemplateConfig.joinChars); + console.log('hi'); + return content; + } + return ''; } } diff --git a/src/worker/getUserInput.ts b/src/worker/getUserInput.ts index 8261c98..a35c16f 100644 --- a/src/worker/getUserInput.ts +++ b/src/worker/getUserInput.ts @@ -70,7 +70,13 @@ export default function getUserInput( const vars = template.variableTable.variables(); const userInputRequest: IUserInputRequestDTO = { templateName, - variables: vars.map(({ name, style, value }) => ({ name, style, value })), + variables: vars.map(({ name, style, value, getSubTemplates }) => ({ + name, + style, + value, + getSubTemplates: getSubTemplates, + hasSubTemplates: getSubTemplates().size > 0, + })), destDir: { basePath: workspacePath, relativePath: destDirRelativePath, From 9fb4510bb457213e01f3784c34128a1d5d941300 Mon Sep 17 00:00:00 2001 From: Jeremy Robertson Date: Fri, 25 Oct 2019 00:44:04 -0600 Subject: [PATCH 2/2] examples folder --- examples/subTemplates/___ClassName___.js | 9 ++++++ .../__tests__/___ClassName___.test.js | 12 +++++++ .../subTemplates/subTemplates/objectStub.js | 1 + examples/subTemplates/subTemplates/plain.js | 1 + examples/subTemplates/template.config.json | 32 +++++++++++++++++++ tsconfig.json | 2 +- 6 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 examples/subTemplates/___ClassName___.js create mode 100644 examples/subTemplates/__tests__/___ClassName___.test.js create mode 100644 examples/subTemplates/subTemplates/objectStub.js create mode 100644 examples/subTemplates/subTemplates/plain.js create mode 100644 examples/subTemplates/template.config.json diff --git a/examples/subTemplates/___ClassName___.js b/examples/subTemplates/___ClassName___.js new file mode 100644 index 0000000..e76bf2a --- /dev/null +++ b/examples/subTemplates/___ClassName___.js @@ -0,0 +1,9 @@ +import { Operations } from '../../helpers'; + +class ___ClassName___ extends Operations { + constructor(start, ___interfaceList___) { + super(start, { ___interfaceList___ }); + } +} + +export default ___ClassName___; diff --git a/examples/subTemplates/__tests__/___ClassName___.test.js b/examples/subTemplates/__tests__/___ClassName___.test.js new file mode 100644 index 0000000..c1761ac --- /dev/null +++ b/examples/subTemplates/__tests__/___ClassName___.test.js @@ -0,0 +1,12 @@ +import ___ClassName___ from '../../src/___ClassName___'; + +let chain = null; + +describe.only('___ClassName___', () => { + beforeEach(() => { + chain = new ___ClassName___(_START_, ___interfaceStubs___); + }); + it('chain is not null', () => { + expect(chain).not.toBeNull(); + }); +}); diff --git a/examples/subTemplates/subTemplates/objectStub.js b/examples/subTemplates/subTemplates/objectStub.js new file mode 100644 index 0000000..949edcb --- /dev/null +++ b/examples/subTemplates/subTemplates/objectStub.js @@ -0,0 +1 @@ +{/*___item___*/} \ No newline at end of file diff --git a/examples/subTemplates/subTemplates/plain.js b/examples/subTemplates/subTemplates/plain.js new file mode 100644 index 0000000..ad2e47a --- /dev/null +++ b/examples/subTemplates/subTemplates/plain.js @@ -0,0 +1 @@ +___item___ \ No newline at end of file diff --git a/examples/subTemplates/template.config.json b/examples/subTemplates/template.config.json new file mode 100644 index 0000000..83d48d7 --- /dev/null +++ b/examples/subTemplates/template.config.json @@ -0,0 +1,32 @@ +{ + "name": "Operations With Tests", + "variables": [ + { + "name": "className", + "style": { + "case": "auto" + } + }, + { + "name": "interfaces", + "style": { + "noTransformation": true + }, + "subTemplates": [ + { + "file": "plain.js", + "as": "interfaceList", + "variableAlias": "item", + "joinChars": "," + }, + { + "file": "objectStub.js", + "as": "interfaceStubs", + "variableAlias": "item", + "joinChars": "," + } + ] + } + ], + "allowExistingFolder": true +} diff --git a/tsconfig.json b/tsconfig.json index ad3fb1f..2c590f2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,5 +14,5 @@ // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ // "noUnusedParameters": true, /* Report errors on unused parameters. */ }, - "exclude": ["node_modules", ".vscode-test"] + "exclude": ["node_modules", ".vscode-test", "examples"] }