- Move the Tauri app (React frontend + Rust backend) into desktop/ via git mv — configs unchanged (relative paths: tauri.conf.json, vite.config.ts) - Root package.json becomes a private bun workspace container with orchestration scripts; desktop package renamed gridline-desktop - Scaffold www/ with Astro 7 + Tailwind v4 (hand-wired vite plugin, static output, ready for Dokploy) - Update release.yml for the new layout: projectPath: desktop, desktop/src-tauri resource paths, rust-cache workspace path - Update README + AGENTS.md structure trees and dev commands - Verified: vitest (1074), cargo test (354), desktop build, tauri dev (window launches), tauri build (dmg + app bundles), Astro build
43 lines
1.2 KiB
TypeScript
43 lines
1.2 KiB
TypeScript
export interface ParsedCsv {
|
|
headers: string[];
|
|
rows: string[][];
|
|
}
|
|
|
|
export function parseCsv(input: string): ParsedCsv {
|
|
const text = input.replace(/^\uFEFF/, "");
|
|
if (text.trim() === "") throw new Error("CSV input is empty");
|
|
|
|
const rows: string[][] = [];
|
|
let field = "";
|
|
let row: string[] = [];
|
|
let inQuotes = false;
|
|
|
|
for (let i = 0; i < text.length; i++) {
|
|
const ch = text[i];
|
|
if (inQuotes) {
|
|
if (ch === '"') {
|
|
if (text[i + 1] === '"') { field += '"'; i++; }
|
|
else inQuotes = false;
|
|
} else field += ch;
|
|
} else if (ch === '"') {
|
|
inQuotes = true;
|
|
} else if (ch === ',') {
|
|
row.push(field); field = "";
|
|
} else if (ch === '\n' || ch === '\r') {
|
|
if (ch === '\r' && text[i + 1] === '\n') i++;
|
|
row.push(field); field = "";
|
|
rows.push(row); row = [];
|
|
} else field += ch;
|
|
}
|
|
if (field !== "" || row.length > 0) { row.push(field); rows.push(row); }
|
|
if (rows.length === 0) throw new Error("CSV input is empty");
|
|
|
|
const [headers, ...data] = rows;
|
|
const width = headers.length;
|
|
const padded = data.map((r) => {
|
|
const out = r.slice(0, width);
|
|
while (out.length < width) out.push("");
|
|
return out;
|
|
});
|
|
return { headers, rows: padded };
|
|
} |