csv-report.js in JavaScript
A Node report: read a CSV, derive columns, group, and print a table.
#!/usr/bin/env node
import { mkdtemp, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { createReadStream } from "node:fs";
import { createInterface } from "node:readline";
const CSV = `day,lang,chars,seconds,errors
2026-07-20,go,1820,60,4
2026-07-20,rust,1510,60,9
2026-07-21,go,1980,60,2
2026-07-21,"sql, standard",1240,60,1
2026-07-22,go,2050,60,3
2026-07-29,rust,1740,60,2
`;
function parseLine(line) {
const fields = [];
let current = "";
let quoted = false;
for (const char of line) {
if (char === '"') {
quoted = !quoted;
} else if (char === "," && !quoted) {
fields.push(current);
current = "";
} else {
current += char;
}
}
fields.push(current);
return fields.map((f) => f.trim());
}
async function readRows(path) {
const lines = createInterface({ input: createReadStream(path) });
let header;
const rows = [];
for await (const line of lines) {
if (!line.trim()) continue;
const fields = parseLine(line);
if (!header) {
header = fields;
continue;
}
rows.push(Object.fromEntries(header.map((key, i) => [key, fields[i]])));
}
return rows;
}
function derive(row) {
const chars = Number(row.chars);
return {
...row,
wpm: chars / 5 / (Number(row.seconds) / 60),
accuracy: (1 - Number(row.errors) / chars) * 100,
};
}
function summarize(rows) {
const groups = new Map();
for (const row of rows) {
const group = groups.get(row.lang) ?? { runs: 0, wpm: 0, best: 0 };
group.runs += 1;
group.wpm += row.wpm;
group.best = Math.max(group.best, row.wpm);
groups.set(row.lang, group);
}
return [...groups.entries()]
.map(([lang, g]) => ({ lang, runs: g.runs, mean: g.wpm / g.runs,
best: g.best }))
.sort((a, b) => b.mean - a.mean);
}
function table(rows) {
const headers = ["lang", "runs", "mean", "best"];
const widths = headers.map((key) =>
Math.max(key.length, ...rows.map((row) => String(row[key]).length)));
const line = (values) =>
values.map((value, i) => String(value).padEnd(widths[i])).join(" ");
const body = rows.map((row) =>
line([row.lang, row.runs, row.mean.toFixed(1), row.best.toFixed(1)]));
return [line(headers), ...body].join("\n");
}
const dir = await mkdtemp(join(tmpdir(), "report-"));
const path = join(dir, "runs.csv");
await writeFile(path, CSV);
const rows = (await readRows(path)).map(derive);
console.log(`${rows.length} runs read`);
console.log(table(summarize(rows)));
const mean = rows.reduce((t, row) => t + row.accuracy, 0) / rows.length;
console.log(`mean accuracy ${mean.toFixed(2)}%`);
How it works
- The parser handles quoted fields, because a real CSV has them.
- Grouping uses a Map, so insertion order is kept.
- The table is padded from the widest value in each column.
Keywords and builtins used here
MapMathNumberObjectStringasyncawaitcharconstcontinueelseforfromfunctionifimportletofreturn
The run, in numbers
- Lines
- 99
- Characters to type
- 2494
- Tokens
- 749
- Three-star pace
- 115 tpm
At the three-star pace of 115 tokens a minute, this run takes about 391 seconds.
Step 1 of 1 in Encore, step 18 of 18 in Node.js.