Add generators: code, completion, and docs generators
This commit is contained in:
174
src/generators/code-generator.ts
Normal file
174
src/generators/code-generator.ts
Normal file
@@ -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<LanguageTarget, string> = {
|
||||||
|
'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, string> = {
|
||||||
|
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<String>';
|
||||||
|
if (type === 'string') return required ? 'String' : 'Option<String>';
|
||||||
|
if (type === 'number') return required ? 'i64' : 'Option<i64>';
|
||||||
|
if (type === 'boolean') return required ? 'bool' : 'Option<bool>';
|
||||||
|
if (type === 'array') return required ? 'Vec<String>' : 'Option<Vec<String>>';
|
||||||
|
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> = {
|
||||||
|
string: 'String',
|
||||||
|
number: 'i64',
|
||||||
|
boolean: 'bool',
|
||||||
|
array: 'Vec<String>',
|
||||||
|
};
|
||||||
|
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<LanguageTarget, string> = {
|
||||||
|
'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}`;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user