import * as fs from 'fs'; import * as path from 'path'; import type { CsoundReference } from './types'; export class TypeScriptGenerator { private outputDir: string; constructor(outputDir: string) { this.outputDir = outputDir; } sanitizeCategoryName(category: string): string { return category .toLowerCase() .replace(/[^a-z0-9]+/g, '-') .replace(/^-+|-+$/g, ''); } sanitizeVariableName(category: string): string { return category .replace(/[^a-zA-Z0-9]+/g, ' ') .split(' ') .map((word, index) => index === 0 ? word.toLowerCase() : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase() ) .join('') .replace(/^[0-9]/, '_$&'); } escapeString(str: string): string { return str .replace(/\\/g, '\\\\') .replace(/'/g, "\\'") .replace(/\n/g, '\\n') .replace(/\r/g, '\\r') .replace(/\t/g, '\\t'); } generateReferenceObject(ref: CsoundReference, indent: string = ' '): string { const lines: string[] = []; lines.push(`{`); lines.push(`${indent}name: '${this.escapeString(ref.name)}',`); lines.push(`${indent}type: '${ref.type}',`); lines.push(`${indent}category: '${this.escapeString(ref.category)}',`); lines.push(`${indent}description: '${this.escapeString(ref.description)}',`); if (ref.syntax) { lines.push(`${indent}syntax: '${this.escapeString(ref.syntax)}',`); } if (ref.example) { const escapedExample = this.escapeString(ref.example); lines.push(`${indent}example: '${escapedExample}',`); } if (ref.rates && ref.rates.length > 0) { lines.push(`${indent}rates: [${ref.rates.map(r => `'${r}'`).join(', ')}],`); } if (ref.parameters && ref.parameters.length > 0) { lines.push(`${indent}parameters: [`); for (const param of ref.parameters) { lines.push(`${indent} {`); lines.push(`${indent} name: '${this.escapeString(param.name)}',`); lines.push(`${indent} description: '${this.escapeString(param.description)}',`); lines.push(`${indent} type: '${param.type}'`); lines.push(`${indent} },`); } lines.push(`${indent}],`); } if (ref.seeAlso && ref.seeAlso.length > 0) { lines.push(`${indent}seeAlso: [${ref.seeAlso.map(s => `'${this.escapeString(s)}'`).join(', ')}]`); } lines.push(`}`); return lines.join('\n'); } generateCategoryFile( category: string, references: CsoundReference[], fileName: string ): string { const varName = this.sanitizeVariableName(category); const lines: string[] = []; lines.push(`import type { CsoundReference } from './types'`); lines.push(``); lines.push(`// ${category}`); lines.push(`export const ${varName}: CsoundReference[] = [`); for (const ref of references) { const refLines = this.generateReferenceObject(ref, ' '); lines.push(refLines + ','); } lines.push(`]`); lines.push(``); return lines.join('\n'); } generateMainFile(categories: Map): string { const lines: string[] = []; lines.push(`import type { CsoundReference } from './types'`); const imports: string[] = []; const varNames: string[] = []; for (const [category] of categories) { const fileName = this.sanitizeCategoryName(category); const varName = this.sanitizeVariableName(category); imports.push(`import { ${varName} } from './${fileName}'`); varNames.push(varName); } lines.push(...imports); lines.push(``); lines.push(`export type { CsoundReference }`); lines.push(``); lines.push(`export const allCsoundReferences: CsoundReference[] = [`); for (const varName of varNames) { lines.push(` ...${varName},`); } lines.push(`]`); lines.push(``); lines.push(`export const csoundReferenceMap = new Map()`); lines.push(`allCsoundReferences.forEach(ref => {`); lines.push(` csoundReferenceMap.set(ref.name, ref)`); lines.push(`})`); lines.push(``); lines.push(`export function getCsoundReference(name: string): CsoundReference | undefined {`); lines.push(` return csoundReferenceMap.get(name)`); lines.push(`}`); lines.push(``); lines.push(`export function getCsoundReferencesByCategory(category: string): CsoundReference[] {`); lines.push(` return allCsoundReferences.filter(ref => ref.category === category)`); lines.push(`}`); lines.push(``); lines.push(`export {`); for (const varName of varNames) { lines.push(` ${varName},`); } lines.push(`}`); lines.push(``); return lines.join('\n'); } writeCategoryFiles(categories: Map): void { if (!fs.existsSync(this.outputDir)) { fs.mkdirSync(this.outputDir, { recursive: true }); } let fileCount = 0; for (const [category, references] of categories) { const fileName = this.sanitizeCategoryName(category); const filePath = path.join(this.outputDir, `${fileName}.ts`); const content = this.generateCategoryFile(category, references, fileName); fs.writeFileSync(filePath, content, 'utf-8'); fileCount++; console.log(`Generated: ${fileName}.ts (${references.length} opcodes)`); } console.log(`\nGenerated ${fileCount} category files`); } writeMainFile(categories: Map): void { const filePath = path.join(this.outputDir, 'csoundReference.ts'); const content = this.generateMainFile(categories); fs.writeFileSync(filePath, content, 'utf-8'); console.log(`Generated: csoundReference.ts`); } copyTypes(): void { if (!fs.existsSync(this.outputDir)) { fs.mkdirSync(this.outputDir, { recursive: true }); } const sourceTypes = path.join(__dirname, 'types.ts'); const destTypes = path.join(this.outputDir, 'types.ts'); if (fs.existsSync(sourceTypes)) { fs.copyFileSync(sourceTypes, destTypes); console.log(`Copied: types.ts`); } } generateAll(categories: Map): void { console.log(`\nGenerating TypeScript files...`); this.copyTypes(); this.writeCategoryFiles(categories); this.writeMainFile(categories); console.log(`\nDone!`); } }