import { unzipSync, strFromU8 } from 'fflate'; import type { Language } from './types'; /** Çok dosyalı girdi: seçilen dosyaları ve zip arşivlerini okur, test edilecek dosyayı ve yardımcıları ayırır. */ export const EXT_LANG: Record = { py: 'python', js: 'javascript', mjs: 'javascript', cjs: 'javascript', jsx: 'javascript', ts: 'typescript', tsx: 'typescript', java: 'java', html: 'html', htm: 'html', }; /** Test edilmez ama kodun okuyabilmesi için yanına konur */ const DATA_EXT = new Set(['json', 'txt', 'csv', 'css', 'xml', 'yaml', 'yml', 'properties', 'md']); const SKIP_DIR = /(^|\/)(node_modules|__pycache__|\.git|\.venv|venv|dist|build|target|out|\.idea|\.vscode)\//; export const ACCEPT = [...Object.keys(EXT_LANG), 'zip', ...DATA_EXT].map((e) => '.' + e).join(','); export const MAX_FILE = 200_000; export const MAX_TOTAL = 600_000; export const MAX_EXTRA = 30; export const extOf = (name: string) => (name.split('.').pop() || '').toLowerCase(); export const baseName = (p: string) => p.split('/').pop() || p; export const isTestFile = (p: string) => /(^|\/)(tests?|__tests__|spec)\//i.test(p) || /(^|\/)(test_[^/]*\.py|[^/]*_test\.py|[^/]*\.(test|spec)\.[jt]sx?|[^/]*Tests?\.java)$/i.test(p); /** Araç/yapılandırma dosyaları: koşucunun kendi dosyalarıyla çakışır, yardımcı olarak gönderilmez */ const isToolingFile = (p: string) => /(^|\/)(package(-lock)?\.json|yarn\.lock|pnpm-lock\.yaml|tsconfig\.json|jest\.config\.[cm]?[jt]s|babel\.config\.[cm]?js|\.babelrc|playwright\.config\.[jt]s|conftest\.py|pytest\.ini|setup\.cfg|tox\.ini|pyproject\.toml|pom\.xml|build\.gradle(\.kts)?|vite\.config\.[jt]s)$/i.test(p); export interface InFile { path: string; text: string } export interface ReadResult { files: InFile[]; skipped: string[]; tooBig: string[] } /** Dosyaları ve zip'leri okur; ikili/çok büyük/desteklenmeyen dosyaları ayıklar. */ export async function readInputs(list: FileList | File[]): Promise { const files: InFile[] = []; const skipped: string[] = []; const tooBig: string[] = []; const add = (path: string, bytes: Uint8Array | string) => { const ext = extOf(path); if (SKIP_DIR.test(path + '/') || SKIP_DIR.test(path)) return; if (!EXT_LANG[ext] && !DATA_EXT.has(ext)) { skipped.push(path); return; } if (isToolingFile(path)) { skipped.push(path); return; } const size = typeof bytes === 'string' ? bytes.length : bytes.length; if (size > MAX_FILE) { (EXT_LANG[ext] ? tooBig : skipped).push(path); return; } const text = typeof bytes === 'string' ? bytes : strFromU8(bytes); if (text.includes('\u0000')) { skipped.push(path); return; } files.push({ path: path.replace(/\\/g, '/').replace(/^\.\//, ''), text }); }; for (const f of [...list]) { if (extOf(f.name) === 'zip') { const entries = unzipSync(new Uint8Array(await f.arrayBuffer())); for (const [p, data] of Object.entries(entries)) if (!p.endsWith('/')) add(p, data); } else { const rel = (f as File & { webkitRelativePath?: string }).webkitRelativePath || f.name; add(rel, await f.text()); } } // zip'teki ortak üst klasörü kaldır (proje/ src/a.py → src/a.py) const parts = files.map((f) => f.path.split('/')); while (parts.length && parts.every((p) => p.length > 1 && p[0] === parts[0][0])) parts.forEach((p) => p.shift()); files.forEach((f, i) => { f.path = parts[i].join('/'); }); return { files, skipped, tooBig }; } export const langOf = (path: string) => EXT_LANG[extOf(path)]; /** Varsayılan hedef: test dosyası olmayan, en uzun kaynak dosyası (çoğunlukla asıl modül). */ export function pickTarget(files: InFile[]): string | null { const code = files.filter((f) => { const l = langOf(f.path); return l && l !== 'html' && !isTestFile(f.path); }); const pool = code.length ? code : files.filter((f) => langOf(f.path) && langOf(f.path) !== 'html'); if (!pool.length) return null; return [...pool].sort((a, b) => b.text.length - a.text.length)[0].path; } /** Hedefe göre yardımcı dosyalar: aynı dil ailesindeki kaynaklar + veri dosyaları; yollar hedefin klasörüne göre. */ export function helpersFor(files: InFile[], target: string): { extra: Record; ignored: string[] } { const tl = langOf(target); const family = (l: string | undefined) => (l === 'typescript' || l === 'javascript' ? 'js' : l); const dir = target.includes('/') ? target.slice(0, target.lastIndexOf('/') + 1) : ''; const extra: Record = {}; const ignored: string[] = []; for (const f of files) { if (f.path === target) continue; const l = langOf(f.path); const ok = !l ? DATA_EXT.has(extOf(f.path)) : family(l) === family(tl) || (l === 'html' && tl === 'javascript'); if (!ok || isTestFile(f.path)) { ignored.push(f.path); continue; } const rel = dir && f.path.startsWith(dir) ? f.path.slice(dir.length) : f.path; if (Object.keys(extra).length < MAX_EXTRA) extra[rel] = f.text; else ignored.push(f.path); } return { extra, ignored }; }