157 lines
4.6 KiB
Python
157 lines
4.6 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
gen_index.py - Generate index.typ from questions.json.
|
|
|
|
This script generates an index.typ file that includes all questions
|
|
and their answers in the specified format.
|
|
"""
|
|
|
|
import argparse
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
from rich.console import Console
|
|
|
|
from common import DATA_DIR, setup_logging
|
|
|
|
console = Console()
|
|
|
|
|
|
def load_questions_json() -> list[dict]:
|
|
"""Load questions from questions.json."""
|
|
questions_path = DATA_DIR / "questions.json"
|
|
if not questions_path.exists():
|
|
console.print(f"[red]Error: questions.json not found at {questions_path}[/red]")
|
|
sys.exit(1)
|
|
import json
|
|
|
|
with open(questions_path, "r", encoding="utf-8") as f:
|
|
return json.load(f)
|
|
|
|
|
|
def read_typ_content(target: str) -> str | None:
|
|
"""Read typ file content."""
|
|
typ_path = DATA_DIR / target
|
|
if not typ_path.exists():
|
|
console.print(f"[yellow]Warning: {target} not found[/yellow]")
|
|
return None
|
|
try:
|
|
return typ_path.read_text(encoding="utf-8")
|
|
except Exception as e:
|
|
console.print(f"[yellow]Warning: Failed to read {target}: {e}[/yellow]")
|
|
return None
|
|
|
|
|
|
def indent_text(text: str, indent: int = 2) -> str:
|
|
"""Indent text by specified spaces."""
|
|
lines = text.strip().split("\n")
|
|
spaces = " " * indent
|
|
return "\n".join(spaces + line if line.strip() else "" for line in lines)
|
|
|
|
|
|
def generate_index(questions: list[dict], dry_run: bool, logger) -> str:
|
|
"""Generate index.typ content."""
|
|
lines = [
|
|
'#import "@local/phomework:0.1.0": homework, question, answer, shadow',
|
|
"",
|
|
]
|
|
|
|
enable_shadow = (
|
|
"true"
|
|
if any(DATA_DIR / f"A_{q['question']}.md" for q in questions)
|
|
else "false"
|
|
)
|
|
|
|
lines.append(
|
|
f'#homework(title: "计算机网络第三次作业", secret: read(".secret"), enable_shadow: {enable_shadow})['
|
|
)
|
|
|
|
def sort_key(q: dict) -> tuple:
|
|
name = q["question"]
|
|
prefix = name[0]
|
|
try:
|
|
num = int(name[1:])
|
|
except ValueError:
|
|
num = float("inf")
|
|
return (0 if prefix == "R" else 1, prefix, num)
|
|
|
|
sorted_questions = sorted(questions, key=sort_key)
|
|
|
|
for q in sorted_questions:
|
|
question_name = q["question"]
|
|
typ_target = q["target"]
|
|
|
|
lines.append(f' #question(title: "{question_name}")[')
|
|
content = read_typ_content(typ_target)
|
|
if content:
|
|
lines.append(indent_text(content, 4))
|
|
else:
|
|
lines.append(" [题目内容加载失败]")
|
|
lines.append(" ]")
|
|
lines.append("")
|
|
lines.append(" #answer[")
|
|
|
|
answer_file = DATA_DIR / f"A_{question_name}.md"
|
|
if answer_file.exists():
|
|
lines.append(" 请填写答案。")
|
|
lines.append("")
|
|
lines.append(f' #shadow(read("./data/A_{question_name}.md"))')
|
|
else:
|
|
lines.append(" [答案文件不存在]")
|
|
|
|
lines.append(" ]")
|
|
|
|
lines.append("]")
|
|
return "\n".join(lines) + "\n"
|
|
|
|
|
|
def parse_args() -> argparse.Namespace:
|
|
"""Parse command line arguments."""
|
|
parser = argparse.ArgumentParser(
|
|
description="Generate index.typ from questions.json"
|
|
)
|
|
parser.add_argument("--dry-run", action="store_true", help="Do not write files")
|
|
parser.add_argument(
|
|
"--force", action="store_true", help="Force overwrite without warning"
|
|
)
|
|
parser.add_argument("--verbose", action="store_true", help="Enable debug logging")
|
|
return parser.parse_args()
|
|
|
|
|
|
def main() -> None:
|
|
"""Main entry point."""
|
|
args = parse_args()
|
|
logger = setup_logging("gen_index", args.verbose)
|
|
|
|
questions = load_questions_json()
|
|
logger.info(f"Loaded {len(questions)} questions")
|
|
|
|
if not questions:
|
|
console.print("[yellow]No questions found in questions.json[/yellow]")
|
|
sys.exit(1)
|
|
|
|
content = generate_index(questions, args.dry_run, logger)
|
|
|
|
output_path = Path("index.typ")
|
|
|
|
if output_path.exists() and not args.force and not args.dry_run:
|
|
console.print(
|
|
f"[yellow]Warning: {output_path} already exists and will be overwritten![/yellow]"
|
|
)
|
|
response = input("Continue? [y/N]: ")
|
|
if response.lower() != "y":
|
|
console.print("[yellow]Aborted.[/yellow]")
|
|
sys.exit(0)
|
|
|
|
if args.dry_run:
|
|
logger.info(f"[DRY-RUN] Would write {output_path}")
|
|
logger.debug(f"Content preview:\n{content[:500]}...")
|
|
else:
|
|
output_path.write_text(content, encoding="utf-8")
|
|
logger.info(f"Wrote {output_path}")
|
|
console.print(f"[green]Successfully wrote {output_path}[/green]")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|