108 lines
3.7 KiB
Python
108 lines
3.7 KiB
Python
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,
|
|
)
|