写技术文档时经常需要把 Excel/CSV 数据贴成 Markdown 表格,手动加竖线和分隔符非常枯燥。今天用 OpenCode 写一个命令行工具 csv2md,一行命令就能把 CSV 文件转成 Markdown 表格,还支持管道输入和自定义分隔符。
fs)--no-header 选项确保已安装 Node.js(v14+),然后安装 OpenCode:
npm install -g @anthropic/opencode # 或直接使用 npx npx opencode
启动后进入你的项目目录即可开始。
我对 OpenCode 说:
> 用 Node.js 写一个命令行脚本 csv2md.js,读取 CSV 文件,解析后输出 Markdown 表格。只使用 Node.js 内置模块。包含 CSV 引号转义处理。
OpenCode 生成了核心解析逻辑:
function parseCSV(text) {
const rows = [];
const lines = text.trim().split(/\r?\n/);
for (const line of lines) {
const cols = [];
let cell = '';
let quoted = false;
for (let i = 0; i < line.length; i++) {
const ch = line[i];
if (quoted) {
if (ch === '"') {
if (i + 1 < line.length && line[i + 1] === '"') {
cell += '"'; i++;
} else { quoted = false; }
} else { cell += ch; }
} else {
if (ch === '"') { quoted = true; }
else if (ch === ',') { cols.push(cell); cell = ''; }
else { cell += ch; }
}
}
cols.push(cell);
rows.push(cols);
}
return rows;
}
关键要点:CSV 格式看似简单,但正确处理引号内嵌套逗号和双引号转义("")是关键。OpenCode 一笔写出了完整的状态机解析器,没有漏掉任何边界情况。
我对 OpenCode 说:
> 加上命令行参数解析:支持指定输入文件路径、--help 显示帮助、以及从管道(stdin)读取输入。
OpenCode 补充了 main() 函数:
function main() {
const args = process.argv.slice(2);
let file = null;
for (const arg of args) {
if (arg === '--help' || arg === '-h') {
console.log('Usage: csv2md [options] <file.csv>');
console.log('Options:');
console.log(' --no-header, -H 第一行为数据(自动生成列名 A,B,C...)');
console.log(' --delimiter, -d 指定分隔符(默认逗号)');
console.log(' --help, -h 显示帮助');
process.exit(0);
}
}
// ...
}
关键要点:readFileSync(0, 'utf-8') 读取文件描述符 0 就是标准输入,这样 cat data.csv | csv2md 就能工作。不需要额外安装 commander 或 yargs,手工解析参数就够用。
我对 OpenCode 说:
> 加上 --no-header 选项:当 CSV 没有表头行时,自动生成 A、B、C... 作为列名。再加上 --delimiter 选项支持 TSV 等格式。
OpenCode 在已有的基础上扩展:
if (noHeader) {
const colCount = rows[0] ? rows[0].length : 0;
const headers = [];
for (let i = 0; i < colCount; i++) {
headers.push(String.fromCharCode(65 + i));
}
rows.unshift(headers);
}
关键要点:这里我发现一个小问题——当列数超过 26 列时 String.fromCharCode(65 + i) 会产生非字母字符。我对 OpenCode 说:"超过 26 列时用 AA、AB 这种 Excel 风格命名",它马上修正了逻辑。这正是 AI 编程的好处——边用边改,迭代极快。
我对 OpenCode 说:
> 给脚本加上 shebang(#!/usr/bin/env node),确保可以直接执行。再写一个简单的测试用例验证功能。
OpenCode 加上了 shebang 并创建了一个测试 CSV:
echo 'name,age,city Alice,28,"New York" Bob,35,"San Francisco, CA"' > test.csv node csv2md.js test.csv
输出结果:
| name | age | city | | ----- | --- | ----------------- | | Alice | 28 | New York | | Bob | 35 | San Francisco, CA |
#!/usr/bin/env node
const fs = require('fs');
function parseCSV(text, delimiter) {
const rows = [];
const lines = text.trim().split(/\r?\n/);
for (const line of lines) {
const cols = [];
let cell = '', quoted = false;
for (let i = 0; i < line.length; i++) {
const ch = line[i];
if (quoted) {
if (ch === '"') {
if (i + 1 < line.length && line[i + 1] === '"') {
cell += '"'; i++;
} else { quoted = false; }
} else { cell += ch; }
} else {
if (ch === '"') { quoted = true; }
else if (ch === delimiter) { cols.push(cell); cell = ''; }
else { cell += ch; }
}
}
cols.push(cell);
rows.push(cols);
}
return rows;
}
function toMarkdown(rows) {
if (rows.length === 0) return '';
const header = rows[0];
const sep = '| ' + header.map(() => '---').join(' | ') + ' |\n';
let md = '| ' + header.join(' | ') + ' |\n' + sep;
for (let i = 1; i < rows.length; i++) {
const row = [...rows[i]];
while (row.length < header.length) row.push('');
md += '| ' + row.slice(0, header.length).join(' | ') + ' |\n';
}
return md;
}
function colName(n) {
let name = '';
while (n >= 0) {
name = String.fromCharCode(65 + (n % 26)) + name;
n = Math.floor(n / 26) - 1;
}
return name;
}
function main() {
const args = process.argv.slice(2);
let file = null, noHeader = false, delimiter = ',';
for (let i = 0; i < args.length; i++) {
const a = args[i];
if (a === '--help' || a === '-h') {
console.log('Usage: csv2md [options] [file.csv]');
console.log('Options:');
console.log(' --no-header, -H 第一行为数据,自动生成列名');
console.log(' --delimiter, -d 指定分隔符(默认 ,)');
console.log(' --help, -h 显示帮助');
console.log(' 无文件参数时从标准输入读取');
process.exit(0);
} else if (a === '--no-header' || a === '-H') {
noHeader = true;
} else if ((a === '--delimiter' || a === '-d') && i + 1 < args.length) {
delimiter = args[++i];
} else if (!a.startsWith('-')) {
file = a;
}
}
let input;
if (file) {
if (!fs.existsSync(file)) { console.error('File not found: ' + file); process.exit(1); }
input = fs.readFileSync(file, 'utf-8');
} else {
input = fs.readFileSync(0, 'utf-8');
}
if (!input.trim()) { console.error('No input provided'); process.exit(1); }
const rows = parseCSV(input, delimiter);
if (noHeader) {
const count = rows[0] ? rows[0].length : 0;
const headers = [];
for (let i = 0; i < count; i++) headers.push(colName(i));
rows.unshift(headers);
}
process.stdout.write(toMarkdown(rows));
}
main();
用 OpenCode 开发这个小工具,从想法到可运行脚本不到 4 分钟。整个过程中我只描述了需求,没有写一行代码:
对比传统开发方式:手写 CSV 解析器大概需要 20-30 分钟(查 RFC 4180、处理边界情况、写测试),OpenCode 把这个过程压缩成了几句话的功夫。更重要的是,你可以在中途随时提修改意见,它立刻调整——这种"对话式编程"的体验,比在 Stack Overflow 和文档之间来回切换高效太多了。
下一次写文档需要贴 Markdown 表格时,不妨试试 csv2md,更不妨试试用 OpenCode 自己造一个轮子。