273 lines
7.4 KiB
Go
273 lines
7.4 KiB
Go
// workflow_commands.go — gitea.py 工作流命令的 CLI 接線(#6):
|
||
// whoami、role、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: "role", usage: "目前角色(Supervisor→supervisor,Agents→worker)", run: runRole})
|
||
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})
|
||
// pulls 由 dailyops_commands.go 註冊(#8):無子命令時走本檔的 runPulls
|
||
// 工作流清單,有子命令(list/view/create/merge/comment)走日常操作。
|
||
register(&command{name: "next", usage: "下一件該做的事", run: runNext})
|
||
}
|
||
|
||
// errNoRepoFlag 是 --repo 值格式錯誤。
|
||
var errNoRepoFlag = errors.New("--repo 需為 owner/repo 形式")
|
||
|
||
// newScannerFunc 建立工作流 Scanner;正式環境用 API client。
|
||
// 宣告為變數讓接線層測試可注入假 client(不走網路),
|
||
// 彌補 workflow 套件單元測試覆蓋不到 CLI 分派路徑的缺口。
|
||
var newScannerFunc = newWorkflowClient
|
||
|
||
// 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 := newScannerFunc(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},
|
||
)
|
||
}
|
||
|
||
// runRole:teai role。不在 Supervisor/Agents 團隊時輸出 null(exit 0)。
|
||
func runRole(env *Env, args []string) error {
|
||
if len(args) != 0 {
|
||
return &ErrUsage{Msg: "role 不接受參數"}
|
||
}
|
||
s, err := newScannerFunc(env)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
role, err := s.Role()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
p := printer(env)
|
||
if role == "" {
|
||
return p.PrintNull()
|
||
}
|
||
return p.PrintOne(
|
||
[]gitea.Field{{Name: "role"}},
|
||
gitea.Values{role},
|
||
)
|
||
}
|
||
|
||
// runOrgs:teai orgs。
|
||
func runOrgs(env *Env, args []string) error {
|
||
if len(args) != 0 {
|
||
return &ErrUsage{Msg: "orgs 不接受參數"}
|
||
}
|
||
s, err := newScannerFunc(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 := parseFlags(fs, args); err != nil {
|
||
return err
|
||
}
|
||
if fs.NArg() != 0 {
|
||
return &ErrUsage{Msg: "members 不接受位置參數"}
|
||
}
|
||
s, err := newScannerFunc(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 := newScannerFunc(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 := parseFlags(fs, args); err != nil {
|
||
return err
|
||
}
|
||
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 := newScannerFunc(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 := newScannerFunc(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
|
||
}
|