"""Shared OKF v0.1 parsing utilities (Python standard library only).""" from __future__ import annotations import re from pathlib import Path RESERVED = {"index.md", "log.md"} _LINK_RE = re.compile(r"\[[^\]]*\]\(([^)]+)\)") class FrontmatterError(ValueError): """Raised when a frontmatter block is present but cannot be parsed.""" def split_frontmatter(text): """Return (raw_frontmatter, body). raw is None if there is no leading '---' block.""" if not text.startswith("---"): return None, text lines = text.splitlines() if lines[0].strip() != "---": return None, text for i in range(1, len(lines)): if lines[i].strip() == "---": return "\n".join(lines[1:i]), "\n".join(lines[i + 1:]) raise FrontmatterError("opening '---' without a closing '---'") def _scalar(value): value = value.strip() if len(value) >= 2 and value[0] == value[-1] and value[0] in "\"'": return value[1:-1] return value def parse_yaml_subset(raw): """Parse the small YAML subset OKF uses: `key: value` and `key: [a, b]`.""" meta = {} for line in raw.splitlines(): stripped = line.strip() if not stripped or stripped.startswith("#"): continue if ":" not in line: raise FrontmatterError("unparseable frontmatter line: %r" % line) key, _, value = line.partition(":") key, value = key.strip(), value.strip() if value.startswith("[") and value.endswith("]"): inner = value[1:-1].strip() meta[key] = [s for s in (_scalar(x) for x in inner.split(",")) if s] if inner else [] else: meta[key] = _scalar(value) return meta def parse_frontmatter(text): """Return (meta, body). meta is None when no frontmatter block is present.""" raw, body = split_frontmatter(text) if raw is None: return None, text return parse_yaml_subset(raw), body def iter_concepts(bundle_dir): """Yield Path for every non-reserved .md file under bundle_dir, sorted.""" root = Path(bundle_dir) for path in sorted(root.rglob("*.md")): if path.name not in RESERVED: yield path def concept_id(bundle_dir, path): """Concept ID = bundle-relative path with the .md suffix removed.""" rel = Path(path).relative_to(Path(bundle_dir)).as_posix() return rel[:-3] if rel.endswith(".md") else rel def extract_links(body): """Return the list of link targets from markdown links in body.""" return _LINK_RE.findall(body)