leetkick
Version:
A CLI tool for scaffolding LeetCode exercises with language-specific testing setups
103 lines (82 loc) • 3.37 kB
text/typescript
import {readdir, copyFile, mkdir, stat, writeFile} from 'fs/promises';
import {join, dirname} from 'path';
import {existsSync} from 'fs';
import {fileURLToPath} from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const TEMPLATES_DIR = join(__dirname, '../../templates');
export async function getAvailableLanguages(): Promise<string[]> {
try {
const entries = await readdir(TEMPLATES_DIR, {withFileTypes: true});
return entries
.filter(entry => entry.isDirectory())
.map(entry => entry.name);
} catch (error) {
throw new Error(`Failed to read templates directory: ${error}`);
}
}
export async function initializeLanguage(language: string): Promise<void> {
const templateDir = join(TEMPLATES_DIR, language);
const targetDir = join(process.cwd(), language);
if (!existsSync(templateDir)) {
throw new Error(`Template for language '${language}' not found`);
}
// Create language directory
await mkdir(targetDir, {recursive: true});
// For Kotlin and Java, we need to create the source directory structure
if (language === 'kotlin' || language === 'java') {
await mkdir(join(targetDir, 'src', 'main', language), {recursive: true});
await mkdir(join(targetDir, 'src', 'test', language), {recursive: true});
}
// For Python, we need to create src/ and tests/ directories
if (language === 'python') {
await mkdir(join(targetDir, 'src'), {recursive: true});
await mkdir(join(targetDir, 'tests'), {recursive: true});
}
// For Rust, we need to create the src directory and lib.rs
if (language === 'rust') {
await mkdir(join(targetDir, 'src'), {recursive: true});
// Create initial lib.rs file
const libContent = `// LeetKick Rust Workspace
// This file serves as the main library entry point for all LeetCode problems.
// Each problem is implemented as a separate module in the src/ directory.
// Problem modules will be automatically declared here when you fetch problems
`;
await writeFile(join(targetDir, 'src', 'lib.rs'), libContent);
}
// Copy all non-template files (config files)
const templateFiles = await readdir(templateDir);
for (const file of templateFiles) {
// Skip template files (they're used per-problem, not per-language)
if (file.includes('_template.')) {
continue;
}
// Cargo.toml is now workspace-level for Rust, so copy it
const sourcePath = join(templateDir, file);
// Special handling for gitignore file to avoid conflicts in the CLI repo
const targetFileName = file === 'gitignore' ? '.gitignore' : file;
const targetPath = join(targetDir, targetFileName);
// Check if it's a directory or file
const fileStats = await stat(sourcePath);
if (fileStats.isDirectory()) {
await copyDirectoryRecursive(sourcePath, targetPath);
} else {
await copyFile(sourcePath, targetPath);
}
}
}
async function copyDirectoryRecursive(
src: string,
dest: string,
): Promise<void> {
await mkdir(dest, {recursive: true});
const entries = await readdir(src, {withFileTypes: true});
for (const entry of entries) {
const srcPath = join(src, entry.name);
const destPath = join(dest, entry.name);
if (entry.isDirectory()) {
await copyDirectoryRecursive(srcPath, destPath);
} else {
await copyFile(srcPath, destPath);
}
}
}