import re import os from typing import List, Dict, Optional from src.core.models import Challenge class ChallengeLoader: def __init__(self, challenges_dir: str = "src/challenges/"): self.challenges_dir = challenges_dir def load_all(self) -> Dict[str, Challenge]: challenges = {} if not os.path.exists(self.challenges_dir): return challenges for filename in os.listdir(self.challenges_dir): if filename.endswith(".md"): path = os.path.join(self.challenges_dir, filename) challenge = self.parse_file(path) if challenge: key = filename.replace(".md", "") challenges[key] = challenge return challenges def parse_file(self, path: str) -> Optional[Challenge]: with open(path, "r", encoding="utf-8") as f: content = f.read() # Parse YAML frontmatter if present fm_data = {} if content.startswith("---"): parts = content.split("---", 2) if len(parts) >= 3: fm_raw = parts[1] content = parts[2] for line in fm_raw.splitlines(): if ":" in line: k, v = line.split(":", 1) fm_data[k.strip().lower()] = v.strip() name = fm_data.get("name") difficulty = fm_data.get("difficulty") language = fm_data.get("language") subject = fm_data.get("subject") or fm_data.get("topic") # Regex extraction fallback for markdown headers if not name: name_match = re.search(r"# (?:Challenge: )?(.*)", content) name = name_match.group(1).strip() if name_match else None if not difficulty: diff_match = re.search(r"\*\*Difficulty:\*\* (.*)", content) difficulty = diff_match.group(1).strip() if diff_match else "Medium" if not language: lang_match = re.search(r"\*\*Language:\*\* (.*)", content) language = lang_match.group(1).strip() if lang_match else "Python" if not subject: subj_match = re.search(r"\*\*(?:Subject|Topic):\*\* (.*)", content) subject = subj_match.group(1).strip() if subj_match else "General Concepts" desc_match = re.search(r"## Description\n(.*?)(?=\n##|$)", content, re.DOTALL) req_match = re.search(r"## Requirements\n(.*?)(?=\n##|$)", content, re.DOTALL) hint_match = re.search(r"## Hints\n(.*?)(?=\n##|$)", content, re.DOTALL) valid_match = re.search(r"## Validation\n-\s*\*\*Check:\*\* (.*)", content) exp_match = re.search(r"\*\*Expected Output:\*\* (.*)", content) if not name: return None description = desc_match.group(1).strip() if desc_match else "" requirements = ( [ r.strip("- ").strip() for r in req_match.group(1).strip().split("\n") if r.strip("- ") ] if req_match else [] ) hints = ( [ h.strip("- ").strip() for h in hint_match.group(1).strip().split("\n") if h.strip("- ") ] if hint_match else [] ) validation_check = valid_match.group(1).strip() if valid_match else "" expected_output = exp_match.group(1).strip() if exp_match else "" return Challenge( name=name, difficulty=difficulty, language=language, subject=subject, description=description, requirements=requirements, hints=hints, validation_check=validation_check, expected_output=expected_output, )