Coverage for little_loops / frontmatter.py: 17%
41 statements
« prev ^ index » next coverage.py v7.12.0, created at 2026-03-18 16:18 -0500
« prev ^ index » next coverage.py v7.12.0, created at 2026-03-18 16:18 -0500
1"""Frontmatter parsing utilities for little-loops.
3Provides shared YAML-subset frontmatter parsing and stripping used by
4issue_parser, sync, and issue_history modules.
5"""
7from __future__ import annotations
9import logging
10import re
11from typing import Any
13logger = logging.getLogger(__name__)
16def parse_frontmatter(content: str, *, coerce_types: bool = False) -> dict[str, Any]:
17 """Extract YAML frontmatter from content.
19 Looks for content between opening and closing '---' markers.
20 Parses a subset of YAML: simple ``key: value`` pairs only. Lists,
21 block scalars, and nested structures are not supported and will emit
22 a ``logging.WARNING``. Returns empty dict if no frontmatter found.
24 Args:
25 content: File content to parse
26 coerce_types: If True, coerce digit strings to int
28 Returns:
29 Dictionary of frontmatter fields, or empty dict
30 """
31 if not content or not content.startswith("---"):
32 return {}
34 end_match = re.search(r"\n---\s*\n", content[3:])
35 if not end_match:
36 return {}
38 frontmatter_text = content[4 : 3 + end_match.start()]
40 result: dict[str, Any] = {}
41 for line in frontmatter_text.split("\n"):
42 line = line.strip()
43 if not line or line.startswith("#"):
44 continue
45 if line.startswith("- "):
46 logger.warning("Unsupported YAML list syntax in frontmatter: %r", line)
47 continue
48 if ":" in line:
49 key, value = line.split(":", 1)
50 key = key.strip()
51 value = value.strip()
52 if value.startswith("|") or value.startswith(">"):
53 logger.warning("Unsupported YAML block scalar in frontmatter: %r", line)
54 result[key] = None
55 continue
56 if value.lower() in ("null", "~", ""):
57 result[key] = None
58 elif coerce_types and value.isdigit():
59 result[key] = int(value)
60 else:
61 result[key] = value
62 return result
65def strip_frontmatter(content: str) -> str:
66 """Remove YAML frontmatter from content, returning the body.
68 Strips the ``---`` delimited frontmatter block (if present) and
69 returns everything after the closing delimiter.
71 Args:
72 content: File content possibly starting with frontmatter
74 Returns:
75 Content with frontmatter removed. Returns original content
76 unchanged if no valid frontmatter block is found.
77 """
78 if not content or not content.startswith("---"):
79 return content
81 end_match = re.search(r"\n---\s*\n", content[3:])
82 if not end_match:
83 return content
85 return content[3 + end_match.end() :]