UNPKG

lookatni-file-markers

Version:

Advanced file organization system with unique markers for code sharing and project management. Extract files from marked content, generate markers, and validate project integrity.

466 lines (465 loc) 21.6 kB
#!/usr/bin/env node "use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.LookAtniExtractor = void 0; const fs = __importStar(require("fs")); const path = __importStar(require("path")); const FS_CHAR = String.fromCharCode(28); const MARKER_START = `//${FS_CHAR}/`; const MARKER_END = `/${FS_CHAR}//`; const MARKER_REGEX = new RegExp(`^//${FS_CHAR}/ (.+) /${FS_CHAR}//$`); const colors = { RED: '\x1b[0;31m', GREEN: '\x1b[0;32m', YELLOW: '\x1b[1;33m', BLUE: '\x1b[0;34m', PURPLE: '\x1b[0;35m', CYAN: '\x1b[0;36m', NC: '\x1b[0m' }; class LookAtniExtractor { constructor(options) { this.options = options; this.stats = { totalFiles: 0, successCount: 0, errorCount: 0, bytesExtracted: 0, duration: 0, sourceSize: 0 }; } printHelp() { console.log(`${colors.CYAN}🚀 LookAtni File Extractor v4.0 - The Magic Code Decomposer${colors.NC}`); console.log("============================================================"); console.log("Uso: tsx extractFiles.ts <arquivo_codigo> [diretorio_destino] [opcoes]"); console.log(""); console.log("Parâmetros:"); console.log(" arquivo_codigo : Arquivo com código marcado (obrigatório)"); console.log(" diretorio_destino : Onde extrair os arquivos (padrão: ./extracted)"); console.log(""); console.log("Opções:"); console.log(" -i, --interactive : Modo interativo (confirma cada arquivo)"); console.log(" -v, --verbose : Saída detalhada"); console.log(" -d, --dry-run : Simula extração sem criar arquivos"); console.log(" -s, --stats : Mostra estatísticas detalhadas"); console.log(" -f, --format : Valida formato dos marcadores"); console.log(" -h, --help : Esta ajuda"); console.log(""); console.log(`Formato de marcadores: ${MARKER_START} caminho/arquivo ${MARKER_END} (marcadores invisíveis)`); console.log(""); console.log("Exemplos:"); console.log(" tsx extractFiles.ts lookatni-code.txt ./meu-projeto"); console.log(" tsx extractFiles.ts codigo.txt ./src --interactive"); console.log(" tsx extractFiles.ts projeto.txt ./destino --dry-run --stats"); console.log(""); console.log("💡 Dicas:"); console.log(" • Use --dry-run para testar antes de extrair"); console.log(" • Use --interactive para controle total"); console.log(" • Use --stats para análise detalhada"); } validateMarkers(content) { const lines = content.split('\n'); const errors = []; const markers = []; console.log("🔍 Validando formato dos marcadores..."); for (const line of lines) { if (line.startsWith(MARKER_START)) { markers.push(line); } } const malformedMarkers = markers.filter(marker => !MARKER_REGEX.test(marker)); if (malformedMarkers.length > 0) { errors.push(`${colors.RED}${malformedMarkers.length} marcador(es) mal formado(s) encontrado(s)${colors.NC}`); errors.push("Marcadores problemáticos:"); malformedMarkers.slice(0, 5).forEach(marker => errors.push(` ${marker}`)); } const fileNames = markers.map(marker => { const match = MARKER_REGEX.exec(marker); return match ? match[1] : marker; }); const duplicates = fileNames.filter((item, index) => fileNames.indexOf(item) !== index); if (duplicates.length > 0) { errors.push(`${colors.YELLOW}⚠️ ${duplicates.length} arquivo(s) duplicado(s) encontrado(s)${colors.NC}`); errors.push("Arquivos duplicados:"); [...new Set(duplicates)].forEach(duplicate => errors.push(` ${duplicate}`)); } const suspiciousPaths = fileNames.filter(fileName => fileName.includes('../') || fileName.includes('/../') || fileName.startsWith('/')); if (suspiciousPaths.length > 0) { errors.push(`${colors.YELLOW}⚠️ ${suspiciousPaths.length} caminho(s) suspeito(s) encontrado(s)${colors.NC}`); errors.push("Caminhos suspeitos:"); suspiciousPaths.slice(0, 3).forEach(path => errors.push(` ${path}`)); } return { isValid: errors.length === 0, errors }; } async extractFiles(sourceFile, destDir) { const startTime = Date.now(); console.log(`${colors.CYAN}🚀 LookAtni File Extractor v4.0${colors.NC}`); console.log(`${colors.CYAN}Usando marcadores invisíveis: ${MARKER_START} arquivo ${MARKER_END}${colors.NC}`); console.log("================================"); if (!fs.existsSync(sourceFile)) { throw new Error(`${colors.RED}❌ Erro: Arquivo '${sourceFile}' não encontrado!${colors.NC}\n${colors.YELLOW}💡 Verifique se o caminho está correto${colors.NC}`); } const content = fs.readFileSync(sourceFile, 'utf-8'); this.stats.sourceSize = Buffer.byteLength(content, 'utf-8'); if (this.options.validateFormat) { const validation = this.validateMarkers(content); if (validation.isValid) { console.log(`${colors.GREEN}✅ Formato dos marcadores válido!${colors.NC}`); } else { validation.errors.forEach(error => console.log(error)); throw new Error(`${colors.RED}❌ Problemas encontrados no formato dos marcadores${colors.NC}`); } } const lines = content.split('\n'); const markers = []; for (const line of lines) { if (line.startsWith(MARKER_START)) { const match = MARKER_REGEX.exec(line); if (match) { markers.push(match[1]); } } } if (markers.length === 0) { throw new Error(`${colors.RED}❌ Erro: Nenhum marcador ${MARKER_START} encontrado no arquivo!${colors.NC}\n${colors.YELLOW}💡 Verifique se o arquivo está no formato: ${MARKER_START} caminho/arquivo ${MARKER_END}${colors.NC}`); } this.stats.totalFiles = markers.length; console.log(`${colors.BLUE}📖 Arquivo fonte: ${sourceFile}${colors.NC}`); console.log(`${colors.BLUE}📁 Destino: ${destDir}${colors.NC}`); console.log(`${colors.BLUE}🔍 Marcadores encontrados: ${markers.length}${colors.NC}`); if (this.options.dryRun) { console.log(`${colors.YELLOW}🔍 MODO SIMULAÇÃO - Nenhum arquivo será criado${colors.NC}`); } if (!this.options.dryRun && fs.existsSync(destDir)) { console.log(`${colors.YELLOW}🧹 Limpando diretório existente: ${destDir}${colors.NC}`); fs.rmSync(destDir, { recursive: true, force: true }); } console.log(""); console.log(`${colors.PURPLE}📁 Arquivos a serem extraídos:${colors.NC}`); markers.forEach((file, index) => { if (this.options.verbose) { console.log(` ${(index + 1).toString().padStart(2)}. ${file}`); } else { console.log(` ${(index + 1).toString().padStart(2)}. ${file}`); } }); console.log(""); if (this.options.interactive) { console.log(`${colors.CYAN}🤔 Modo interativo ativado${colors.NC}`); const readline = require('readline'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); const answer = await new Promise((resolve) => { rl.question('Deseja continuar com a extração? (s/N): ', resolve); }); rl.close(); if (!answer.toLowerCase().startsWith('s')) { console.log("Operação cancelada pelo usuário."); return; } } console.log(`${colors.GREEN}🚀 Iniciando extração de ${this.stats.totalFiles} arquivo(s)...${colors.NC}`); console.log(""); for (let i = 0; i < markers.length; i++) { const fileName = markers[i]; const nextFileName = markers[i + 1]; console.log(`[${(i + 1).toString().padStart(2)}/${this.stats.totalFiles}] 📄 ${fileName}`); if (this.options.interactive) { const readline = require('readline'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); const answer = await new Promise((resolve) => { rl.question(' Extrair este arquivo? (s/N/a=todos): ', resolve); }); rl.close(); if (answer.toLowerCase().startsWith('a')) { this.options.interactive = false; } else if (!answer.toLowerCase().startsWith('s')) { console.log(` ${colors.YELLOW}⏭️ Pulando arquivo${colors.NC}`); continue; } } const fileDir = path.dirname(fileName); const fullDestPath = path.join(destDir, fileName); const fullDestDir = path.dirname(fullDestPath); if (!this.options.dryRun) { fs.mkdirSync(fullDestDir, { recursive: true }); } if (this.options.verbose && fileDir !== '.') { console.log(` ${colors.BLUE}📂 Criando diretório: ${fullDestDir}${colors.NC}`); } const startMarker = `${MARKER_START} ${fileName} ${MARKER_END}`; const endMarker = nextFileName ? `${MARKER_START} ${nextFileName} ${MARKER_END}` : null; let fileContent = ''; let capturing = false; for (const line of lines) { if (line === startMarker) { capturing = true; continue; } if (capturing && endMarker && line === endMarker) { break; } if (capturing) { fileContent += line + '\n'; } } if (fileContent.endsWith('\n')) { fileContent = fileContent.slice(0, -1); } if (this.options.dryRun) { if (fileContent.length > 0) { const lineCount = fileContent.split('\n').length; const byteCount = Buffer.byteLength(fileContent, 'utf-8'); console.log(` ${colors.GREEN}✅ Simulação OK (${lineCount} linhas, ${byteCount} bytes)${colors.NC}`); this.stats.successCount++; this.stats.bytesExtracted += byteCount; } else { console.log(` ${colors.RED}❌ Simulação: Arquivo vazio ou não encontrado${colors.NC}`); this.stats.errorCount++; } } else { try { fs.writeFileSync(fullDestPath, fileContent, 'utf-8'); if (fs.existsSync(fullDestPath) && fs.statSync(fullDestPath).size > 0) { const lineCount = fileContent.split('\n').length; const byteCount = Buffer.byteLength(fileContent, 'utf-8'); console.log(` ${colors.GREEN}✅ Sucesso (${lineCount} linhas, ${byteCount} bytes)${colors.NC}`); this.stats.successCount++; this.stats.bytesExtracted += byteCount; } else { console.log(` ${colors.RED}❌ Erro: Arquivo vazio ou não criado${colors.NC}`); this.stats.errorCount++; } } catch (error) { console.log(` ${colors.RED}❌ Erro ao criar arquivo: ${error}${colors.NC}`); this.stats.errorCount++; } } } this.stats.duration = Date.now() - startTime; this.printSummary(sourceFile, destDir); } printSummary(sourceFile, destDir) { console.log(""); console.log(`${colors.CYAN}🎉 Extração concluída em ${this.stats.duration}ms!${colors.NC}`); console.log("================================"); console.log(`${colors.PURPLE}📊 Resumo:${colors.NC}`); console.log(` • ${colors.BLUE}Arquivo fonte: ${sourceFile}${colors.NC}`); console.log(` • ${colors.BLUE}Destino: ${destDir}${colors.NC}`); console.log(` • ${colors.GREEN}✅ Sucessos: ${this.stats.successCount}${colors.NC}`); console.log(` • ${colors.RED}❌ Erros: ${this.stats.errorCount}${colors.NC}`); console.log(` • ${colors.PURPLE}📁 Total: ${this.stats.totalFiles} arquivos${colors.NC}`); console.log(` • ${colors.CYAN}💾 Bytes extraídos: ${this.stats.bytesExtracted}${colors.NC}`); console.log(` • ${colors.YELLOW}⏱️ Tempo: ${this.stats.duration}ms${colors.NC}`); if (this.options.showStats) { this.printDetailedStats(destDir); } this.printUsefulCommands(sourceFile, destDir); } printDetailedStats(destDir) { console.log(""); console.log(`${colors.CYAN}📈 Estatísticas Detalhadas:${colors.NC}`); console.log("================================"); if (!this.options.dryRun && this.stats.successCount > 0 && fs.existsSync(destDir)) { console.log(`${colors.GREEN}🔍 Análise de arquivos criados:${colors.NC}`); console.log(`\n${colors.BLUE}🌳 Arquivos criados:${colors.NC}`); try { const files = this.getAllFiles(destDir); files.slice(0, 10).forEach(file => { const relativePath = path.relative(destDir, file); const stat = fs.statSync(file); console.log(` ${relativePath} (${stat.size} bytes)`); }); if (files.length > 10) { console.log(` ... e mais ${files.length - 10} arquivo(s)`); } } catch (error) { console.log(` Erro ao listar arquivos: ${error}`); } } console.log(`\n${colors.BLUE}📄 Estatísticas do arquivo fonte:${colors.NC}`); console.log(` • Tamanho: ${this.stats.sourceSize} bytes`); console.log(` • Marcadores: ${this.stats.totalFiles}`); const efficiency = this.stats.sourceSize > 0 ? Math.round((this.stats.bytesExtracted * 100) / this.stats.sourceSize) : 0; console.log(` • Eficiência: ${efficiency}% do arquivo original`); } getAllFiles(dir) { const files = []; try { const items = fs.readdirSync(dir, { withFileTypes: true }); for (const item of items) { const fullPath = path.join(dir, item.name); if (item.isDirectory()) { files.push(...this.getAllFiles(fullPath)); } else { files.push(fullPath); } } } catch (error) { } return files; } printUsefulCommands(sourceFile, destDir) { console.log(""); if (!this.options.dryRun && this.stats.successCount > 0) { console.log(`${colors.GREEN}🔍 Arquivos extraídos em: ${destDir}${colors.NC}`); console.log(""); console.log(`${colors.CYAN}🚀 Para usar o projeto:${colors.NC}`); console.log(` ${colors.YELLOW}cd ${destDir}${colors.NC}`); console.log(` ${colors.YELLOW}npm install${colors.NC}`); console.log(` ${colors.YELLOW}npm run dev${colors.NC}`); console.log(""); console.log(`${colors.CYAN}🔧 Para extrair novamente:${colors.NC}`); console.log(` ${colors.YELLOW}tsx extractFiles.ts ${sourceFile} ./novo-destino${colors.NC}`); } if (this.stats.errorCount > 0) { console.log(""); console.log(`${colors.RED}⚠️ Alguns arquivos não foram extraídos corretamente.${colors.NC}`); console.log(`${colors.YELLOW}💡 Verifique se os marcadores estão no formato: ${MARKER_START} caminho/arquivo ${MARKER_END}${colors.NC}`); console.log(`${colors.YELLOW}💡 Use --format para validar os marcadores${colors.NC}`); } console.log(""); console.log(`${colors.PURPLE}💡 Comandos úteis:${colors.NC}`); console.log(` ${colors.YELLOW}# Listar marcadores:${colors.NC}`); console.log(` grep '^${MARKER_START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}' ${sourceFile}`); console.log(` ${colors.YELLOW}# Buscar arquivo específico:${colors.NC}`); console.log(` grep '^${MARKER_START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')} src/App.tsx ${MARKER_END.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}' ${sourceFile} -A 20`); console.log(` ${colors.YELLOW}# Contar marcadores:${colors.NC}`); console.log(` grep '^${MARKER_START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}' ${sourceFile} | wc -l`); console.log(` ${colors.YELLOW}# Verificar formato:${colors.NC}`); console.log(` tsx extractFiles.ts ${sourceFile} --format`); console.log(` ${colors.YELLOW}# Modo simulação:${colors.NC}`); console.log(` tsx extractFiles.ts ${sourceFile} ./destino --dry-run --stats`); } } exports.LookAtniExtractor = LookAtniExtractor; async function main() { const args = process.argv.slice(2); const options = { interactive: false, verbose: false, dryRun: false, showStats: false, validateFormat: false }; let sourceFile = ''; let destDir = './extracted'; for (let i = 0; i < args.length; i++) { const arg = args[i]; switch (arg) { case '-i': case '--interactive': options.interactive = true; break; case '-v': case '--verbose': options.verbose = true; break; case '-d': case '--dry-run': options.dryRun = true; break; case '-s': case '--stats': options.showStats = true; break; case '-f': case '--format': options.validateFormat = true; break; case '-h': case '--help': new LookAtniExtractor(options).printHelp(); process.exit(0); break; default: if (arg.startsWith('-')) { console.error(`❌ Opção desconhecida: ${arg}`); console.error("Use --help para ajuda"); process.exit(1); } else { if (!sourceFile) { sourceFile = arg; } else if (destDir === './extracted') { destDir = arg; } else { console.error("❌ Muitos argumentos!"); process.exit(1); } } break; } } if (!sourceFile) { new LookAtniExtractor(options).printHelp(); process.exit(1); } try { const extractor = new LookAtniExtractor(options); await extractor.extractFiles(sourceFile, destDir); process.exit(0); } catch (error) { console.error(error instanceof Error ? error.message : String(error)); process.exit(1); } } if (require.main === module) { main().catch(console.error); }