-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathnormalize_csv_escaping.py
More file actions
71 lines (57 loc) · 2.23 KB
/
Copy pathnormalize_csv_escaping.py
File metadata and controls
71 lines (57 loc) · 2.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#!/usr/bin/env python3
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
"""
Back-fill CSV formula-injection escaping onto already-committed data CSVs.
The scrapers escape on write (see csv_safety.escape_formula); this applies the
SAME transform to existing files in place, without re-hitting the API. It reads
and rewrites with the csv module's default dialect -- exactly what the scrapers
use -- so unchanged cells stay byte-identical and a later re-scrape of a
normalised day produces no diff. Idempotent: already-escaped cells (leading ')
are left alone, and a file is rewritten only if a cell actually changes.
uv run python normalize_csv_escaping.py # all 20*/*.csv
uv run python normalize_csv_escaping.py 2026/questions-thunderbird-desktop-2026-05-01.csv ...
"""
import csv
import glob
import sys
from csv_safety import escape_formula
def raise_field_limit():
"""Match the scrapers: allow very large `content` cells."""
n = sys.maxsize
while True:
try:
csv.field_size_limit(n)
return
except OverflowError:
n = int(n / 10)
def normalize_file(path):
"""Escape every cell in `path`; rewrite only if something changed. Returns
the number of cells changed."""
with open(path, newline="", encoding="utf-8") as f:
rows = list(csv.reader(f))
changed = 0
out = []
for row in rows:
new = [escape_formula(c) for c in row]
changed += sum(1 for a, b in zip(row, new) if a != b)
out.append(new)
if changed:
with open(path, "w", newline="", encoding="utf-8") as f:
csv.writer(f).writerows(out)
return changed
def main():
raise_field_limit()
paths = sys.argv[1:] or sorted(glob.glob("20*/*.csv"))
files_changed = cells_changed = 0
for p in paths:
n = normalize_file(p)
if n:
files_changed += 1
cells_changed += n
print(f"escaped {n} cell(s): {p}")
print(f"\n{files_changed}/{len(paths)} files changed "
f"({cells_changed} cells escaped)")
if __name__ == "__main__":
main()