| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106 |
- #!/usr/bin/env node
- const fs = require('fs');
- const path = require('path');
- function main() {
- const input = arg('--input');
- if (!input) throw new Error('用法:node scripts/export-software-markdown.js --input <software-csv> [--output <markdown>]');
- const output = arg('--output') || input.replace(/\.csv$/i, '.md');
- const rows = parseCsv(fs.readFileSync(input, 'utf8').replace(/^\uFEFF/, ''))
- .filter(row => row.some(cell => String(cell || '').trim()));
- if (!rows.length) throw new Error(`CSV 为空:${input}`);
- const summary = readSummary(input);
- const audit = summary.duplicateAudit || {};
- const sourceDuplicateRemovedCount = summary.sourceDuplicateRemovedCount ?? summary.duplicateCount ?? 0;
- const finalDuplicateGroupCount = summary.finalDuplicateGroupCount ?? countDuplicateGroups(audit);
- const lines = [
- '# 软件端去重博主名单',
- '',
- `来源:\`${path.basename(input)}\``,
- '',
- `- 原始行数:${summary.sourceRows ?? rows.length - 1}`,
- `- 去重后行数:${summary.outputRows ?? rows.length - 1}`,
- `- 源表去重丢弃行数:${sourceDuplicateRemovedCount}`,
- `- 最终名单重复组数:${finalDuplicateGroupCount}`,
- '- 去重口径:软件端交付表按全局博主唯一;同一平台 + 规范化主页链接不重复;没有主页链接时使用同一平台 + 规范化博主名称。',
- '- 排名规则:去重后按每个 brief 重新从 1 排名。',
- `- 同 brief URL 重复组:${count(audit.sameBriefUrl)}`,
- `- 同 brief 名称重复组:${count(audit.sameBriefName)}`,
- `- 全局 URL 重复组:${count(audit.globalUrl)}`,
- `- 全局平台+名称重复组:${count(audit.globalNamePlatform)}`,
- '',
- markdownTable(rows)
- ];
- fs.writeFileSync(output, `\uFEFF${lines.join('\n')}`);
- console.log(JSON.stringify({ input, output, rows: rows.length - 1 }, null, 2));
- }
- function arg(name) {
- const index = process.argv.indexOf(name);
- return index >= 0 ? process.argv[index + 1] : '';
- }
- function readSummary(input) {
- const summaryPath = input.replace(/\.csv$/i, '.summary.json');
- if (!fs.existsSync(summaryPath)) return {};
- return JSON.parse(fs.readFileSync(summaryPath, 'utf8'));
- }
- function count(value) {
- return Array.isArray(value) ? value.length : 0;
- }
- function countDuplicateGroups(audit) {
- if (!audit) return 0;
- return Object.values(audit).reduce((sum, groups) => sum + count(groups), 0);
- }
- function markdownTable(rows) {
- const [header, ...body] = rows;
- const lines = [
- `| ${header.map(markdownCell).join(' | ')} |`,
- `| ${header.map(() => '---').join(' | ')} |`
- ];
- for (const row of body) lines.push(`| ${row.map(markdownCell).join(' | ')} |`);
- return lines.join('\n');
- }
- function markdownCell(value) {
- return String(value ?? '').replace(/\|/g, '|').replace(/\r?\n/g, ' ').trim();
- }
- function parseCsv(text) {
- const rows = [];
- let row = [];
- let cell = '';
- let quoted = false;
- for (let index = 0; index < text.length; index += 1) {
- const char = text[index];
- if (char === '\r') continue;
- if (char === '"' && quoted && text[index + 1] === '"') {
- cell += '"';
- index += 1;
- } else if (char === '"') {
- quoted = !quoted;
- } else if (char === ',' && !quoted) {
- row.push(cell);
- cell = '';
- } else if (char === '\n' && !quoted) {
- row.push(cell);
- rows.push(row);
- row = [];
- cell = '';
- } else {
- cell += char;
- }
- }
- if (cell || row.length) {
- row.push(cell);
- rows.push(row);
- }
- return rows;
- }
- main();
|