|
12 | 12 | from typing import Dict, List, Any, Optional |
13 | 13 |
|
14 | 14 | # Add scripts directory to path to import shared module |
15 | | -SCRIPTS_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), 'scripts') |
| 15 | +SCRIPTS_DIR = Path(__file__).resolve().parents[3] / "scripts" |
16 | 16 | if os.path.exists(SCRIPTS_DIR): |
17 | 17 | import sys |
18 | | - sys.path.insert(0, SCRIPTS_DIR) |
| 18 | + sys.path.insert(0, str(SCRIPTS_DIR)) |
19 | 19 | try: |
20 | 20 | from chess_metadata import get_metadata |
21 | 21 | except ImportError: |
@@ -88,6 +88,18 @@ def get_metadata(impl_dir): return {} |
88 | 88 | 'test/', |
89 | 89 | ) |
90 | 90 |
|
| 91 | +FEATURE_CATALOG: List[str] = [ |
| 92 | + "perft", |
| 93 | + "fen", |
| 94 | + "ai", |
| 95 | + "castling", |
| 96 | + "en_passant", |
| 97 | + "promotion", |
| 98 | + "pgn", |
| 99 | + "uci", |
| 100 | + "chess960", |
| 101 | +] |
| 102 | + |
91 | 103 | def find_project_root(): |
92 | 104 | """Find the project root directory.""" |
93 | 105 | current_dir = os.getcwd() |
@@ -237,6 +249,47 @@ def resolve_entrypoint_file(impl_path: str, language: str, meta: Dict[str, Any]) |
237 | 249 | # Pick highest score; for ties prefer shortest path. |
238 | 250 | return max(candidates, key=lambda rel: (_entrypoint_score(rel), -len(rel))) |
239 | 251 |
|
| 252 | +def _normalize_feature_name(feature: str) -> str: |
| 253 | + """Normalize feature names for matching and deduplication.""" |
| 254 | + return feature.strip().lower().replace('-', '_').replace(' ', '_') |
| 255 | + |
| 256 | +def format_feature_summary(meta: Dict[str, Any]) -> str: |
| 257 | + """Render features as labels with completion ratio against the catalog.""" |
| 258 | + raw_features = meta.get('features', []) |
| 259 | + if not isinstance(raw_features, list): |
| 260 | + raw_features = [] |
| 261 | + |
| 262 | + normalized_catalog = [_normalize_feature_name(item) for item in FEATURE_CATALOG] |
| 263 | + catalog_set = set(normalized_catalog) |
| 264 | + |
| 265 | + # Deduplicate while preserving original order from metadata. |
| 266 | + normalized_seen = set() |
| 267 | + normalized_features: List[str] = [] |
| 268 | + for item in raw_features: |
| 269 | + feature = _normalize_feature_name(str(item)) |
| 270 | + if feature and feature not in normalized_seen: |
| 271 | + normalized_seen.add(feature) |
| 272 | + normalized_features.append(feature) |
| 273 | + |
| 274 | + matched_count = sum(1 for feature in normalized_features if feature in catalog_set) |
| 275 | + total_count = len(normalized_catalog) |
| 276 | + completion_pct = int(round((matched_count / total_count) * 100)) if total_count > 0 else 0 |
| 277 | + |
| 278 | + # Show catalog features in canonical order first, then unknown extras. |
| 279 | + feature_labels: List[str] = [] |
| 280 | + for catalog_feature in normalized_catalog: |
| 281 | + if catalog_feature in normalized_seen: |
| 282 | + feature_labels.append(catalog_feature) |
| 283 | + for feature in normalized_features: |
| 284 | + if feature not in catalog_set: |
| 285 | + feature_labels.append(feature) |
| 286 | + |
| 287 | + if feature_labels: |
| 288 | + labels = " ".join(f"`{feature}`" for feature in feature_labels) |
| 289 | + return f"{matched_count}/{total_count} ({completion_pct}%) {labels}" |
| 290 | + |
| 291 | + return f"0/{total_count} (0%) -" |
| 292 | + |
240 | 293 | def load_performance_data(): |
241 | 294 | """Load performance benchmark data from individual files""" |
242 | 295 | project_root = find_project_root() |
@@ -442,8 +495,7 @@ def update_readme() -> bool: |
442 | 495 | mem_disp = f"{int(round(peak_memory))}" if peak_memory > 0 else "-" |
443 | 496 |
|
444 | 497 | # Features |
445 | | - features = meta.get('features', []) if isinstance(meta.get('features'), list) else [] |
446 | | - feature_summary = ', '.join(features) if features else '-' |
| 498 | + feature_summary = format_feature_summary(meta) |
447 | 499 |
|
448 | 500 | lang_name = f"{lang_emoji} {language.title()}" |
449 | 501 | loc_display = str(loc_data['loc']) |
|
0 commit comments