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 <git@nroo.de> Reviewed-on: https://gitea.com/gitea/tea/pulls/415 Reviewed-by: Norwin <noerw@noreply.gitea.io> Reviewed-by: techknowlogick <techknowlogick@gitea.io> Reviewed-by: 6543 <6543@obermui.de> Co-authored-by: justusbunsi <justusbunsi@noreply.gitea.io> Co-committed-by: justusbunsi <justusbunsi@noreply.gitea.io>
This commit is contained in:
parent
1e59dee685
commit
3cf084cb96
|
@ -13,6 +13,10 @@ import (
|
||||||
"github.com/urfave/cli/v2"
|
"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
|
// CmdPullsList represents a sub command of issues to list pulls
|
||||||
var CmdPullsList = cli.Command{
|
var CmdPullsList = cli.Command{
|
||||||
Name: "list",
|
Name: "list",
|
||||||
|
@ -20,7 +24,7 @@ var CmdPullsList = cli.Command{
|
||||||
Usage: "List pull requests of the repository",
|
Usage: "List pull requests of the repository",
|
||||||
Description: `List pull requests of the repository`,
|
Description: `List pull requests of the repository`,
|
||||||
Action: RunPullsList,
|
Action: RunPullsList,
|
||||||
Flags: flags.IssuePRFlags,
|
Flags: append([]cli.Flag{pullFieldsFlag}, flags.IssuePRFlags...),
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunPullsList return list of pulls
|
// RunPullsList return list of pulls
|
||||||
|
@ -46,6 +50,11 @@ func RunPullsList(cmd *cli.Context) error {
|
||||||
return err
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,3 +72,16 @@ func formatUserName(u *gitea.User) string {
|
||||||
}
|
}
|
||||||
return u.FullName
|
return u.FullName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func formatBoolean(b bool, allowIcons bool) string {
|
||||||
|
if !allowIcons {
|
||||||
|
return fmt.Sprintf("%v", b)
|
||||||
|
}
|
||||||
|
|
||||||
|
styled := "✔"
|
||||||
|
if !b {
|
||||||
|
styled = "✖"
|
||||||
|
}
|
||||||
|
|
||||||
|
return styled
|
||||||
|
}
|
||||||
|
|
|
@ -54,19 +54,20 @@ var IssueFields = []string{
|
||||||
func printIssues(issues []*gitea.Issue, output string, fields []string) {
|
func printIssues(issues []*gitea.Issue, output string, fields []string) {
|
||||||
labelMap := map[int64]string{}
|
labelMap := map[int64]string{}
|
||||||
var printables = make([]printable, len(issues))
|
var printables = make([]printable, len(issues))
|
||||||
|
machineReadable := isMachineReadable(output)
|
||||||
|
|
||||||
for i, x := range issues {
|
for i, x := range issues {
|
||||||
// pre-serialize labels for performance
|
// pre-serialize labels for performance
|
||||||
for _, label := range x.Labels {
|
for _, label := range x.Labels {
|
||||||
if _, ok := labelMap[label.ID]; !ok {
|
if _, ok := labelMap[label.ID]; !ok {
|
||||||
labelMap[label.ID] = formatLabel(label, !isMachineReadable(output), "")
|
labelMap[label.ID] = formatLabel(label, !machineReadable, "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// store items with printable interface
|
// store items with printable interface
|
||||||
printables[i] = &printableIssue{x, &labelMap}
|
printables[i] = &printableIssue{x, &labelMap}
|
||||||
}
|
}
|
||||||
|
|
||||||
t := tableFromItems(fields, printables)
|
t := tableFromItems(fields, printables, machineReadable)
|
||||||
t.print(output)
|
t.print(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,7 +76,7 @@ type printableIssue struct {
|
||||||
formattedLabels *map[int64]string
|
formattedLabels *map[int64]string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x printableIssue) FormatField(field string) string {
|
func (x printableIssue) FormatField(field string, machineReadable bool) string {
|
||||||
switch field {
|
switch field {
|
||||||
case "index":
|
case "index":
|
||||||
return fmt.Sprintf("%d", x.Index)
|
return fmt.Sprintf("%d", x.Index)
|
||||||
|
|
|
@ -6,7 +6,6 @@ package print
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
"code.gitea.io/sdk/gitea"
|
||||||
|
@ -23,19 +22,8 @@ var ciStatusSymbols = map[gitea.StatusState]string{
|
||||||
// PullDetails print an pull rendered to stdout
|
// PullDetails print an pull rendered to stdout
|
||||||
func PullDetails(pr *gitea.PullRequest, reviews []*gitea.PullReview, ciStatus *gitea.CombinedStatus) {
|
func PullDetails(pr *gitea.PullRequest, reviews []*gitea.PullReview, ciStatus *gitea.CombinedStatus) {
|
||||||
base := pr.Base.Name
|
base := pr.Base.Name
|
||||||
head := pr.Head.Name
|
head := formatPRHead(pr)
|
||||||
if pr.Head.RepoID != pr.Base.RepoID {
|
state := formatPRState(pr)
|
||||||
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"
|
|
||||||
}
|
|
||||||
|
|
||||||
out := fmt.Sprintf(
|
out := fmt.Sprintf(
|
||||||
"# #%d %s (%s)\n@%s created %s\t**%s** <- **%s**\n\n%s\n\n",
|
"# #%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)
|
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 {
|
func formatReviews(reviews []*gitea.PullReview) string {
|
||||||
result := ""
|
result := ""
|
||||||
if len(reviews) == 0 {
|
if len(reviews) == 0 {
|
||||||
|
@ -114,37 +121,120 @@ func formatReviews(reviews []*gitea.PullReview) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// PullsList prints a listing of pulls
|
// PullsList prints a listing of pulls
|
||||||
func PullsList(prs []*gitea.PullRequest, output string) {
|
func PullsList(prs []*gitea.PullRequest, output string, fields []string) {
|
||||||
t := tableWithHeader(
|
printPulls(prs, output, fields)
|
||||||
"Index",
|
|
||||||
"Title",
|
|
||||||
"State",
|
|
||||||
"Author",
|
|
||||||
"Milestone",
|
|
||||||
"Updated",
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, pr := range prs {
|
|
||||||
if pr == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
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),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// store items with printable interface
|
||||||
|
printables[i] = &printablePull{x, &labelMap}
|
||||||
|
}
|
||||||
|
|
||||||
|
t := tableFromItems(fields, printables, machineReadable)
|
||||||
t.print(output)
|
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 ""
|
||||||
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ func ReposList(repos []*gitea.Repository, output string, fields []string) {
|
||||||
for i, r := range repos {
|
for i, r := range repos {
|
||||||
printables[i] = &printableRepo{r}
|
printables[i] = &printableRepo{r}
|
||||||
}
|
}
|
||||||
t := tableFromItems(fields, printables)
|
t := tableFromItems(fields, printables, isMachineReadable(output))
|
||||||
t.print(output)
|
t.print(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,7 +107,7 @@ var RepoFields = []string{
|
||||||
|
|
||||||
type printableRepo struct{ *gitea.Repository }
|
type printableRepo struct{ *gitea.Repository }
|
||||||
|
|
||||||
func (x printableRepo) FormatField(field string) string {
|
func (x printableRepo) FormatField(field string, machineReadable bool) string {
|
||||||
switch field {
|
switch field {
|
||||||
case "description":
|
case "description":
|
||||||
return x.Description
|
return x.Description
|
||||||
|
|
|
@ -24,16 +24,16 @@ type table struct {
|
||||||
|
|
||||||
// printable can be implemented for structs to put fields dynamically into a table
|
// printable can be implemented for structs to put fields dynamically into a table
|
||||||
type printable interface {
|
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
|
// 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}
|
t := table{headers: fields}
|
||||||
for _, v := range values {
|
for _, v := range values {
|
||||||
row := make([]string, len(fields))
|
row := make([]string, len(fields))
|
||||||
for i, f := range fields {
|
for i, f := range fields {
|
||||||
row[i] = v.FormatField(f)
|
row[i] = v.FormatField(f, machineReadable)
|
||||||
}
|
}
|
||||||
t.addRowSlice(row)
|
t.addRowSlice(row)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ func TrackedTimesList(times []*gitea.TrackedTime, outputType string, fields []st
|
||||||
totalDuration += t.Time
|
totalDuration += t.Time
|
||||||
printables[i] = &printableTrackedTime{t, outputType}
|
printables[i] = &printableTrackedTime{t, outputType}
|
||||||
}
|
}
|
||||||
t := tableFromItems(fields, printables)
|
t := tableFromItems(fields, printables, isMachineReadable(outputType))
|
||||||
|
|
||||||
if printTotal {
|
if printTotal {
|
||||||
total := make([]string, len(fields))
|
total := make([]string, len(fields))
|
||||||
|
@ -45,7 +45,7 @@ type printableTrackedTime struct {
|
||||||
outputFormat string
|
outputFormat string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t printableTrackedTime) FormatField(field string) string {
|
func (t printableTrackedTime) FormatField(field string, machineReadable bool) string {
|
||||||
switch field {
|
switch field {
|
||||||
case "id":
|
case "id":
|
||||||
return fmt.Sprintf("%d", t.ID)
|
return fmt.Sprintf("%d", t.ID)
|
||||||
|
|
Loading…
Reference in New Issue