You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
117 lines
3.5 KiB
117 lines
3.5 KiB
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const MAX_FILES = 500;
|
|
const MAX_PY = 16000;
|
|
const MAX_CSV = 8000;
|
|
|
|
function walk(dir, base, out) {
|
|
if (out.length >= MAX_FILES) return;
|
|
let entries;
|
|
try {
|
|
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
} catch {
|
|
return;
|
|
}
|
|
for (const ent of entries) {
|
|
if (out.length >= MAX_FILES) return;
|
|
if (ent.name === '.git') continue;
|
|
const full = path.join(dir, ent.name);
|
|
const rel = path.posix.join(base.replace(/\\/g, '/'), ent.name.replace(/\\/g, '/'));
|
|
if (ent.isDirectory()) {
|
|
walk(full, rel, out);
|
|
} else {
|
|
out.push(rel);
|
|
}
|
|
}
|
|
}
|
|
|
|
function readSnippet(repoRoot, rel, maxChars) {
|
|
const full = path.join(repoRoot, rel);
|
|
if (!fs.existsSync(full) || !fs.statSync(full).isFile()) return null;
|
|
const buf = fs.readFileSync(full);
|
|
const text = buf.toString('utf8').slice(0, maxChars);
|
|
return { path: rel, bytes: buf.length, truncated: buf.length > maxChars, snippet: text };
|
|
}
|
|
|
|
/**
|
|
* 扫描仓库中与成绩实训相关的文件(启发式路径 + 内容片段,供大模型引用)
|
|
*/
|
|
function scanStudentRepo(repoRoot) {
|
|
const files = [];
|
|
walk(repoRoot, '', files);
|
|
|
|
const norm = (r) => r.replace(/\\/g, '/').toLowerCase();
|
|
const byEnd = (suffix) => files.find((f) => norm(f).endsWith(suffix));
|
|
const byName = (name) => files.find((f) => path.basename(f).toLowerCase() === name.toLowerCase());
|
|
|
|
let py =
|
|
byEnd('score_analysis.py') ||
|
|
byName('score_analysis.py') ||
|
|
files.find((f) => /\.py$/i.test(f) && /score|analysis|grade/i.test(f));
|
|
let csv =
|
|
byEnd('scores.csv') ||
|
|
byName('scores.csv') ||
|
|
files.find((f) => /\.csv$/i.test(f) && /score|grade|成绩/i.test(f));
|
|
let png =
|
|
byEnd('score_chart.png') ||
|
|
byName('score_chart.png') ||
|
|
files.find((f) => /\.png$/i.test(f) && /score|chart/i.test(f));
|
|
|
|
const myshixunDir = files.map((f) => f.split('/')[0]).find((seg) => /myshixun/i.test(seg));
|
|
if (myshixunDir && !py) {
|
|
py = files.find((f) => f.startsWith(myshixunDir + '/') && /\.py$/i.test(f) && /score|analysis/i.test(f));
|
|
}
|
|
|
|
const pySnip = py ? readSnippet(repoRoot, py, MAX_PY) : null;
|
|
const csvSnip = csv ? readSnippet(repoRoot, csv, MAX_CSV) : null;
|
|
let pngInfo = { exists: false, path: png || null, bytes: 0 };
|
|
if (png && fs.existsSync(path.join(repoRoot, png))) {
|
|
pngInfo = { exists: true, path: png, bytes: fs.statSync(path.join(repoRoot, png)).size };
|
|
}
|
|
|
|
const snippet = pySnip?.snippet || '';
|
|
const flags = {
|
|
has_read_csv: /read_csv|pandas|pd\.read/i.test(snippet),
|
|
has_mean: /\bmean\s*\(/i.test(snippet),
|
|
has_matplotlib: /matplotlib|pyplot|plt\./i.test(snippet),
|
|
};
|
|
|
|
return {
|
|
repo_root: repoRoot,
|
|
file_count: files.length,
|
|
file_list_sample: files.slice(0, 100),
|
|
score_analysis_py: pySnip,
|
|
scores_csv: csvSnip,
|
|
score_chart_png: pngInfo,
|
|
flags,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 列出仓库内文件(相对路径 + 字节数),按路径排序
|
|
*/
|
|
function listRepoFiles(repoRoot, maxFiles = 500) {
|
|
const rels = [];
|
|
walk(repoRoot, '', rels);
|
|
rels.sort((a, b) => a.localeCompare(b, 'en'));
|
|
const out = [];
|
|
for (const rel of rels) {
|
|
if (out.length >= maxFiles) break;
|
|
const full = path.join(repoRoot, rel);
|
|
try {
|
|
const st = fs.statSync(full);
|
|
if (!st.isFile()) continue;
|
|
out.push({
|
|
path: rel.replace(/\\/g, '/'),
|
|
size: st.size,
|
|
});
|
|
} catch {
|
|
/* skip */
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
module.exports = { scanStudentRepo, listRepoFiles };
|