diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..9b73521 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +# Editor configuration, see http://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f259cad --- /dev/null +++ b/.gitignore @@ -0,0 +1,41 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# compiled output +/dist +/tmp + +# sqlite files in test +/test/*.db + +# test files in test +/test/*-sdb.json + +# dependencies +/node_modules +/bower_components + +# IDEs and editors +/.idea +/.vscode +.project +.classpath +.c9/ +*.launch +.settings/ + +# misc +/.sass-cache +/connect.lock +/coverage/* +/libpeerconnection.log +npm-debug.log +testem.log +/typings + +# e2e +/e2e/*.js +/e2e/*.map + +#System Files +.DS_Store +Thumbs.db diff --git a/README.md b/README.md new file mode 100644 index 0000000..156c598 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# Nicassa Generator + +nicassa-generator is a CLI toolkit for creating source code. + +You require parsers such as nicassa-db-parser or nicassa-parser-ts-express-api + +## Installation + +`$ sudo npm install -g nicassa-generator` + +Please note nicassa-generator is written in TypeScript. + +## Features + +TBD diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..e1f49a4 --- /dev/null +++ b/TODO.md @@ -0,0 +1,5 @@ +# TODO + +# express.ts.routes + +Add a dynamic BasePath als prefix to all Controller Endpoints diff --git a/launcher.js b/launcher.js new file mode 100644 index 0000000..bb31bfa --- /dev/null +++ b/launcher.js @@ -0,0 +1,19 @@ +#!/usr/bin/env node + +// workaround for multiuser enviorment +var cacheDirectory = process.env.TMP; +if (process.platform != 'win32') { + // we only have to take care in non windows enviorments + if (process.env.HOME != '') { + cacheDirectory = process.env.HOME + '/.ts-node-cache'; + } +} + +require('ts-node').register({ + ignore: [], // needed in order to run from /usr/local... + cacheDirectory: cacheDirectory, + cache: true, + project: __dirname +}); + +require('./lib/cli.ts'); diff --git a/lib/actions/addgen.ts b/lib/actions/addgen.ts new file mode 100644 index 0000000..cee8f2b --- /dev/null +++ b/lib/actions/addgen.ts @@ -0,0 +1,146 @@ +let fs = require('fs'); +let process = require('process'); + +import { TopLevel } from '../persistance/toplevel'; +import { NicassaGenerator } from '../persistance/nicassagenerator'; +import { GeneratorConfigBasic } from '../persistance/generatorconfig.basic'; + +import { CodeGeneratorFactory } from '../generator/codegeneratorfactory.class'; + +export class AddGenerator { + fileName: string; + type: string; + name: string; + + run(opts: any) { + this.fileName = opts.file; + this.type = opts.type; + this.name = opts.name; + if (opts.name === undefined) { + this.name = this.type; + } + + if (!fs.existsSync(this.fileName)) { + console.error('error: file not found "' + this.fileName + '"'); + process.exit(-1); + } + + if (!this.sectionExistInFile()) { + console.error('error: nicassa generators section does not exist in "' + this.fileName + '". Dir you run init?'); + process.exit(-1); + } + + if (!this.isGeneratorTypeOK()) { + console.error('error: unknown generator type: ' + this.type); + process.exit(-1); + } + + if (!this.isTypeAndNameOK()) { + console.error('error: generator type already exist or no/invalid name was given in "' + this.fileName + '"'); + process.exit(-1); + } + + let str = fs.readFileSync(this.fileName); + let codeGenerator = CodeGeneratorFactory.getCodeGenerator(this.type, null, str); + let config = codeGenerator.getDefaultConfig(this.name); + let data = this.createJsonString(config); + + try { + fs.writeFileSync(this.fileName, data); + } catch (err) { + console.error('error: can\'t create file "' + this.fileName + '"'); + // console.error(err); + process.exit(-1); + } + } + + protected createJsonString(generatorConfig: GeneratorConfigBasic): string { + let str = null; + + try { + if (fs.existsSync(this.fileName)) { + str = fs.readFileSync(this.fileName); + } + } catch (err) { + console.error('error: can\'t read file "' + this.fileName + '"'); + // console.error(err); + process.exit(-1); + } + + if (str === null) { + let toplevel: TopLevel = { + nicassaGenerator: null + } + str = JSON.stringify(toplevel, null, 2); + } + + let toplevel: TopLevel = JSON.parse(str); + let nicassaGenerator: NicassaGenerator = toplevel.nicassaGenerator; + if (nicassaGenerator.generators === undefined) { + nicassaGenerator.generators = []; + } + + nicassaGenerator.generators.push(generatorConfig); + + let result = JSON.stringify(toplevel, null, 2); + return result; + } + + protected sectionExistInFile(): boolean { + let str = null; + + try { + str = fs.readFileSync(this.fileName); + } catch (err) { + console.error('error: can\'t read file "' + this.fileName + '"'); + process.exit(-1); + } + + let toplevel: TopLevel = JSON.parse(str); + return (toplevel.nicassaGenerator !== undefined); + } + + protected isGeneratorTypeOK(): boolean { + let arr: string[] = [ + 'sequelize.ts.dal', + 'express.ts.routes' + ]; + return (arr.indexOf(this.type) !== -1); + } + + protected isTypeAndNameOK(): boolean { + let str = null; + + try { + str = fs.readFileSync(this.fileName); + } catch (err) { + console.error('error: can\'t read file "' + this.fileName + '"'); + process.exit(-1); + } + + let toplevel: TopLevel = JSON.parse(str); + let nicassaGenerator: NicassaGenerator = toplevel.nicassaGenerator; + + if (nicassaGenerator.generators === undefined || + nicassaGenerator.generators === null || + nicassaGenerator.generators.length < 1) { + return true; + } + + let names: string[] = []; + for (let i = 0; i < nicassaGenerator.generators.length; i++) { + let gen = nicassaGenerator.generators[i]; + if (gen.type === this.type) { + names.push(gen.name); + } + } + + // if we have a same name, we have a clash + return (names.indexOf(this.name) === -1); + } +} + +export default function run(opts: any) { + let instance = new AddGenerator(); + return instance.run(opts); +} diff --git a/lib/actions/gen.ts b/lib/actions/gen.ts new file mode 100644 index 0000000..75629b0 --- /dev/null +++ b/lib/actions/gen.ts @@ -0,0 +1,86 @@ +let fs = require('fs'); +let process = require('process'); + +import { TopLevel } from '../persistance/toplevel'; +import { NicassaGenerator } from '../persistance/nicassagenerator'; +import { GeneratorConfigBasic } from '../persistance/generatorconfig.basic'; + +import { CodeGeneratorFactory } from '../generator/codegeneratorfactory.class'; +import { BaseGenerator } from '../generator/basegenerator'; + +export class RunGenerator { + fileName: string; + name: string; + generators: GeneratorConfigBasic[] = []; + + public async run(opts: any) : Promise{ + this.fileName = opts.file; + this.name = opts.name; + + if (!fs.existsSync(this.fileName)) { + console.error('error: file not found "' + this.fileName + '"'); + process.exit(-1); + } + + if (this.name !== undefined) { + let gen: GeneratorConfigBasic = BaseGenerator.getGeneratorByName(this.fileName, this.name); + if (gen === null) { + console.error('error: no generator with name: "' + this.name + '" was found in "' + this.fileName + '"'); + process.exit(-1); + } + this.generators.push(gen); + } else { + this.generators = this.getAllActiveGenerators(); + } + + if (this.generators.length === 0) { + console.error('error: no active generators in "' + this.fileName + '"'); + process.exit(-1); + } + + let str = fs.readFileSync(this.fileName); + return await this.runGenerators(str); + } + + protected async runGenerators(str: string) : Promise{ + for (let i = 0; i < this.generators.length; i++) { + let gen = this.generators[i]; + let codeGenerator = CodeGeneratorFactory.getCodeGenerator(gen.type, gen, str); + await codeGenerator.run(); + } + return await true; + } + + protected getAllActiveGenerators(): GeneratorConfigBasic[] { + let str = null; + + try { + str = fs.readFileSync(this.fileName); + } catch (err) { + console.error('error: can\'t read file "' + this.fileName + '"'); + process.exit(-1); + } + + let result: GeneratorConfigBasic[] = []; + + let toplevel: TopLevel = JSON.parse(str); + let nicassaGenerator: NicassaGenerator = toplevel.nicassaGenerator; + if (nicassaGenerator === undefined || nicassaGenerator.generators === undefined) { + return result; + } + + for (let i = 0; i < nicassaGenerator.generators.length; i++) { + let gen = nicassaGenerator.generators[i]; + if (gen.active) { + result.push(gen); + } + } + + return result; + } +} + +export default async function run(opts: any) { + let instance = new RunGenerator(); + return await instance.run(opts); +} diff --git a/lib/actions/init.ts b/lib/actions/init.ts new file mode 100644 index 0000000..70b0bcb --- /dev/null +++ b/lib/actions/init.ts @@ -0,0 +1,85 @@ +let fs = require('fs'); +let process = require('process'); + +import { GeneratorConfigBasic } from '../persistance/generatorconfig.basic'; +import { TopLevel } from '../persistance/toplevel'; +import { NicassaGenerator } from '../persistance/nicassagenerator'; + +export class Init { + fileName: string; + + run(opts: any) { + this.fileName = opts.file; + + if (fs.existsSync(this.fileName)) { + if (this.sectionExistInFile()) { + console.error('error: section already exist in "' + this.fileName + '"'); + process.exit(-1); + } + } + + let data = this.createJsonString(); + + try { + fs.writeFileSync(this.fileName, data); + } catch (err) { + console.error('error: can\'t create file "' + this.fileName + '"'); + // console.error(err); + process.exit(-1); + } + } + + protected sectionExistInFile(): boolean { + let str = null; + + try { + str = fs.readFileSync(this.fileName); + } catch (err) { + console.error('error: can\'t read file "' + this.fileName + '"'); + // console.error(err); + process.exit(-1); + } + + let toplevel: TopLevel = JSON.parse(str); + return (toplevel.nicassaGenerator !== undefined); + } + + protected createJsonString(): string { + let str = null; + + try { + if (fs.existsSync(this.fileName)) { + str = fs.readFileSync(this.fileName); + } + } catch (err) { + console.error('error: can\'t read file "' + this.fileName + '"'); + // console.error(err); + process.exit(-1); + } + + if (str === null) { + let toplevel: TopLevel = { + nicassaGenerator: null + } + str = JSON.stringify(toplevel, null, 2); + } + + let generators: GeneratorConfigBasic[] = []; + + let nicassaGenerator: NicassaGenerator = { + formatVersion: '1.0', + generators: generators + }; + + let toplevel: TopLevel = JSON.parse(str); + toplevel.nicassaGenerator = nicassaGenerator; + + let result = JSON.stringify(toplevel, null, 2); + return result; + } +} + +export default function run(opts: any) { + let instance = new Init(); + return instance.run(opts); +} diff --git a/lib/cli.ts b/lib/cli.ts new file mode 100644 index 0000000..e50bca7 --- /dev/null +++ b/lib/cli.ts @@ -0,0 +1,10 @@ +import { CmdLineParser } from './tools/cmdlineparser.class'; +let args = CmdLineParser.parse(); + +if (args == null) { + process.exit(0); +} + +const action = require(args.module).default; +action(args.opts); + diff --git a/lib/generator/basegenerator.ts b/lib/generator/basegenerator.ts new file mode 100644 index 0000000..0192cb6 --- /dev/null +++ b/lib/generator/basegenerator.ts @@ -0,0 +1,59 @@ +let fs = require('fs'); +let process = require('process'); + +import { TopLevel } from '../persistance/toplevel'; +import { NicassaGenerator } from '../persistance/nicassagenerator'; +import { GeneratorConfigBasic } from '../persistance/generatorconfig.basic'; + +import { FileManger } from './filemanager'; + +export abstract class BaseGenerator { + protected dataTypeMapping: { [mappings: string]: { [dbtype: string]: string } } + protected templateDir: string; + + constructor(private generatorConfigBasic: GeneratorConfigBasic, protected nicassaJson: string) { } + + public async run(): Promise { + FileManger.createTargetDirIfNeeded(this); + return await this.generateCode(); + } + + public abstract getDefaultConfig(name: string): GeneratorConfigBasic; + + public getTemplateDir(): string { + return this.templateDir; + } + + public getTargetDir(): string { + return this.generatorConfigBasic.targetDir; + } + + protected async abstract generateCode(): Promise; + + + public static getGeneratorByName(fileName: string, generatorName: string): GeneratorConfigBasic { + let str = null; + + try { + str = fs.readFileSync(fileName); + } catch (err) { + console.error('error: can\'t read file "' + fileName + '"'); + process.exit(-1); + } + + let toplevel: TopLevel = JSON.parse(str); + let nicassaGenerator: NicassaGenerator = toplevel.nicassaGenerator; + if (nicassaGenerator === undefined || nicassaGenerator.generators === undefined) { + return null; + } + + for (let i = 0; i < nicassaGenerator.generators.length; i++) { + let gen = nicassaGenerator.generators[i]; + if (gen.name === generatorName) { + return gen; + } + } + + return null; + } +} diff --git a/lib/generator/codegeneratorfactory.class.ts b/lib/generator/codegeneratorfactory.class.ts new file mode 100644 index 0000000..f477814 --- /dev/null +++ b/lib/generator/codegeneratorfactory.class.ts @@ -0,0 +1,23 @@ +import { GeneratorConfigBasic } from '../persistance/generatorconfig.basic'; + +import { BaseGenerator } from './basegenerator'; +import { ExpressTSRoutesGenerator } from './express.ts.routes/express.ts.routes.generator'; +import { SequelizeTSDalGenerator } from './sequelize.ts.dal/sequelize.ts.dal.generator'; + + +export class CodeGeneratorFactory { + public static getCodeGenerator(type: string, generatorConfigBasic: GeneratorConfigBasic, nicassaJson: string): BaseGenerator { + let result: BaseGenerator; + switch (type) { + case 'express.ts.routes': + result = new ExpressTSRoutesGenerator(generatorConfigBasic, nicassaJson); + break; + case 'sequelize.ts.dal': + result = new SequelizeTSDalGenerator(generatorConfigBasic, nicassaJson); + break; + default: + throw ('unsupported generator type: ' + type); + } + return result; + } +} diff --git a/lib/generator/express.ts.routes/express.ts.routes.generator.ts b/lib/generator/express.ts.routes/express.ts.routes.generator.ts new file mode 100644 index 0000000..9a7f578 --- /dev/null +++ b/lib/generator/express.ts.routes/express.ts.routes.generator.ts @@ -0,0 +1,132 @@ +const fs = require('fs'); +const process = require('process'); + +import { GeneratorConfigBasic } from '../../persistance/generatorconfig.basic'; +import { GeneratorConfigExpressTSRoutes } from '../../persistance/generatorconfig.express.ts.routes'; +import { GeneratorConfigSequelizeTSDal } from '../../persistance/generatorconfig.sequelize.ts.dal'; + +import { BaseGenerator } from '../basegenerator'; +import { RenderTemplate } from '../rendertemplate'; + +import { MetadataSymbolTable } from '../symboltable/metadata/metadatasymboltable'; +import { MetadataSymbolTableReader } from '../symboltable/metadata/metadatasymboltable.reader'; + +import { DBSymbolTable } from '../symboltable/db/dbsymboltable'; +import { DBSymbolTableReader } from '../symboltable/db/dbsymboltable.reader'; +import { SequelizeTsDalTypeMapper } from '../sequelize.ts.dal/sequelize.ts.dal.typemapper.class'; +import { SequelizeTypescriptMapping } from '../sequelize.ts.dal/sequelize.typescript.mapping'; + +export class ExpressTSRoutesGenerator extends BaseGenerator { + protected generatorConfig: GeneratorConfigExpressTSRoutes; + protected metadataSymbolTable: MetadataSymbolTable; + protected dbSymbolTable: DBSymbolTable; + + constructor(generatorConfigBasic: GeneratorConfigBasic, nicassaJson: string) { + super(generatorConfigBasic, nicassaJson); + this.generatorConfig = generatorConfigBasic; + this.templateDir = __dirname + '/templates'; + } + + public getDefaultConfig(name: string): GeneratorConfigExpressTSRoutes { + let type = "express.ts.routes"; + + let result: GeneratorConfigExpressTSRoutes = { + name: name, + type: type, + active: true, + targetDir: './src', + nicassaParserDBFile: null, + nicassaParserDBGeneratorName: null, + }; + + return result; + } + + protected async generateCode(): Promise { + this.metadataSymbolTable = MetadataSymbolTableReader.readFromJsonString(this.nicassaJson); + this.setLengthToMetaData(); + + let data = { + controllers: this.metadataSymbolTable.controllers, + referenceTypes: this.metadataSymbolTable.referenceTypes, + }; + + await RenderTemplate.renderTemplate(true, this, 'routes.ts.ejs', data); + + return await true; + } + + protected setLengthToMetaData() { + if (this.generatorConfig.nicassaParserDBFile === undefined || this.generatorConfig.nicassaParserDBFile === null) { + return; + } + + if (!fs.existsSync(this.generatorConfig.nicassaParserDBFile)) { + console.error('error: can\'t find nicassaParserDBFile: ' + this.generatorConfig.nicassaParserDBFile + '\''); + process.exit(-1); + } + + let json = null; + try { + json = fs.readFileSync(this.generatorConfig.nicassaParserDBFile); + } catch (err) { + console.error('error: can\'t read nicassaParserDBFile: ' + this.generatorConfig.nicassaParserDBFile + '\''); + process.exit(-1); + } + + if (this.generatorConfig.nicassaParserDBGeneratorName === undefined || this.generatorConfig.nicassaParserDBGeneratorName === null) { + console.error('error: can\'t find nicassaParserDBGeneratorName for the nicassaParserDBFile: ' + this.generatorConfig.nicassaParserDBFile + '\''); + process.exit(-1); + } + + // we need the config... + let gen: GeneratorConfigSequelizeTSDal = BaseGenerator.getGeneratorByName + (this.generatorConfig.nicassaParserDBFile, this.generatorConfig.nicassaParserDBGeneratorName); + + // .. to mapp with the settings + let dataTypeMapping = SequelizeTypescriptMapping.dataTypeMapping; + let typeMapper: SequelizeTsDalTypeMapper = new SequelizeTsDalTypeMapper(gen, dataTypeMapping); + + this.dbSymbolTable = DBSymbolTableReader.readFromJsonString(json, gen.filter, typeMapper); + this.updateMetaSymbolTableLengthFromDbSymbolTable(this.metadataSymbolTable, this.dbSymbolTable); + } + + protected updateMetaSymbolTableLengthFromDbSymbolTable(meta: MetadataSymbolTable, db: DBSymbolTable) { + if (db.entities === undefined || db.entities === null || db.entities.length === 0) { + return; + } + + if (meta.referenceTypes === undefined || meta.referenceTypes === null || meta.referenceTypes.length === 0) { + return; + } + + // create the map + let entityColumnMap: any = {}; + for (let i = 0; i < db.entities.length; i++) { + let entity = db.entities[i]; + entityColumnMap[entity.getMappedName('TypeScript')] = {}; + for (let k = 0; k < entity.columns.length; k++) { + let column = entity.columns[k]; + if (column.length === undefined || column.length === null || column.length < 0) { + continue; + } + entityColumnMap[entity.getMappedName('TypeScript')][column.getMappedName('TypeScript')] = column.length; + } + } + + for (let i = 0; i < meta.referenceTypes.length; i++) { + let ref = meta.referenceTypes[i]; + if (!entityColumnMap.hasOwnProperty(ref.name)) { + continue; + } + for (let k = 0; k < ref.properties.length; k++) { + let prop = ref.properties[k]; + if (!entityColumnMap[ref.name].hasOwnProperty(prop.name)) { + continue; + } + prop.length = entityColumnMap[ref.name][prop.name]; + } + } + } +} + diff --git a/lib/generator/express.ts.routes/templates/routes.ts.ejs b/lib/generator/express.ts.routes/templates/routes.ts.ejs new file mode 100644 index 0000000..3faed6f --- /dev/null +++ b/lib/generator/express.ts.routes/templates/routes.ts.ejs @@ -0,0 +1,179 @@ +/* tslint:disable:max-line-length */ +//////////////////////////////////////////////////////////////////// +// +// GENERATED CLASS +// +// DO NOT EDIT +// +//////////////////////////////////////////////////////////////////// + +<%controllers.forEach(function(ctrl){%> +import { <%- ctrl.name%> } from '<%- ctrl.getMappedLocation()%>';<% +}); %> + +const models: any = { +<%referenceTypes.forEach(function(refType){ +%> '<%- refType.name%>': {<%refType.properties.forEach(function(prop){%> + '<%- prop.name%>': { typeName: '<%- prop.type.name%>', required: <%- prop.required%>, length: <%- prop.length%> },<% +}); %> + }, +<% +}); %> +}; + +export function RegisterRoutes(app: any) { +<%controllers.forEach(function(ctrl){%><% + ctrl.methods.forEach(function(method){ +%> app.<%- method.method%>('/<%- ctrl.path%><%- method.getPath()%>', (req: any, res: any, next: any) => { + const params = {<% + method.parameters.forEach(function(param){ +%> + '<%- param.name%>': { typeName: '<%- param.type.name%>', required: <%- param.required%> <%if(param.type.isArray) {%>arrayType: '<%- param.type.elementType.name%>' <%}%>},<% +}); %> + }; + + let validatedParams: any[] = []; + try { + validatedParams = getValidatedParams(params, req, '<%- method.bodyParamName%>'); + } catch (err) { + res.status(err.status || 500); + res.json(err); + return; + } + + const controller = new <%- ctrl.name%>(); + promiseHandler(controller.<%- method.name%>.apply(controller, validatedParams), res, next); + }); +<%}); %><%}); %> + function promiseHandler(promise: any, response: any, next: any) { + return promise + .then((data: any) => { + if (data) { + response.json(data); + } else { + response.status(204); + response.end(); + } + }) + .catch((error: any) => { + // response.status(error.status || 500); + // response.json(error); + next(error); + }); + } + + function getRequestParams(request: any, bodyParamName?: string) { + const merged: any = {}; + if (bodyParamName) { + merged[bodyParamName] = request.body; + } + + for (let attrname in request.params) { merged[attrname] = request.params[attrname]; } + for (let attrname in request.query) { merged[attrname] = request.query[attrname]; } + return merged; + } + + function getValidatedParams(params: any, request: any, bodyParamName?: string): any[] { + const requestParams = getRequestParams(request, bodyParamName); + + return Object.keys(params).map(key => { + return validateParam(params[key], requestParams[key], key); + }); + } +} + +function validateParam(typeData: any, value: any, name?: string) { + if (value === undefined) { + if (typeData.required) { + throw new InvalidRequestException(name + ' is a required parameter.'); + } else { + return undefined; + } + } + + switch (typeData.typeName) { + case 'string': + return validateString(value, name, typeData.length); + case 'boolean': + return validateBool(value, name); + case 'number': + return validateNumber(value, name); + case 'array': + return validateArray(value, typeData.arrayType, name); + case 'datetime': + if (!typeData.required) { + return null; + } + return validateDate(value, name); + case 'buffer': + return value; + default: + return validateModel(value, typeData.typeName); + } +} + +function validateNumber(numberValue: string, name: string): number { + const parsedNumber = parseInt(numberValue, 10); + if (isNaN(parsedNumber)) { + throw new InvalidRequestException(name + 'should be a valid number.'); + } + + return parsedNumber; +} + +function validateString(stringValue: string, name: string, length: number) { + let str = stringValue.toString(); + if (str.length > length) { + throw new InvalidRequestException(name + 'maximum length of ' + length + ' exceeded.'); + } + return str; +} + +function validateDate(stringValue: string, name: string) { + if (stringValue !== undefined && stringValue !== null) { + if (stringValue.trim().length !== 0) { + let date = new Date(stringValue); + if (date instanceof Date && isFinite(date)) { + return date; + } + } + } + throw new InvalidRequestException(name + 'should be valid date value.'); +} + +function validateBool(boolValue: any, name: string): boolean { + if (boolValue === true || boolValue === false) { return boolValue; } + if (boolValue.toLowerCase() === 'true') { return true; } + if (boolValue.toLowerCase() === 'false') { return false; } + + throw new InvalidRequestException(name + 'should be valid boolean value.'); +} + +function validateModel(modelValue: any, typeName: string): any { + const modelDefinition = models[typeName]; + + Object.keys(modelDefinition).forEach((key: string) => { + const property = modelDefinition[key]; + modelValue[key] = validateParam(property, modelValue[key], key); + }); + + return modelValue; +} + +function validateArray(array: any[], arrayType: string, arrayName: string): any[] { + return array.map(element => validateParam({ + required: true, + typeName: arrayType, + }, element)); +} + +interface Exception extends Error { + status: number; +} + +class InvalidRequestException implements Exception { + public status = 400; + public name = 'Invalid Request'; + + constructor(public message: string) { } +} diff --git a/lib/generator/filemanager.ts b/lib/generator/filemanager.ts new file mode 100644 index 0000000..54c6497 --- /dev/null +++ b/lib/generator/filemanager.ts @@ -0,0 +1,35 @@ +const process = require('process'); +const fs = require('fs'); +const path = require('path'); + +import { BaseGenerator } from './basegenerator'; + +export class FileManger { + public static createTargetDirIfNeeded(generator: BaseGenerator) { + try { + if (fs.existsSync(generator.getTargetDir())) { + return; + } + + fs.mkdirSync(generator.getTargetDir()); + } catch (err) { + console.error(err); + process.exit(-1); + } + } + + public static fileExistInProjectDir(generator: BaseGenerator, fileName: string) { + try { + let file = path.join(generator.getTargetDir(), fileName) + if (fs.existsSync(file)) { + return true; + } + return false; + } catch (err) { + console.error(err); + process.exit(-1); + } + + } + +} diff --git a/lib/generator/rendertemplate.ts b/lib/generator/rendertemplate.ts new file mode 100644 index 0000000..e0f875b --- /dev/null +++ b/lib/generator/rendertemplate.ts @@ -0,0 +1,53 @@ +const fs = require('fs'); +const path = require('path'); +const process = require('process'); + +import * as Ejs from 'ejs'; + +import { BaseGenerator } from './basegenerator'; + +export class RenderTemplate { + public static async renderTemplate(active: boolean, generator: BaseGenerator, templateName: string, data?: any, outputFileName?: string): Promise { + let genText: string = null; + + if (!active) { + return await genText; + } + + let dir = generator.getTemplateDir(); + let templatePath: string = path.join(dir, templateName); + + if (!fs.existsSync(templatePath)) { + console.error('error: requested template \'' + templateName + '\' does not exist here: ' + templatePath); + process.exit(-1); + } + + genText = await RenderTemplate.renderEjs(generator, templatePath, templateName, data, outputFileName); + return await genText; + } + + private static async renderEjs(generator: BaseGenerator, templatePath: string, templateName: string, data?: any, outputFileName?: string): Promise { + let opts: Ejs.Options = null; + + return new Promise((resolve, reject) => { + Ejs.renderFile(templatePath, data, opts, (err: Error, str: string) => { + if (err) { + reject(err); + } + + let targetName = path.basename(templateName, ".ejs"); + if (outputFileName !== undefined && outputFileName !== null) { + targetName = outputFileName; + } + + try { + fs.writeFileSync(path.join(generator.getTargetDir(), targetName), str); + } catch (err) { + reject(err); + } + + resolve(str); + }); + }); + } +} diff --git a/lib/generator/sequelize.ts.dal/enums/columname.enum.ts b/lib/generator/sequelize.ts.dal/enums/columname.enum.ts new file mode 100644 index 0000000..8c6ee7e --- /dev/null +++ b/lib/generator/sequelize.ts.dal/enums/columname.enum.ts @@ -0,0 +1,8 @@ +export type ColumnNameEnum = "None" | "TypeScript" | "Sequelize" | "Database"; + +export const ColumnNameEnum = { + None: "None" as ColumnNameEnum, + TypeScript: "TypeScript" as ColumnNameEnum, + Sequelize: "Sequelize" as ColumnNameEnum, + Database: "Database" as ColumnNameEnum +}; diff --git a/lib/generator/sequelize.ts.dal/enums/columntypename.enum.ts b/lib/generator/sequelize.ts.dal/enums/columntypename.enum.ts new file mode 100644 index 0000000..99d97d0 --- /dev/null +++ b/lib/generator/sequelize.ts.dal/enums/columntypename.enum.ts @@ -0,0 +1,6 @@ +export type ColumnTypeNameEnum = "TypeScript" | "Sequelize"; + +export const ColumnTypeNameEnum = { + TypeScript: "TypeScript" as ColumnTypeNameEnum, + Sequelize: "Sequelize" as ColumnTypeNameEnum, +}; diff --git a/lib/generator/sequelize.ts.dal/enums/tablename.enum.ts b/lib/generator/sequelize.ts.dal/enums/tablename.enum.ts new file mode 100644 index 0000000..8328780 --- /dev/null +++ b/lib/generator/sequelize.ts.dal/enums/tablename.enum.ts @@ -0,0 +1,9 @@ +export type TableNameEnum = "None" | "TypeScript" | "TypeScriptInstance" | "TypeScriptModel" |"Sequelize"; + +export const TableNameEnum = { + None: "None" as TableNameEnum, + TypeScript: "TypeScript" as TableNameEnum, + TypeScriptInstance: "TypeScriptInstance" as TableNameEnum, + TypeScriptModel: "TypeScriptModel" as TableNameEnum, + Sequelize: "Sequelize" as TableNameEnum, +}; diff --git a/lib/generator/sequelize.ts.dal/sequelize.ts.dal.generator.ts b/lib/generator/sequelize.ts.dal/sequelize.ts.dal.generator.ts new file mode 100644 index 0000000..f7a0865 --- /dev/null +++ b/lib/generator/sequelize.ts.dal/sequelize.ts.dal.generator.ts @@ -0,0 +1,169 @@ +import { Filter } from 'nicassa-parser-db'; + +import { GeneratorConfigBasic } from '../../persistance/generatorconfig.basic'; +import { GeneratorConfigSequelizeTSDal } from '../../persistance/generatorconfig.sequelize.ts.dal'; + +import { ModelNaming } from '../../persistance/naming/modelnaming'; +import { CaseEnum } from '../../persistance/naming/case.enum'; +import { PropertyNaming } from '../../persistance/naming/propertynaming'; +import { EntityNaming } from '../../persistance/naming/entitynaming'; + +import { SequelizeTypescriptMapping } from './sequelize.typescript.mapping'; + +import { FileManger } from '../filemanager'; +import { BaseGenerator } from '../basegenerator'; +import { RenderTemplate } from '../rendertemplate'; + +import { SymbolNameMapper } from '../symbolnamemapper'; +import { DBSymbolTable } from '../symboltable/db/dbsymboltable'; +import { DBSymbolTableReader } from '../symboltable/db/dbsymboltable.reader'; +import { SequelizeTsDalTypeMapper } from './sequelize.ts.dal.typemapper.class'; + + +export class SequelizeTSDalGenerator extends BaseGenerator { + protected dbSymbolTable: DBSymbolTable; + protected generatorConfig: GeneratorConfigSequelizeTSDal; + protected typeMapper: SequelizeTsDalTypeMapper; + + constructor(generatorConfigBasic: GeneratorConfigBasic, nicassaJson: string) { + super(generatorConfigBasic, nicassaJson); + this.generatorConfig = generatorConfigBasic; + this.dataTypeMapping = SequelizeTypescriptMapping.dataTypeMapping; + this.templateDir = __dirname + '/templates'; + this.typeMapper = new SequelizeTsDalTypeMapper(this.generatorConfig, this.dataTypeMapping); + } + + public getDefaultConfig(name: string): GeneratorConfigSequelizeTSDal { + let filter: Filter = { + excludeTables: false, + excludeViews: false, + exculdeColumns: [], + exculde: [], + only: [] + } + + let entityNaming: EntityNaming = { + caseType: CaseEnum.PascalCase, + filenameCaseType: CaseEnum.Lower, + instanceCaseType: CaseEnum.CamelCase, + removeUnderscore: true, + removeInvalidCharacters: true, + } + + let propertyNaming: PropertyNaming = { + caseType: CaseEnum.CamelCase, + removeUnderscore: true, + removeInvalidCharacters: true, + plurarlizeCollectionNavigationProperties: true + } + + let modelNaming: ModelNaming = { + entityNaming: entityNaming, + propertyNaming: propertyNaming + } + + let type = "sequelize.ts.dal"; + + let result: GeneratorConfigSequelizeTSDal = { + name: name, + type: type, + active: true, + targetDir: './' + name, + cleanTargetDir: true, + createProject: true, + projectName: null, + namespace: null, + entityContainerName: null, + modelNaming: modelNaming, + dataTypeMapping: {}, + filter: filter + }; + + return result; + } + + + protected async generateCode(): Promise { + this.dbSymbolTable = DBSymbolTableReader.readFromJsonString(this.nicassaJson, this.generatorConfig.filter, this.typeMapper); + + let createPackageJson: boolean = false; + let createIndex: boolean = false; + let createProject: boolean = this.generatorConfig.createProject; + if (createProject) { + createPackageJson = !FileManger.fileExistInProjectDir(this, 'package.json'); + createIndex = !FileManger.fileExistInProjectDir(this, 'index.ts'); + } + let projectName = this.generatorConfig.projectName; + if (projectName === undefined || projectName === null) { + projectName = 'undefined'; + } + projectName = SymbolNameMapper.titleCase(projectName); + projectName = SymbolNameMapper.dotCase(projectName); + projectName = SymbolNameMapper.lower(projectName); + let createDecorators = false; + + let entitycontainerBaseFileName = this.createEntitycontainerBaseFileName(); + + let data = { + tables: this.dbSymbolTable.tables, + views: this.dbSymbolTable.views, + columns: this.dbSymbolTable.columns, + entities: this.dbSymbolTable.entities, + relations: this.dbSymbolTable.relations, + namespace: this.generatorConfig.namespace, + projectName: projectName, + entitycontainerBaseFileName: entitycontainerBaseFileName, + entityContainerName: this.createEntitycontainerName(), + createDecorators: createDecorators + }; + + await RenderTemplate.renderTemplate(true, this, 'entities.ts.ejs', data); + await RenderTemplate.renderTemplate(createDecorators, this, 'decorators.ts.ejs', data); + await RenderTemplate.renderTemplate(true, this, 'models.ts.ejs', data); + await RenderTemplate.renderTemplate(true, this, 'asserts.ts.ejs', data); + await RenderTemplate.renderTemplate(true, this, 'entitycontainer.ts.ejs', data, entitycontainerBaseFileName + '.ts'); + await RenderTemplate.renderTemplate(createPackageJson, this, 'package.json.ejs', data); + await RenderTemplate.renderTemplate(createIndex, this, 'index.ts.ejs', data); + await RenderTemplate.renderTemplate(createProject, this, 'generated.exports.ts.ejs', data); + + return await true; + } + + protected createEntitycontainerName(): string { + let ext = 'Container'; + let result: string = 'SequelizeTypescript' + ext; + if (this.generatorConfig.entityContainerName === undefined || this.generatorConfig.entityContainerName === null) { + return result; + } + + result = this.generatorConfig.entityContainerName; + result = SymbolNameMapper.titleCase(result); + result = SymbolNameMapper.removeWhitespace(result); + result = SymbolNameMapper.identifierfy(result); + + if (result.substr((-1) * ext.length) !== ext) { + result += ext; + } + + return result; + } + + protected createEntitycontainerBaseFileName(): string { + let ext = '.container'; + let result: string = 'sequelize.typescript' + ext; + if (this.generatorConfig.entityContainerName === undefined || this.generatorConfig.entityContainerName === null) { + return result; + } + + result = this.generatorConfig.entityContainerName; + result = SymbolNameMapper.lower(result); + result = SymbolNameMapper.removeWhitespace(result); + + if (result.substr((-1) * ext.length) !== ext) { + result += ext; + } + + return result; + } +} + diff --git a/lib/generator/sequelize.ts.dal/sequelize.ts.dal.typemapper.class.ts b/lib/generator/sequelize.ts.dal/sequelize.ts.dal.typemapper.class.ts new file mode 100644 index 0000000..a387b5a --- /dev/null +++ b/lib/generator/sequelize.ts.dal/sequelize.ts.dal.typemapper.class.ts @@ -0,0 +1,92 @@ +import { GeneratorConfigSequelizeTSDal } from '../../persistance/generatorconfig.sequelize.ts.dal'; + +import { TypeMapper } from '../symboltable/db/typemapper.class'; + +import { TableSymbol } from '../symboltable/db/tablesymbol'; +import { ColumnSymbol } from '../symboltable/db/columnsymbol'; + +import { TableNameEnum } from './enums/tablename.enum' +import { ColumnNameEnum } from './enums/columname.enum' + +import { SymbolNameMapper } from '../symbolnamemapper' + +export class SequelizeTsDalTypeMapper extends TypeMapper { + + constructor(protected generatorConfig: GeneratorConfigSequelizeTSDal, dataTypeMapping: { [mappings: string]: { [dbtype: string]: string } }) { + super(dataTypeMapping); + } + + public columnDataTypeMapper(table: TableSymbol, column: ColumnSymbol, kind: string): string { + // TODO -> custom mappings from this.generator.dataTypeMapping + let type = column.dataType.toLowerCase(); + return this.dataTypeMapping[kind][type]; + } + + + public /*override*/ tableNameMapper(table: TableSymbol, kind: string): string { + let tableName: TableNameEnum = kind; + let map = table.name; + + switch (tableName) { + case TableNameEnum.None: + // nop + break; + case TableNameEnum.TypeScriptInstance: + map = this.tableNameMapper(table, TableNameEnum.TypeScript); + map += 'Instance'; + break; + case TableNameEnum.TypeScriptModel: + map = this.tableNameMapper(table, TableNameEnum.TypeScript); + map += 'Model'; + break; + case TableNameEnum.TypeScript: + if (this.generatorConfig.modelNaming.entityNaming.removeInvalidCharacters) { + map = SymbolNameMapper.titleCase(map); + map = SymbolNameMapper.removeWhitespace(map); + map = SymbolNameMapper.identifierfy(map); + } + + if (this.generatorConfig.modelNaming.entityNaming.removeUnderscore) { + map = SymbolNameMapper.removeUnderscore(map); + } + map = SymbolNameMapper.pascal(map); + break; + default: + console.log('error: can\'t map table name with type: ' + kind + ' maybe a typo in your template?'); + process.exit(-1); + break; + } + + return map; + } + + public /*override*/ columnNameMapper(column: ColumnSymbol, kind: string): string { + let columnNameEnum: ColumnNameEnum = kind; + let map = column.name; + + switch (columnNameEnum) { + case ColumnNameEnum.None: + // nop + break; + case ColumnNameEnum.TypeScript: + if (this.generatorConfig.modelNaming.propertyNaming.removeInvalidCharacters) { + map = SymbolNameMapper.titleCase(map); + map = SymbolNameMapper.removeWhitespace(map); + map = SymbolNameMapper.identifierfy(map); + } + + if (this.generatorConfig.modelNaming.propertyNaming.removeUnderscore) { + map = SymbolNameMapper.removeUnderscore(map); + } + map = SymbolNameMapper.camel(map); + break; + default: + console.log('error: can\'t map column name with type: ' + kind + ' maybe a typo in your template?'); + process.exit(-1); + break; + } + + return map; + } +} + diff --git a/lib/generator/sequelize.ts.dal/sequelize.typescript.mapping.ts b/lib/generator/sequelize.ts.dal/sequelize.typescript.mapping.ts new file mode 100644 index 0000000..48ddc56 --- /dev/null +++ b/lib/generator/sequelize.ts.dal/sequelize.typescript.mapping.ts @@ -0,0 +1,98 @@ +export class SequelizeTypescriptMapping { + public static dataTypeMapping: { [mappings: string]: { [dbtype: string]: string } } = { + "TypeScript": { + tinyint: "boolean", + boolean: "boolean", + smallint: "number", + int: "number", + integer: "number", + mediumint: "number", + bigint: "number", + year: "number", + float: "number", + double: "number", + decimal: "number", + "double precision": "number", + real: "number", + numeric: "number", + money: "number", + timestamp: "Date", + date: "Date", + datetime: "Date", + datetime2: "Date", + tinyblob: "Buffer", + mediumblob: "Buffer", + longblob: "Buffer", + blob: "Buffer", + image: "Buffer", + binary: "Buffer", + varbinary: "Buffer", + bit: "boolean", + bytea: "Buffer", + char: "string", + nchar: "string", + character: "string", + varchar: "string", + nvarchar: "string", + tinytext: "string", + mediumtext: "string", + longtext: "string", + text: "string", + ntext: "string", + "enum": "string", + "set": "string", + time: "string", + geometry: "string", + "character varying": "string", + "USER-DEFINED": "string", + "uniqueidentifier": "string" + }, + "Sequelize": { + tinyint: 'Sequelize.BOOLEAN', + boolean: 'Sequelize.BOOLEAN', + smallint: 'Sequelize.INTEGER', + int: 'Sequelize.INTEGER', + integer: 'Sequelize.INTEGER', + mediumint: 'Sequelize.INTEGER', + bigint: 'Sequelize.INTEGER', + year: 'Sequelize.INTEGER', + float: 'Sequelize.DECIMAL', + double: 'Sequelize.DECIMAL', + decimal: 'Sequelize.DECIMAL', + "double precision": 'Sequelize.DECIMAL', + real: 'Sequelize.DECIMAL', + numeric: 'Sequelize.DECIMAL', + money: 'Sequelize.DECIMAL', + timestamp: 'Sequelize.DATE', + date: 'Sequelize.DATE', + datetime: 'Sequelize.DATE', + datetime2: 'Sequelize.DATE', + tinyblob: 'Sequelize.BLOB', + mediumblob: 'Sequelize.BLOB', + longblob: 'Sequelize.BLOB', + blob: 'Sequelize.BLOB', + image: 'Sequelize.BLOB', + binary: 'Sequelize.BLOB', + varbinary: 'Sequelize.BLOB', + bit: 'Sequelize.BOOLEAN', + bytea: 'Sequelize.BLOB', + char: 'Sequelize.STRING', + nchar: 'Sequelize.STRING', + varchar: 'Sequelize.STRING', + nvarchar: 'Sequelize.STRING', + tinytext: 'Sequelize.STRING', + mediumtext: 'Sequelize.STRING', + longtext: 'Sequelize.STRING', + text: 'Sequelize.STRING', + ntext: 'Sequelize.STRING', + "enum": 'Sequelize.ENUM', + "set": 'Sequelize.STRING', + time: 'Sequelize.STRING', + geometry: 'Sequelize.STRING', + "character varying": 'Sequelize.STRING', + character: 'Sequelize.STRING', + "USER-DEFINED": 'Sequelize.STRING', + uniqueidentifier: 'Sequelize.UUID' + } + } +} diff --git a/lib/generator/sequelize.ts.dal/templates/asserts.ts.ejs b/lib/generator/sequelize.ts.dal/templates/asserts.ts.ejs new file mode 100644 index 0000000..0ee2df7 --- /dev/null +++ b/lib/generator/sequelize.ts.dal/templates/asserts.ts.ejs @@ -0,0 +1,194 @@ +/* tslint:disable:max-line-length */ +//////////////////////////////////////////////////////////////////// +// +// GENERATED CLASS +// +// DO NOT EDIT +// +//////////////////////////////////////////////////////////////////// + +import entities = require('./entities');<% if(namespace !== undefined && namespace !== null){ %> + +export namespace <%- namespace%> {<% } %> + + +export class Asserts { + private static BOOLEAN_TYPE: string = typeof (true); + private static NUMBER_TYPE: string = typeof (1); + private static STRING_TYPE: string = typeof (''); + private static FUNCTION_TYPE: string = typeof (function () { }); + private static DATE_EXPECTED_TYPE: string = 'Date'; + private static BUFFER_EXPECTED_TYPE: string = 'Buffer'; + private static BUFFER_EXISTS: boolean = typeof Buffer !== 'undefined'; // in node exists, in js not, so only validate in node + private static asserters: { [typeName: string]: (entity: any, allowUndefined?: boolean) => void } = {}; + + public static initalize() { + if (Object.keys(Asserts.asserters).length !== 0) { + return; + }<% +entities.forEach(function(entity){%> + Asserts.asserters['<%-entity.getMappedName('TypeScript')%>'] = Asserts.<%-entity.getMappedName('TypeScript')%>;<%});%> + } +<%entities.forEach(function(entity){%> + public static <%-entity.getMappedName('TypeScript')%>(entity: entities.<% + if(namespace !== undefined && namespace !== null){ %><%- namespace%>.<% } +%><%- entity.getMappedName('TypeScript')%>, allowUndefined?: boolean): void { + if (entity === undefined || entity === null) { + if (allowUndefined) { + return; + } + throw new Error('Invalid <%- entity.getMappedName('TypeScript')%> provided. It is \'' + (typeof entity) + '\'.'); + } + let fieldNames: string[] = Object.keys(entity); + if (fieldNames.length === 0) { + throw new Error('Invalid <%- entity.getMappedName('TypeScript')%> provided. It is an empty object.'); + } + + let i: number = fieldNames.length; + while (i-- > 0) { + switch (fieldNames[i]) {<% + entity.columns.forEach(function(column){%> + case '<%- column.getMappedName("TypeScript")%>': Asserts.assertValidFieldType('<%- entity.getMappedName("TypeScript")%>', '<%- column.getMappedName("TypeScript")%>', entity, '<%- column.getMappedDataType("TypeScript")%>'); break;<% + });%> + default: + throw new Error('Invalid <%- entity.getMappedName("TypeScript")%> provided. Field \'' + fieldNames[i] + '\' is not supported.') + } + } + } +<%}); +%> + private static assertValidFieldType(pojoName: string, fieldName: string, pojo: any, expectedType: string): void { + + let value: any = pojo[fieldName]; + let actualType: string = typeof value; + + if (value === undefined || value === null) { + return; + } + + switch (expectedType) { + case Asserts.BOOLEAN_TYPE: + if (actualType !== Asserts.BOOLEAN_TYPE && actualType !== Asserts.NUMBER_TYPE) { + err(); + } + pojo[fieldName] = Boolean(value); + return; + + case Asserts.NUMBER_TYPE: + if (actualType === Asserts.NUMBER_TYPE) { + return; + } + if (actualType === Asserts.STRING_TYPE) { + let newValue: number = parseFloat(value); + if (isNaN(newValue)) { + err(); + } + pojo[fieldName] = newValue; + } + return; + + case Asserts.STRING_TYPE: + if (actualType !== Asserts.STRING_TYPE) { + pojo[fieldName] = value.toString(); + } + return; + + case Asserts.DATE_EXPECTED_TYPE: + let getTime: Function = value.getTime; + if (typeof getTime === Asserts.FUNCTION_TYPE) { + let timeValue: number = value.getTime(); + if (isNaN(timeValue)) { + err(); + } + if (!(value instanceof Date)) { + pojo[fieldName] = new Date(timeValue); + } + return; + } + + if (actualType === Asserts.STRING_TYPE) { + let newDate: Date = new Date(value); + if (!isNaN(newDate.getTime())) { + pojo[fieldName] = newDate; + return; + } + } + err(); + return; + + case Asserts.BUFFER_EXPECTED_TYPE: + if (!Asserts.BUFFER_EXISTS) { + return; + } + + if (!(value instanceof Buffer)) { + err(); + } + return; + } + + // one pojo of array of array of pojos? + if (expectedType.length > 3 && expectedType.substr(expectedType.length - 2, 2) === '[]') { + let individualPojoType: string = expectedType.substr(0, expectedType.length - 6); + + let asserter: Function = Asserts.asserters[individualPojoType]; + if (typeof asserter !== Asserts.FUNCTION_TYPE) { + err(); + } + + if (isNaN(value.length)) { + err(); + } + for (let i = 0; i < value.length; i++) { + try { + asserter(value[i], true); + } catch (e) { + err('Error at index \'' + i + '\': ' + e.message); + } + } + + // all instances valid + return; + } + + let asserter: Function = Asserts.asserters[expectedType.substr(0, expectedType.length - 4)]; + if (typeof asserter !== Asserts.FUNCTION_TYPE) { + expectedTypeErr(); + } + + try { + asserter(value, true); + } catch (e) { + err(e.message); + } + + function err(extraMessage?: string): void { + let message: string = 'Invalid ' + pojoName + ' provided. Field \'' + fieldName + '\' with value \'' + Asserts.safeValue(value) + '\' is not a valid \'' + expectedType + '\'.'; + if (extraMessage !== undefined) { + message += ' ' + extraMessage; + } + throw new Error(message); + } + + function expectedTypeErr(): void { + throw new Error('Cannot validate \'' + pojoName + '\' field \'' + fieldName + '\' since expected type provided \'' + expectedType + '\' is not understood.'); + } + } + + private static safeValue(value: any): string { + if (value === undefined || value === null) { + return typeof value; + } + + let s: string = value.toString(); + return s.substr(0, 100); + } + + +} + +Asserts.initalize(); // static constructor + +<% if(namespace !== undefined && namespace !== null){ %> +} // namespace <%- namespace%> +<% } %> diff --git a/lib/generator/sequelize.ts.dal/templates/decorators.ts.ejs b/lib/generator/sequelize.ts.dal/templates/decorators.ts.ejs new file mode 100644 index 0000000..db42ecd --- /dev/null +++ b/lib/generator/sequelize.ts.dal/templates/decorators.ts.ejs @@ -0,0 +1,22 @@ +/* tslint:disable:max-line-length */ +//////////////////////////////////////////////////////////////////// +// +// GENERATED CLASS +// +// DO NOT EDIT +// +//////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////// +// Decorators +//////////////////////////////////////////////////////////////////// + +<% if(namespace !== undefined && namespace !== null){ %> +export namespace <%- namespace%> { +<% } +%>export function SequelizeEntitiy(value?: string): any { + return () => { return; }; +}<% +if(namespace !== undefined && namespace !== null){ %> +} // namespace <%- namespace%> +<% } %> diff --git a/lib/generator/sequelize.ts.dal/templates/entities.ts.ejs b/lib/generator/sequelize.ts.dal/templates/entities.ts.ejs new file mode 100644 index 0000000..97e0913 --- /dev/null +++ b/lib/generator/sequelize.ts.dal/templates/entities.ts.ejs @@ -0,0 +1,49 @@ +/* tslint:disable:max-line-length */ +//////////////////////////////////////////////////////////////////// +// +// GENERATED CLASS +// +// DO NOT EDIT +// +//////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////// +// PoJo Entities for Tables and Views +//////////////////////////////////////////////////////////////////// + +<% if(namespace !== undefined && namespace !== null){ %> +export namespace <%- namespace%> { +<% } %><% if(createDecorators){ +%>import { SequelizeEntitiy } from './decorators'; +<% } %><% if(tables.length>0){ %> +//////////////////////////////////////////////////////////////////// +// TABLES +//////////////////////////////////////////////////////////////////// +<% } %><%tables.forEach(function(table){%> +// table: '<%- table.getMappedName('None')%>' +<% if(createDecorators){ +%>@SequelizeEntitiy('<%- projectName%>') +<% } +%>export<% if(createDecorators){%> class <% } else {%> interface <% } %><%- table.getMappedName('TypeScript')%> {<% +table.columns.forEach(function(column){%> + <%- column.getMappedName('TypeScript')%><% if(column.nullable) {%>?<%}%>: <%- column.getMappedDataType('TypeScript');%>;<% +});%> +} +<%}); %> +<% if(views.length>0){ %> +//////////////////////////////////////////////////////////////////// +// VIEWS +//////////////////////////////////////////////////////////////////// +<% } %><%views.forEach(function(view){%> +// view: '<%- view.getMappedName('None')%>' +<% if(createDecorators){ +%>@SequelizeEntitiy('<%- projectName%>') +<% } +%>export<% if(createDecorators){%> class <% } else {%> interface <% } %><%- view.getMappedName('TypeScript')%> {<% +view.columns.forEach(function(column){%> + <%- column.getMappedName('TypeScript')%><% if(column.nullable) {%>?<%}%>: <%- column.getMappedDataType('TypeScript');%>;<% +});%> +} +<%}); %><% if(namespace !== undefined && namespace !== null){ %> +} // namespace <%- namespace%> +<% } %> diff --git a/lib/generator/sequelize.ts.dal/templates/entitycontainer.ts.ejs b/lib/generator/sequelize.ts.dal/templates/entitycontainer.ts.ejs new file mode 100644 index 0000000..799022d --- /dev/null +++ b/lib/generator/sequelize.ts.dal/templates/entitycontainer.ts.ejs @@ -0,0 +1,148 @@ +/* tslint:disable:max-line-length */ +//////////////////////////////////////////////////////////////////// +// +// GENERATED CLASS +// +// DO NOT EDIT +// +//////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////// +// NOTE: remove the auto generated timestamps fields from +// connection by adding 'define: { timestamps: false }' in the options +//////////////////////////////////////////////////////////////////// + +import sequelize = require('sequelize'); +let Sequelize: sequelize.SequelizeStatic = require('sequelize'); + +import models = require('./models'); +import entities = require('./entities');<% if(namespace !== undefined && namespace !== null){ %> + +export namespace <%- namespace%> {<% } %> + +export class <%- entityContainerName%> { + protected _connection: sequelize.Sequelize; +<%entities.forEach(function(entity){%> + <%- entity.getMappedName('TypeScriptModel')%>: models.<% + if(namespace !== undefined && namespace !== null){ %><%- namespace%>.<% } +%><%- entity.getMappedName('TypeScriptModel')%>;<%}); +%> + + constructor(connection?: sequelize.Sequelize) { + this._connection = connection; + } + + get connection(): sequelize.Sequelize { + return this._connection; + } + + set connection(value: sequelize.Sequelize) { + this.destroyModels(); + this._connection = value; + this.createModels(); + this.createRelationships(); + } + + connect(database: string, username: string, password: string, + options?: sequelize.Options): sequelize.Sequelize { + let con: sequelize.Sequelize = new sequelize(database, username, password, options); + this.connection = con; + return con; + } + + disconnect() { + if (this.connection != null) { + this.connection.close(); + } + this.connection = null; + } + + protected destroyModels() {<% +entities.forEach(function(entity){%> + this.<%- entity.getMappedName('TypeScriptModel')%> = null;<%}); +%> + } + protected createModels() {<% +entities.forEach(function(entity){%> + this.<%- entity.getMappedName('TypeScriptModel')%> = <% + if(entity.isView){ %> // view <% + } else { %> // table <% + } %> + this.connection.define<%- namespace%>.<% } +%><%- entity.getMappedName('TypeScriptInstance')%>, entities.<% + if(namespace !== undefined && namespace !== null){ %><%- namespace%>.<% } +%><%- entity.getMappedName('TypeScript')%>>( + '<%- entity.name%>', {<% +entity.columns.forEach(function(column){%> + <%- column.getMappedName('TypeScript')%>: { field: '<%- column.name%>', type: <%- column.getMappedDataType('Sequelize')%>,<% + if(column.pk){ %> primaryKey: true,<% + } %><% + if(column.defaultValue !== undefined && + column.defaultValue !== null && + column.defaultValue !== ''){ %> defaultValue: <%- column.defaultValue%>,<% + } %> },<% +});%> + }, + { + freezeTableName: true, + classMethods: { + load<%- entity.getMappedName('TypeScript')%>: (params: any): Promise> => { + let where: { [key: string]: any } = {}; + if (params !== undefined && params !== null) {<% + entity.columns.forEach(function(column){%> + if (params['<%- column.getMappedName('TypeScript')%>'] !== undefined) { where['<%- column.name%>'] = params['<%- column.getMappedName('TypeScript')%>']; };<% + });%> + } + return this.<%- entity.getMappedName('TypeScriptModel')%>.find({where: where}); + }, + get<%- entity.getMappedName('TypeScript')%>: (params: any): Promise[]> => { + let where: { [key: string]: any } = {}; + if (params !== undefined && params !== null) {<% + entity.columns.forEach(function(column){%> + if (params['<%- column.getMappedName('TypeScript')%>'] !== undefined) { where['<%- column.name%>'] = params['<%- column.getMappedName('TypeScript')%>']; };<% + });%> + } + return this.<%- entity.getMappedName('TypeScriptModel')%>.findAll({where: where}); + }, + toEntity(instance: any): entities.<%- entity.getMappedName('TypeScript')%> { + let entity: any = {}; + if (instance !== undefined && instance !== null) {<% + entity.columns.forEach(function(column){%> + if (instance['<%- column.getMappedName('TypeScript')%>'] !== undefined) { entity['<%- column.getMappedName('TypeScript')%>'] = instance['<%- column.getMappedName('TypeScript')%>']; };<% + });%> + } + return >entity; + }, + toJson(instance: any): string { + let entity: any = {}; + if (instance !== undefined && instance !== null) {<% + entity.columns.forEach(function(column){%> + if (instance['<%- column.getMappedName('TypeScript')%>'] !== undefined) { entity['<%- column.getMappedName('TypeScript')%>'] = instance['<%- column.getMappedName('TypeScript')%>']; };<% + });%> + } + let str = JSON.stringify(entity, null, 2); + return str; + } + } + });<% + if(!entity.hasPKs){ %> + this.<%- entity.getMappedName('TypeScriptModel')%>.removeAttribute('id');<% +} %><%}); +%> + } + + protected createRelationships() {<% +relations.forEach(function(relation){%> + this.<%- relation.table.getMappedName('TypeScriptModel') + %>.belongsTo(this.<%- relation.destination.table.getMappedName('TypeScriptModel') + %>, {foreignKey: '<%- relation.source.name + %>'}); + this.<%- relation.destination.table.getMappedName('TypeScriptModel') + %>.hasMany(this.<%- relation.table.getMappedName('TypeScriptModel') + %>, {foreignKey: '<%- relation.destination.name + %>'}); +<%});%> } +}<% if(namespace !== undefined && namespace !== null){ %> +} // namespace <%- namespace%> +<% } %> diff --git a/lib/generator/sequelize.ts.dal/templates/generated.exports.ts.ejs b/lib/generator/sequelize.ts.dal/templates/generated.exports.ts.ejs new file mode 100644 index 0000000..ee64aed --- /dev/null +++ b/lib/generator/sequelize.ts.dal/templates/generated.exports.ts.ejs @@ -0,0 +1,12 @@ +//////////////////////////////////////////////////////////////////// +// +// GENERATED FILE +// +// DO NOT EDIT +// +//////////////////////////////////////////////////////////////////// + +export * from './asserts'; +export * from './entities'; +export * from './models'; +export * from './<%-entitycontainerBaseFileName%>'; diff --git a/lib/generator/sequelize.ts.dal/templates/index.ts.ejs b/lib/generator/sequelize.ts.dal/templates/index.ts.ejs new file mode 100644 index 0000000..bf1692d --- /dev/null +++ b/lib/generator/sequelize.ts.dal/templates/index.ts.ejs @@ -0,0 +1 @@ +export * from './generated.exports'; diff --git a/lib/generator/sequelize.ts.dal/templates/models.ts.ejs b/lib/generator/sequelize.ts.dal/templates/models.ts.ejs new file mode 100644 index 0000000..26eaaff --- /dev/null +++ b/lib/generator/sequelize.ts.dal/templates/models.ts.ejs @@ -0,0 +1,63 @@ +/* tslint:disable:max-line-length */ +//////////////////////////////////////////////////////////////////// +// +// GENERATED CLASS +// +// DO NOT EDIT +// +//////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////// +// Sequelize MODEL Definitions +//////////////////////////////////////////////////////////////////// + +import sequelize = require('sequelize');<% +if(entities.length>0){ %> +import entities = require('./entities'); +<% } %><% if(namespace !== undefined && namespace !== null){ %> +export namespace <%- namespace%> { +<% } %> +<% + +if(tables.length>0){ %> +//////////////////////////////////////////////////////////////////// +// TABLES +//////////////////////////////////////////////////////////////////// +<% } %><%tables.forEach(function(table){%> +export interface <%- table.getMappedName('TypeScriptInstance')%> extends sequelize.Instance<%- namespace%>.<% } +%><%- table.getMappedName('TypeScript')%>>, entities.<% + if(namespace !== undefined && namespace !== null){ %><%- namespace%>.<% } +%><%- table.getMappedName('TypeScript')%> { } +export interface <%- table.getMappedName('TypeScriptModel')%> extends sequelize.Model<<%- table.getMappedName('TypeScriptInstance')%>, entities.<% + if(namespace !== undefined && namespace !== null){ %><%- namespace%>.<% } +%><%- table.getMappedName('TypeScript')%>> { + load<%- table.getMappedName('TypeScript')%>(params: any): Promise<<%- table.getMappedName('TypeScriptInstance')%>>; + get<%- table.getMappedName('TypeScript')%>(params: any): Promise<<%- table.getMappedName('TypeScriptInstance')%>[]>; + toEntity(instance: any): <%- table.getMappedName('TypeScriptInstance')%>; + toJson(instance: any): string; +} +<%}); %> +<% + +if(views.length>0){ %> +//////////////////////////////////////////////////////////////////// +// VIEWS +//////////////////////////////////////////////////////////////////// +<% } %><%views.forEach(function(view){%> +export interface <%- view.getMappedName('TypeScriptInstance')%> extends sequelize.Instance<%- namespace%>.<% } +%><%- view.getMappedName('TypeScript')%>>, entities.<% + if(namespace !== undefined && namespace !== null){ %><%- namespace%>.<% } +%><%- view.getMappedName('TypeScript')%> { } +export interface <%- view.getMappedName('TypeScriptModel')%> extends sequelize.Model<<%- view.getMappedName('TypeScriptInstance')%>, entities.<% + if(namespace !== undefined && namespace !== null){ %><%- namespace%>.<% } +%><%- view.getMappedName('TypeScript')%>> { + load<%- view.getMappedName('TypeScript')%>(params: any): Promise<<%- view.getMappedName('TypeScriptInstance')%>>; + get<%- view.getMappedName('TypeScript')%>(params: any): Promise<<%- view.getMappedName('TypeScriptInstance')%>[]>; + toEntity(instance: any): <%- view.getMappedName('TypeScript')%>; + toJson(instance: any): string; +} +<%}); %><% if(namespace !== undefined && namespace !== null){ %> +} // namespace <%- namespace%> +<% } %> diff --git a/lib/generator/sequelize.ts.dal/templates/package.json.ejs b/lib/generator/sequelize.ts.dal/templates/package.json.ejs new file mode 100644 index 0000000..0da63e5 --- /dev/null +++ b/lib/generator/sequelize.ts.dal/templates/package.json.ejs @@ -0,0 +1,21 @@ +{ + "name": "<%- projectName%>", + "version": "0.0.1", + "description": "", + "main": "index.ts", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "private": true, + "license": "ISC", + "dependencies": { + "@types/node": "^6.0.55", + "@types/sequelize": "^4.0.39", + "sequelize": "^3.28.0", + "typescript": "^2.1.4" + }, + "devDependencies": { + "tslint": "^4.2.0" + } +} diff --git a/lib/generator/sequelize.ts.dal/templates/sequelize-entities.ts.ejs b/lib/generator/sequelize.ts.dal/templates/sequelize-entities.ts.ejs new file mode 100644 index 0000000..dd44982 --- /dev/null +++ b/lib/generator/sequelize.ts.dal/templates/sequelize-entities.ts.ejs @@ -0,0 +1,22 @@ +/* tslint:disable:max-line-length */ +//////////////////////////////////////////////////////////////////// +// +// GENERATED CLASS +// +// DO NOT EDIT +// +// See sequelize-auto-ts for edits +// +//////////////////////////////////////////////////////////////////// + +<%schema.idFields.forEach(function(field){%> +export type <%- field.fieldNameProperCase()%> = number;<% +}); %> +<%schema.tables.forEach(function(table){%> +export interface <%- table.entityName()%> {<% +table.columns.forEach(function(column){%> + <%- column.fieldNameAndIsNullable()%>:<%- column.customFieldType();%>;<% +});%> +} +<%}); %> + diff --git a/lib/generator/symbolnamemapper.ts b/lib/generator/symbolnamemapper.ts new file mode 100644 index 0000000..967bf90 --- /dev/null +++ b/lib/generator/symbolnamemapper.ts @@ -0,0 +1,54 @@ +import ChangeCase = require('change-case'); +let identifierfy = require('identifierfy'); + +let identifierfyOptions = { + prefixInvalidIdentifiers: false, + prefixReservedWords: false +}; + +// https://github.com/blakeembrey/change-case +// https://github.com/novemberborn/identifierfy + +export class SymbolNameMapper { + public static titleCase(value: string): string { + return ChangeCase.titleCase + (value); + } + + public static dotCase(value: string): string { + return ChangeCase.dotCase(value); + } + + public static headerCase(value: string): string { + return ChangeCase.headerCase(value); + } + + public static pascal(value: string): string { + return ChangeCase.pascal(value); + } + + public static camel(value: string): string { + return ChangeCase.camel(value); + } + + public static identifierfy(value: string): string { + return identifierfy(value, identifierfyOptions); + } + + public static removeUnderscore(value: string): string { + return value.replace(/[_]/g, ''); + } + + public static removeWhitespace(value: string): string { + return value.replace(/\s+/g, '') ; + } + + public static lower(value: string): string { + return value.toLowerCase(); + } + + public static upper(value: string): string { + return value.toUpperCase(); + } +} + diff --git a/lib/generator/symboltable/db/columnsymbol.ts b/lib/generator/symboltable/db/columnsymbol.ts new file mode 100644 index 0000000..6f2c279 --- /dev/null +++ b/lib/generator/symboltable/db/columnsymbol.ts @@ -0,0 +1,16 @@ +import { TableSymbol } from './tablesymbol'; + +export interface ColumnSymbol { + table: TableSymbol; + name: string; + dataType: string; + nullable: boolean; + defaultValue: string; + length: number; + precision: string; + pk: boolean; + referencedTableName: string; + referencedColumnName: string; + getMappedDataType(kind: string): string; + getMappedName(kind: string): string; +} diff --git a/lib/generator/symboltable/db/dbsymboltable.reader.ts b/lib/generator/symboltable/db/dbsymboltable.reader.ts new file mode 100644 index 0000000..0d6f320 --- /dev/null +++ b/lib/generator/symboltable/db/dbsymboltable.reader.ts @@ -0,0 +1,309 @@ +import { Filter, Schema, NicassaParserDB } from 'nicassa-parser-db'; +import { DBSymbolTable } from './dbsymboltable'; + +import { TopLevel } from '../../../persistance/toplevel'; + +import { TableSymbol } from './tablesymbol'; +import { ColumnSymbol } from './columnsymbol'; +import { RelationshipSymbol } from './relationship'; + +import { TypeMapper } from './typemapper.class'; + +export class DBSymbolTableReader { + public static readFromJsonString(json: string, filter: Filter, typeMapper: TypeMapper): DBSymbolTable { + let schema: Schema = DBSymbolTableReader.readSchema(json); + let result = DBSymbolTableReader.createSymbolTable(schema, filter, typeMapper); + return result; + } + + protected static readSchema(json: string): Schema { + let toplevel: TopLevel = JSON.parse(json); + let nicassaParserDB: NicassaParserDB = toplevel.nicassaParserDB; + if (nicassaParserDB === undefined || nicassaParserDB.schema === undefined) { + return null; + } + + return nicassaParserDB.schema; + } + + protected static createSymbolTable(schema: Schema, filter: Filter, typeMapper: TypeMapper): DBSymbolTable { + let result: DBSymbolTable = { + tables: [], + views: [], + entities: [], + columns: [], + relations: [] + } + + if (schema === undefined || schema === null) { + return result; + } + + if (filter === undefined || filter === null) { + filter = { + excludeTables: false, + excludeViews: false, + exculdeColumns: [], + exculde: [], + only: [] + } + } + + let excludeTables: boolean = false; + let excludeViews: boolean = false; + let exculdeColumns = DBSymbolTableReader.createExculdeColumnsFromFilter(filter); + let exculdes = DBSymbolTableReader.createExculdesFromFilter(filter); + let only = DBSymbolTableReader.createOnlyItemsFromFilter(filter); + + if (filter !== undefined || filter !== null) { + if (filter.excludeTables) { + excludeTables = true; + } + if (filter.excludeViews) { + excludeViews = true; + } + } + + let names: string[] = []; + + if (schema.tables !== undefined && !excludeTables) { + for (let i = 0; i < schema.tables.length; i++) { + let t = schema.tables[i]; + + // excluded + if (only.length == 0 && exculdes.indexOf(t.name) != -1) { + continue; + } + if (only.length > 0 && only.indexOf(t.name) == -1) { + continue; + } + + names.push(t.name); + let tableSymbol: TableSymbol = { + name: t.name, + columns: [], + isView: false, + hasPKs: false, + getMappedName: (kind: string) => { + return typeMapper.tableNameMapper(tableSymbol, kind); + } + } + result.tables.push(tableSymbol); + result.entities.push(tableSymbol); + + if (t.columns === undefined || t.columns === null) { + continue; + } + + for (let k = 0; k < t.columns.length; k++) { + let col = t.columns[k]; + + // excluded + if (exculdeColumns.indexOf(col.name) != -1) { + continue; + } + if (exculdeColumns.indexOf(t.name + '.' + col.name) != -1) { + continue; + } + + let columnSymbol: ColumnSymbol = { + table: tableSymbol, + name: col.name, + dataType: col.dataType, + nullable: col.nullable, + defaultValue: col.defaultValue, + length: col.length, + precision: col.precision, + pk: col.pk, + referencedTableName: col.referencedTableName, + referencedColumnName: col.referencedColumnName, + getMappedDataType: (kind: string) => { + return typeMapper.columnDataTypeMapper(tableSymbol, columnSymbol, kind); + }, + getMappedName: (kind: string) => { + return typeMapper.columnNameMapper(columnSymbol, kind); + } + } + // the table has at least one primary key + if (col.pk) { + tableSymbol.hasPKs = true; + } + + result.columns.push(columnSymbol); + tableSymbol.columns.push(columnSymbol); + } + + } + } + + if (schema.views !== undefined && !excludeViews) { + for (let i = 0; i < schema.views.length; i++) { + let v = schema.views[i]; + + // excluded + if (only.length == 0 && exculdes.indexOf(v.name) != -1) { + continue; + } + if (only.length > 0 && only.indexOf(v.name) == -1) { + continue; + } + + let tableSymbol: TableSymbol = { + name: v.name, + columns: [], + isView: true, + hasPKs: false, + getMappedName: (kind: string) => { + return typeMapper.tableNameMapper(tableSymbol, kind); + } + } + result.views.push(tableSymbol); + result.entities.push(tableSymbol); + + if (v.columns === undefined || v.columns === null) { + continue; + } + + for (let k = 0; k < v.columns.length; k++) { + let col = v.columns[k]; + + // excluded + if (exculdeColumns.indexOf(col.name) != -1) { + continue; + } + if (exculdeColumns.indexOf(v.name + '.' + col.name) != -1) { + continue; + } + + let columnSymbol: ColumnSymbol = { + table: tableSymbol, + name: col.name, + dataType: col.dataType, + nullable: col.nullable, + defaultValue: col.defaultValue, + length: col.length, + precision: col.precision, + pk: col.pk, + referencedTableName: col.referencedTableName, + referencedColumnName: col.referencedColumnName, + getMappedDataType: (kind: string) => { + return typeMapper.columnDataTypeMapper(tableSymbol, columnSymbol, kind); + }, + getMappedName: (kind: string) => { + return typeMapper.columnNameMapper(columnSymbol, kind); + } + } + + result.columns.push(columnSymbol); + tableSymbol.columns.push(columnSymbol); + } + + } + } + + for (let i = 0; i < result.columns.length; i++) { + let col = result.columns[i]; + if (col.referencedTableName === undefined || col.referencedTableName === null) { + continue; + } + if (names.indexOf(col.referencedTableName) != -1) { + continue; + } + // the referenced table has been filtered + col.referencedTableName = null; + col.referencedColumnName = null; + } + + this.updateAssociations(result, schema); + + // sort tables & views - they are in random order anyway + // note! we want to keep the columns in database order + let nameSorter: Function = (a: any, b: any) => { + var x = a['name']; + var y = b['name']; + return ((x < y) ? -1 : ((x > y) ? 1 : 0)); + } + result.tables.sort(nameSorter); + result.views.sort(nameSorter); + result.entities.sort(nameSorter); + + return result; + } + + protected static createExculdeColumnsFromFilter(filter: Filter): string[] { + let columns: any = []; + + if (filter === undefined || filter === null || filter.exculdeColumns === undefined || + filter.exculdeColumns === null || filter.exculdeColumns.length < 0) { + return columns; + } + columns = filter.exculdeColumns; + return columns; + } + + protected static createExculdesFromFilter(filter: Filter): string[] { + let excludes: any = []; + + if (filter === undefined || filter === null || filter.exculde === undefined || + filter.exculde === null || filter.exculde.length < 0) { + return excludes; + } + + excludes = filter.exculde; + return excludes; + } + + protected static createOnlyItemsFromFilter(filter: Filter): string[] { + let onlyItems: any = []; + + if (filter === undefined || filter === null || filter.only === undefined || + filter.only === null || filter.only.length < 0) { + return onlyItems; + } + + onlyItems = filter.only; + return onlyItems; + } + + + protected static updateAssociations(symbolTable: DBSymbolTable, schema: Schema) { + let tblColMap: { [key: string]: ColumnSymbol } = {}; + + for (let i = 0; i < symbolTable.columns.length; i++) { + let col = symbolTable.columns[i]; + let key = col.table.name + '.' + col.name; + tblColMap[key] = col; + } + + for (let i = 0; i < symbolTable.tables.length; i++) { + let t = symbolTable.tables[i]; + + for (let k = 0; k < t.columns.length; k++) { + let col = t.columns[k]; + + if (col.referencedTableName === undefined || col.referencedTableName === null) { + continue; + } + + let key = col.referencedTableName + '.' + col.referencedColumnName; + let refCol = tblColMap[key]; + + if (refCol === undefined || refCol === null) { + let err = col.table.name + '.' + col.name + ' -> ' + key; + console.error('error: invalid relation found ' + err); + process.exit(-1); + } + + // console.log('relation:' + col.table.name + '.' + col.name + ' -> ' + key); + + let r: RelationshipSymbol = { + table: t, + source: col, + destination: refCol + } + symbolTable.relations.push(r); + } + } + } + +} diff --git a/lib/generator/symboltable/db/dbsymboltable.ts b/lib/generator/symboltable/db/dbsymboltable.ts new file mode 100644 index 0000000..8607266 --- /dev/null +++ b/lib/generator/symboltable/db/dbsymboltable.ts @@ -0,0 +1,11 @@ +import { TableSymbol } from './tablesymbol'; +import { ColumnSymbol } from './columnsymbol'; +import { RelationshipSymbol } from './relationship'; + +export interface DBSymbolTable { + tables: TableSymbol[]; + views: TableSymbol[]; + entities: TableSymbol[]; + columns: ColumnSymbol[]; + relations: RelationshipSymbol[]; +} diff --git a/lib/generator/symboltable/db/relationship.ts b/lib/generator/symboltable/db/relationship.ts new file mode 100644 index 0000000..9fa9c32 --- /dev/null +++ b/lib/generator/symboltable/db/relationship.ts @@ -0,0 +1,8 @@ +import { TableSymbol } from './tablesymbol'; +import { ColumnSymbol } from './columnsymbol'; + +export interface RelationshipSymbol { + table: TableSymbol; + source: ColumnSymbol; + destination: ColumnSymbol; +} diff --git a/lib/generator/symboltable/db/tablesymbol.ts b/lib/generator/symboltable/db/tablesymbol.ts new file mode 100644 index 0000000..a9f51ae --- /dev/null +++ b/lib/generator/symboltable/db/tablesymbol.ts @@ -0,0 +1,9 @@ +import { ColumnSymbol } from './columnsymbol'; + +export interface TableSymbol { + name: string; + columns: ColumnSymbol[]; + isView: boolean; + hasPKs: boolean; + getMappedName(kind: string): string; +} diff --git a/lib/generator/symboltable/db/typemapper.class.ts b/lib/generator/symboltable/db/typemapper.class.ts new file mode 100644 index 0000000..74ad460 --- /dev/null +++ b/lib/generator/symboltable/db/typemapper.class.ts @@ -0,0 +1,24 @@ +import { TableSymbol } from '../../symboltable/db/tablesymbol'; +import { ColumnSymbol } from '../../symboltable/db/columnsymbol'; + +export class TypeMapper { + + constructor(protected dataTypeMapping: { [mappings: string]: { [dbtype: string]: string } }) { + + } + + public columnDataTypeMapper(table: TableSymbol, column: ColumnSymbol, kind: string): string { + // TODO -> custom mappings from this.generator.dataTypeMapping + let type = column.dataType.toLowerCase(); + return this.dataTypeMapping[kind][type]; + } + + public /*virtual*/ tableNameMapper(table: TableSymbol, kind: string): string { + return table.name; + } + + public /*virtual*/ columnNameMapper(column: ColumnSymbol, kind: string): string { + return column.name; + } +} + diff --git a/lib/generator/symboltable/metadata/arraytypesymbol.ts b/lib/generator/symboltable/metadata/arraytypesymbol.ts new file mode 100644 index 0000000..c130804 --- /dev/null +++ b/lib/generator/symboltable/metadata/arraytypesymbol.ts @@ -0,0 +1,5 @@ +import { TypeSymbol } from './typesymbol'; + +export interface ArrayTypeSymbol extends TypeSymbol { + elementType: TypeSymbol; +} diff --git a/lib/generator/symboltable/metadata/controllersymbol.ts b/lib/generator/symboltable/metadata/controllersymbol.ts new file mode 100644 index 0000000..b26b788 --- /dev/null +++ b/lib/generator/symboltable/metadata/controllersymbol.ts @@ -0,0 +1,10 @@ +import { MethodSymbol } from './methodsymbol'; + +export interface ControllerSymbol { + location: string; + methods: MethodSymbol[]; + name: string; + path: string; + jwtUserProperty: string; + getMappedLocation(): string; +} diff --git a/lib/generator/symboltable/metadata/injecttypesymbol.ts b/lib/generator/symboltable/metadata/injecttypesymbol.ts new file mode 100644 index 0000000..b0a2a70 --- /dev/null +++ b/lib/generator/symboltable/metadata/injecttypesymbol.ts @@ -0,0 +1,6 @@ +import { ParameterSymbol } from './parametersymbol'; + +export interface InjectTypeSymbol { + parameter: ParameterSymbol; + typeName: string; +} diff --git a/lib/generator/symboltable/metadata/metadatasymboltable.reader.ts b/lib/generator/symboltable/metadata/metadatasymboltable.reader.ts new file mode 100644 index 0000000..9a3d370 --- /dev/null +++ b/lib/generator/symboltable/metadata/metadatasymboltable.reader.ts @@ -0,0 +1,271 @@ +import { Metadata, Type, InjectType, PrimitiveType, ArrayType, ReferenceType, Parameter, Property, NicassaParserTSExpressApi } from 'nicassa-parser-ts-express-api'; + +import { TopLevel } from '../../../persistance/toplevel'; + +import { MetadataSymbolTable } from './metadatasymboltable'; +import { ControllerSymbol } from './controllersymbol'; +import { MethodSymbol } from './methodsymbol'; +import { TypeSymbol } from './typesymbol'; +import { ParameterSymbol } from './parametersymbol'; +import { InjectTypeSymbol } from './injecttypesymbol'; +import { PropertySymbol } from './propertysymbol'; +import { ReferenceTypeSymbol } from './referencetypesymbol'; +import { PrimitiveTypeSymbol } from './primitivetypesymbol'; +import { ArrayTypeSymbol } from './arraytypesymbol'; + +export class MetadataSymbolTableReader { + public static readFromJsonString(json: string): MetadataSymbolTable { + let metadata: Metadata = MetadataSymbolTableReader.readSchema(json); + let result = MetadataSymbolTableReader.createSymbolTable(metadata); + return result; + } + + protected static readSchema(json: string): Metadata { + let toplevel: TopLevel = JSON.parse(json); + let nicassaParserTSExpressApi: NicassaParserTSExpressApi = toplevel.nicassaParserTSExpressApi; + if (nicassaParserTSExpressApi === undefined || nicassaParserTSExpressApi.metadata === undefined) { + return null; + } + + return nicassaParserTSExpressApi.metadata; + } + + protected static createSymbolTable(metadata: Metadata): MetadataSymbolTable { + let result: MetadataSymbolTable = { + controllers: [], + referenceTypes: [] + } + + if (metadata === undefined || metadata === null) { + return result; + } + + let excludeControllers: boolean = false; + let excludeReferenceTypes: boolean = false; + let excludeMethods: string[] = []; + let exculdes: string[] = []; + let only: string[] = []; + + if (metadata.Controllers !== undefined && !excludeControllers) { + for (let i = 0; i < metadata.Controllers.length; i++) { + let ctrl = metadata.Controllers[i]; + + // excluded + if (only.length == 0 && exculdes.indexOf(ctrl.name) != -1) { + continue; + } + if (only.length > 0 && only.indexOf(ctrl.name) == -1) { + continue; + } + + let controllerSymbol: ControllerSymbol = { + location: ctrl.location, + methods: [], + name: ctrl.name, + path: ctrl.path, + jwtUserProperty: ctrl.jwtUserProperty, + getMappedLocation: (): string => { + if (controllerSymbol.location === undefined || controllerSymbol.location === null) { + return controllerSymbol.location + } + return controllerSymbol.location.replace(/\.[^/.]+$/, ""); + } + } + result.controllers.push(controllerSymbol); + + if (ctrl.methods === undefined || ctrl.methods === null) { + continue; + } + + for (let k = 0; k < ctrl.methods.length; k++) { + let method = ctrl.methods[k]; + + // excluded + if (excludeMethods.indexOf(method.name) != -1) { + continue; + } + if (excludeMethods.indexOf(ctrl.name + '.' + method.name) != -1) { + continue; + } + + let methodSymbol: MethodSymbol = { + controller: controllerSymbol, + description: method.description, + example: method.example, + method: method.method, + name: method.name, + parameters: [], + path: method.path, + type: null, + tags: method.tags, + bodyParamName: null, + getPath: (): string => { + if (methodSymbol.path === undefined || methodSymbol.path === null) { + return methodSymbol.path + } + return methodSymbol.path.replace(/{/g, ':').replace(/}/g, ''); + } + } + + const bodyParameter = method.parameters.find(parameter => parameter.in === 'body'); + methodSymbol.bodyParamName = bodyParameter ? bodyParameter.name : undefined; + + methodSymbol.type = MetadataSymbolTableReader.createTypeSymbol(method.type, controllerSymbol, methodSymbol); + MetadataSymbolTableReader.createParameterSymbols(controllerSymbol, methodSymbol, method.parameters); + + controllerSymbol.methods.push(methodSymbol); + } + } + } + + if (metadata.ReferenceTypes !== undefined && !excludeReferenceTypes) { + for (let key in metadata.ReferenceTypes) { + let referenceType = metadata.ReferenceTypes[key]; + + // excluded + if (only.length == 0 && exculdes.indexOf(referenceType.name) != -1) { + continue; + } + if (only.length > 0 && only.indexOf(referenceType.name) == -1) { + continue; + } + if (exculdes.length > 0 && exculdes.indexOf(referenceType.name) > -1) { + continue; + } + + let referenceTypeSymbol: ReferenceTypeSymbol = { + controller: null, + parameter: null, + method: null, + isPrimitive: false, + isArray: false, + isReferenceType: true, + description: referenceType.description, + name: referenceType.name, + properties: [] + } + referenceTypeSymbol.properties = MetadataSymbolTableReader.createProperties(referenceTypeSymbol, referenceType.properties); + result.referenceTypes.push(referenceTypeSymbol); + } + } + return result; + } + + protected static createProperties(referenceTypeSymbol: ReferenceTypeSymbol, properties: Property[]): PropertySymbol[] { + let result: PropertySymbol[] = []; + if (properties === undefined || properties === null || properties.length == 0) { + return result; + } + + for (let i = 0; i < properties.length; i++) { + let property = properties[i]; + + let propertySymbol: PropertySymbol = { + reference: referenceTypeSymbol, + description: property.description, + name: property.name, + length: -1, + type: null, + required: property.required + } + propertySymbol.type = MetadataSymbolTableReader.createTypeSymbol(property.type); + result.push(propertySymbol); + } + + return result; + } + + protected static createParameterSymbols(controllerSymbol: ControllerSymbol, methodSymbol: MethodSymbol, parameters: Parameter[]) { + if (parameters === undefined || parameters === null || parameters.length == 0) { + return; + } + + for (let k = 0; k < parameters.length; k++) { + let parameter = parameters[k]; + let parameterSymbol: ParameterSymbol = { + controller: controllerSymbol, + method: methodSymbol, + description: parameter.description, + in: parameter.in, + name: parameter.name, + required: parameter.required, + type: null, + injected: undefined + }; + + parameterSymbol.type = MetadataSymbolTableReader.createTypeSymbol(parameter.type, controllerSymbol, methodSymbol, parameterSymbol); + parameterSymbol.injected = MetadataSymbolTableReader.createInjectedSymbol(parameterSymbol, parameter.injected); + methodSymbol.parameters.push(parameterSymbol); + } + } + + protected static createInjectedSymbol(parameterSymbol: ParameterSymbol, injectType: InjectType): InjectTypeSymbol { + if (injectType === null || injectType === undefined) { + return undefined; + } + + let result: InjectTypeSymbol = { + parameter: parameterSymbol, + typeName: injectType + }; + + return result; + } + + protected static createPrimitiveTypeSymbol(type: PrimitiveType, controllerSymbol?: ControllerSymbol, methodSymbol?: MethodSymbol, parameterSymbol?: ParameterSymbol): PrimitiveTypeSymbol { + let result: PrimitiveTypeSymbol = { + controller: controllerSymbol, + method: methodSymbol, + parameter: parameterSymbol, + isPrimitive: true, + isArray: false, + isReferenceType: false, + name: type + } + return result; + } + + protected static createArrayTypeSymbolSymbol(type: ArrayType, controllerSymbol?: ControllerSymbol, methodSymbol?: MethodSymbol, parameterSymbol?: ParameterSymbol): ArrayTypeSymbol { + let result: ArrayTypeSymbol = { + controller: controllerSymbol, + method: methodSymbol, + parameter: parameterSymbol, + isPrimitive: false, + isArray: true, + isReferenceType: false, + elementType: null + } + result.elementType = MetadataSymbolTableReader.createTypeSymbol(type.elementType, controllerSymbol, methodSymbol, parameterSymbol); + return result; + } + + protected static createReferenceTypeSymbol(type: ReferenceType, controllerSymbol?: ControllerSymbol, methodSymbol?: MethodSymbol, parameterSymbol?: ParameterSymbol): ReferenceTypeSymbol { + let result: ReferenceTypeSymbol = { + controller: controllerSymbol, + method: methodSymbol, + parameter: parameterSymbol, + isPrimitive: false, + isArray: false, + isReferenceType: true, + description: type.description, + name: type.name, + properties: [] + } + + result.properties = MetadataSymbolTableReader.createProperties(result, type.properties); + return result; + } + + protected static createTypeSymbol(type: Type, controllerSymbol?: ControllerSymbol, methodSymbol?: MethodSymbol, parameterSymbol?: ParameterSymbol): TypeSymbol { + if (typeof type === 'string' || type instanceof String) { + return MetadataSymbolTableReader.createPrimitiveTypeSymbol(type, controllerSymbol, methodSymbol, parameterSymbol); + } + + const arrayType = type as ArrayType; + if (arrayType.elementType) { + return MetadataSymbolTableReader.createPrimitiveTypeSymbol(type, controllerSymbol, methodSymbol, parameterSymbol); + } + + return MetadataSymbolTableReader.createReferenceTypeSymbol((type as ReferenceType), controllerSymbol, methodSymbol, parameterSymbol); + } +} diff --git a/lib/generator/symboltable/metadata/metadatasymboltable.ts b/lib/generator/symboltable/metadata/metadatasymboltable.ts new file mode 100644 index 0000000..deeadca --- /dev/null +++ b/lib/generator/symboltable/metadata/metadatasymboltable.ts @@ -0,0 +1,7 @@ +import { ControllerSymbol } from './controllersymbol'; +import { ReferenceTypeSymbol } from './referencetypesymbol'; + +export interface MetadataSymbolTable { + controllers: ControllerSymbol[]; + referenceTypes: ReferenceTypeSymbol[]; +} diff --git a/lib/generator/symboltable/metadata/methodsymbol.ts b/lib/generator/symboltable/metadata/methodsymbol.ts new file mode 100644 index 0000000..ce85362 --- /dev/null +++ b/lib/generator/symboltable/metadata/methodsymbol.ts @@ -0,0 +1,17 @@ +import { ControllerSymbol } from './controllersymbol'; +import { ParameterSymbol } from './parametersymbol'; +import { TypeSymbol } from './typesymbol'; + +export interface MethodSymbol { + controller: ControllerSymbol; + description: string; + example: any; + method: string; + name: string; + parameters: ParameterSymbol[]; + path: string; + type: TypeSymbol; + tags: string[]; + bodyParamName?: string; + getPath(): string; +} diff --git a/lib/generator/symboltable/metadata/parametersymbol.ts b/lib/generator/symboltable/metadata/parametersymbol.ts new file mode 100644 index 0000000..214344d --- /dev/null +++ b/lib/generator/symboltable/metadata/parametersymbol.ts @@ -0,0 +1,15 @@ +import { ControllerSymbol } from './controllersymbol'; +import { MethodSymbol } from './methodsymbol'; +import { TypeSymbol } from './typesymbol'; +import { InjectTypeSymbol } from './injecttypesymbol'; + +export interface ParameterSymbol { + controller: ControllerSymbol; + method: MethodSymbol + description: string; + in: string; + name: string; + required: boolean; + type: TypeSymbol; + injected?: InjectTypeSymbol; +} diff --git a/lib/generator/symboltable/metadata/primitivetypesymbol.ts b/lib/generator/symboltable/metadata/primitivetypesymbol.ts new file mode 100644 index 0000000..ede8701 --- /dev/null +++ b/lib/generator/symboltable/metadata/primitivetypesymbol.ts @@ -0,0 +1,5 @@ +import { TypeSymbol } from './typesymbol'; + +export interface PrimitiveTypeSymbol extends TypeSymbol { + name: string; +} diff --git a/lib/generator/symboltable/metadata/propertysymbol.ts b/lib/generator/symboltable/metadata/propertysymbol.ts new file mode 100644 index 0000000..0be1a7d --- /dev/null +++ b/lib/generator/symboltable/metadata/propertysymbol.ts @@ -0,0 +1,12 @@ +import { ReferenceTypeSymbol } from './referencetypesymbol'; +import { PropertySymbol } from './propertysymbol'; +import { TypeSymbol } from './typesymbol'; + +export interface PropertySymbol { + reference: ReferenceTypeSymbol; + description: string; + name: string; + length: number; + type: TypeSymbol; + required: boolean; +} diff --git a/lib/generator/symboltable/metadata/referencetypesymbol.ts b/lib/generator/symboltable/metadata/referencetypesymbol.ts new file mode 100644 index 0000000..ba194cc --- /dev/null +++ b/lib/generator/symboltable/metadata/referencetypesymbol.ts @@ -0,0 +1,8 @@ +import { TypeSymbol } from './typesymbol'; +import { PropertySymbol } from './propertysymbol'; + +export interface ReferenceTypeSymbol extends TypeSymbol { + description: string; + name: string; + properties: PropertySymbol[]; +} diff --git a/lib/generator/symboltable/metadata/typesymbol.ts b/lib/generator/symboltable/metadata/typesymbol.ts new file mode 100644 index 0000000..4455327 --- /dev/null +++ b/lib/generator/symboltable/metadata/typesymbol.ts @@ -0,0 +1,12 @@ +import { ControllerSymbol } from './controllersymbol'; +import { ParameterSymbol } from './parametersymbol'; +import { MethodSymbol } from './methodsymbol'; + +export interface TypeSymbol { + controller?: ControllerSymbol; + method?: MethodSymbol; + parameter?: ParameterSymbol; + isPrimitive: boolean; + isArray: boolean; + isReferenceType: boolean; +} diff --git a/lib/persistance/generatorconfig.basic.ts b/lib/persistance/generatorconfig.basic.ts new file mode 100644 index 0000000..7d70e33 --- /dev/null +++ b/lib/persistance/generatorconfig.basic.ts @@ -0,0 +1,6 @@ +export interface GeneratorConfigBasic { + name: string; + type: string; + active: boolean; + targetDir: string; +} diff --git a/lib/persistance/generatorconfig.express.ts.routes.ts b/lib/persistance/generatorconfig.express.ts.routes.ts new file mode 100644 index 0000000..a6e5ce6 --- /dev/null +++ b/lib/persistance/generatorconfig.express.ts.routes.ts @@ -0,0 +1,6 @@ +import { GeneratorConfigBasic } from './generatorconfig.basic'; + +export interface GeneratorConfigExpressTSRoutes extends GeneratorConfigBasic { + nicassaParserDBFile?: string; + nicassaParserDBGeneratorName?: string; +} diff --git a/lib/persistance/generatorconfig.sequelize.ts.dal.ts b/lib/persistance/generatorconfig.sequelize.ts.dal.ts new file mode 100644 index 0000000..42fdfc1 --- /dev/null +++ b/lib/persistance/generatorconfig.sequelize.ts.dal.ts @@ -0,0 +1,17 @@ +import { Filter } from 'nicassa-parser-db'; + +import { ModelNaming } from './naming/modelnaming'; + +import { GeneratorConfigBasic } from './generatorconfig.basic'; + +export interface GeneratorConfigSequelizeTSDal extends GeneratorConfigBasic { + cleanTargetDir: boolean; + createProject: boolean; + projectName?: string; + namespace?: string; + filter?: Filter; + entityContainerName?: string; + // detectManyToManyAssociations: boolean; // TBD: hardcore stuff... + modelNaming: ModelNaming; + dataTypeMapping?: { [mappings: string]: { [dbtype: string]: string } }; +} diff --git a/lib/persistance/naming/case.enum.ts b/lib/persistance/naming/case.enum.ts new file mode 100644 index 0000000..02c1c05 --- /dev/null +++ b/lib/persistance/naming/case.enum.ts @@ -0,0 +1,11 @@ +export type CaseEnum = "Unchanged" | "Capitalized" | "FirstLetterUppercase" | "CamelCase" | "PascalCase" | "Lower" | "Upper"; + +export const CaseEnum = { + Unchanged: "Unchanged" as CaseEnum, + Capitalized: "Capitalized" as CaseEnum, + FirstLetterUppercase: "FirstLetterUppercase" as CaseEnum, + CamelCase: "CamelCase" as CaseEnum, + PascalCase: "PascalCase" as CaseEnum, + Lower: "Lower" as CaseEnum, + Upper: "Upper" as CaseEnum +}; diff --git a/lib/persistance/naming/entitynaming.ts b/lib/persistance/naming/entitynaming.ts new file mode 100644 index 0000000..e4c115b --- /dev/null +++ b/lib/persistance/naming/entitynaming.ts @@ -0,0 +1,13 @@ +import { CaseEnum } from './case.enum'; + +export interface EntityNaming { + caseType: CaseEnum; + filenameCaseType: CaseEnum; + instanceCaseType: CaseEnum; + // removePrefixes?: string[]; + // removeSuffixes?: string[]; + // addPrefix?: string; + // addSuffix?: string; + removeUnderscore: boolean; + removeInvalidCharacters: boolean; +} diff --git a/lib/persistance/naming/modelnaming.ts b/lib/persistance/naming/modelnaming.ts new file mode 100644 index 0000000..4f61de9 --- /dev/null +++ b/lib/persistance/naming/modelnaming.ts @@ -0,0 +1,7 @@ +import { EntityNaming } from './entitynaming'; +import { PropertyNaming } from './propertynaming'; + +export interface ModelNaming { + entityNaming: EntityNaming; + propertyNaming: PropertyNaming; +} diff --git a/lib/persistance/naming/propertynaming.ts b/lib/persistance/naming/propertynaming.ts new file mode 100644 index 0000000..06612ef --- /dev/null +++ b/lib/persistance/naming/propertynaming.ts @@ -0,0 +1,12 @@ +import { CaseEnum } from './case.enum'; + +export interface PropertyNaming { + caseType: CaseEnum; + // removePrefixes?: string[]; + // removeSuffixes?: string[]; + // addPrefix?: string; + // addSuffix?: string; + removeUnderscore: boolean; + removeInvalidCharacters: boolean; + plurarlizeCollectionNavigationProperties: boolean; +} diff --git a/lib/persistance/nicassagenerator.ts b/lib/persistance/nicassagenerator.ts new file mode 100644 index 0000000..f1e0f57 --- /dev/null +++ b/lib/persistance/nicassagenerator.ts @@ -0,0 +1,6 @@ +import { GeneratorConfigBasic } from './generatorconfig.basic'; + +export interface NicassaGenerator { + formatVersion: string; + generators: GeneratorConfigBasic[]; +} diff --git a/lib/persistance/toplevel.ts b/lib/persistance/toplevel.ts new file mode 100644 index 0000000..cfc71a1 --- /dev/null +++ b/lib/persistance/toplevel.ts @@ -0,0 +1,10 @@ +import { NicassaParserDB } from 'nicassa-parser-db'; +import { NicassaParserTSExpressApi } from 'nicassa-parser-ts-express-api'; + +import { NicassaGenerator } from '../persistance/nicassagenerator'; + +export interface TopLevel { + nicassaParserDB?: NicassaParserDB; + nicassaParserTSExpressApi?: NicassaParserTSExpressApi; + nicassaGenerator?: NicassaGenerator +} diff --git a/lib/tools/cmdlineparser.class.ts b/lib/tools/cmdlineparser.class.ts new file mode 100644 index 0000000..3d0a608 --- /dev/null +++ b/lib/tools/cmdlineparser.class.ts @@ -0,0 +1,63 @@ +import * as parser from 'nomnom'; + +export class CmdLineParser { + public static parse(): any { + parser.command('init') + .option('file', { + abbr: 'f', + metavar: 'sdb.json', + required: true, + help: 'path for to a typescript-mdd config file [required]' + }) + .help('creates a new nicassa-generator config file or adds a section to an existing json file'); + + parser.command('addgen') + .option('file', { + abbr: 'f', + metavar: 'nicassa.json', + required: true, + help: 'path to a nicassa-generator config file [required]' + }) + .option('type', { + abbr: 't', + metavar: 'generator', + required: true, + help: 'type of the generator (sequelize.ts.dal, express.ts.routes)' + }) + .option('name', { + abbr: 'n', + metavar: 'name', + help: 'optional name of the generator - the default name is the generator typename - every name must be unique' + }) + .help('adds generator to a given typescript-mdd config file'); + + parser.command('gen') + .option('file', { + abbr: 'f', + metavar: 'nicassa.json', + required: true, + help: 'path to a nicassa-generator config file [required]' + }) + .option('name', { + abbr: 'n', + metavar: 'name', + help: 'only run the code generator with the given name - this can also be an inactive generator' + }) + .help('runs a generator in a given nicassa-generator config file - if no name is given, all active generators will be started'); + + + var opts = parser.parse(); + var action = null; + + if (opts[0] === undefined || opts[0] === '') { + action = null; + } else { + action = { + module: '../lib/actions/' + opts[0], + opts: opts + }; + } + + return action; + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..a73a81a --- /dev/null +++ b/package.json @@ -0,0 +1,59 @@ +{ + "name": "nicassa-generator", + "description": "CLI based code generator for Sequelize, Node, Typescript, Angular 2, Android, Swagger, ORM Lite", + "version": "0.0.3", + "author": "Harald Fielker ", + "repository": { + "type": "git", + "url": "https://github.com/egandro/nicassa-generator.git" + }, + "bugs": { + "url": "https://github.com/egandro/nicassa-generator/issues" + }, + "bin": { + "nicassa-generator": "launcher.js" + }, + "dependencies": { + "@types/change-case": "^2.3.1", + "@types/ejs": "^2.3.33", + "@types/node": "^6.0.55", + "@types/nomnom": "^0.0.28", + "change-case": "^3.0.0", + "ejs": "^2.5.5", + "identifierfy": "^1.1.1", + "mysql": "^2.12.0", + "nicassa-parser-db": "^0.0.3", + "nicassa-parser-ts-express-api": "^0.0.3", + "nomnom": "^1.8.1", + "pg": "^6.1.2", + "pg-hstore": "^2.3.2", + "prompt": "^1.0.0", + "sqlite3": "^3.1.8", + "tedious": "^1.14.0", + "ts-node": "^2.0.0", + "typescript": "^2.1.4" + }, + "devDependencies": { + "codelyzer": "^2.0.0-beta.4", + "tslint": "^4.2.0" + }, + "keywords": [ + "mysql", + "sqlite", + "postgresql", + "postgres", + "mssql", + "orm", + "nodejs", + "tyoescript", + "android", + "angular2", + "mdd", + "object relational mapper", + "json", + "schema reader", + "orm lite", + "swagger" + ], + "license": "MIT" +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..cecf3c0 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": [ + "es6", + "dom" + ], + "module": "commonjs", + "moduleResolution": "node", + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "noImplicitAny": true, + "preserveConstEnums": true, + "suppressImplicitAnyIndexErrors": false, + "noResolve": false, + "noUnusedParameters": false, + "noUnusedLocals": true, + "strictNullChecks": true, + "sourceMap": false, + "typeRoots": [ + "./node_modules/@types" + ] + } +} diff --git a/tslint.json b/tslint.json new file mode 100644 index 0000000..640d02c --- /dev/null +++ b/tslint.json @@ -0,0 +1,107 @@ +{ + "rulesDirectory": [ + "node_modules/codelyzer" + ], + "rules": { + "class-name": true, + "comment-format": [ + true, + "check-space" + ], + "curly": true, + "eofline": true, + "forin": true, + "indent": [ + true, + "spaces" + ], + "label-position": true, + "max-line-length": [ + true, + 140 + ], + "member-access": false, + "member-ordering": [ + true, + "static-before-instance", + "variables-before-functions" + ], + "no-arg": true, + "no-bitwise": true, + "no-console": [ + true, + "debug", + "info", + "time", + "timeEnd", + "trace" + ], + "no-construct": true, + "no-debugger": true, + "no-duplicate-variable": true, + "no-empty": false, + "no-eval": true, + "no-inferrable-types": true, + "no-shadowed-variable": true, + "no-string-literal": false, + "no-switch-case-fall-through": true, + "no-trailing-whitespace": true, + "no-unused-expression": true, + "no-use-before-declare": true, + "no-var-keyword": true, + "object-literal-sort-keys": false, + "one-line": [ + true, + "check-open-brace", + "check-catch", + "check-else", + "check-whitespace" + ], + "quotemark": [ + true, + "single" + ], + "radix": true, + "semicolon": [ + "always" + ], + "triple-equals": [ + true, + "allow-null-check" + ], + "typedef-whitespace": [ + true, + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + } + ], + "variable-name": false, + "whitespace": [ + true, + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type" + ], + + "directive-selector": [true, "attribute", "app", "camelCase"], + "component-selector": [true, "element", "app", "kebab-case"], + "use-input-property-decorator": true, + "use-output-property-decorator": true, + "use-host-property-decorator": true, + "no-input-rename": true, + "no-output-rename": true, + "use-life-cycle-interface": true, + "use-pipe-transform-interface": true, + "component-class-suffix": true, + "directive-class-suffix": true, + "no-access-missing-member": true, + "templates-use-public": true, + "invoke-injectable": true + } +}