-
Notifications
You must be signed in to change notification settings - Fork 70
Expand file tree
/
Copy pathgenerate_readme.py
More file actions
executable file
·324 lines (253 loc) · 9.41 KB
/
Copy pathgenerate_readme.py
File metadata and controls
executable file
·324 lines (253 loc) · 9.41 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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
#!/usr/bin/env python3
"""
Generate README.md from template and JSON data.
This script reads projects.json and readme.tpl to create the comparison table.
"""
import json
def score_to_emoji(score):
"""
Map score strings to their visual representation with emojis.
Args:
score: Can be a string like "x", "wip-3", "8", or an integer
Returns:
String with appropriate emoji representation
Examples:
"x" → "❌"
"wip-3" → "🚧3️⃣"
"8" or 8 → "✅8️⃣"
"10" or 10 → "✅🔟"
"""
# Convert to string for consistent handling
score_str = str(score).strip().lower()
# Map for emoji numbers 0-10
emoji_numbers = {
"0": "0️⃣",
"1": "1️⃣",
"2": "2️⃣",
"3": "3️⃣",
"4": "4️⃣",
"5": "5️⃣",
"6": "6️⃣",
"7": "7️⃣",
"8": "8️⃣",
"9": "9️⃣",
"10": "🔟",
}
# Handle "x" - Cross
if score_str == "x":
return "❌"
# Handle "wip-N" - Work in progress with number
if score_str.startswith("wip-"):
number = score_str.split("-")[1]
emoji_num = emoji_numbers.get(number, number)
return f"🚧{emoji_num}"
# Handle plain numbers - Green tick with number
if score_str.isdigit():
emoji_num = emoji_numbers.get(score_str, score_str)
return f"✅{emoji_num}"
# Return as-is if no pattern matches
return score_str
def load_json(filepath="projects.json"):
"""Load project data from JSON file."""
with open(filepath, "r", encoding="utf-8") as f:
return json.load(f)
def generate_table_header(projects):
"""Generate the table header with project names."""
header = "| Feature "
separator = "| :------- "
for project in projects:
name = project["name"]
repo = project["repo"]
url = f"https://github.com/{repo}"
header += f"| [{name}]({url}) "
separator += "| " + "-" * (len(url) + len(name) + 4) + " "
header += "|\n"
separator += "|\n"
return header + separator
def generate_logo_row(projects):
"""Generate the logo row."""
row = "| Logo "
for project in projects:
logo_url = project["logo_url"]
logo_alt = project["logo_alt"]
cell = f'<img src="{logo_url}" style="width: 50px" alt="{logo_alt}"/>'
row += f"| {cell} "
row += "|\n"
return row
def generate_badge_row(
feature_name,
feature_link,
projects,
badge_template,
use_lowercase=False,
use_branch=False,
):
"""
Generic function to generate a row with badges.
Args:
feature_name: Name of the feature (e.g., "Github Stars")
feature_link: Link for the feature header (e.g., "features.md#github-stars")
projects: List of projects
badge_template: Format string for the badge URL. Use {repo} and {branch} as placeholders
use_lowercase: Whether to lowercase the repo name
use_branch: Whether to include branch in the badge URL
"""
row = f"| [{feature_name}]({feature_link}) "
for project in projects:
repo = project["repo"].lower() if use_lowercase else project["repo"]
if use_branch:
branch = project.get("branch", "master")
badge = badge_template.format(repo=repo, branch=branch)
else:
badge = badge_template.format(repo=repo)
row += f"| {badge} "
row += "|\n"
return row
def generate_license_row(projects):
"""Generate license row."""
row = "| [License](features.md#license) "
for project in projects:
if "license_custom" in project:
# Custom license display
custom = project["license_custom"]
badge = f""
else:
repo = project["repo"]
badge = f""
row += f"| {badge} "
row += "|\n"
return row
def generate_default_row(feature, projects):
"""
Generate a default row for features without a custom processor.
Uses score_to_emoji to convert values.
Also checks for feature_key + '_url' to create links.
"""
feature_name = feature["name"]
feature_link = feature.get("link")
feature_key = feature_name.lower().replace(" ", "_").replace("/", "_")
feature_url_key = feature_key + "_url"
# Build row header
if feature_link:
row = f"| [{feature_name}]({feature_link}) "
else:
row = f"| {feature_name} "
# Add cells for each project
for project in projects:
# Try to find the feature value in the project
value = project.get(feature_key, "❌")
# Convert value using score_to_emoji if it's a simple value
if isinstance(value, (str, int)):
cell = score_to_emoji(value)
else:
cell = value
# Check if there's a URL field for this feature (even for X/❌ to link to reason/issue)
if feature_url_key in project:
url = project[feature_url_key]
cell = f"[{cell}]({url})"
row += f"| {cell} "
row += "|\n"
return row
def generate_comparison_table(data):
"""Generate the complete comparison table dynamically based on features."""
projects = data["projects"]
features = data.get("features", [])
# Generate header
table = generate_table_header(projects)
# Loop over features and generate each row
for feature in features:
processor_name = feature.get("processor")
# Match processor name and call appropriate function
match processor_name:
case "generate_logo_row":
table += generate_logo_row(projects)
case "generate_badge_row":
# Read badge configuration from feature
feature_name = feature["name"]
feature_link = feature.get("link")
badge_template = feature.get("badge_template", "")
use_lowercase = feature.get("use_lowercase", False)
use_branch = feature.get("use_branch", False)
table += generate_badge_row(
feature_name,
feature_link,
projects,
badge_template,
use_lowercase=use_lowercase,
use_branch=use_branch,
)
case "generate_license_row":
table += generate_license_row(projects)
case _:
# Use default conversion for unknown or null processors
table += generate_default_row(feature, projects)
return table
def validate_projects_json(data):
"""Validate the projects.json structure."""
projects = data["projects"]
features = data.get("features", [])
# Collect all validation errors
errors = []
# Check for missing required fields
required_fields = {"name", "repo", "logo_url", "logo_alt"}
for project in projects:
missing_fields = required_fields - project.keys()
if missing_fields:
errors.append(
f"Project '{project.get('name', 'Unknown')}' is missing fields: {missing_fields}"
)
# Check for undocumented keys
# Build standard keys (fields that don't need to be in features)
standard_keys = {"name", "repo", "branch", "logo_url", "logo_alt", "license_custom"}
# Build feature keys from features array
feature_keys = set()
for feature in features:
feature_name = feature["name"].lower().replace(" ", "_").replace("/", "_")
feature_keys.add(feature_name)
# Also add _url variant
feature_keys.add(f"{feature_name}_url")
# Track which projects have which keys
all_keys = set()
project_key_map = {}
for project in projects:
project_name = project.get("name", "Unknown")
for key in project.keys():
all_keys.add(key)
if key not in project_key_map:
project_key_map[key] = []
project_key_map[key].append(project_name)
# Find unmapped keys
unmapped_keys = all_keys - standard_keys - feature_keys
if unmapped_keys:
errors.append(
f"Found {len(unmapped_keys)} project key(s) not mapped to any feature:"
)
for key in sorted(unmapped_keys):
projects_with_key = project_key_map[key]
errors.append(f" • '{key}' in: {', '.join(projects_with_key)}")
# If any errors were found, print them and raise exception
if errors:
print("projects.json validation FAILED:")
for error in errors:
print(error)
raise ValueError(f"projects.json validation failed with {len(errors)} error(s)")
def generate_readme(
template_file="readme.tpl", output_file="readme.md", json_file="projects.json"
):
"""Generate README.md from template and JSON data."""
# Load data
data = load_json(json_file)
# Read template
with open(template_file, "r", encoding="utf-8") as f:
template = f.read()
# Validate data
validate_projects_json(data)
# Generate table
table = generate_comparison_table(data)
# Replace placeholder in template
output = template.replace("{{COMPARISON_TABLE}}", table)
# Write output
with open(output_file, "w", encoding="utf-8") as f:
f.write(output)
if __name__ == "__main__":
generate_readme()