// milestones_commands.go — 跨倉庫里程碑總覽命令的 CLI 接線(#31): // teai milestones(不帶 --repo)。 // // 核心邏輯在 internal/workflow(MilestoneOverview),本檔只做旗標 // 剖析、資料源接線與輸出格式化(與 stalled_commands.go 同模式)。 // 輸出與 gitea.py milestones 位元組相容(MilestoneEntry 自訂 // MarshalJSON;縮排 2、停用 HTML 轉義、空清單輸出 [],見 #26 慣例)。 package cli import ( "fmt" "time" "gitea.alterminal.com/alterminal/teai/internal/gitea" "gitea.alterminal.com/alterminal/teai/internal/workflow" ) // newMilestoneSourceFunc 建立總覽的資料源;宣告為變數讓接線層測試 // 可注入假資料源(與 newStalledSourceFunc 同模式)。 var newMilestoneSourceFunc = newMilestoneSource // newMilestoneSource 依全域選項建立 workflow.MilestoneSource(API 實作)。 func newMilestoneSource(env *Env) (workflow.MilestoneSource, 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.APIClient{Client: client}, nil } // milestonesNowFunc 回傳總覽的基準時刻;宣告為變數讓測試可固定時刻。 var milestonesNowFunc = time.Now // milestoneEntryRow 把 MilestoneEntry 轉成 table 列(unassigned 僅 JSON // 模式輸出完整摘要;table 給人工抽查用,取計數)。 func milestoneEntryRow(e workflow.MilestoneEntry) gitea.Values { dueOn := "" if e.DueOn != nil { dueOn = *e.DueOn } dueHours := "" if e.DueHours != nil { dueHours = fmt.Sprintf("%.1f", *e.DueHours) } updatedAt := "" if e.UpdatedAt != nil { updatedAt = *e.UpdatedAt } id := "" if e.ID != nil { id = fmt.Sprintf("%d", *e.ID) } return gitea.Values{ e.Repo, id, e.Title, e.URL, e.OpenIssues, e.ClosedIssues, dueOn, dueHours, e.Overdue, len(e.Unassigned), updatedAt, } } // runMilestonesOverview:teai milestones(跨倉庫總覽)。 // JSON 輸出與 gitea.py milestones 位元組相容;空清單輸出 [](#26 慣例: // gitea.py 對空清單不輸出,teai 輸出 [] 供 JSON 消費者明確判斷)。 func runMilestonesOverview(env *Env) error { src, err := newMilestoneSourceFunc(env) if err != nil { return err } entries, err := workflow.MilestoneOverview(src, env.Globals.URL, milestonesNowFunc()) if err != nil { return err } p := printer(env) if p.Format == gitea.FormatTable { rows := make([]gitea.Values, 0, len(entries)) for i := range entries { rows = append(rows, milestoneEntryRow(entries[i])) } return p.PrintList([]gitea.Field{ {Name: "repo"}, {Name: "id"}, {Name: "title"}, {Name: "url"}, {Name: "open_issues"}, {Name: "closed_issues"}, {Name: "due_on"}, {Name: "due_hours"}, {Name: "overdue"}, {Name: "unassigned"}, {Name: "updated_at"}, }, rows) } if len(entries) == 0 { _, err := fmt.Fprintln(env.Out, "[]") return err } return printJSON(env.Out, entries) }