#!/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()