From 496e73c8a193a2d7335ccfe83d1577f3545a59c8 Mon Sep 17 00:00:00 2001 From: 7000pctAUTO Date: Fri, 30 Jan 2026 07:10:13 +0000 Subject: [PATCH] Add generators: code, completion, and docs generators --- src/generators/code-generator.ts | 174 +++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 src/generators/code-generator.ts diff --git a/src/generators/code-generator.ts b/src/generators/code-generator.ts new file mode 100644 index 0000000..985557c --- /dev/null +++ b/src/generators/code-generator.ts @@ -0,0 +1,174 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import Handlebars from 'handlebars'; +import type { CLISpec, LanguageTarget } from '../types/spec.js'; +import { capitalize, camelCase, snakeCase, quoteString } from '../utils/helpers.js'; + +const TEMPLATE_DIR = path.resolve(process.cwd(), 'src', 'templates'); + +function getTemplatePath(language: LanguageTarget): string { + const templateMap: Record = { + 'python': 'python.handlebars', + 'go': 'go.handlebars', + 'rust': 'rust.handlebars', + 'node-commander': 'node-commander.handlebars', + 'node-yargs': 'node-yargs.handlebars', + }; + + return path.join(TEMPLATE_DIR, templateMap[language]); +} + +function getTemplateContent(language: LanguageTarget): string { + const templatePath = getTemplatePath(language); + if (!fs.existsSync(templatePath)) { + throw new Error(`Template not found: ${templatePath}`); + } + return fs.readFileSync(templatePath, 'utf-8'); +} + +function registerHandlebarsHelpers(): void { + Handlebars.registerHelper('eq', function (a, b) { + return a === b; + }); + + Handlebars.registerHelper('toPythonType', function (type: string) { + const types: Record = { + string: 'str', + number: 'int', + boolean: 'bool', + array: 'list', + }; + return types[type] || 'str'; + }); + + Handlebars.registerHelper('toJson', function (value: unknown) { + return JSON.stringify(value); + }); + + Handlebars.registerHelper('escape', function (str: string) { + return str.replace(/"/g, '\\"').replace(/\n/g, ' ').replace(/\s+/g, ' '); + }); + + Handlebars.registerHelper('defaultValue', function (value: unknown, defaultVal: unknown) { + if (value !== undefined && value !== null) { + return typeof value === 'string' ? `"${value}"` : value; + } + if (defaultVal !== undefined) { + return typeof defaultVal === 'string' ? `"${defaultVal}"` : defaultVal; + } + return '""'; + }); + + Handlebars.registerHelper('commandFuncName', function (name: string) { + return `do_${name.replace(/-/g, '_')}`; + }); + + Handlebars.registerHelper('goFuncName', function (name: string) { + return capitalize(camelCase(name)); + }); + + Handlebars.registerHelper('goVarName', function (name: string) { + return snakeCase(name); + }); + + Handlebars.registerHelper('pascalCase', function (str: string) { + return str.split(/[-_]/).map(word => capitalize(camelCase(word))).join(''); + }); + + Handlebars.registerHelper('snakeCase', function (str: string) { + return snakeCase(str); + }); + + Handlebars.registerHelper('camelCase', function (str: string) { + return camelCase(str); + }); + + Handlebars.registerHelper('rustArgType', function (type: string, required: boolean, variadic: boolean) { + if (variadic) return 'Vec'; + if (type === 'string') return required ? 'String' : 'Option'; + if (type === 'number') return required ? 'i64' : 'Option'; + if (type === 'boolean') return required ? 'bool' : 'Option'; + if (type === 'array') return required ? 'Vec' : 'Option>'; + return 'String'; + }); + + Handlebars.registerHelper('startsWith', function (str: string, prefix: string) { + return str.startsWith(prefix); + }); + + Handlebars.registerHelper('trimLeft', function (str: string, chars: number) { + return str.slice(chars); + }); + + Handlebars.registerHelper('requiredCount', function (args: Array<{ required?: boolean }>) { + return args.filter(arg => arg.required).length || 0; + }); + + Handlebars.registerHelper('hasSubcommands', function (cmds: Array<{ subcommands?: unknown[] }>) { + return cmds.some(cmd => cmd.subcommands && cmd.subcommands.length > 0); + }); + + Handlebars.registerHelper('concat', function (...args: (string | Handlebars.HelperOptions)[]) { + const strings = args.slice(0, -1).filter((a): a is string => typeof a === 'string'); + return strings.join(''); + } as Handlebars.HelperDelegate); + + Handlebars.registerHelper('quoteString', function (str: string) { + return quoteString(str, 'python'); + }); + + Handlebars.registerHelper('toRustType', function (type: string) { + const types: Record = { + string: 'String', + number: 'i64', + boolean: 'bool', + array: 'Vec', + }; + return types[type] || 'String'; + }); +} + +function compileTemplate(content: string): Handlebars.TemplateDelegate { + registerHandlebarsHelpers(); + return Handlebars.compile(content, { + noEscape: true, + strict: false, + }); +} + +export interface GenerationResult { + success: boolean; + code?: string; + error?: string; +} + +export function generateCode(spec: CLISpec, language: LanguageTarget): GenerationResult { + try { + const templateContent = getTemplateContent(language); + const template = compileTemplate(templateContent); + const code = template({ spec }); + return { success: true, code }; + } catch (error) { + return { + success: false, + error: error instanceof Error ? error.message : 'Unknown error during code generation', + }; + } +} + +export function getLanguageExtension(language: LanguageTarget): string { + const extensions: Record = { + 'python': '.py', + 'go': '.go', + 'rust': '.rs', + 'node-commander': '.ts', + 'node-yargs': '.ts', + }; + return extensions[language]; +} + +export function getOutputFileName(spec: CLISpec, language: LanguageTarget): string { + const baseName = spec.bin || spec.name; + const extension = getLanguageExtension(language); + return `${baseName}${extension}`; +}