author_glossary.py 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. from __future__ import annotations
  4. import json
  5. from dataclasses import dataclass
  6. from functools import lru_cache
  7. from pathlib import Path
  8. from typing import Any
  9. SCHEMA_VERSION = "webnovel-author-glossary/v1"
  10. @dataclass(frozen=True)
  11. class AuthorTerm:
  12. technical: str
  13. author: str
  14. explanation: str
  15. def to_dict(self) -> dict[str, str]:
  16. return {
  17. "technical": self.technical,
  18. "author": self.author,
  19. "explanation": self.explanation,
  20. }
  21. def default_glossary_path() -> Path:
  22. return Path(__file__).resolve().parents[2] / "references" / "author_glossary.json"
  23. def _load_payload(path: str | Path | None = None) -> dict[str, Any]:
  24. glossary_path = Path(path) if path else default_glossary_path()
  25. return json.loads(glossary_path.read_text(encoding="utf-8"))
  26. def load_terms(path: str | Path | None = None) -> dict[str, AuthorTerm]:
  27. payload = _load_payload(path)
  28. if payload.get("schema_version") != SCHEMA_VERSION:
  29. raise ValueError(f"unknown author glossary schema: {payload.get('schema_version')}")
  30. terms: dict[str, AuthorTerm] = {}
  31. for raw in payload.get("terms") or []:
  32. if not isinstance(raw, dict):
  33. continue
  34. technical = str(raw.get("technical") or "").strip()
  35. author = str(raw.get("author") or "").strip()
  36. explanation = str(raw.get("explanation") or "").strip()
  37. if not technical or not author or not explanation:
  38. continue
  39. terms[technical] = AuthorTerm(
  40. technical=technical,
  41. author=author,
  42. explanation=explanation,
  43. )
  44. return terms
  45. @lru_cache(maxsize=1)
  46. def _default_terms() -> dict[str, AuthorTerm]:
  47. return load_terms()
  48. def lookup(term: str, *, terms: dict[str, AuthorTerm] | None = None) -> AuthorTerm | None:
  49. term = str(term or "").strip()
  50. if not term:
  51. return None
  52. source = terms if terms is not None else _default_terms()
  53. if term in source:
  54. return source[term]
  55. lower_map = {key.lower(): value for key, value in source.items()}
  56. return lower_map.get(term.lower())
  57. def author_label(term: str, *, terms: dict[str, AuthorTerm] | None = None) -> str:
  58. found = lookup(term, terms=terms)
  59. return found.author if found else str(term)
  60. def explain(term: str, *, terms: dict[str, AuthorTerm] | None = None) -> str:
  61. found = lookup(term, terms=terms)
  62. if found:
  63. return found.explanation
  64. return f"{term}:系统暂未登记这个术语,先按原词显示。"