diff --git a/modules/print/issue.go b/modules/print/issue.go index c352b89..df8856f 100644 --- a/modules/print/issue.go +++ b/modules/print/issue.go @@ -26,20 +26,14 @@ func IssueDetails(issue *gitea.Issue) { // IssuesList prints a listing of issues func IssuesList(issues []*gitea.Issue, output string) { - var values [][]string - headers := []string{ + t := tableWithHeader( "Index", "Title", "State", "Author", "Milestone", "Updated", - } - - if len(issues) == 0 { - outputList(output, headers, values) - return - } + ) for _, issue := range issues { author := issue.Poster.FullName @@ -50,38 +44,29 @@ func IssuesList(issues []*gitea.Issue, output string) { if issue.Milestone != nil { mile = issue.Milestone.Title } - values = append( - values, - []string{ - strconv.FormatInt(issue.Index, 10), - issue.Title, - string(issue.State), - author, - mile, - FormatTime(issue.Updated), - }, + t.addRow( + strconv.FormatInt(issue.Index, 10), + issue.Title, + string(issue.State), + author, + mile, + FormatTime(issue.Updated), ) } - outputList(output, headers, values) + t.print(output) } // IssuesPullsList prints a listing of issues & pulls // TODO combine with IssuesList func IssuesPullsList(issues []*gitea.Issue, output string) { - var values [][]string - headers := []string{ + t := tableWithHeader( "Index", "State", "Kind", "Author", "Updated", "Title", - } - - if len(issues) == 0 { - outputList(output, headers, values) - return - } + ) for _, issue := range issues { name := issue.Poster.FullName @@ -92,18 +77,15 @@ func IssuesPullsList(issues []*gitea.Issue, output string) { if issue.PullRequest != nil { kind = "Pull" } - values = append( - values, - []string{ - strconv.FormatInt(issue.Index, 10), - string(issue.State), - kind, - name, - FormatTime(issue.Updated), - issue.Title, - }, + t.addRow( + strconv.FormatInt(issue.Index, 10), + string(issue.State), + kind, + name, + FormatTime(issue.Updated), + issue.Title, ) } - outputList(output, headers, values) + t.print(output) } diff --git a/modules/print/label.go b/modules/print/label.go index 936327c..ad6d96d 100644 --- a/modules/print/label.go +++ b/modules/print/label.go @@ -14,33 +14,24 @@ import ( // LabelsList prints a listing of labels func LabelsList(labels []*gitea.Label, output string) { - var values [][]string - headers := []string{ + t := tableWithHeader( "Index", "Color", "Name", "Description", - } - - if len(labels) == 0 { - outputList(output, headers, values) - return - } + ) p := termenv.ColorProfile() for _, label := range labels { color := termenv.String(label.Color) - values = append( - values, - []string{ - strconv.FormatInt(label.ID, 10), - fmt.Sprint(color.Background(p.Color("#" + label.Color))), - label.Name, - label.Description, - }, + t.addRow( + strconv.FormatInt(label.ID, 10), + fmt.Sprint(color.Background(p.Color("#"+label.Color))), + label.Name, + label.Description, ) } - outputList(output, headers, values) + t.print(output) } diff --git a/modules/print/login.go b/modules/print/login.go index 870cded..61d388c 100644 --- a/modules/print/login.go +++ b/modules/print/login.go @@ -33,24 +33,23 @@ func LoginDetails(login *config.Login, output string) { // LoginsList prints a listing of logins func LoginsList(logins []config.Login, output string) { - var values [][]string - headers := []string{ + t := tableWithHeader( "Name", "URL", "SSHHost", "User", "Default", - } + ) for _, l := range logins { - values = append(values, []string{ + t.addRow( l.Name, l.URL, l.GetSSHHost(), l.User, fmt.Sprint(l.Default), - }) + ) } - outputList(output, headers, values) + t.print(output) } diff --git a/modules/print/milestone.go b/modules/print/milestone.go index a40503d..269862d 100644 --- a/modules/print/milestone.go +++ b/modules/print/milestone.go @@ -25,7 +25,6 @@ func MilestoneDetails(milestone *gitea.Milestone) { // MilestonesList prints a listing of milestones func MilestonesList(miles []*gitea.Milestone, output string, state gitea.StateType) { - headers := []string{ "Title", } @@ -37,7 +36,7 @@ func MilestonesList(miles []*gitea.Milestone, output string, state gitea.StateTy "DueDate", ) - var values [][]string + t := table{headers: headers} for _, m := range miles { var deadline = "" @@ -56,8 +55,9 @@ func MilestonesList(miles []*gitea.Milestone, output string, state gitea.StateTy fmt.Sprintf("%d/%d", m.OpenIssues, m.ClosedIssues), deadline, ) - - values = append(values, item) + t.addRowSlice(item) } - outputList(output, headers, values) + + t.sort(0, true) + t.print(output) } diff --git a/modules/print/notification.go b/modules/print/notification.go index 1f58d2d..31d4b01 100644 --- a/modules/print/notification.go +++ b/modules/print/notification.go @@ -12,7 +12,6 @@ import ( // NotificationsList prints a listing of notification threads func NotificationsList(news []*gitea.NotificationThread, output string, showRepository bool) { - var values [][]string headers := []string{ "Type", "Index", @@ -22,6 +21,8 @@ func NotificationsList(news []*gitea.NotificationThread, output string, showRepo headers = append(headers, "Repository") } + t := table{headers: headers} + for _, n := range news { if n.Subject == nil { continue @@ -41,11 +42,10 @@ func NotificationsList(news []*gitea.NotificationThread, output string, showRepo if showRepository { item = append(item, n.Repository.FullName) } - values = append(values, item) + t.addRowSlice(item) } - if len(values) != 0 { - outputList(output, headers, values) + if t.Len() != 0 { + t.print(output) } - return } diff --git a/modules/print/organization.go b/modules/print/organization.go index 194257f..f073d90 100644 --- a/modules/print/organization.go +++ b/modules/print/organization.go @@ -17,28 +17,23 @@ func OrganizationsList(organizations []*gitea.Organization, output string) { return } - headers := []string{ + t := tableWithHeader( "Name", "FullName", "Website", "Location", "Description", - } - - var values [][]string + ) for _, org := range organizations { - values = append( - values, - []string{ - org.UserName, - org.FullName, - org.Website, - org.Location, - org.Description, - }, + t.addRow( + org.UserName, + org.FullName, + org.Website, + org.Location, + org.Description, ) } - outputList(output, headers, values) + t.print(output) } diff --git a/modules/print/pull.go b/modules/print/pull.go index da4d6f8..d072aa0 100644 --- a/modules/print/pull.go +++ b/modules/print/pull.go @@ -60,20 +60,14 @@ func PullDetails(pr *gitea.PullRequest, reviews []*gitea.PullReview) { // PullsList prints a listing of pulls func PullsList(prs []*gitea.PullRequest, output string) { - var values [][]string - headers := []string{ + t := tableWithHeader( "Index", "Title", "State", "Author", "Milestone", "Updated", - } - - if len(prs) == 0 { - outputList(output, headers, values) - return - } + ) for _, pr := range prs { if pr == nil { @@ -87,18 +81,15 @@ func PullsList(prs []*gitea.PullRequest, output string) { if pr.Milestone != nil { mile = pr.Milestone.Title } - values = append( - values, - []string{ - strconv.FormatInt(pr.Index, 10), - pr.Title, - string(pr.State), - author, - mile, - FormatTime(*pr.Updated), - }, + t.addRow( + strconv.FormatInt(pr.Index, 10), + pr.Title, + string(pr.State), + author, + mile, + FormatTime(*pr.Updated), ) } - outputList(output, headers, values) + t.print(output) } diff --git a/modules/print/release.go b/modules/print/release.go index 40b6068..da5acf1 100644 --- a/modules/print/release.go +++ b/modules/print/release.go @@ -10,19 +10,13 @@ import ( // ReleasesList prints a listing of releases func ReleasesList(releases []*gitea.Release, output string) { - var values [][]string - headers := []string{ + t := tableWithHeader( "Tag-Name", "Title", "Published At", "Status", "Tar URL", - } - - if len(releases) == 0 { - outputList(output, headers, values) - return - } + ) for _, release := range releases { status := "released" @@ -31,17 +25,14 @@ func ReleasesList(releases []*gitea.Release, output string) { } else if release.IsPrerelease { status = "prerelease" } - values = append( - values, - []string{ - release.TagName, - release.Title, - FormatTime(release.PublishedAt), - status, - release.TarURL, - }, + t.addRow( + release.TagName, + release.Title, + FormatTime(release.PublishedAt), + status, + release.TarURL, ) } - outputList(output, headers, values) + t.print(output) } diff --git a/modules/print/repo.go b/modules/print/repo.go index 59b024c..cbd4951 100644 --- a/modules/print/repo.go +++ b/modules/print/repo.go @@ -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 diff --git a/modules/print/list.go b/modules/print/table.go similarity index 51% rename from modules/print/list.go rename to modules/print/table.go index 55aa5f9..920eb37 100644 --- a/modules/print/list.go +++ b/modules/print/table.go @@ -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 // license that can be found in the LICENSE file. @@ -7,19 +7,67 @@ package print import ( "fmt" "os" + "sort" "strconv" "strings" "github.com/olekukonko/tablewriter" ) -var ( - showLog bool -) +// table provides infrastructure to easily print (sorted) lists in different formats +type table struct { + headers []string + values [][]string + sortDesc bool // used internally by sortable interface + sortColumn uint // ↑ +} -// errorf printf content as an error information -func errorf(format string, a ...interface{}) { - fmt.Printf(format, a...) +func tableWithHeader(header ...string) table { + return table{headers: header} +} + +// 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 @@ -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") - } -} diff --git a/modules/print/times.go b/modules/print/times.go index 60fcc17..9c60f8e 100644 --- a/modules/print/times.go +++ b/modules/print/times.go @@ -23,7 +23,12 @@ func formatDuration(seconds int64, outputType string) string { // TrackedTimesList print list of tracked times to stdout 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 for _, t := range times { @@ -35,29 +40,16 @@ func TrackedTimesList(times []*gitea.TrackedTime, outputType string, from, until } totalDuration += t.Time - - outputValues = append( - outputValues, - []string{ - FormatTime(t.Created), - "#" + strconv.FormatInt(t.Issue.Index, 10), - t.UserName, - formatDuration(t.Time, outputType), - }, + tab.addRow( + FormatTime(t.Created), + "#"+strconv.FormatInt(t.Issue.Index, 10), + t.UserName, + formatDuration(t.Time, outputType), ) } if printTotal { - outputValues = append(outputValues, []string{ - "TOTAL", "", "", formatDuration(totalDuration, outputType), - }) + tab.addRow("TOTAL", "", "", formatDuration(totalDuration, outputType)) } - - headers := []string{ - "Created", - "Issue", - "User", - "Duration", - } - outputList(outputType, headers, outputValues) + tab.print(outputType) }