Version 1.0
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
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,
|
||||
)
|
||||
Reference in New Issue
Block a user