scripts/csv_to_xlsx.py
scripts/csv_to_xlsx.pyBrowse 11 files
826 tokens
3,446 bytes
Token encoding: o200k_base
Snapshot 24fd22b
← Back to SKILL.md
1#!/usr/bin/env python32"""Convert a CSV file to a styled .xlsx workbook with type inference.3 4Type inference per cell (disable with --no-infer):5 int, float, bool ("true"/"false", case-insensitive), ISO date6 (YYYY-MM-DD) and ISO datetime; everything else stays a string.7 8Styling applied by default (disable with --plain):9 bold header row with a light fill, frozen top row, autofilter over the10 data range, and column widths sized to the longest cell (capped at 60).11 12Usage:13 csv_to_xlsx.py data.csv out.xlsx14 csv_to_xlsx.py data.csv out.xlsx --sheet-name Import --encoding cp125215 csv_to_xlsx.py data.csv out.xlsx --delimiter ';' --no-infer16"""17from __future__ import annotations18 19import argparse20import csv21import json22import sys23from datetime import date, datetime24 25from openpyxl import Workbook26from openpyxl.styles import Font, PatternFill27from openpyxl.utils import get_column_letter28 29MAX_COL_WIDTH = 6030COL_PADDING = 231DEFAULT_COL_WIDTH = 832 33 34def infer(text):35 if text == "":36 return None37 low = text.lower()38 if low in ("true", "false"):39 return low == "true"40 for caster in (int, float):41 try:42 return caster(text)43 except ValueError:44 pass45 for parser in (date.fromisoformat, datetime.fromisoformat):46 try:47 return parser(text)48 except ValueError:49 pass50 return text51 52 53def main(argv=None):54 ap = argparse.ArgumentParser(description="CSV -> styled .xlsx converter.")55 ap.add_argument("csv_file", help="input CSV path")56 ap.add_argument("output", help="output .xlsx path")57 ap.add_argument("--sheet-name", default="Sheet1")58 ap.add_argument("--encoding", default="utf-8",59 help="CSV file encoding (default utf-8)")60 ap.add_argument("--delimiter", default=",")61 ap.add_argument("--no-infer", action="store_true",62 help="keep every cell as a string")63 ap.add_argument("--plain", action="store_true",64 help="skip header styling / freeze / autofilter")65 args = ap.parse_args(argv)66 67 with open(args.csv_file, newline="", encoding=args.encoding) as fh:68 rows = list(csv.reader(fh, delimiter=args.delimiter))69 70 wb = Workbook()71 ws = wb.active72 ws.title = args.sheet_name73 for i, row in enumerate(rows):74 if args.no_infer or i == 0:75 ws.append(row)76 else:77 ws.append([infer(cell) for cell in row])78 79 if rows and not args.plain:80 header_font = Font(bold=True)81 header_fill = PatternFill("solid", fgColor="DDEBF7")82 for cell in ws[1]:83 cell.font = header_font84 cell.fill = header_fill85 ws.freeze_panes = "A2"86 ws.auto_filter.ref = ws.dimensions87 for col_idx in range(1, ws.max_column + 1):88 longest = max((len(str(r[col_idx - 1])) for r in rows89 if len(r) >= col_idx), default=DEFAULT_COL_WIDTH)90 ws.column_dimensions[get_column_letter(col_idx)].width = \91 min(longest + COL_PADDING, MAX_COL_WIDTH)92 93 wb.save(args.output)94 print(json.dumps({"ok": True, "output": args.output,95 "rows": len(rows)}, ensure_ascii=False))96 return 097 98 99if __name__ == "__main__":100 try:101 sys.exit(main())102 except Exception as exc: # noqa: BLE001103 print(json.dumps({"ok": False, "error": str(exc)}), file=sys.stderr)104 sys.exit(1)105