UNPKG

subcodex

Version:

Lenguaje de programación en español simple, educativo y brutal: SubCodeX 0.0.4 versión estable

407 lines (402 loc) 14.9 kB
#!/usr/bin/env node import { SubCodeXInterpreter } from './src/interpreter.js'; import * as fs from 'fs/promises'; import * as fsSync from 'fs'; import * as path from 'path'; import { Command } from 'commander'; import chalk from 'chalk'; import chalkAnimation from 'chalk-animation'; import figlet from 'figlet'; import gradient from 'gradient-string'; import { createSpinner } from 'nanospinner'; import Configstore from 'configstore'; import inquirer from 'inquirer'; import { createRequire } from 'module'; import { performance } from 'perf_hooks'; const require = createRequire(import.meta.url); const pkg = require('../package.json'); const config = new Configstore(pkg.name, { firstRun: true }); const VERSION = pkg.version; const theme = { success: chalk.green.bold, error: chalk.red.bold, warning: chalk.yellow.bold, info: chalk.blue, title: chalk.magenta.bold, muted: chalk.gray, header: chalk.cyan.bold, highlight: chalk.white.bgBlue, accent: chalk.yellowBright, dim: chalk.dim, rainbow: gradient.rainbow, pastel: gradient.pastel, }; const SUPPORTED_EXTENSIONS = ['.subx', '.subcx', '.subcodex']; const MAX_CONCURRENT_FILES = 5; const EXAMPLE_TEMPLATE = `# Archivo de ejemplo de SubCodeX # Creado el: ${new Date().toLocaleString('es-ES')} decir "¡Hola, SubCodeX!" decir "Este es un programa de ejemplo" # Variables variable nombre = "Programador" decir "Bienvenido, " + nombre # Condicionales si nombre == "Programador" entonces decir "¡Eres un desarrollador!" sino decir "¡Hola, usuario!" fin # Bucles repetir 3 decir "SubCodeX es genial!" fin decir "¡Fin del programa!"`; const sleep = (ms = 1000) => new Promise((r) => setTimeout(r, ms)); const formatFileSize = (bytes) => { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`; }; const formatDuration = (ms) => { if (ms < 1000) return `${ms.toFixed(2)}ms`; return `${(ms / 1000).toFixed(2)}s`; }; async function welcomeAnimation() { console.clear(); const welcomeTitle = 'SUBCODEX'; const styledTitle = theme.pastel.multiline(figlet.textSync(welcomeTitle, { font: 'Big' })); const animation = chalkAnimation.karaoke(styledTitle); await sleep(2000); animation.stop(); console.log(theme.title(`\n 🚀 Bienvenido al CLI oficial de SubCodeX v${VERSION}`)); console.log(theme.muted(' Un lenguaje para aprender, crear y soñar en español.\n')); console.log(theme.dim(' Desarrollado con ❤️ para la comunidad hispanohablante\n')); } function printHeader(title) { const separator = '═'.repeat(60); console.log(theme.header(`\n${separator}`)); console.log(theme.header(` ${title}`)); console.log(theme.header(`${separator}\n`)); } function printFooter() { console.log(theme.muted('\n' + '═'.repeat(60))); } async function getSubCodeXFiles() { try { const files = await fs.readdir(process.cwd()); const subxFiles = []; for (const file of files) { const ext = path.extname(file).toLowerCase(); if (SUPPORTED_EXTENSIONS.includes(ext)) { const filePath = path.join(process.cwd(), file); const stats = await fs.stat(filePath); subxFiles.push({ name: file, path: filePath, size: stats.size, lastModified: stats.mtime, }); } } return subxFiles.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime()); } catch (error) { throw new Error(`Error al escanear directorio: ${error}`); } } async function validateFile(filePath) { try { await fs.access(filePath); const stats = await fs.stat(filePath); if (!stats.isFile()) { throw new Error(`${filePath} no es un archivo válido`); } const ext = path.extname(filePath).toLowerCase(); if (!SUPPORTED_EXTENSIONS.includes(ext)) { throw new Error(`Extensión no soportada: ${ext}. Usa: ${SUPPORTED_EXTENSIONS.join(', ')}`); } } catch (error) { throw new Error(`Error al validar archivo: ${error}`); } } async function ejecutarCodigo(archivo, options) { const startTime = performance.now(); try { await validateFile(archivo); const interpreter = new SubCodeXInterpreter(); const resultado = await interpreter.ejecutarArchivo(archivo); const endTime = performance.now(); const duration = endTime - startTime; if (options.time) { console.log(theme.muted(` ⏱️ Tiempo de ejecución: ${formatDuration(duration)}`)); } if (resultado.success) { return { success: true, duration, output: resultado.output || [] }; } else { return { success: false, error: resultado.error || 'Error desconocido', duration }; } } catch (error) { const endTime = performance.now(); const duration = endTime - startTime; return { success: false, error: error instanceof Error ? error.message : String(error), duration }; } } async function ejecutarMultiplesArchivos(archivos, options) { if (archivos.length > MAX_CONCURRENT_FILES) { console.error(theme.error(`❌ Error: Se pueden ejecutar un máximo de ${MAX_CONCURRENT_FILES} archivos a la vez.`)); process.exit(1); } printHeader(`Ejecutando ${archivos.length} archivo(s)`); let totalDuration = 0; let exitosos = 0; for (const [index, archivo] of archivos.entries()) { console.log(theme.header(`\n📄 Archivo ${index + 1}/${archivos.length}: ${theme.accent(archivo)}`)); console.log(theme.muted('-'.repeat(50))); try { const resultado = await ejecutarCodigo(archivo, options); if (resultado.duration) { totalDuration += resultado.duration; } if (resultado.success) { exitosos++; } else { console.error(theme.error(` Error: ${resultado.error}`)); } } catch (error) { console.error(theme.error(` Error: ${error}`)); } } printHeader('Resumen de Ejecución'); console.log(theme.success(`✅ Exitosos: ${exitosos}/${archivos.length}`)); if (options.time) { console.log(theme.info(`⏱️ Tiempo total: ${formatDuration(totalDuration)}`)); } printFooter(); } async function crearArchivoEjemplo(nombreArchivo) { const spinner = createSpinner('Creando archivo de ejemplo...').start(); try { const fullPath = path.resolve(nombreArchivo); try { await fs.access(fullPath); spinner.error({ text: `❌ El archivo ${theme.warning(nombreArchivo)} ya existe` }); const { sobrescribir } = await inquirer.prompt({ type: 'confirm', name: 'sobrescribir', message: '¿Deseas sobrescribir el archivo existente?', default: false, }); if (!sobrescribir) { console.log(theme.info('Operación cancelada')); return; } } catch { } await fs.writeFile(fullPath, EXAMPLE_TEMPLATE, 'utf8'); spinner.success({ text: `${theme.success('✅ Archivo creado exitosamente')}: ${theme.info(nombreArchivo)}` }); console.log(theme.muted(` 📍 Ubicación: ${fullPath}`)); console.log(theme.muted(` 📝 Tamaño: ${formatFileSize(Buffer.byteLength(EXAMPLE_TEMPLATE, 'utf8'))}`)); console.log(theme.accent(` 🚀 Para ejecutar: ${chalk.bold('subcodex run ' + nombreArchivo)}`)); } catch (error) { spinner.error({ text: `❌ Error al crear archivo: ${error}` }); } } async function seleccionarYEjecutarArchivo() { const spinner = createSpinner('Buscando archivos SubCodeX...').start(); try { const files = await getSubCodeXFiles(); spinner.stop(); if (files.length === 0) { console.log(theme.warning('❌ No se encontraron archivos SubCodeX en este directorio.')); console.log(theme.muted(' 💡 Crea uno nuevo con el comando "init"')); return; } const choices = files.map(file => ({ name: `${file.name} ${theme.dim(`(${formatFileSize(file.size)}, ${file.lastModified.toLocaleString('es-ES')})`)}`, value: file.name, short: file.name, })); const { fileToRun } = await inquirer.prompt([ { type: 'list', name: 'fileToRun', message: `📂 Selecciona un archivo para ejecutar (${files.length} encontrado${files.length !== 1 ? 's' : ''}):`, choices, pageSize: 10, }, ]); await ejecutarMultiplesArchivos([fileToRun], {}); } catch (error) { spinner.error({ text: `❌ Error al buscar archivos: ${error}` }); } } async function menuPrincipalInteractivo() { if (config.get('firstRun') !== false) { await welcomeAnimation(); config.set('firstRun', false); } else { console.clear(); console.log(theme.title(`SubCodeX v${VERSION}`)); } while (true) { const { accion } = await inquirer.prompt({ type: 'list', name: 'accion', message: '🎯 ¿Qué deseas hacer?', choices: [ { name: '🚀 Ejecutar un archivo', value: 'run' }, { name: '📝 Crear un archivo de ejemplo', value: 'init' }, { name: '📊 Ver información del proyecto', value: 'info' }, new inquirer.Separator(), { name: '❌ Salir', value: 'exit' }, ], pageSize: 10, }); try { switch (accion) { case 'run': await seleccionarYEjecutarArchivo(); break; case 'init': const { nombre } = await inquirer.prompt({ type: 'input', name: 'nombre', message: '📝 Nombre para el nuevo archivo (sin extensión):', default: 'mi_programa', validate: (input) => { if (!input.trim()) return 'El nombre no puede estar vacío'; if (!/^[a-zA-Z0-9_\-]+$/.test(input)) return 'Usa solo letras, números, guiones y guiones bajos'; return true; }, }); await crearArchivoEjemplo(`${nombre}.subx`); break; case 'info': await mostrarInformacion(); break; case 'exit': console.log(theme.rainbow('\n🌈 ¡Hasta la próxima! Que tengas un gran día programando.')); process.exit(0); default: console.log(theme.error('❌ Opción no válida')); } } catch (error) { console.error(theme.error(`❌ Error: ${error}`)); } await inquirer.prompt({ type: 'input', name: 'continue', message: theme.muted('Presiona Enter para volver al menú...'), }); } } async function mostrarInformacion() { printHeader('Información del Proyecto'); try { const files = await getSubCodeXFiles(); const totalSize = files.reduce((sum, file) => sum + file.size, 0); console.log(theme.info(`📦 Versión: ${VERSION}`)); console.log(theme.info(`📂 Directorio actual: ${process.cwd()}`)); console.log(theme.info(`📄 Archivos SubCodeX encontrados: ${files.length}`)); console.log(theme.info(`💾 Tamaño total: ${formatFileSize(totalSize)}`)); if (files.length > 0) { console.log(theme.header('\n📋 Archivos encontrados:')); files.forEach((file, index) => { console.log(` ${index + 1}. ${theme.accent(file.name)} ${theme.dim(`(${formatFileSize(file.size)})`)}`); }); } } catch (error) { console.error(theme.error(`❌ Error al obtener información: ${error}`)); } printFooter(); } const program = new Command(); program .name('subcodex') .alias('subx') .description(theme.title('🚀 SubCodeX - Un lenguaje de programación en español')) .version(VERSION, '-v, --version', 'Muestra la versión actual de SubCodeX'); program .command('run <archivos...>') .alias('r') .description('🚀 Ejecuta uno o más archivos SubCodeX') .option('-w, --watch', 'Observar cambios en los archivos') .option('-t, --time', 'Mostrar tiempo de ejecución') .action(async (archivos, options) => { if (options.watch) { console.log(theme.info(`👀 Modo observador activado para: ${archivos.join(', ')}`)); console.log(theme.muted(' (Presiona Ctrl+C para salir)')); archivos.forEach(archivo => { fsSync.watchFile(archivo, { interval: 500 }, async () => { console.clear(); printHeader(`Cambio detectado en: ${archivo}`); try { const resultado = await ejecutarCodigo(archivo, options); if (!resultado.success) { console.error(theme.error(`\nError: ${resultado.error}`)); } } catch (error) { console.error(theme.error(`\nError: ${error}`)); } }); }); } else { await ejecutarMultiplesArchivos(archivos, options); } }); program .command('init [nombre]') .alias('crear') .description('📝 Crea un archivo de ejemplo SubCodeX') .action(async (nombre = 'ejemplo') => { await crearArchivoEjemplo(`${nombre}.subx`); }); async function main() { try { if (process.argv.length > 2) { await program.parseAsync(process.argv); } else { await menuPrincipalInteractivo(); } } catch (error) { console.error(theme.error(`❌ Error: ${error}`)); process.exit(1); } } main(); //# sourceMappingURL=index.js.map