internal/workflow + cli:gitea.py 工作流命令 Go 化(#6)
- internal/workflow:Client 介面+判定語義(最後留言、作者/審核者跟進、 assignee 交集、最久未更新優先),含假 client 單元測試 - internal/workflow/giteaapi.go:以 internal/gitea(#5)實作 Client - internal/cli:註冊 whoami/orgs/members/mine/pulls/next;全域選項允許 出現在命令之後;結束碼 0/2/3 - 與 gitea.py 交叉驗證:next/mine/pulls/members(--has-work) 拾取一致 - 依 #5 分支 feature/issue5-gitea-client 為基底
This commit is contained in:
@@ -0,0 +1,242 @@
|
||||
// workflow_commands.go — gitea.py 工作流命令的 CLI 接線(#6):
|
||||
// whoami、orgs、members [--has-work]、mine、pulls [--mine|--reviewer] [--repo]、next。
|
||||
//
|
||||
// 輸出走 internal/gitea 的 Printer(--output json|table);錯誤經 cli.exitCodeFor
|
||||
// 映射結束碼(用法 2/API 3)。token 解析選序見 internal/gitea/auth.go。
|
||||
package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"gitea.alterminal.com/alterminal/teai/internal/gitea"
|
||||
"gitea.alterminal.com/alterminal/teai/internal/workflow"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register := func(c *command) {
|
||||
commands[c.name] = c
|
||||
}
|
||||
register(&command{name: "whoami", usage: "目前帳號", run: runWhoami})
|
||||
register(&command{name: "orgs", usage: "我所屬的組織", run: runOrgs})
|
||||
register(&command{name: "members", usage: "Agents 團隊成員;--has-work 只列有未完成工作者", run: runMembers})
|
||||
register(&command{name: "mine", usage: "分派給我、待跟進的 issues", run: runMine})
|
||||
register(&command{name: "pulls", usage: "跨倉庫 PR 跟進清單(--mine/--reviewer/--repo)", run: runPulls})
|
||||
register(&command{name: "next", usage: "下一件該做的事", run: runNext})
|
||||
}
|
||||
|
||||
// errNoRepoFlag 是 --repo 值格式錯誤。
|
||||
var errNoRepoFlag = errors.New("--repo 需為 owner/repo 形式")
|
||||
|
||||
// newWorkflowClient 依全域選項建立 workflow 所需的 API client。
|
||||
func newWorkflowClient(env *Env) (*workflow.Scanner, error) {
|
||||
token, _, err := gitea.ResolveToken(gitea.AuthOptions{
|
||||
FlagToken: env.Globals.Token,
|
||||
SiteURL: env.Globals.URL,
|
||||
ConfigPath: env.Globals.ConfigPath,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client, err := gitea.New(env.Globals.URL, token, env.Globals.Timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &workflow.Scanner{Client: &workflow.APIClient{Client: client}}, nil
|
||||
}
|
||||
|
||||
// printer 依全域選項建立輸出器。
|
||||
func printer(env *Env) gitea.Printer {
|
||||
return gitea.Printer{Format: env.Globals.Output, Out: env.Out}
|
||||
}
|
||||
|
||||
// runWhoami:teai whoami。
|
||||
func runWhoami(env *Env, args []string) error {
|
||||
if len(args) != 0 {
|
||||
return &ErrUsage{Msg: "whoami 不接受參數"}
|
||||
}
|
||||
s, err := newWorkflowClient(env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
name, err := s.Whoami()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return printer(env).PrintOne(
|
||||
[]gitea.Field{{Name: "username"}},
|
||||
gitea.Values{name},
|
||||
)
|
||||
}
|
||||
|
||||
// runOrgs:teai orgs。
|
||||
func runOrgs(env *Env, args []string) error {
|
||||
if len(args) != 0 {
|
||||
return &ErrUsage{Msg: "orgs 不接受參數"}
|
||||
}
|
||||
s, err := newWorkflowClient(env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
orgs, err := s.Orgs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rows := make([]gitea.Values, 0, len(orgs))
|
||||
for _, o := range orgs {
|
||||
rows = append(rows, gitea.Values{o})
|
||||
}
|
||||
return printer(env).PrintList(
|
||||
[]gitea.Field{{Name: "username"}},
|
||||
rows,
|
||||
)
|
||||
}
|
||||
|
||||
// runMembers:teai members [--has-work]。輸出每行一個帳號(同 gitea.py)。
|
||||
func runMembers(env *Env, args []string) error {
|
||||
fs := flag.NewFlagSet("teai members", flag.ContinueOnError)
|
||||
fs.SetOutput(env.Err)
|
||||
hasWork := fs.Bool("has-work", false, "只列出有未完成工作的成員")
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return &ErrUsage{Msg: err.Error()}
|
||||
}
|
||||
if fs.NArg() != 0 {
|
||||
return &ErrUsage{Msg: "members 不接受位置參數"}
|
||||
}
|
||||
s, err := newWorkflowClient(env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
names, err := s.Members(*hasWork)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// gitea.py members 每行輸出一個帳號(非 JSON);保持一致以便交叉驗證。
|
||||
for _, name := range names {
|
||||
fmt.Fprintln(env.Out, name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// runMine:teai mine。
|
||||
func runMine(env *Env, args []string) error {
|
||||
if len(args) != 0 {
|
||||
return &ErrUsage{Msg: "mine 不接受參數"}
|
||||
}
|
||||
s, err := newWorkflowClient(env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
issues, err := s.Mine()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return printer(env).PrintList(
|
||||
[]gitea.Field{
|
||||
{Name: "repo"}, {Name: "number"}, {Name: "title"}, {Name: "url"},
|
||||
},
|
||||
issueRows(issues),
|
||||
)
|
||||
}
|
||||
|
||||
// runPulls:teai pulls [--mine|--reviewer] [--repo owner/repo]。
|
||||
func runPulls(env *Env, args []string) error {
|
||||
fs := flag.NewFlagSet("teai pulls", flag.ContinueOnError)
|
||||
fs.SetOutput(env.Err)
|
||||
mine := fs.Bool("mine", false, "限定作者是目前帳號")
|
||||
reviewer := fs.Bool("reviewer", false, "限定自己是 requested reviewer")
|
||||
repo := fs.String("repo", "", "限定單一倉庫(owner/repo)")
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return &ErrUsage{Msg: err.Error()}
|
||||
}
|
||||
if fs.NArg() != 0 {
|
||||
return &ErrUsage{Msg: "pulls 不接受位置參數"}
|
||||
}
|
||||
if *mine && *reviewer {
|
||||
return &ErrUsage{Msg: "--mine 與 --reviewer 不能併用"}
|
||||
}
|
||||
var owner, name string
|
||||
if *repo != "" {
|
||||
parts := strings.SplitN(*repo, "/", 2)
|
||||
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
|
||||
return &ErrUsage{Msg: errNoRepoFlag.Error()}
|
||||
}
|
||||
owner, name = parts[0], parts[1]
|
||||
}
|
||||
s, err := newWorkflowClient(env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
items, err := s.Pulls(*mine, *reviewer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rows := make([]gitea.Values, 0, len(items))
|
||||
for _, item := range items {
|
||||
if *repo != "" && (item.Owner != owner || item.Repo != name) {
|
||||
continue
|
||||
}
|
||||
rows = append(rows, gitea.Values{
|
||||
item.Owner + "/" + item.Repo,
|
||||
item.Pull.Number,
|
||||
item.Pull.Title,
|
||||
item.Pull.WebURL(),
|
||||
})
|
||||
}
|
||||
return printer(env).PrintList(
|
||||
[]gitea.Field{
|
||||
{Name: "repo"}, {Name: "number"}, {Name: "title"}, {Name: "url"},
|
||||
},
|
||||
rows,
|
||||
)
|
||||
}
|
||||
|
||||
// runNext:teai next。無工作輸出 null(exit 0)。
|
||||
func runNext(env *Env, args []string) error {
|
||||
if len(args) != 0 {
|
||||
return &ErrUsage{Msg: "next 不接受參數"}
|
||||
}
|
||||
s, err := newWorkflowClient(env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
work, err := s.Next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p := printer(env)
|
||||
if work == nil {
|
||||
return p.PrintNull()
|
||||
}
|
||||
// gitea.py 的 _work_dict:role 僅 pull 帶(issue 不輸出 role 欄位)。
|
||||
fields := []gitea.Field{
|
||||
{Name: "type"}, {Name: "role"}, {Name: "repo"},
|
||||
{Name: "number"}, {Name: "title"}, {Name: "url"},
|
||||
}
|
||||
row := gitea.Values{work.Type, work.Role, work.Repo, work.Number, work.Title, work.URL}
|
||||
if work.Role == "" {
|
||||
fields = []gitea.Field{
|
||||
{Name: "type"}, {Name: "repo"},
|
||||
{Name: "number"}, {Name: "title"}, {Name: "url"},
|
||||
}
|
||||
row = gitea.Values{work.Type, work.Repo, work.Number, work.Title, work.URL}
|
||||
}
|
||||
return p.PrintOne(fields, row)
|
||||
}
|
||||
|
||||
// issueRows 把 issues 轉成輸出列(repo 欄位由掃描脈絡補齊)。
|
||||
func issueRows(issues []workflow.Issue) []gitea.Values {
|
||||
rows := make([]gitea.Values, 0, len(issues))
|
||||
for i := range issues {
|
||||
issue := &issues[i]
|
||||
rows = append(rows, gitea.Values{
|
||||
issue.FullName(),
|
||||
issue.Num(),
|
||||
issue.Title,
|
||||
issue.WebURL(),
|
||||
})
|
||||
}
|
||||
return rows
|
||||
}
|
||||
Reference in New Issue
Block a user