Skip to content

Commit 6b564bc

Browse files
authored
♻️ refactor: align lightweight issue views and command helpers (#36)
1 parent 628fe52 commit 6b564bc

7 files changed

Lines changed: 252 additions & 114 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@ gh-llm issue view 77924 --repo PaddlePaddle/Paddle --expand minimized,details
123123
gh-llm issue view 77924 --repo PaddlePaddle/Paddle --show meta,description
124124
```
125125

126+
When `--show` does not include `timeline` (for example `--show meta`, `--show summary`, or `--show actions`), both `pr view` and `issue view` stay on the lightweight metadata path and skip timeline bootstrap.
127+
126128
`--expand` values:
127129

128130
- PR: `resolved`, `minimized`, `details`, `all`

skills/github-conversation/SKILL.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ gh-llm issue view <issue> --repo <owner/repo>
9898
gh-llm issue timeline-expand <page> --issue <issue> --repo <owner/repo>
9999
```
100100

101+
For lightweight inspection, prefer non-timeline `--show` combinations such as `--show meta`, `--show summary`, or `--show actions`; `gh-llm` keeps those paths on metadata-only loading unless `timeline` is explicitly requested.
102+
101103
### Write simple updates
102104

103105
```bash

src/gh_llm/commands/issue.py

Lines changed: 49 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,15 @@
33
from dataclasses import dataclass
44
from typing import TYPE_CHECKING, Any
55

6-
from gh_llm.commands.options import raise_unknown_option_value, resolve_file_or_inline_text
6+
from gh_llm.commands.options import (
7+
add_body_input_arguments,
8+
maybe_resolve_subject,
9+
raise_unknown_option_value,
10+
resolve_file_or_inline_text,
11+
resolve_subject,
12+
)
713
from gh_llm.github_api import GitHubClient
8-
from gh_llm.pager import DEFAULT_PAGE_SIZE, TimelinePager
14+
from gh_llm.pager import DEFAULT_PAGE_SIZE, TimelinePager, build_context_from_meta
915
from gh_llm.render import (
1016
render_comment_node_detail,
1117
render_description,
@@ -86,12 +92,11 @@ def register_issue_parser(subparsers: Any) -> None:
8692

8793
comment_edit_parser = issue_subparsers.add_parser("comment-edit", help="edit one issue comment by node id")
8894
comment_edit_parser.add_argument("comment_id", help="comment id, e.g. IC_xxx")
89-
comment_edit_body_group = comment_edit_parser.add_mutually_exclusive_group(required=True)
90-
comment_edit_body_group.add_argument("--body", help="new comment body")
91-
comment_edit_body_group.add_argument(
92-
"-F",
93-
"--body-file",
94-
help="read new comment body from file (use `-` to read from standard input)",
95+
add_body_input_arguments(
96+
comment_edit_parser,
97+
required=True,
98+
body_help="new comment body",
99+
file_help="read new comment body from file (use `-` to read from standard input)",
95100
)
96101
comment_edit_parser.add_argument("--issue", help="Issue number/url")
97102
comment_edit_parser.add_argument("--repo", help="repository in OWNER/REPO format")
@@ -111,14 +116,20 @@ def cmd_issue_view(args: Any) -> int:
111116
client = GitHubClient()
112117
pager = TimelinePager(client)
113118

114-
meta = client.resolve_issue(selector=args.issue, repo=args.repo)
115-
context, first_page, last_page = pager.build_initial(
116-
meta,
117-
page_size=page_size,
118-
show_minimized_details=expand.minimized,
119-
show_details_blocks=expand.details,
120-
)
121-
shown_pages: set[int] = {1}
119+
meta = _resolve_issue_meta(client=client, args=args)
120+
context = build_context_from_meta(meta=meta, page_size=page_size)
121+
first_page: TimelinePage | None = None
122+
last_page: TimelinePage | None = None
123+
shown_pages: set[int] = set()
124+
125+
if show.timeline:
126+
context, first_page, last_page = pager.build_initial(
127+
meta,
128+
page_size=page_size,
129+
show_minimized_details=expand.minimized,
130+
show_details_blocks=expand.details,
131+
)
132+
shown_pages.add(1)
122133

123134
wrote_output = False
124135

@@ -137,6 +148,7 @@ def print_block(lines: list[str]) -> None:
137148
if show.description:
138149
print_block(render_description(context))
139150
if show.timeline:
151+
assert first_page is not None
140152
print_block(["## Timeline"])
141153
print_block(render_page(1, context, first_page))
142154

@@ -232,10 +244,7 @@ def cmd_issue_details_expand(args: Any) -> int:
232244

233245
def cmd_issue_comment_edit(args: Any) -> int:
234246
client = GitHubClient()
235-
if args.repo is not None and args.issue is None:
236-
raise RuntimeError("`--issue` is required when `--repo` is provided")
237-
if args.issue is not None:
238-
client.resolve_issue(selector=args.issue, repo=args.repo)
247+
_resolve_optional_issue(client=client, args=args)
239248
updated_comment_id = client.edit_comment(
240249
comment_id=str(args.comment_id),
241250
body=resolve_file_or_inline_text(args, text_attr="body", file_attr="body_file"),
@@ -247,26 +256,37 @@ def cmd_issue_comment_edit(args: Any) -> int:
247256

248257
def cmd_issue_comment_expand(args: Any) -> int:
249258
client = GitHubClient()
250-
if args.repo is not None and args.issue is None:
251-
raise RuntimeError("`--issue` is required when `--repo` is provided")
252-
if args.issue is not None:
253-
client.resolve_issue(selector=args.issue, repo=args.repo)
259+
_resolve_optional_issue(client=client, args=args)
254260
node = client.fetch_comment_node(str(args.comment_id))
255261
for line in render_comment_node_detail(str(args.comment_id), node):
256262
print(line)
257263
return 0
258264

259265

266+
def _resolve_issue_meta(*, client: GitHubClient, args: Any) -> PullRequestMeta:
267+
return resolve_subject(
268+
selector=getattr(args, "issue", None),
269+
repo=getattr(args, "repo", None),
270+
selector_flag="--issue",
271+
resolver=client.resolve_issue,
272+
)
273+
274+
275+
def _resolve_optional_issue(*, client: GitHubClient, args: Any) -> PullRequestMeta | None:
276+
return maybe_resolve_subject(
277+
selector=getattr(args, "issue", None),
278+
repo=getattr(args, "repo", None),
279+
selector_flag="--issue",
280+
resolver=client.resolve_issue,
281+
)
282+
283+
260284
def _resolve_context_and_meta(
261285
*, client: GitHubClient, pager: TimelinePager, args: Any
262286
) -> tuple[TimelineContext, PullRequestMeta]:
263-
selector = getattr(args, "issue", None)
264-
repo = getattr(args, "repo", None)
265-
if repo is not None and selector is None:
266-
raise RuntimeError("`--issue` is required when `--repo` is provided")
267287
page_size = getattr(args, "page_size", None)
268288
effective_page_size = DEFAULT_PAGE_SIZE if page_size is None else int(page_size)
269-
meta = client.resolve_issue(selector=selector, repo=repo)
289+
meta = _resolve_issue_meta(client=client, args=args)
270290
context, _, _ = pager.build_initial(meta=meta, page_size=effective_page_size)
271291
return context, meta
272292

src/gh_llm/commands/options.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,30 @@
33
import sys
44
from difflib import get_close_matches
55
from pathlib import Path
6-
from typing import Any, NoReturn
6+
from typing import TYPE_CHECKING, Any, NoReturn
7+
8+
if TYPE_CHECKING:
9+
from collections.abc import Callable
10+
11+
12+
def add_body_input_arguments(
13+
parser: Any,
14+
*,
15+
required: bool,
16+
body_help: str,
17+
file_help: str,
18+
default: str | None = None,
19+
) -> None:
20+
group = parser.add_mutually_exclusive_group(required=required)
21+
body_kwargs: dict[str, object] = {"help": body_help}
22+
if default is not None:
23+
body_kwargs["default"] = default
24+
group.add_argument("--body", **body_kwargs)
25+
group.add_argument(
26+
"-F",
27+
"--body-file",
28+
help=file_help,
29+
)
730

831

932
def read_text_from_path_or_stdin(path: str) -> str:
@@ -28,6 +51,35 @@ def resolve_file_or_inline_text(
2851
return str(text)
2952

3053

54+
def resolve_subject[T](
55+
*,
56+
selector: str | None,
57+
repo: str | None,
58+
selector_flag: str,
59+
resolver: Callable[[str | None, str | None], T],
60+
) -> T:
61+
_validate_selector_for_repo(selector=selector, repo=repo, selector_flag=selector_flag)
62+
return resolver(selector, repo)
63+
64+
65+
def maybe_resolve_subject[T](
66+
*,
67+
selector: str | None,
68+
repo: str | None,
69+
selector_flag: str,
70+
resolver: Callable[[str | None, str | None], T],
71+
) -> T | None:
72+
_validate_selector_for_repo(selector=selector, repo=repo, selector_flag=selector_flag)
73+
if selector is None:
74+
return None
75+
return resolver(selector, repo)
76+
77+
78+
def _validate_selector_for_repo(*, selector: str | None, repo: str | None, selector_flag: str) -> None:
79+
if repo is not None and selector is None:
80+
raise RuntimeError(f"`{selector_flag}` is required when `--repo` is provided")
81+
82+
3183
def raise_unknown_option_value(
3284
*,
3385
flag: str,

0 commit comments

Comments
 (0)