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
JavaScript
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