Files
TactiTerm/src/core/loader.py
T
2026-07-21 22:24:25 +00:00

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,
)