-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathutils.py
More file actions
175 lines (133 loc) · 5.33 KB
/
Copy pathutils.py
File metadata and controls
175 lines (133 loc) · 5.33 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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
"""Shared utilities for build scripts.
This module provides common functions for loading spreadsheets,
validating data, and injecting content into HTML templates.
"""
from pathlib import Path
from typing import Any, Dict, List, Optional
import openpyxl
def load_spreadsheet(filepath: Path) -> List[Dict[str, Any]]:
"""Load Excel spreadsheet and return list of row dictionaries.
Args:
filepath: Path to the .xlsx file
Returns:
List of dictionaries, one per row, with column headers as keys.
Empty cells are converted to empty strings.
Raises:
FileNotFoundError: If the spreadsheet doesn't exist
openpyxl.utils.exceptions.InvalidFileException: If file is not valid xlsx
"""
wb = openpyxl.load_workbook(filepath, read_only=True, data_only=True)
sheet = wb.active
# Get headers from first row
headers = [cell.value for cell in sheet[1]]
# Validate headers - no None values allowed
if None in headers:
raise ValueError(f"Spreadsheet has empty header cells: {headers}")
rows = []
for row in sheet.iter_rows(min_row=2, values_only=True):
# Skip completely empty rows
if not any(cell is not None for cell in row):
continue
# Create dict, converting None to empty string for consistency
row_dict = {}
for header, value in zip(headers, row):
if value is None:
row_dict[header] = ''
else:
row_dict[header] = value
rows.append(row_dict)
wb.close()
return rows
def load_spreadsheet_all_sheets(filepath: Path) -> Dict[str, List[Dict[str, Any]]]:
"""Load Excel spreadsheet with all sheets.
Args:
filepath: Path to the .xlsx file
Returns:
Dictionary with sheet names as keys, each containing list of row dicts.
Empty cells are converted to empty strings.
Raises:
FileNotFoundError: If the spreadsheet doesn't exist
"""
wb = openpyxl.load_workbook(filepath, read_only=True, data_only=True)
data = {}
for sheet_name in wb.sheetnames:
sheet = wb[sheet_name]
# Get headers from first row
headers = [cell.value for cell in sheet[1]]
rows = []
for row in sheet.iter_rows(min_row=2, values_only=True):
# Skip completely empty rows
if not any(cell is not None for cell in row):
continue
row_dict = {}
for header, value in zip(headers, row):
if value is None:
row_dict[header] = ''
else:
row_dict[header] = value
rows.append(row_dict)
data[sheet_name] = rows
wb.close()
return data
def inject_content(template_path: Path, output_path: Path,
replacements: Dict[str, str]) -> None:
"""Inject generated content into template at marker locations.
Markers in the template should be HTML comments like: <!-- MARKER_NAME -->
Args:
template_path: Path to the template HTML file
output_path: Path where the generated HTML will be written
replacements: Dictionary mapping marker names to HTML content
Raises:
FileNotFoundError: If template doesn't exist
ValueError: If a marker is not found in the template
"""
content = template_path.read_text(encoding='utf-8')
for marker, html in replacements.items():
pattern = f'<!-- {marker} -->'
if pattern not in content:
raise ValueError(
f"Marker '{pattern}' not found in template {template_path}"
)
content = content.replace(pattern, html)
output_path.write_text(content, encoding='utf-8')
def validate_required_fields(row: Dict[str, Any], required: List[str],
row_num: int) -> List[str]:
"""Validate that required fields are present and non-empty.
Args:
row: Dictionary of field values from a spreadsheet row
required: List of required field names
row_num: Row number (for error messages), 1-indexed from data rows
Returns:
List of error messages (empty if all fields valid)
"""
errors = []
for field in required:
value = row.get(field)
if value is None or (isinstance(value, str) and value.strip() == ''):
errors.append(f"Row {row_num}: Missing required field '{field}'")
return errors
def validate_url_format(url: str) -> bool:
"""Check if a string looks like a valid URL.
Args:
url: String to validate
Returns:
True if URL starts with http:// or https://, False otherwise
"""
if not url or not isinstance(url, str):
return False
url = url.strip()
return url.startswith('http://') or url.startswith('https://')
def check_file_exists(filepath: Path, base_dir: Path) -> Optional[str]:
"""Check if a referenced file exists.
Args:
filepath: Filename (not full path) referenced in spreadsheet
base_dir: Directory where the file should exist
Returns:
Error message if file doesn't exist, None if it exists
"""
if not filepath or not str(filepath).strip():
return None # Empty is OK for optional fields
full_path = base_dir / str(filepath).strip()
if not full_path.exists():
return f"File not found: {full_path}"
return None