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 | ||||
| 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) | ||||
| } | ||||
|  | ||||
| @ -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) | ||||
| } | ||||
|  | ||||
| @ -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) | ||||
| } | ||||
|  | ||||
| @ -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) | ||||
| } | ||||
|  | ||||
| @ -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 | ||||
| } | ||||
|  | ||||
| @ -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) | ||||
| } | ||||
|  | ||||
| @ -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) | ||||
| } | ||||
|  | ||||
| @ -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) | ||||
| } | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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") | ||||
| 	} | ||||
| } | ||||
| @ -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) | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Norwin
						Norwin