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:
parent
4a11cf455f
commit
a91168fd36
|
@ -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,
|
strconv.FormatInt(issue.Index, 10),
|
||||||
[]string{
|
issue.Title,
|
||||||
strconv.FormatInt(issue.Index, 10),
|
string(issue.State),
|
||||||
issue.Title,
|
author,
|
||||||
string(issue.State),
|
mile,
|
||||||
author,
|
FormatTime(issue.Updated),
|
||||||
mile,
|
|
||||||
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,
|
strconv.FormatInt(issue.Index, 10),
|
||||||
[]string{
|
string(issue.State),
|
||||||
strconv.FormatInt(issue.Index, 10),
|
kind,
|
||||||
string(issue.State),
|
name,
|
||||||
kind,
|
FormatTime(issue.Updated),
|
||||||
name,
|
issue.Title,
|
||||||
FormatTime(issue.Updated),
|
|
||||||
issue.Title,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
outputList(output, headers, values)
|
t.print(output)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
strconv.FormatInt(label.ID, 10),
|
||||||
[]string{
|
fmt.Sprint(color.Background(p.Color("#"+label.Color))),
|
||||||
strconv.FormatInt(label.ID, 10),
|
label.Name,
|
||||||
fmt.Sprint(color.Background(p.Color("#" + label.Color))),
|
label.Description,
|
||||||
label.Name,
|
|
||||||
label.Description,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
outputList(output, headers, values)
|
t.print(output)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
org.UserName,
|
||||||
[]string{
|
org.FullName,
|
||||||
org.UserName,
|
org.Website,
|
||||||
org.FullName,
|
org.Location,
|
||||||
org.Website,
|
org.Description,
|
||||||
org.Location,
|
|
||||||
org.Description,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
outputList(output, headers, values)
|
t.print(output)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
strconv.FormatInt(pr.Index, 10),
|
||||||
[]string{
|
pr.Title,
|
||||||
strconv.FormatInt(pr.Index, 10),
|
string(pr.State),
|
||||||
pr.Title,
|
author,
|
||||||
string(pr.State),
|
mile,
|
||||||
author,
|
FormatTime(*pr.Updated),
|
||||||
mile,
|
|
||||||
FormatTime(*pr.Updated),
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
outputList(output, headers, values)
|
t.print(output)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
release.TagName,
|
||||||
[]string{
|
release.Title,
|
||||||
release.TagName,
|
FormatTime(release.PublishedAt),
|
||||||
release.Title,
|
status,
|
||||||
FormatTime(release.PublishedAt),
|
release.TarURL,
|
||||||
status,
|
|
||||||
release.TarURL,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
outputList(output, headers, values)
|
t.print(output)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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(
|
FormatTime(t.Created),
|
||||||
outputValues,
|
"#"+strconv.FormatInt(t.Issue.Index, 10),
|
||||||
[]string{
|
t.UserName,
|
||||||
FormatTime(t.Created),
|
formatDuration(t.Time, outputType),
|
||||||
"#" + strconv.FormatInt(t.Issue.Index, 10),
|
|
||||||
t.UserName,
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue