Spaces:
Sleeping
Sleeping
Download scripts/lib/syntopicon_reference_parser.py from defenestrate/asa: direct link, hf CLI and curl.
- Browser
- Download file 5.36 kB
-
https://huggingface.co/spaces/defenestrate/asa/resolve/main/scripts/lib/syntopicon_reference_parser.py
- Command line
-
hf download hf://spaces/defenestrate/asa/scripts/lib/syntopicon_reference_parser.py
-
curl -L -o syntopicon_reference_parser.py https://huggingface.co/spaces/defenestrate/asa/resolve/main/scripts/lib/syntopicon_reference_parser.py
5.36 kB
| """Parse `## References` markdown tables on syntopicon entry files into | |
| structured reference dicts, resolved against a (session_date, row_index) | |
| lookup map produced by the lecture chunker. | |
| """ | |
| from __future__ import annotations | |
| import logging | |
| import re | |
| from pathlib import Path | |
| logger = logging.getLogger(__name__) | |
| _DATE_RE = re.compile(r"^\d{4}-\d{2}-\d{2}$") | |
| _PROBLEM_ID_RE = re.compile(r"^P\d+$") | |
| def parse_row_spec(rows_raw: str) -> list[int]: | |
| """Parse a Row(s) cell. Supports '159', '159, 232', '159-162'. | |
| Returns sorted unique list. Whitespace tolerated. Returns empty list on | |
| unparseable input. | |
| """ | |
| s = rows_raw.strip() | |
| if not s: | |
| return [] | |
| if "-" in s and "," not in s: | |
| # Range form | |
| try: | |
| lo, hi = s.split("-", 1) | |
| return list(range(int(lo.strip()), int(hi.strip()) + 1)) | |
| except ValueError: | |
| return [] | |
| if "," in s: | |
| try: | |
| return sorted({int(p.strip()) for p in s.split(",") if p.strip()}) | |
| except ValueError: | |
| return [] | |
| try: | |
| return [int(s)] | |
| except ValueError: | |
| return [] | |
| def parse_references( | |
| markdown_path: Path, | |
| row_lookup: dict[tuple[str, int], tuple[str, str]], | |
| ) -> list[dict]: | |
| """Parse the ## References table in a syntopicon markdown file. | |
| Returns a list of reference dicts. Each dict has session_date, speaker, | |
| row_index, how_used, notes, problem_id (if notes match /^P\\d+$/), | |
| lecture_turn_doc_id, and lecture_problem_doc_id. | |
| Unresolvable rows are logged at WARNING and skipped. Malformed rows and | |
| bad dates are also skipped, with logging. Files without ## References | |
| return [] silently. | |
| """ | |
| text = markdown_path.read_text(encoding="utf-8") | |
| lines = text.splitlines() | |
| # Find ## References heading | |
| in_section = False | |
| in_table = False | |
| skipped_separator = False | |
| refs: list[dict] = [] | |
| for line in lines: | |
| stripped = line.strip() | |
| if stripped.lower().startswith("## references"): | |
| in_section = True | |
| continue | |
| if not in_section: | |
| continue | |
| if stripped.startswith("## "): | |
| # Different section; stop | |
| break | |
| if not stripped: | |
| if in_table: | |
| # Blank line ends table | |
| break | |
| continue | |
| if not in_table: | |
| # Skip until table header | |
| if stripped.startswith("|"): | |
| in_table = True | |
| continue | |
| if not skipped_separator: | |
| # The line right after the header is the separator |---|---| | |
| if "---" in stripped: | |
| skipped_separator = True | |
| continue | |
| # No separator? still skip if it looks like one; otherwise treat as data | |
| if stripped.startswith("|"): | |
| skipped_separator = True | |
| # fall through to parse this row as data? No — skip header again. | |
| continue | |
| # Parse a data row | |
| cells = [c.strip() for c in stripped.strip("|").split("|")] | |
| if len(cells) != 5: | |
| logger.warning( | |
| "syntopicon_reference_parser: malformed row in %s (got %d cells, want 5): %r", | |
| markdown_path, len(cells), stripped, | |
| ) | |
| continue | |
| session_date, speaker, rows_raw, how_used, notes = cells | |
| if not _DATE_RE.match(session_date): | |
| logger.warning( | |
| "syntopicon_reference_parser: bad date format %r in %s", | |
| session_date, markdown_path, | |
| ) | |
| continue | |
| row_indices = parse_row_spec(rows_raw) | |
| problem_id = notes if _PROBLEM_ID_RE.match(notes.strip()) else None | |
| for row_index in row_indices: | |
| key = (session_date, row_index) | |
| if key not in row_lookup: | |
| logger.warning( | |
| "syntopicon_reference_parser: unresolvable row %s in %s (row=%d)", | |
| session_date, markdown_path, row_index, | |
| ) | |
| continue | |
| turn_id, problem_did = row_lookup[key] | |
| refs.append({ | |
| "session_date": session_date, | |
| "speaker": speaker, | |
| "row_index": row_index, | |
| "how_used": how_used, | |
| "notes": notes, | |
| "problem_id": problem_id, | |
| "lecture_turn_doc_id": turn_id, | |
| "lecture_problem_doc_id": problem_did, | |
| }) | |
| if in_section and not refs: | |
| logger.warning( | |
| "syntopicon_reference_parser: %s has '## References' heading but no resolvable rows", | |
| markdown_path, | |
| ) | |
| return refs | |
| def parse_all_references( | |
| syntopicon_dir: Path, | |
| row_lookup: dict[tuple[str, int], tuple[str, str]], | |
| ) -> dict[str, list[dict]]: | |
| """Walk the syntopicon directory; for each *.md file (excluding _registry.json | |
| and other non-term files), parse references. Returns {term_slug: refs}. | |
| The term_slug is the markdown filename stem (e.g., 'demand' for 'demand.md'). | |
| """ | |
| out: dict[str, list[dict]] = {} | |
| for md_path in sorted(syntopicon_dir.glob("*.md")): | |
| if md_path.stem.startswith("_"): | |
| continue | |
| out[md_path.stem] = parse_references(md_path, row_lookup) | |
| return out | |