Coverage for little_loops / issue_template.py: 14%
56 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"""Issue template assembly using per-type section definition files.
3Reads per-type template files (bug-sections.json, feat-sections.json,
4enh-sections.json) from templates/ and assembles structured markdown for
5issue files. Used by sync pull to produce v2.0-compliant issues.
6"""
8from __future__ import annotations
10import json
11from pathlib import Path
12from typing import Any
15def _default_templates_dir() -> Path:
16 """Return the bundled templates/ directory relative to this package."""
17 return Path(__file__).resolve().parent.parent.parent / "templates"
20def load_issue_sections(issue_type: str, templates_dir: Path | None = None) -> dict[str, Any]:
21 """Load per-type sections JSON from the given or default templates directory.
23 Args:
24 issue_type: Issue type prefix (BUG, FEAT, ENH).
25 templates_dir: Optional override path. Defaults to bundled templates/.
27 Returns:
28 Parsed JSON data as a dict.
30 Raises:
31 FileNotFoundError: If the per-type sections file does not exist.
32 """
33 base = templates_dir if templates_dir is not None else _default_templates_dir()
34 filename = f"{issue_type.lower()}-sections.json"
35 path = base / filename
36 with open(path, encoding="utf-8") as f:
37 return json.load(f)
40def assemble_issue_markdown(
41 sections_data: dict[str, Any],
42 issue_type: str,
43 variant: str,
44 issue_id: str,
45 title: str,
46 frontmatter: dict[str, Any],
47 content: dict[str, str] | None = None,
48 labels: list[str] | None = None,
49) -> str:
50 """Assemble structured markdown from template sections and content.
52 Args:
53 sections_data: Parsed per-type sections data.
54 issue_type: Issue type prefix (BUG, FEAT, ENH).
55 variant: Creation variant name (full, minimal, legacy).
56 issue_id: Issue identifier (e.g. "ENH-517").
57 title: Issue title text.
58 frontmatter: Dict of YAML frontmatter key-value pairs.
59 content: Optional mapping of section name to content string.
60 Sections not in this dict get their creation_template placeholder.
61 labels: Optional list of label strings for the Labels section.
63 Returns:
64 Complete markdown string with frontmatter, heading, and sections.
65 """
66 content = content or {}
67 variant_config = sections_data.get("creation_variants", {}).get(variant)
68 if variant_config is None:
69 raise ValueError(f"Unknown creation variant: {variant!r}")
71 common_sections = sections_data.get("common_sections", {})
72 type_sections = sections_data.get("type_sections", {})
73 exclude_deprecated = variant_config.get("exclude_deprecated", False)
74 include_common = variant_config.get("include_common", [])
75 include_type = variant_config.get("include_type_sections", False)
77 parts: list[str] = []
79 # YAML frontmatter
80 parts.append("---")
81 for key, value in frontmatter.items():
82 parts.append(f"{key}: {value}")
83 parts.append("---")
84 parts.append("")
86 # Title heading
87 parts.append(f"# {issue_id}: {title}")
88 parts.append("")
90 # Common sections from variant
91 for section_name in include_common:
92 section_def = common_sections.get(section_name)
93 if section_def is None:
94 continue
95 if exclude_deprecated and section_def.get("deprecated", False):
96 continue
97 _append_section(parts, section_name, section_def, content)
99 # Type-specific sections
100 if include_type and type_sections:
101 for section_name, section_def in type_sections.items():
102 if exclude_deprecated and section_def.get("deprecated", False):
103 continue
104 _append_section(parts, section_name, section_def, content)
106 # Ensure Labels section is present (even if not in variant's include_common)
107 if labels is not None and "Labels" not in include_common:
108 labels_str = ", ".join(f"`{lbl}`" for lbl in labels) if labels else ""
109 parts.append("## Labels")
110 parts.append("")
111 parts.append(labels_str)
112 parts.append("")
114 return "\n".join(parts)
117def _append_section(
118 parts: list[str],
119 section_name: str,
120 section_def: dict[str, Any],
121 content: dict[str, str],
122) -> None:
123 """Append a single section to the parts list."""
124 body = content.get(section_name, section_def.get("creation_template", ""))
125 parts.append(f"## {section_name}")
126 parts.append("")
127 if body:
128 parts.append(body)
129 parts.append("")