-
-
Notifications
You must be signed in to change notification settings - Fork 4
✨ feat: add PR body scaffold command #33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
9c890d4
657123c
076c1b5
bc65bd7
e949648
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| from __future__ import annotations | ||
|
|
||
| import re | ||
| import unicodedata | ||
| from dataclasses import dataclass | ||
|
|
||
| DEFAULT_BODY_SCAFFOLD_SECTIONS = ( | ||
| "Motivation", | ||
| "Changes", | ||
| "Validation", | ||
| "Related Issues", | ||
| ) | ||
|
|
||
| _HEADING_RE = re.compile(r"(?m)^\s{0,3}#{1,6}\s+(.*?)\s*$") | ||
| _MARKDOWN_LINK_RE = re.compile(r"\[(?P<label>[^\]]+)\]\([^)]*\)") | ||
|
|
||
|
|
||
| @dataclass(frozen=True) | ||
| class PullRequestBodyScaffold: | ||
| body: str | ||
| added_sections: tuple[str, ...] | ||
|
|
||
|
|
||
| def parse_required_sections(raw_values: list[str]) -> list[str]: | ||
| values: list[str] = [] | ||
| seen: set[str] = set() | ||
|
|
||
| for raw in raw_values: | ||
| for part in raw.split(","): | ||
| section = part.strip() | ||
| if not section: | ||
| continue | ||
| normalized = normalize_section_title(section) | ||
| if normalized in seen: | ||
| continue | ||
| seen.add(normalized) | ||
| values.append(section) | ||
|
|
||
| return values | ||
|
|
||
|
|
||
| def normalize_section_title(value: str) -> str: | ||
| text = _MARKDOWN_LINK_RE.sub(lambda match: match.group("label"), value.strip()) | ||
| text = text.strip("# ") | ||
| text = text.removesuffix(":").removesuffix(":") | ||
|
|
||
| chars: list[str] = [] | ||
| for char in text.casefold(): | ||
| if char.isspace(): | ||
| continue | ||
| category = unicodedata.category(char) | ||
| if category.startswith(("L", "N")): | ||
| chars.append(char) | ||
| return "".join(chars) | ||
|
|
||
|
|
||
| def extract_markdown_section_titles(text: str) -> list[str]: | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 这里的 section 提取只识别 我在当前 head 上实测: uv run gh-llm pr body-template --repo DocRaptor/docraptor-ruby --requirements 'Why is this change needed?,Any screenshots?'输出里会再次追加
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 这个我觉得还好…… |
||
| titles: list[str] = [] | ||
| for match in _HEADING_RE.finditer(text): | ||
| title = match.group(1).strip() | ||
| title = re.sub(r"\s+#+\s*$", "", title).strip() | ||
| if title: | ||
| titles.append(title) | ||
| return titles | ||
|
|
||
|
|
||
| def build_pull_request_body_scaffold( | ||
| template_text: str | None, | ||
| *, | ||
| required_sections: list[str], | ||
| ) -> PullRequestBodyScaffold: | ||
| cleaned_template = (template_text or "").strip() | ||
| if not cleaned_template: | ||
| scaffold_sections = required_sections or list(DEFAULT_BODY_SCAFFOLD_SECTIONS) | ||
| return PullRequestBodyScaffold( | ||
| body=_render_section_scaffold(scaffold_sections), | ||
| added_sections=tuple(scaffold_sections), | ||
| ) | ||
|
|
||
| existing_titles = {normalize_section_title(title) for title in extract_markdown_section_titles(cleaned_template)} | ||
| added_sections: list[str] = [] | ||
| blocks = [cleaned_template] | ||
|
|
||
| for section in required_sections: | ||
| normalized = normalize_section_title(section) | ||
| if normalized in existing_titles: | ||
| continue | ||
| existing_titles.add(normalized) | ||
| added_sections.append(section) | ||
| blocks.append(_render_one_section(section)) | ||
|
|
||
| return PullRequestBodyScaffold( | ||
| body="\n\n".join(block.rstrip() for block in blocks if block.strip()).rstrip() + "\n", | ||
| added_sections=tuple(added_sections), | ||
| ) | ||
|
|
||
|
|
||
| def _render_section_scaffold(sections: list[str]) -> str: | ||
| blocks = [_render_one_section(section) for section in sections] | ||
| return "\n\n".join(blocks).rstrip() + "\n" | ||
|
|
||
|
|
||
| def _render_one_section(section: str) -> str: | ||
| return f"## {section}\n\n<!-- TODO: fill {section} -->" | ||
Uh oh!
There was an error while loading. Please reload this page.