Files
skills/tracker/adapters.py

329 lines
12 KiB
Python

import json
from .models import normalize_resource # type: ignore[reportMissingImports]
JSON_FIELDS = "number,title,body,state,labels,comments,author,assignees,url,createdAt,updatedAt"
class Adapter:
provider = ""
executable = ""
def __init__(self, repo=None):
self.repo = repo
def _repo_args(self):
return ["--repo", self.repo] if self.repo else []
def command(self, operation, **kwargs):
method = getattr(self, f"command_{operation.replace('.', '_')}")
return method(**kwargs)
def normalize(self, value, kind):
return normalize_resource(value, kind=kind, provider=self.provider)
def json_value(self, stdout):
text = (stdout or "").strip()
if not text:
return {}
try:
return json.loads(text)
except (TypeError, ValueError):
return {"output": text}
class GitHubAdapter(Adapter):
provider = "github"
executable = "gh"
def command_issue_create(self, title, body, labels, assignees):
args = ["gh", "issue", "create", "--title", title, "--body", body]
for label in labels:
args += ["--label", label]
for user in assignees:
args += ["--assignee", user]
return args + self._repo_args()
def command_pr_create(self, title, body, head, base, labels, assignees):
args = ["gh", "pr", "create", "--title", title, "--body", body]
if head:
args += ["--head", head]
if base:
args += ["--base", base]
for label in labels:
args += ["--label", label]
for user in assignees:
args += ["--assignee", user]
return args + self._repo_args()
def _view(self, kind, number, comments=True):
command = "pr" if kind == "pr" else "issue"
args = ["gh", command, "view", str(number)]
if comments:
args.append("--comments")
return args + ["--json", JSON_FIELDS] + self._repo_args()
def command_issue_get(self, number, comments=True):
return self._view("issue", number, comments)
def command_pr_get(self, number, comments=True):
return self._view("pr", number, comments)
def command_issue_list(self, state, labels, limit):
args = ["gh", "issue", "list", "--state", state, "--limit", str(limit)]
for label in labels:
args += ["--label", label]
return args + ["--json", JSON_FIELDS] + self._repo_args()
def command_pr_list(self, state, limit):
return ["gh", "pr", "list", "--state", state, "--limit", str(limit), "--json", JSON_FIELDS] + self._repo_args()
def _edit(self, kind, number, title=None, body=None, add_label=None, remove_label=None, assignee=None, unassign=None):
command = "pr" if kind == "pr" else "issue"
args = ["gh", command, "edit", str(number)]
if title is not None:
args += ["--title", title]
if body is not None:
args += ["--body", body]
if add_label:
args += ["--add-label", add_label]
if remove_label:
args += ["--remove-label", remove_label]
if assignee:
args += ["--add-assignee", assignee]
if unassign:
args += ["--remove-assignee", unassign]
return args + self._repo_args()
def command_issue_edit(self, **kwargs):
return self._edit("issue", **kwargs)
def command_pr_edit(self, **kwargs):
return self._edit("pr", **kwargs)
def command_issue_comment(self, number, body):
return ["gh", "issue", "comment", str(number), "--body", body] + self._repo_args()
def command_pr_comment(self, number, body):
return ["gh", "pr", "comment", str(number), "--body", body] + self._repo_args()
def command_issue_close(self, number):
return ["gh", "issue", "close", str(number)] + self._repo_args()
def command_pr_close(self, number):
return ["gh", "pr", "close", str(number)] + self._repo_args()
def command_pr_diff(self, number):
return ["gh", "pr", "diff", str(number)] + self._repo_args()
def command_label_list(self):
return ["gh", "label", "list", "--limit", "1000", "--json", "name,color,description"] + self._repo_args()
def command_label_create(self, name, color, description):
args = ["gh", "label", "create", name, "--color", color]
if description:
args += ["--description", description]
return args + self._repo_args()
def command_issue_resolve(self, number):
return self.command_issue_get(number)
class GitLabAdapter(Adapter):
provider = "gitlab"
executable = "glab"
def _format(self):
return ["-F", "json"]
def _surface(self, kind):
return "mr" if kind == "pr" else "issue"
def command_issue_create(self, title, body, labels, assignees):
args = ["glab", "issue", "create", "--title", title, "--description", body]
if labels:
args += ["--label", ",".join(labels)]
if assignees:
args += ["--assignee", ",".join(assignees)]
return args + self._repo_args()
def command_pr_create(self, title, body, head, base, labels, assignees):
args = ["glab", "mr", "create", "--title", title, "--description", body]
if head:
args += ["--source-branch", head]
if base:
args += ["--target-branch", base]
if labels:
args += ["--label", ",".join(labels)]
if assignees:
args += ["--assignee", ",".join(assignees)]
return args + self._repo_args()
def command_issue_get(self, number, comments=True):
args = ["glab", "issue", "view", str(number)]
if comments:
args.append("--comments")
return args + self._format() + self._repo_args()
def command_pr_get(self, number, comments=True):
args = ["glab", "mr", "view", str(number)]
if comments:
args.append("--comments")
return args + self._format() + self._repo_args()
def command_issue_list(self, state, labels, limit):
args = ["glab", "issue", "list", "--state", state, "--per-page", str(limit)]
if labels:
args += ["--label", ",".join(labels)]
return args + self._format() + self._repo_args()
def command_pr_list(self, state, limit):
return ["glab", "mr", "list", "--state", state, "--per-page", str(limit)] + self._format() + self._repo_args()
def _edit(self, kind, number, title=None, body=None, add_label=None, remove_label=None, assignee=None, unassign=None):
args = ["glab", self._surface(kind), "update", str(number)]
if title is not None:
args += ["--title", title]
if body is not None:
args += ["--description", body]
if add_label:
args += ["--label", add_label]
if remove_label:
args += ["--unlabel", remove_label]
if assignee:
args += ["--assignee", assignee]
if unassign:
args += ["--unassign", unassign]
return args + self._repo_args()
def command_issue_edit(self, **kwargs):
return self._edit("issue", **kwargs)
def command_pr_edit(self, **kwargs):
return self._edit("pr", **kwargs)
def command_issue_comment(self, number, body):
return ["glab", "issue", "note", str(number), "--message", body] + self._repo_args()
def command_pr_comment(self, number, body):
return ["glab", "mr", "note", str(number), "--message", body] + self._repo_args()
def command_issue_close(self, number):
return ["glab", "issue", "close", str(number)] + self._repo_args()
def command_pr_close(self, number):
return ["glab", "mr", "close", str(number)] + self._repo_args()
def command_pr_diff(self, number):
return ["glab", "mr", "diff", str(number)] + self._repo_args()
def command_label_list(self):
return ["glab", "label", "list"] + self._format() + self._repo_args()
def command_label_create(self, name, color, description):
args = ["glab", "label", "create", name, "--color", color]
if description:
args += ["--description", description]
return args + self._repo_args()
class GiteaAdapter(Adapter):
provider = "gitea"
executable = "tea"
def _format(self):
return ["-o", "json"]
def _surface(self, kind):
return "pr" if kind == "pr" else "issue"
def command_issue_create(self, title, body, labels, assignees):
args = ["tea", "issue", "create", "--title", title, "--description", body]
if labels:
args += ["--labels", ",".join(labels)]
if assignees:
args += ["--assignees", ",".join(assignees)]
return args + self._repo_args()
def command_pr_create(self, title, body, head, base, labels, assignees):
args = ["tea", "pr", "create", "--title", title, "--description", body]
if head:
args += ["--head", head]
if base:
args += ["--base", base]
if labels:
args += ["--labels", ",".join(labels)]
if assignees:
args += ["--assignees", ",".join(assignees)]
return args + self._repo_args()
def _view(self, kind, number, comments=True):
args = ["tea", self._surface(kind), str(number)]
if comments:
args.append("--comments")
return args + self._format() + self._repo_args()
def command_issue_get(self, number, comments=True):
return self._view("issue", number, comments)
def command_pr_get(self, number, comments=True):
return self._view("pr", number, comments)
def command_issue_list(self, state, labels, limit):
args = ["tea", "issue", "list", "--state", state, "--limit", str(limit)]
if labels:
args += ["--labels", ",".join(labels)]
return args + self._format() + self._repo_args()
def command_pr_list(self, state, limit):
return ["tea", "pr", "list", "--state", state, "--limit", str(limit)] + self._format() + self._repo_args()
def _edit(self, kind, number, title=None, body=None, add_label=None, remove_label=None, assignee=None, unassign=None):
args = ["tea", self._surface(kind), "edit", str(number)]
if title is not None:
args += ["--title", title]
if body is not None:
args += ["--description", body]
if add_label:
args += ["--add-label", add_label]
if remove_label:
args += ["--remove-label", remove_label]
if assignee:
args += ["--add-assignee", assignee]
if unassign:
args += ["--remove-assignee", unassign]
return args + self._repo_args()
def command_issue_edit(self, **kwargs):
return self._edit("issue", **kwargs)
def command_pr_edit(self, **kwargs):
return self._edit("pr", **kwargs)
def command_issue_comment(self, number, body):
return ["tea", "comment", str(number), body] + self._repo_args()
def command_pr_comment(self, number, body):
return ["tea", "comment", str(number), body] + self._repo_args()
def command_issue_close(self, number):
return ["tea", "issue", "close", str(number)] + self._repo_args()
def command_pr_close(self, number):
return ["tea", "pr", "close", str(number)] + self._repo_args()
def command_pr_diff(self, number):
return ["tea", "api", f"/repos/{{owner}}/{{repo}}/pulls/{number}.diff"] + self._repo_args()
def command_label_list(self):
return ["tea", "label", "list"] + self._format() + self._repo_args()
def command_label_create(self, name, color, description):
args = ["tea", "label", "create", "--name", name, "--color", color]
if description:
args += ["--description", description]
return args + self._repo_args()
ADAPTERS = {"github": GitHubAdapter, "gitlab": GitLabAdapter, "gitea": GiteaAdapter}