From 3cf084cb9631c702a81e29ea02676c9928408c7c Mon Sep 17 00:00:00 2001 From: justusbunsi Date: Wed, 29 Sep 2021 04:36:33 +0800 Subject: [PATCH] PR listing: add --fields & expose additional fields (#415) This PR adds the `--fields` flag to `tea pr ls` (#342), and exposes more fields specific to the `PullRequest` type: ``` --fields value, -f value Comma-separated list of fields to print. Available values: index,state,author,author-id,url,title,body,mergeable,base,base-commit,head,diff,patch,created,updated,deadline,assignees,milestone,labels,comments (default: "index,title,state,author,milestone,updated,labels") ``` Co-authored-by: justusbunsi <61625851+justusbunsi@users.noreply.github.com> Co-authored-by: Norwin Reviewed-on: https://gitea.com/gitea/tea/pulls/415 Reviewed-by: Norwin Reviewed-by: techknowlogick Reviewed-by: 6543 <6543@obermui.de> Co-authored-by: justusbunsi Co-committed-by: justusbunsi --- cmd/pulls/list.go | 13 ++- modules/print/formatters.go | 13 +++ modules/print/issue.go | 7 +- modules/print/pull.go | 174 +++++++++++++++++++++++++++--------- modules/print/repo.go | 4 +- modules/print/table.go | 6 +- modules/print/times.go | 4 +- 7 files changed, 167 insertions(+), 54 deletions(-) diff --git a/cmd/pulls/list.go b/cmd/pulls/list.go index 5037b48..54f3a1a 100644 --- a/cmd/pulls/list.go +++ b/cmd/pulls/list.go @@ -13,6 +13,10 @@ import ( "github.com/urfave/cli/v2" ) +var pullFieldsFlag = flags.FieldsFlag(print.PullFields, []string{ + "index", "title", "state", "author", "milestone", "updated", "labels", +}) + // CmdPullsList represents a sub command of issues to list pulls var CmdPullsList = cli.Command{ Name: "list", @@ -20,7 +24,7 @@ var CmdPullsList = cli.Command{ Usage: "List pull requests of the repository", Description: `List pull requests of the repository`, Action: RunPullsList, - Flags: flags.IssuePRFlags, + Flags: append([]cli.Flag{pullFieldsFlag}, flags.IssuePRFlags...), } // RunPullsList return list of pulls @@ -46,6 +50,11 @@ func RunPullsList(cmd *cli.Context) error { return err } - print.PullsList(prs, ctx.Output) + fields, err := pullFieldsFlag.GetValues(cmd) + if err != nil { + return err + } + + print.PullsList(prs, ctx.Output, fields) return nil } diff --git a/modules/print/formatters.go b/modules/print/formatters.go index e5a6d88..af7a899 100644 --- a/modules/print/formatters.go +++ b/modules/print/formatters.go @@ -72,3 +72,16 @@ func formatUserName(u *gitea.User) string { } return u.FullName } + +func formatBoolean(b bool, allowIcons bool) string { + if !allowIcons { + return fmt.Sprintf("%v", b) + } + + styled := "✔" + if !b { + styled = "✖" + } + + return styled +} diff --git a/modules/print/issue.go b/modules/print/issue.go index 5e52898..4f52455 100644 --- a/modules/print/issue.go +++ b/modules/print/issue.go @@ -54,19 +54,20 @@ var IssueFields = []string{ func printIssues(issues []*gitea.Issue, output string, fields []string) { labelMap := map[int64]string{} var printables = make([]printable, len(issues)) + machineReadable := isMachineReadable(output) for i, x := range issues { // pre-serialize labels for performance for _, label := range x.Labels { if _, ok := labelMap[label.ID]; !ok { - labelMap[label.ID] = formatLabel(label, !isMachineReadable(output), "") + labelMap[label.ID] = formatLabel(label, !machineReadable, "") } } // store items with printable interface printables[i] = &printableIssue{x, &labelMap} } - t := tableFromItems(fields, printables) + t := tableFromItems(fields, printables, machineReadable) t.print(output) } @@ -75,7 +76,7 @@ type printableIssue struct { formattedLabels *map[int64]string } -func (x printableIssue) FormatField(field string) string { +func (x printableIssue) FormatField(field string, machineReadable bool) string { switch field { case "index": return fmt.Sprintf("%d", x.Index) diff --git a/modules/print/pull.go b/modules/print/pull.go index 4fda110..5007bde 100644 --- a/modules/print/pull.go +++ b/modules/print/pull.go @@ -6,7 +6,6 @@ package print import ( "fmt" - "strconv" "strings" "code.gitea.io/sdk/gitea" @@ -23,19 +22,8 @@ var ciStatusSymbols = map[gitea.StatusState]string{ // PullDetails print an pull rendered to stdout func PullDetails(pr *gitea.PullRequest, reviews []*gitea.PullReview, ciStatus *gitea.CombinedStatus) { base := pr.Base.Name - head := pr.Head.Name - if pr.Head.RepoID != pr.Base.RepoID { - if pr.Head.Repository != nil { - head = pr.Head.Repository.Owner.UserName + ":" + head - } else { - head = "delete:" + head - } - } - - state := pr.State - if pr.Merged != nil { - state = "merged" - } + head := formatPRHead(pr) + state := formatPRState(pr) out := fmt.Sprintf( "# #%d %s (%s)\n@%s created %s\t**%s** <- **%s**\n\n%s\n\n", @@ -79,6 +67,25 @@ func PullDetails(pr *gitea.PullRequest, reviews []*gitea.PullReview, ciStatus *g outputMarkdown(out, pr.HTMLURL) } +func formatPRHead(pr *gitea.PullRequest) string { + head := pr.Head.Name + if pr.Head.RepoID != pr.Base.RepoID { + if pr.Head.Repository != nil { + head = pr.Head.Repository.Owner.UserName + ":" + head + } else { + head = "delete:" + head + } + } + return head +} + +func formatPRState(pr *gitea.PullRequest) string { + if pr.Merged != nil { + return "merged" + } + return string(pr.State) +} + func formatReviews(reviews []*gitea.PullReview) string { result := "" if len(reviews) == 0 { @@ -114,37 +121,120 @@ func formatReviews(reviews []*gitea.PullReview) string { } // PullsList prints a listing of pulls -func PullsList(prs []*gitea.PullRequest, output string) { - t := tableWithHeader( - "Index", - "Title", - "State", - "Author", - "Milestone", - "Updated", - ) +func PullsList(prs []*gitea.PullRequest, output string, fields []string) { + printPulls(prs, output, fields) +} - for _, pr := range prs { - if pr == nil { - continue +// PullFields are all available fields to print with PullsList() +var PullFields = []string{ + "index", + "state", + "author", + "author-id", + "url", + + "title", + "body", + + "mergeable", + "base", + "base-commit", + "head", + "diff", + "patch", + + "created", + "updated", + "deadline", + + "assignees", + "milestone", + "labels", + "comments", +} + +func printPulls(pulls []*gitea.PullRequest, output string, fields []string) { + labelMap := map[int64]string{} + var printables = make([]printable, len(pulls)) + machineReadable := isMachineReadable(output) + + for i, x := range pulls { + // pre-serialize labels for performance + for _, label := range x.Labels { + if _, ok := labelMap[label.ID]; !ok { + labelMap[label.ID] = formatLabel(label, !machineReadable, "") + } } - author := pr.Poster.FullName - if len(author) == 0 { - author = pr.Poster.UserName - } - mile := "" - if pr.Milestone != nil { - mile = pr.Milestone.Title - } - t.addRow( - strconv.FormatInt(pr.Index, 10), - pr.Title, - string(pr.State), - author, - mile, - FormatTime(*pr.Updated), - ) + // store items with printable interface + printables[i] = &printablePull{x, &labelMap} } + t := tableFromItems(fields, printables, machineReadable) t.print(output) } + +type printablePull struct { + *gitea.PullRequest + formattedLabels *map[int64]string +} + +func (x printablePull) FormatField(field string, machineReadable bool) string { + switch field { + case "index": + return fmt.Sprintf("%d", x.Index) + case "state": + return formatPRState(x.PullRequest) + case "author": + return formatUserName(x.Poster) + case "author-id": + return x.Poster.UserName + case "url": + return x.HTMLURL + case "title": + return x.Title + case "body": + return x.Body + case "created": + return FormatTime(*x.Created) + case "updated": + return FormatTime(*x.Updated) + case "deadline": + if x.Deadline == nil { + return "" + } + return FormatTime(*x.Deadline) + case "milestone": + if x.Milestone != nil { + return x.Milestone.Title + } + return "" + case "labels": + var labels = make([]string, len(x.Labels)) + for i, l := range x.Labels { + labels[i] = (*x.formattedLabels)[l.ID] + } + return strings.Join(labels, " ") + case "assignees": + var assignees = make([]string, len(x.Assignees)) + for i, a := range x.Assignees { + assignees[i] = formatUserName(a) + } + return strings.Join(assignees, " ") + case "comments": + return fmt.Sprintf("%d", x.Comments) + case "mergeable": + isMergeable := x.Mergeable && x.State == gitea.StateOpen + return formatBoolean(isMergeable, !machineReadable) + case "base": + return x.Base.Ref + case "base-commit": + return x.MergeBase + case "head": + return formatPRHead(x.PullRequest) + case "diff": + return x.DiffURL + case "patch": + return x.PatchURL + } + return "" +} diff --git a/modules/print/repo.go b/modules/print/repo.go index 9a0d812..dbe193a 100644 --- a/modules/print/repo.go +++ b/modules/print/repo.go @@ -18,7 +18,7 @@ func ReposList(repos []*gitea.Repository, output string, fields []string) { for i, r := range repos { printables[i] = &printableRepo{r} } - t := tableFromItems(fields, printables) + t := tableFromItems(fields, printables, isMachineReadable(output)) t.print(output) } @@ -107,7 +107,7 @@ var RepoFields = []string{ type printableRepo struct{ *gitea.Repository } -func (x printableRepo) FormatField(field string) string { +func (x printableRepo) FormatField(field string, machineReadable bool) string { switch field { case "description": return x.Description diff --git a/modules/print/table.go b/modules/print/table.go index 498fe58..7f6988b 100644 --- a/modules/print/table.go +++ b/modules/print/table.go @@ -24,16 +24,16 @@ type table struct { // printable can be implemented for structs to put fields dynamically into a table type printable interface { - FormatField(field string) string + FormatField(field string, machineReadable bool) string } // high level api to print a table of items with dynamic fields -func tableFromItems(fields []string, values []printable) table { +func tableFromItems(fields []string, values []printable, machineReadable bool) table { t := table{headers: fields} for _, v := range values { row := make([]string, len(fields)) for i, f := range fields { - row[i] = v.FormatField(f) + row[i] = v.FormatField(f, machineReadable) } t.addRowSlice(row) } diff --git a/modules/print/times.go b/modules/print/times.go index 0fbfc3b..deb04e1 100644 --- a/modules/print/times.go +++ b/modules/print/times.go @@ -18,7 +18,7 @@ func TrackedTimesList(times []*gitea.TrackedTime, outputType string, fields []st totalDuration += t.Time printables[i] = &printableTrackedTime{t, outputType} } - t := tableFromItems(fields, printables) + t := tableFromItems(fields, printables, isMachineReadable(outputType)) if printTotal { total := make([]string, len(fields)) @@ -45,7 +45,7 @@ type printableTrackedTime struct { outputFormat string } -func (t printableTrackedTime) FormatField(field string) string { +func (t printableTrackedTime) FormatField(field string, machineReadable bool) string { switch field { case "id": return fmt.Sprintf("%d", t.ID)