From 61338cc377b31d2aab158eba7f499ceccd6d9ba8 Mon Sep 17 00:00:00 2001 From: Roland Schneider Date: Thu, 20 Nov 2025 08:06:05 +0100 Subject: [PATCH] refactor utility methods to naming.utils.ts --- src/services/angular-generator.service.ts | 46 ++++------------- src/services/crud-generator.service.ts | 28 +++------- src/services/entity-generator.service.ts | 43 ++-------------- .../generic-table-generator.service.ts | 12 ++--- src/utils/naming.utils.ts | 51 +++++++++++++++++++ 5 files changed, 76 insertions(+), 104 deletions(-) create mode 100644 src/utils/naming.utils.ts diff --git a/src/services/angular-generator.service.ts b/src/services/angular-generator.service.ts index 71d84d8..ab843a3 100644 --- a/src/services/angular-generator.service.ts +++ b/src/services/angular-generator.service.ts @@ -8,6 +8,7 @@ import * as fs from 'fs'; import { promises as fsPromises } from 'fs'; import { TableColumn } from 'typeorm'; import { GenericTableGeneratorService } from './generic-table-generator.service'; +import { mapDbToTsType, toCamelCase, toKebabCase, toPascalCase, toSingular } from '../utils/naming.utils'; // Interface for structured field metadata passed to templates interface FieldDefinition { @@ -42,7 +43,7 @@ export class AngularGeneratorService { const fields: FieldDefinition[] = (columns || []) .map(c => ({ name: c.name, - tsType: this.mapDbToTsType(c.type), + tsType: mapDbToTsType(c.type), })); names['modelProperties'] = fields.map(f => `${f.name}: ${f.tsType};`).join('\n '); @@ -126,15 +127,6 @@ private async generateTableComponent(tableName: string, columns: TableColumn[], return `
${label}\n ${input}
`; } - private mapDbToTsType(dbType: string): string { - if (dbType.includes('int') || dbType.includes('serial')) return 'number'; - if (['float', 'double', 'decimal', 'numeric', 'real'].includes(dbType)) return 'number'; - if (dbType.includes('char') || dbType.includes('text') || dbType === 'uuid') return 'string'; - if (dbType === 'boolean' || dbType === 'bool') return 'boolean'; - if (dbType.includes('date') || dbType.includes('time')) return 'Date'; - return 'any'; - } - // ... (All other generate... and naming methods are unchanged) private async generateModel(names: NamingConvention, featureDir: string) { const modelsDir = path.join(featureDir, 'models'); @@ -183,36 +175,16 @@ private async generateTableComponent(tableName: string, columns: TableColumn[], fs.writeFileSync(path.join(compDir, `${names.singular}-form.component.html`), htmlContent); } private getNamingConvention(tableName: string): NamingConvention { - const singular = this.toSingular(tableName); - const pascal = this.toPascalCase(singular); + const singular = toSingular(tableName); + const pascal = toPascalCase(singular); return { - singular: this.toKebabCase(singular), - plural: this.toKebabCase(tableName), + singular: toKebabCase(singular), + plural: toKebabCase(tableName), pascal: pascal, - camel: this.toCamelCase(singular), + camel: toCamelCase(singular), title: pascal, - kebab: this.toKebabCase(singular), + kebab: toKebabCase(singular), }; } - private toSingular(name: string): string { - const kebab = this.toKebabCase(name); - if (kebab.endsWith('ies')) return kebab.slice(0, -3) + 'y'; - return kebab.endsWith('s') ? kebab.slice(0, -1) : kebab; - } - private toPascalCase(text: string): string { - return this.toKebabCase(text) - .split('-') - .map(word => word.charAt(0).toUpperCase() + word.slice(1)) - .join(''); - } - private toCamelCase(text: string): string { - const pascal = this.toPascalCase(text); - return pascal.charAt(0).toLowerCase() + pascal.slice(1); - } - private toKebabCase(text: string): string { - return text - .replace(/_/g, '-') // Replace underscores with hyphens - .replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2') // Add hyphen before uppercase letters - .toLowerCase(); - } + } \ No newline at end of file diff --git a/src/services/crud-generator.service.ts b/src/services/crud-generator.service.ts index 3fc505f..06bbc26 100644 --- a/src/services/crud-generator.service.ts +++ b/src/services/crud-generator.service.ts @@ -7,6 +7,7 @@ import { TableColumn } from 'typeorm'; import * as path from 'path'; import * as fs from 'fs'; import { promises as fsPromises } from 'fs'; +import { toCamelCase, toKebabCase, toPascalCase, toSingular } from '../utils/naming.utils'; interface NamingConvention { [key: string]: string; @@ -98,34 +99,21 @@ export class CrudGeneratorService { } private getNamingConvention(tableName: string, columns?: TableColumn[]): NamingConvention { - const singular = this.toSingular(tableName); - const pascal = this.toPascalCase(singular); + const singular = toSingular(tableName); + const pascal = toPascalCase(singular); const searchableFields = (columns || []) .filter(c => this.isStringBasedType(c.type)) .map(c => `'${c.name}'`) .join(',\n '); return { - singular: this.toKebabCase(singular), - plural: this.toKebabCase(tableName), + singular: toKebabCase(singular), + plural: toKebabCase(tableName), pascal: pascal, - camel: this.toCamelCase(singular), + camel: toCamelCase(singular), + kebab: toKebabCase(singular), searchableFields: searchableFields }; } - private toSingular(name: string): string { - const kebab = this.toKebabCase(name); - if (kebab.endsWith('ies')) return kebab.slice(0, -3) + 'y'; - return kebab.endsWith('s') ? kebab.slice(0, -1) : kebab; - } - private toPascalCase(text: string): string { - return this.toKebabCase(text).split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(''); - } - private toCamelCase(text: string): string { - const pascal = this.toPascalCase(text); - return pascal.charAt(0).toLowerCase() + pascal.slice(1); - } - private toKebabCase(text: string): string { - return text.replace(/_/g, '-').replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase(); - } + } \ No newline at end of file diff --git a/src/services/entity-generator.service.ts b/src/services/entity-generator.service.ts index 7ff29b2..76ccbe8 100644 --- a/src/services/entity-generator.service.ts +++ b/src/services/entity-generator.service.ts @@ -7,6 +7,7 @@ import { TemplateService } from './template.service'; // <-- 1. IMPORT TemplateS import * as path from 'path'; import * as fs from 'fs'; import { promises as fsPromises } from 'fs'; +import { mapDbToTsType, toPascalCase, toSingular } from '../utils/naming.utils'; @Injectable() export class EntityGeneratorService { @@ -38,14 +39,14 @@ export class EntityGeneratorService { const columns = table.columns; console.log(`Found ${columns.length} columns in table "${tableName}".`); - const singularName = this.toSingular(tableName); - const className = this.toPascalCase(singularName); + const singularName = toSingular(tableName); + const className = toPascalCase(singularName); const props = columns .map((col) => { const typeormDecorator = this.getDecorator(col); const validationDecorators = this.getValidationDecorators(col); - const tsType = this.mapDbToTsType(col.type); + const tsType = mapDbToTsType(col.type); const nullable = col.isNullable ? ' | null' : ''; const defaultValue = col.default ? ` = ${this.formatDefaultValue(col)}` : ''; const allDecorators = [typeormDecorator, validationDecorators].filter(Boolean).join('\n '); @@ -96,7 +97,7 @@ export class EntityGeneratorService { decorators.push('@IsOptional()'); } - const tsType = this.mapDbToTsType(column.type); + const tsType = mapDbToTsType(column.type); switch (tsType) { case 'string': decorators.push('@IsString()'); @@ -160,38 +161,4 @@ export class EntityGeneratorService { return column.default; } - private mapDbToTsType(dbType: string): string { - if (dbType.includes('int') || dbType.includes('serial')) return 'number'; - if (['float', 'double', 'decimal', 'numeric', 'real'].includes(dbType)) return 'number'; - if (dbType.includes('char') || dbType.includes('text') || dbType === 'uuid') return 'string'; - if (dbType === 'boolean' || dbType === 'bool') return 'boolean'; - if (dbType.includes('date') || dbType.includes('time')) return 'Date'; - return 'any'; - } - - private toSingular(name: string): string { - if (name.endsWith('ies')) { - return name.slice(0, -3) + 'y'; - } - return name.endsWith('s') ? name.slice(0, -1) : name; - } - - private toPascalCase(text: string): string { - return this.toKebabCase(text) - .split('-') - .map(word => word.charAt(0).toUpperCase() + word.slice(1)) - .join(''); - } - - private toCamelCase(text: string): string { - const pascal = this.toPascalCase(text); - return pascal.charAt(0).toLowerCase() + pascal.slice(1); - } - - private toKebabCase(text: string): string { - return text - .replace(/_/g, '-') // Replace underscores with hyphens - .replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2') // Add hyphen before uppercase letters - .toLowerCase(); - } } \ No newline at end of file diff --git a/src/services/generic-table-generator.service.ts b/src/services/generic-table-generator.service.ts index 3b1a269..43f25bf 100644 --- a/src/services/generic-table-generator.service.ts +++ b/src/services/generic-table-generator.service.ts @@ -5,6 +5,7 @@ import { TableColumn } from 'typeorm'; import * as path from 'path'; import * as fs from 'fs'; import { promises as fsPromises } from 'fs'; +import { mapDbToTsType } from '../utils/naming.utils'; interface FieldDefinition { name: string; tsType: string; } interface NamingConvention { [key: string]: string; /*...*/ } @@ -23,7 +24,7 @@ export class GenericTableGeneratorService { const fields: FieldDefinition[] = columns.map(c => ({ name: c.name, - tsType: this.mapDbToTsType(c.type), + tsType: mapDbToTsType(c.type), })); names['columnDefinitions'] = this.buildColumnDefinitions(fields); @@ -75,12 +76,5 @@ export class GenericTableGeneratorService { .join('\n'); } - private mapDbToTsType(dbType: string): string { - if (dbType.includes('int') || dbType.includes('serial')) return 'number'; - if (['float', 'double', 'decimal', 'numeric', 'real'].includes(dbType)) return 'number'; - if (dbType.includes('char') || dbType.includes('text') || dbType === 'uuid') return 'string'; - if (dbType === 'boolean' || dbType === 'bool') return 'boolean'; - if (dbType.includes('date') || dbType.includes('time')) return 'Date'; - return 'any'; - } + } \ No newline at end of file diff --git a/src/utils/naming.utils.ts b/src/utils/naming.utils.ts new file mode 100644 index 0000000..f6b7511 --- /dev/null +++ b/src/utils/naming.utils.ts @@ -0,0 +1,51 @@ +// dvbooking-cli/src/utils/naming.utils.ts + +/** + * Converts any string to kebab-case. + * e.g., 'user_roles' -> 'user-roles', 'UserRoles' -> 'user-roles' + */ +export function toKebabCase(text: string): string { + return text + .replace(/_/g, '-') // Replace underscores with hyphens + .replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2') // Add hyphen before uppercase letters + .toLowerCase(); +} + +/** + * Converts a kebab-cased string to its singular form. + */ +export function toSingular(name: string): string { + const kebab = toKebabCase(name); + if (kebab.endsWith('ies')) return kebab.slice(0, -3) + 'y'; + return kebab.endsWith('s') ? kebab.slice(0, -1) : kebab; +} + +/** + * Converts a kebab-cased string to PascalCase. + */ +export function toPascalCase(text: string): string { + return toKebabCase(text) + .split('-') + .map(word => word.charAt(0).toUpperCase() + word.slice(1)) + .join(''); +} + +/** + * Converts a kebab-cased string to camelCase. + */ +export function toCamelCase(text: string): string { + const pascal = toPascalCase(text); + return pascal.charAt(0).toLowerCase() + pascal.slice(1); +} + +/** + * Maps database types to TypeScript types. + */ +export function mapDbToTsType(dbType: string): string { + if (dbType.includes('int') || dbType.includes('serial')) return 'number'; + if (['float', 'double', 'decimal', 'numeric', 'real'].includes(dbType)) return 'number'; + if (dbType.includes('char') || dbType.includes('text') || dbType === 'uuid') return 'string'; + if (dbType === 'boolean' || dbType === 'bool') return 'boolean'; + if (dbType.includes('date') || dbType.includes('time')) return 'Date'; + return 'any'; +} \ No newline at end of file