Skip to content

Commit 14d3071

Browse files
committed
feat: copydeck export script now includes links to demo/code, ID and "reviewed?" fields
1 parent 8dc6c3a commit 14d3071

1 file changed

Lines changed: 78 additions & 9 deletions

File tree

scripts/export_copydeck_csv.py

Lines changed: 78 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,20 @@
11
#!/usr/bin/env python3
22
"""Export copydeck error messages to CSV for Google Sheets review.
33
4+
Produces the column layout the learning team works with, so the export can seed
5+
(or refresh) their review sheet directly:
6+
7+
ID, Error_type, Link to demo / code, Has been reviewed by learning team?,
8+
variant_index, if_condition, Title, Summary, Why, Step_1 .. Step_5
9+
10+
* ID - stable 1-based row number
11+
* Link to demo / code - deep link to the matching demo example,
12+
built from docs/demo-examples.js
13+
* Has been reviewed ... - defaults to FALSE (reviewers flip to TRUE)
14+
15+
The reviewed CSV is fed back in via import_copydeck_csv.py, which only applies
16+
rows marked TRUE by default.
17+
418
Usage:
519
python scripts/export_copydeck_csv.py
620
python scripts/export_copydeck_csv.py --copydeck copydecks/fr/copydeck.json --out review_fr.csv
@@ -9,32 +23,80 @@
923
import argparse
1024
import csv
1125
import json
26+
import re
1227
import sys
1328
from pathlib import Path
1429

15-
COLUMNS = ["error_type", "variant_index", "if_condition", "title", "summary", "why",
16-
"step_1", "step_2", "step_3", "step_4", "step_5"]
30+
COLUMNS = ["ID", "Error_type", "Link to demo / code", "Has been reviewed by learning team?",
31+
"variant_index", "if_condition", "Title", "Summary", "Why",
32+
"Step_1", "Step_2", "Step_3", "Step_4", "Step_5"]
33+
34+
DEFAULT_DEMO_BASE_URL = "https://raspberrypifoundation.github.io/python-friendly-error-messages"
35+
36+
37+
def slugify(title: str) -> str:
38+
"""Match docs/index.html getRowId(): lowercase, non-alphanumeric runs -> '-', trim '-'."""
39+
return re.sub(r"[^a-z0-9]+", "-", title.lower()).strip("-")
1740

1841

19-
def export(copydeck_path: Path, out_path: Path) -> None:
42+
def load_demo_index(demo_path: Path) -> dict:
43+
"""Map expectedVariantId -> (example_number, demo_title) by reading demo-examples.js in order.
44+
45+
The demo page anchors each example as `example-{position}-{slug(demo_title)}`, so we need the
46+
example's display position and its (demo-specific) title, keyed by the variant it demonstrates.
47+
"""
48+
text = demo_path.read_text(encoding="utf-8")
49+
pairs = re.findall(
50+
r'title:\s*"((?:[^"\\]|\\.)*)".*?expectedVariantId:\s*"([^"]+)"',
51+
text,
52+
re.S,
53+
)
54+
index = {}
55+
for position, (raw_title, variant_id) in enumerate(pairs, start=1):
56+
title = json.loads(f'"{raw_title}"') # decode any JSON string escapes
57+
index[variant_id] = (position, title)
58+
return index
59+
60+
61+
def export(copydeck_path: Path, out_path: Path, demo_path: Path, demo_base_url: str) -> None:
2062
with open(copydeck_path) as f:
2163
data = json.load(f)
2264

65+
demo_index = load_demo_index(demo_path) if demo_path and demo_path.exists() else {}
66+
if not demo_index:
67+
print(f"Note: no demo examples found at {demo_path}; 'Link to demo / code' will be blank.",
68+
file=sys.stderr)
69+
2370
rows = []
71+
row_id = 0
72+
missing_links = 0
2473
for error_type, error_def in data["errors"].items():
2574
for i, variant in enumerate(error_def["variants"]):
75+
row_id += 1
76+
variant_id = f"{error_type}/variants/{i}"
77+
78+
link = ""
79+
if variant_id in demo_index:
80+
position, demo_title = demo_index[variant_id]
81+
link = f"{demo_base_url}/#example-{position}-{slugify(demo_title)}"
82+
elif demo_index:
83+
missing_links += 1
84+
2685
steps = variant.get("steps", [])
2786
if_condition = variant.get("if")
2887
row = {
29-
"error_type": error_type,
88+
"ID": row_id,
89+
"Error_type": error_type,
90+
"Link to demo / code": link,
91+
"Has been reviewed by learning team?": "FALSE",
3092
"variant_index": i,
3193
"if_condition": json.dumps(if_condition) if if_condition else "",
32-
"title": variant.get("title", ""),
33-
"summary": variant.get("summary", ""),
34-
"why": variant.get("why", ""),
94+
"Title": variant.get("title", ""),
95+
"Summary": variant.get("summary", ""),
96+
"Why": variant.get("why", ""),
3597
}
3698
for j in range(1, 6):
37-
row[f"step_{j}"] = steps[j - 1] if j - 1 < len(steps) else ""
99+
row[f"Step_{j}"] = steps[j - 1] if j - 1 < len(steps) else ""
38100
rows.append(row)
39101

40102
with open(out_path, "w", newline="", encoding="utf-8") as f:
@@ -43,6 +105,9 @@ def export(copydeck_path: Path, out_path: Path) -> None:
43105
writer.writerows(rows)
44106

45107
print(f"Exported {len(rows)} variants to {out_path}")
108+
if missing_links:
109+
print(f"Warning: {missing_links} variant(s) had no matching demo example; their link is blank.",
110+
file=sys.stderr)
46111

47112

48113
def main():
@@ -51,14 +116,18 @@ def main():
51116
help="Path to source copydeck JSON (default: copydecks/en/copydeck.json)")
52117
parser.add_argument("--out", default="copydeck_review.csv",
53118
help="Output CSV path (default: copydeck_review.csv)")
119+
parser.add_argument("--demo", default="docs/demo-examples.js",
120+
help="Path to demo-examples.js used to build demo links (default: docs/demo-examples.js)")
121+
parser.add_argument("--demo-base-url", default=DEFAULT_DEMO_BASE_URL,
122+
help="Base URL of the published demo (default: the project's GitHub Pages site)")
54123
args = parser.parse_args()
55124

56125
copydeck_path = Path(args.copydeck)
57126
if not copydeck_path.exists():
58127
print(f"Error: {copydeck_path} not found", file=sys.stderr)
59128
sys.exit(1)
60129

61-
export(copydeck_path, Path(args.out))
130+
export(copydeck_path, Path(args.out), Path(args.demo), args.demo_base_url.rstrip("/"))
62131

63132

64133
if __name__ == "__main__":

0 commit comments

Comments
 (0)