Improved list output (#281)

remove unused debug var

move outputList into a struct

so we can add additional functionality for all list output

rename list output to table.go

make table sortable

sort milestones

sort milestones descending

remove unnecessary if

Co-authored-by: Norwin Roosen <git@nroo.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/281
Reviewed-by: khmarbaise <khmarbaise@noreply.gitea.io>
Reviewed-by: 6543 <6543@obermui.de>
Co-Authored-By: Norwin <noerw@noreply.gitea.io>
Co-Committed-By: Norwin <noerw@noreply.gitea.io>
This commit is contained in:
Norwin 2020-12-10 06:04:36 +08:00 committed by 6543
parent 4a11cf455f
commit a91168fd36
11 changed files with 141 additions and 170 deletions

View File

@ -26,20 +26,14 @@ func IssueDetails(issue *gitea.Issue) {
// IssuesList prints a listing of issues // IssuesList prints a listing of issues
func IssuesList(issues []*gitea.Issue, output string) { func IssuesList(issues []*gitea.Issue, output string) {
var values [][]string t := tableWithHeader(
headers := []string{
"Index", "Index",
"Title", "Title",
"State", "State",
"Author", "Author",
"Milestone", "Milestone",
"Updated", "Updated",
} )
if len(issues) == 0 {
outputList(output, headers, values)
return
}
for _, issue := range issues { for _, issue := range issues {
author := issue.Poster.FullName author := issue.Poster.FullName
@ -50,38 +44,29 @@ func IssuesList(issues []*gitea.Issue, output string) {
if issue.Milestone != nil { if issue.Milestone != nil {
mile = issue.Milestone.Title mile = issue.Milestone.Title
} }
values = append( t.addRow(
values,
[]string{
strconv.FormatInt(issue.Index, 10), strconv.FormatInt(issue.Index, 10),
issue.Title, issue.Title,
string(issue.State), string(issue.State),
author, author,
mile, mile,
FormatTime(issue.Updated), FormatTime(issue.Updated),
},
) )
} }
outputList(output, headers, values) t.print(output)
} }
// IssuesPullsList prints a listing of issues & pulls // IssuesPullsList prints a listing of issues & pulls
// TODO combine with IssuesList // TODO combine with IssuesList
func IssuesPullsList(issues []*gitea.Issue, output string) { func IssuesPullsList(issues []*gitea.Issue, output string) {
var values [][]string t := tableWithHeader(
headers := []string{
"Index", "Index",
"State", "State",
"Kind", "Kind",
"Author", "Author",
"Updated", "Updated",
"Title", "Title",
} )
if len(issues) == 0 {
outputList(output, headers, values)
return
}
for _, issue := range issues { for _, issue := range issues {
name := issue.Poster.FullName name := issue.Poster.FullName
@ -92,18 +77,15 @@ func IssuesPullsList(issues []*gitea.Issue, output string) {
if issue.PullRequest != nil { if issue.PullRequest != nil {
kind = "Pull" kind = "Pull"
} }
values = append( t.addRow(
values,
[]string{
strconv.FormatInt(issue.Index, 10), strconv.FormatInt(issue.Index, 10),
string(issue.State), string(issue.State),
kind, kind,
name, name,
FormatTime(issue.Updated), FormatTime(issue.Updated),
issue.Title, issue.Title,
},
) )
} }
outputList(output, headers, values) t.print(output)
} }

View File

@ -14,33 +14,24 @@ import (
// LabelsList prints a listing of labels // LabelsList prints a listing of labels
func LabelsList(labels []*gitea.Label, output string) { func LabelsList(labels []*gitea.Label, output string) {
var values [][]string t := tableWithHeader(
headers := []string{
"Index", "Index",
"Color", "Color",
"Name", "Name",
"Description", "Description",
} )
if len(labels) == 0 {
outputList(output, headers, values)
return
}
p := termenv.ColorProfile() p := termenv.ColorProfile()
for _, label := range labels { for _, label := range labels {
color := termenv.String(label.Color) color := termenv.String(label.Color)
values = append( t.addRow(
values,
[]string{
strconv.FormatInt(label.ID, 10), strconv.FormatInt(label.ID, 10),
fmt.Sprint(color.Background(p.Color("#" + label.Color))), fmt.Sprint(color.Background(p.Color("#"+label.Color))),
label.Name, label.Name,
label.Description, label.Description,
},
) )
} }
outputList(output, headers, values) t.print(output)
} }

View File

@ -33,24 +33,23 @@ func LoginDetails(login *config.Login, output string) {
// LoginsList prints a listing of logins // LoginsList prints a listing of logins
func LoginsList(logins []config.Login, output string) { func LoginsList(logins []config.Login, output string) {
var values [][]string t := tableWithHeader(
headers := []string{
"Name", "Name",
"URL", "URL",
"SSHHost", "SSHHost",
"User", "User",
"Default", "Default",
} )
for _, l := range logins { for _, l := range logins {
values = append(values, []string{ t.addRow(
l.Name, l.Name,
l.URL, l.URL,
l.GetSSHHost(), l.GetSSHHost(),
l.User, l.User,
fmt.Sprint(l.Default), fmt.Sprint(l.Default),
}) )
} }
outputList(output, headers, values) t.print(output)
} }

View File

@ -25,7 +25,6 @@ func MilestoneDetails(milestone *gitea.Milestone) {
// MilestonesList prints a listing of milestones // MilestonesList prints a listing of milestones
func MilestonesList(miles []*gitea.Milestone, output string, state gitea.StateType) { func MilestonesList(miles []*gitea.Milestone, output string, state gitea.StateType) {
headers := []string{ headers := []string{
"Title", "Title",
} }
@ -37,7 +36,7 @@ func MilestonesList(miles []*gitea.Milestone, output string, state gitea.StateTy
"DueDate", "DueDate",
) )
var values [][]string t := table{headers: headers}
for _, m := range miles { for _, m := range miles {
var deadline = "" var deadline = ""
@ -56,8 +55,9 @@ func MilestonesList(miles []*gitea.Milestone, output string, state gitea.StateTy
fmt.Sprintf("%d/%d", m.OpenIssues, m.ClosedIssues), fmt.Sprintf("%d/%d", m.OpenIssues, m.ClosedIssues),
deadline, deadline,
) )
t.addRowSlice(item)
values = append(values, item)
} }
outputList(output, headers, values)
t.sort(0, true)
t.print(output)
} }

View File

@ -12,7 +12,6 @@ import (
// NotificationsList prints a listing of notification threads // NotificationsList prints a listing of notification threads
func NotificationsList(news []*gitea.NotificationThread, output string, showRepository bool) { func NotificationsList(news []*gitea.NotificationThread, output string, showRepository bool) {
var values [][]string
headers := []string{ headers := []string{
"Type", "Type",
"Index", "Index",
@ -22,6 +21,8 @@ func NotificationsList(news []*gitea.NotificationThread, output string, showRepo
headers = append(headers, "Repository") headers = append(headers, "Repository")
} }
t := table{headers: headers}
for _, n := range news { for _, n := range news {
if n.Subject == nil { if n.Subject == nil {
continue continue
@ -41,11 +42,10 @@ func NotificationsList(news []*gitea.NotificationThread, output string, showRepo
if showRepository { if showRepository {
item = append(item, n.Repository.FullName) item = append(item, n.Repository.FullName)
} }
values = append(values, item) t.addRowSlice(item)
} }
if len(values) != 0 { if t.Len() != 0 {
outputList(output, headers, values) t.print(output)
} }
return
} }

View File

@ -17,28 +17,23 @@ func OrganizationsList(organizations []*gitea.Organization, output string) {
return return
} }
headers := []string{ t := tableWithHeader(
"Name", "Name",
"FullName", "FullName",
"Website", "Website",
"Location", "Location",
"Description", "Description",
} )
var values [][]string
for _, org := range organizations { for _, org := range organizations {
values = append( t.addRow(
values,
[]string{
org.UserName, org.UserName,
org.FullName, org.FullName,
org.Website, org.Website,
org.Location, org.Location,
org.Description, org.Description,
},
) )
} }
outputList(output, headers, values) t.print(output)
} }

View File

@ -60,20 +60,14 @@ func PullDetails(pr *gitea.PullRequest, reviews []*gitea.PullReview) {
// 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) {
var values [][]string t := tableWithHeader(
headers := []string{
"Index", "Index",
"Title", "Title",
"State", "State",
"Author", "Author",
"Milestone", "Milestone",
"Updated", "Updated",
} )
if len(prs) == 0 {
outputList(output, headers, values)
return
}
for _, pr := range prs { for _, pr := range prs {
if pr == nil { if pr == nil {
@ -87,18 +81,15 @@ func PullsList(prs []*gitea.PullRequest, output string) {
if pr.Milestone != nil { if pr.Milestone != nil {
mile = pr.Milestone.Title mile = pr.Milestone.Title
} }
values = append( t.addRow(
values,
[]string{
strconv.FormatInt(pr.Index, 10), strconv.FormatInt(pr.Index, 10),
pr.Title, pr.Title,
string(pr.State), string(pr.State),
author, author,
mile, mile,
FormatTime(*pr.Updated), FormatTime(*pr.Updated),
},
) )
} }
outputList(output, headers, values) t.print(output)
} }

View File

@ -10,19 +10,13 @@ import (
// ReleasesList prints a listing of releases // ReleasesList prints a listing of releases
func ReleasesList(releases []*gitea.Release, output string) { func ReleasesList(releases []*gitea.Release, output string) {
var values [][]string t := tableWithHeader(
headers := []string{
"Tag-Name", "Tag-Name",
"Title", "Title",
"Published At", "Published At",
"Status", "Status",
"Tar URL", "Tar URL",
} )
if len(releases) == 0 {
outputList(output, headers, values)
return
}
for _, release := range releases { for _, release := range releases {
status := "released" status := "released"
@ -31,17 +25,14 @@ func ReleasesList(releases []*gitea.Release, output string) {
} else if release.IsPrerelease { } else if release.IsPrerelease {
status = "prerelease" status = "prerelease"
} }
values = append( t.addRow(
values,
[]string{
release.TagName, release.TagName,
release.Title, release.Title,
FormatTime(release.PublishedAt), FormatTime(release.PublishedAt),
status, status,
release.TarURL, release.TarURL,
},
) )
} }
outputList(output, headers, values) t.print(output)
} }

View File

@ -90,7 +90,8 @@ func ReposList(repos []*gitea.Repository, output string, fields []string) {
} }
} }
outputList(output, fields, values) t := table{headers: fields, values: values}
t.print(output)
} }
// RepoDetails print an repo formatted to stdout // RepoDetails print an repo formatted to stdout

View File

@ -1,4 +1,4 @@
// Copyright 2018 The Gitea Authors. All rights reserved. // Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -7,19 +7,67 @@ package print
import ( import (
"fmt" "fmt"
"os" "os"
"sort"
"strconv" "strconv"
"strings" "strings"
"github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter"
) )
var ( // table provides infrastructure to easily print (sorted) lists in different formats
showLog bool type table struct {
) headers []string
values [][]string
sortDesc bool // used internally by sortable interface
sortColumn uint // ↑
}
// errorf printf content as an error information func tableWithHeader(header ...string) table {
func errorf(format string, a ...interface{}) { return table{headers: header}
fmt.Printf(format, a...) }
// it's the callers responsibility to ensure row length is equal to header length!
func (t *table) addRow(row ...string) {
t.addRowSlice(row)
}
// it's the callers responsibility to ensure row length is equal to header length!
func (t *table) addRowSlice(row []string) {
t.values = append(t.values, row)
}
func (t *table) sort(column uint, desc bool) {
t.sortColumn = column
t.sortDesc = desc
sort.Stable(t) // stable to allow multiple calls to sort
}
// sortable interface
func (t table) Len() int { return len(t.values) }
func (t table) Swap(i, j int) { t.values[i], t.values[j] = t.values[j], t.values[i] }
func (t table) Less(i, j int) bool {
const column = 0
if t.sortDesc {
i, j = j, i
}
return t.values[i][t.sortColumn] < t.values[j][t.sortColumn]
}
func (t *table) print(output string) {
switch {
case output == "" || output == "table":
outputtable(t.headers, t.values)
case output == "csv":
outputdsv(t.headers, t.values, ",")
case output == "simple":
outputsimple(t.headers, t.values)
case output == "tsv":
outputdsv(t.headers, t.values, "\t")
case output == "yaml":
outputyaml(t.headers, t.values)
default:
fmt.Printf("unknown output type '" + output + "', available types are:\n- csv: comma-separated values\n- simple: space-separated values\n- table: auto-aligned table format (default)\n- tsv: tab-separated values\n- yaml: YAML format\n")
}
} }
// outputtable prints structured data as table // outputtable prints structured data as table
@ -71,22 +119,3 @@ func outputyaml(headers []string, values [][]string) {
} }
} }
} }
// outputList provides general function to convert given list of items
// into several outputs (table, csv, simple, tsv, yaml)
func outputList(output string, headers []string, values [][]string) {
switch {
case output == "" || output == "table":
outputtable(headers, values)
case output == "csv":
outputdsv(headers, values, ",")
case output == "simple":
outputsimple(headers, values)
case output == "tsv":
outputdsv(headers, values, "\t")
case output == "yaml":
outputyaml(headers, values)
default:
errorf("unknown output type '" + output + "', available types are:\n- csv: comma-separated values\n- simple: space-separated values\n- table: auto-aligned table format (default)\n- tsv: tab-separated values\n- yaml: YAML format\n")
}
}

View File

@ -23,7 +23,12 @@ func formatDuration(seconds int64, outputType string) string {
// TrackedTimesList print list of tracked times to stdout // TrackedTimesList print list of tracked times to stdout
func TrackedTimesList(times []*gitea.TrackedTime, outputType string, from, until time.Time, printTotal bool) { func TrackedTimesList(times []*gitea.TrackedTime, outputType string, from, until time.Time, printTotal bool) {
var outputValues [][]string tab := tableWithHeader(
"Created",
"Issue",
"User",
"Duration",
)
var totalDuration int64 var totalDuration int64
for _, t := range times { for _, t := range times {
@ -35,29 +40,16 @@ func TrackedTimesList(times []*gitea.TrackedTime, outputType string, from, until
} }
totalDuration += t.Time totalDuration += t.Time
tab.addRow(
outputValues = append(
outputValues,
[]string{
FormatTime(t.Created), FormatTime(t.Created),
"#" + strconv.FormatInt(t.Issue.Index, 10), "#"+strconv.FormatInt(t.Issue.Index, 10),
t.UserName, t.UserName,
formatDuration(t.Time, outputType), formatDuration(t.Time, outputType),
},
) )
} }
if printTotal { if printTotal {
outputValues = append(outputValues, []string{ tab.addRow("TOTAL", "", "", formatDuration(totalDuration, outputType))
"TOTAL", "", "", formatDuration(totalDuration, outputType),
})
} }
tab.print(outputType)
headers := []string{
"Created",
"Issue",
"User",
"Duration",
}
outputList(outputType, headers, outputValues)
} }