From 6ea331ce3bb1167449d2eea792dcfd93ee0cd21e Mon Sep 17 00:00:00 2001 From: Norwin Date: Sat, 10 Oct 2020 01:17:31 +0000 Subject: [PATCH] improve formatting of `tea repos` (#223) make fmt code review use OutputMarkdown use FormatTime() improved repo printing - ReposList() now allows selection of fields - RepoDetail() uses glamour and provides more details Co-authored-by: Norwin Roosen Reviewed-on: https://gitea.com/gitea/tea/pulls/223 Reviewed-by: 6543 <6543@noreply.gitea.io> Reviewed-by: Lunny Xiao Co-Authored-By: Norwin Co-Committed-By: Norwin --- cmd/repos/flags.go | 17 +++++ cmd/repos/list.go | 3 +- cmd/repos/search.go | 3 +- modules/print/repo.go | 164 +++++++++++++++++++++++++++++++----------- 4 files changed, 143 insertions(+), 44 deletions(-) diff --git a/cmd/repos/flags.go b/cmd/repos/flags.go index daa329e..7803730 100644 --- a/cmd/repos/flags.go +++ b/cmd/repos/flags.go @@ -6,11 +6,28 @@ package repos import ( "fmt" + "strings" + + "code.gitea.io/tea/modules/print" "code.gitea.io/sdk/gitea" "github.com/urfave/cli/v2" ) +// printFieldsFlag provides a selection of fields to print +var printFieldsFlag = cli.StringFlag{ + Name: "fields", + Aliases: []string{"f"}, + Usage: fmt.Sprintf(`Comma-separated list of fields to print. Available values: + %s + `, strings.Join(print.RepoFields, ",")), + Value: "owner,name,type,ssh", +} + +func getFields(ctx *cli.Context) []string { + return strings.Split(ctx.String("fields"), ",") +} + var typeFilterFlag = cli.StringFlag{ Name: "type", Aliases: []string{"T"}, diff --git a/cmd/repos/list.go b/cmd/repos/list.go index 22f7af2..6a045f7 100644 --- a/cmd/repos/list.go +++ b/cmd/repos/list.go @@ -27,6 +27,7 @@ var CmdReposListFlags = append([]cli.Flag{ Required: false, Usage: "List your starred repos instead", }, + &printFieldsFlag, &typeFilterFlag, &flags.PaginationPageFlag, &flags.PaginationLimitFlag, @@ -79,7 +80,7 @@ func RunReposList(ctx *cli.Context) error { reposFiltered = filterReposByType(rps, typeFilter) } - print.ReposList(reposFiltered) + print.ReposList(reposFiltered, getFields(ctx)) return nil } diff --git a/cmd/repos/search.go b/cmd/repos/search.go index cf26b32..2efab97 100644 --- a/cmd/repos/search.go +++ b/cmd/repos/search.go @@ -50,6 +50,7 @@ var CmdReposSearch = cli.Command{ Required: false, Usage: "Filter archived repos (true|false)", }, + &printFieldsFlag, &flags.PaginationPageFlag, &flags.PaginationLimitFlag, }, flags.LoginOutputFlags...), @@ -122,6 +123,6 @@ func runReposSearch(ctx *cli.Context) error { return err } - print.ReposList(rps) + print.ReposList(rps, getFields(ctx)) return nil } diff --git a/modules/print/repo.go b/modules/print/repo.go index e8b48b7..754a4e9 100644 --- a/modules/print/repo.go +++ b/modules/print/repo.go @@ -6,78 +6,158 @@ package print import ( "fmt" + "log" "strings" + "time" "code.gitea.io/sdk/gitea" "code.gitea.io/tea/cmd/flags" ) +type rp = *gitea.Repository +type fieldFormatter = func(*gitea.Repository) string + +var ( + fieldFormatters map[string]fieldFormatter + + // RepoFields are the available fields to print with ReposList() + RepoFields []string +) + +func init() { + fieldFormatters = map[string]fieldFormatter{ + "description": func(r rp) string { return r.Description }, + "forks": func(r rp) string { return fmt.Sprintf("%d", r.Forks) }, + "id": func(r rp) string { return r.FullName }, + "name": func(r rp) string { return r.Name }, + "owner": func(r rp) string { return r.Owner.UserName }, + "stars": func(r rp) string { return fmt.Sprintf("%d", r.Stars) }, + "ssh": func(r rp) string { return r.SSHURL }, + "updated": func(r rp) string { return FormatTime(r.Updated) }, + "url": func(r rp) string { return r.HTMLURL }, + "permission": func(r rp) string { + if r.Permissions.Admin { + return "admin" + } else if r.Permissions.Push { + return "write" + } + return "read" + }, + "type": func(r rp) string { + if r.Fork { + return "fork" + } + if r.Mirror { + return "mirror" + } + return "source" + }, + } + + for f := range fieldFormatters { + RepoFields = append(RepoFields, f) + } +} + // ReposList prints a listing of the repos -func ReposList(rps []*gitea.Repository) { - if len(rps) == 0 { +func ReposList(repos []*gitea.Repository, fields []string) { + if len(repos) == 0 { fmt.Println("No repositories found") return } - headers := []string{ - "Name", - "Type", - "SSH", - "Owner", - } - var values [][]string - - for _, rp := range rps { - var mode = "source" - if rp.Fork { - mode = "fork" - } - if rp.Mirror { - mode = "mirror" - } - - values = append( - values, - []string{ - rp.FullName, - mode, - rp.SSHURL, - rp.Owner.UserName, - }, - ) + if len(fields) == 0 { + fmt.Println("No fields to print") + return } - OutputList(flags.GlobalOutputValue, headers, values) + formatters := make([]fieldFormatter, len(fields)) + values := make([][]string, len(repos)) + + // find field format functions by header name + for i, f := range fields { + if formatter, ok := fieldFormatters[strings.ToLower(f)]; ok { + formatters[i] = formatter + } else { + log.Fatalf("invalid field '%s'", f) + } + } + + // extract values from each repo and store them in 2D table + for i, repo := range repos { + values[i] = make([]string, len(formatters)) + for j, format := range formatters { + values[i][j] = format(repo) + } + } + + OutputList(flags.GlobalOutputValue, fields, values) } // RepoDetails print an repo formatted to stdout func RepoDetails(repo *gitea.Repository, topics []string) { - output := repo.FullName + title := "# " + repo.FullName if repo.Mirror { - output += " (mirror)" + title += " (mirror)" } if repo.Fork { - output += " (fork)" + title += " (fork)" } if repo.Archived { - output += " (archived)" + title += " (archived)" } if repo.Empty { - output += " (empty)" + title += " (empty)" } - output += "\n" - if len(topics) != 0 { - output += "Topics: " + strings.Join(topics, ", ") + "\n" + title += "\n" + + var desc string + if len(repo.Description) != 0 { + desc = fmt.Sprintf("*%s*\n\n", repo.Description) } - output += "\n" - output += repo.Description + "\n\n" - output += fmt.Sprintf( - "Open Issues: %d, Stars: %d, Forks: %d, Size: %s\n\n", + + stats := fmt.Sprintf( + "Issues: %d, Stars: %d, Forks: %d, Size: %s\n", repo.OpenIssues, repo.Stars, repo.Forks, formatSize(int64(repo.Size)), ) - fmt.Print(output) + // NOTE: for mirrors, this is the time the mirror was last fetched.. + updated := fmt.Sprintf( + "Updated: %s (%s ago)\n", + repo.Updated.Format("2006-01-02 15:04"), + time.Now().Sub(repo.Updated).Truncate(time.Minute), + ) + + urls := fmt.Sprintf( + "- Browse:\t%s\n- Clone:\t%s\n", + repo.HTMLURL, + repo.SSHURL, + ) + if len(repo.Website) != 0 { + urls += fmt.Sprintf("- Web:\t%s\n", repo.Website) + } + + perm := fmt.Sprintf( + "- Permission:\t%s\n", + fieldFormatters["permission"](repo), + ) + + var tops string + if len(topics) != 0 { + tops = fmt.Sprintf("- Topics:\t%s\n", strings.Join(topics, ", ")) + } + + OutputMarkdown(fmt.Sprintf( + "%s%s\n%s\n%s%s%s%s", + title, + desc, + stats, + updated, + urls, + perm, + tops, + )) }