mirror of
https://gitea.com/gitea/tea.git
synced 2026-10-26 11:01:25 +01:00
refactor: reuse the existing service and rendering helpers
services.IsNotFound replaces the hand-rolled 404 checks, convertPage the copied page conversions and lastPathNumber the comment's own number parser. The List wrappers around ListPage of releases, branches, wiki pages, secrets and variables go, pull request filters resolve milestones through Milestones.FindOne like issues, and localFlagsChanged, boolOrder, Cell.Render, stateBadge and dueText replace their copies. Signed-off-by: bircni <bircni@icloud.com>
This commit is contained in:
33 files changed
+85
-212
No files matched your search
@@ -262,7 +262,7 @@ func renderRun(run services.Run, d terminal.Display, hints []string) string {
|
||||
if !d.TTY {
|
||||
return runRecord(run, d, now)
|
||||
}
|
||||
style := func(cell terminal.Cell) string { return paintActionCell(cell, d.Color) }
|
||||
style := func(cell terminal.Cell) string { return cell.Render(d) }
|
||||
muted := func(text string) string { return style(terminal.Cell{Text: text, Muted: true}) }
|
||||
var b strings.Builder
|
||||
heading := run.Workflow
|
||||
@@ -321,15 +321,6 @@ func renderRun(run services.Run, d terminal.Display, hints []string) string {
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// paintActionCell styles a cell's safe text outside a table: its color, muted or bold.
|
||||
func paintActionCell(cell terminal.Cell, color bool) string {
|
||||
name := cell.Color
|
||||
if cell.Muted {
|
||||
name = "muted"
|
||||
}
|
||||
return terminal.Emphasis(terminal.Colorize(name, terminal.SafeLine(cell.Text), color), cell.Bold && color)
|
||||
}
|
||||
|
||||
// jobLines lists jobs, each followed by its steps, for the run view.
|
||||
func jobLines(jobs []services.Job, now time.Time) [][]terminal.Cell {
|
||||
var rows [][]terminal.Cell
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -17,7 +16,6 @@ import (
|
||||
"gitea.dev/tea/internal/clierr"
|
||||
"gitea.dev/tea/internal/services"
|
||||
"gitea.dev/tea/internal/terminal"
|
||||
"gitea.dev/tea/internal/transport"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -144,11 +142,11 @@ func (a *application) renderWorkflow(t target, workflow services.Workflow, runs
|
||||
fields := []terminal.Field{{Label: "File", Value: workflow.ID}, {Label: "Path", Value: workflow.Path}, {Label: "State", Value: workflow.State}}
|
||||
return terminal.View{Title: workflow.Name, Fields: fields, URL: workflow.URL, Body: terminal.Table(runColumns, rows, d)}.Render(d)
|
||||
}
|
||||
muted := func(text string) string { return paintActionCell(terminal.Cell{Text: text, Muted: true}, d.Color) }
|
||||
muted := func(text string) string { return terminal.Cell{Text: text, Muted: true}.Render(d) }
|
||||
var b strings.Builder
|
||||
b.WriteString(paintActionCell(terminal.Cell{Text: workflow.Name, Bold: true}, d.Color) + " " + muted(workflow.ID) + "\n")
|
||||
b.WriteString(paintActionCell(workflowState(workflow), d.Color) + " • " + muted(workflow.Path) + "\n\n")
|
||||
b.WriteString(paintActionCell(terminal.Cell{Text: "RECENT RUNS", Bold: true}, d.Color) + "\n")
|
||||
b.WriteString(terminal.Cell{Text: workflow.Name, Bold: true}.Render(d) + " " + muted(workflow.ID) + "\n")
|
||||
b.WriteString(workflowState(workflow).Render(d) + " • " + muted(workflow.Path) + "\n\n")
|
||||
b.WriteString(terminal.Cell{Text: "RECENT RUNS", Bold: true}.Render(d) + "\n")
|
||||
if len(runs) == 0 {
|
||||
b.WriteString(muted("No runs yet") + "\n")
|
||||
} else {
|
||||
@@ -315,7 +313,7 @@ func (a *application) dispatchRef(ctx context.Context, t target, ref string) (st
|
||||
return defaultBranch()
|
||||
}
|
||||
_, err = services.Branches{Client: t.client}.View(ctx, t.Repo, branch)
|
||||
if api, ok := errors.AsType[*transport.APIError](err); ok && api.Status == http.StatusNotFound {
|
||||
if services.IsNotFound(err) {
|
||||
fallback, err := defaultBranch()
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
||||
@@ -307,9 +307,11 @@ func argumentName(cmd *cobra.Command, argument string) string {
|
||||
return noun + " " + text
|
||||
}
|
||||
|
||||
// localFlagsChanged reports whether any command-specific flag was supplied.
|
||||
func localFlagsChanged(cmd *cobra.Command) bool {
|
||||
// localFlagsChanged reports whether a command-specific flag other than those in except was supplied.
|
||||
func localFlagsChanged(cmd *cobra.Command, except ...string) bool {
|
||||
changed := false
|
||||
cmd.LocalNonPersistentFlags().VisitAll(func(flag *pflag.Flag) { changed = changed || flag.Changed })
|
||||
cmd.LocalNonPersistentFlags().VisitAll(func(flag *pflag.Flag) {
|
||||
changed = changed || flag.Changed && !slices.Contains(except, flag.Name)
|
||||
})
|
||||
return changed
|
||||
}
|
||||
+1
-11
@@ -463,7 +463,7 @@ $ tea auth status --host gitea.com --json`,
|
||||
rows = append(rows, row)
|
||||
}
|
||||
slices.SortFunc(rows, func(x, y authStatusRow) int {
|
||||
return cmp.Or(strings.Compare(x.Host, y.Host), -compareBool(x.environment(), y.environment()), strings.Compare(x.User, y.User))
|
||||
return cmp.Or(strings.Compare(x.Host, y.Host), cmp.Compare(boolOrder(y.environment()), boolOrder(x.environment())), strings.Compare(x.User, y.User))
|
||||
})
|
||||
if a.options.json != "" {
|
||||
return a.writeJSON(cmd, rows)
|
||||
@@ -487,16 +487,6 @@ $ tea auth status --host gitea.com --json`,
|
||||
return status
|
||||
}
|
||||
|
||||
func compareBool(x, y bool) int {
|
||||
switch {
|
||||
case x == y:
|
||||
return 0
|
||||
case x:
|
||||
return 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// accountStatus reports a stored account without revealing its credential.
|
||||
func (a *application) accountStatus(ctx context.Context, s settings.Snapshot, instance, user string, verify bool) authStatusRow {
|
||||
host := s.Hosts.Hosts[instance]
|
||||
|
||||
@@ -385,8 +385,8 @@ func (a *application) completeUsers() cobra.CompletionFunc {
|
||||
// completeBranches completes the local branches, then the repository's branches.
|
||||
func (a *application) completeBranches() cobra.CompletionFunc {
|
||||
return mergeCompletions(a.completeLocalBranches, a.apiCompletion(true, func(ctx context.Context, t target, _ string) ([]cobra.Completion, error) {
|
||||
items, err := services.Branches{Client: t.client}.List(ctx, t.Repo, completionLimit, 1)
|
||||
return describe(items, func(item services.Branch) (string, string) {
|
||||
items, err := services.Branches{Client: t.client}.ListPage(ctx, t.Repo, completionLimit, 1)
|
||||
return describe(items.Items, func(item services.Branch) (string, string) {
|
||||
if item.Protected {
|
||||
return item.Name, "protected"
|
||||
}
|
||||
@@ -406,8 +406,8 @@ func (a *application) completeTags() cobra.CompletionFunc {
|
||||
// completeReleases completes release tags with their titles.
|
||||
func (a *application) completeReleases() cobra.CompletionFunc {
|
||||
return a.apiCompletion(true, func(ctx context.Context, t target, _ string) ([]cobra.Completion, error) {
|
||||
items, err := services.Releases{Client: t.client, Repository: t.Repo}.List(ctx, completionLimit, 1)
|
||||
return describe(items, func(item services.Release) (string, string) { return item.Tag, item.Title }), err
|
||||
items, err := services.Releases{Client: t.client, Repository: t.Repo}.ListPage(ctx, completionLimit, 1)
|
||||
return describe(items.Items, func(item services.Release) (string, string) { return item.Tag, item.Title }), err
|
||||
})
|
||||
}
|
||||
|
||||
@@ -493,8 +493,8 @@ func (a *application) completeRuns() cobra.CompletionFunc {
|
||||
// organization or account selects the repository.
|
||||
func (a *application) completeSecrets(scope func() services.ActionScope) cobra.CompletionFunc {
|
||||
return a.apiCompletion(true, func(ctx context.Context, t target, _ string) ([]cobra.Completion, error) {
|
||||
items, err := services.ActionSecrets{Client: t.client, Scope: actionScope(scope, t)}.List(ctx, completionLimit, 1)
|
||||
return describe(items, func(item services.ActionSecret) (string, string) { return item.Name, item.Description }), err
|
||||
items, err := services.ActionSecrets{Client: t.client, Scope: actionScope(scope, t)}.ListPage(ctx, completionLimit, 1)
|
||||
return describe(items.Items, func(item services.ActionSecret) (string, string) { return item.Name, item.Description }), err
|
||||
})
|
||||
}
|
||||
|
||||
@@ -502,8 +502,8 @@ func (a *application) completeSecrets(scope func() services.ActionScope) cobra.C
|
||||
// an organization or account selects the repository.
|
||||
func (a *application) completeVariables(scope func() services.ActionScope) cobra.CompletionFunc {
|
||||
return a.apiCompletion(true, func(ctx context.Context, t target, _ string) ([]cobra.Completion, error) {
|
||||
items, err := services.ActionVariables{Client: t.client, Scope: actionScope(scope, t)}.List(ctx, completionLimit, 1)
|
||||
return describe(items, func(item services.ActionVariable) (string, string) { return item.Name, item.Description }), err
|
||||
items, err := services.ActionVariables{Client: t.client, Scope: actionScope(scope, t)}.ListPage(ctx, completionLimit, 1)
|
||||
return describe(items.Items, func(item services.ActionVariable) (string, string) { return item.Name, item.Description }), err
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
@@ -18,7 +17,6 @@ import (
|
||||
"gitea.dev/tea/internal/clierr"
|
||||
"gitea.dev/tea/internal/services"
|
||||
"gitea.dev/tea/internal/terminal"
|
||||
"gitea.dev/tea/internal/transport"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -490,7 +488,7 @@ func issueLabel(n int64) string { return fmt.Sprintf("issue %d", n) }
|
||||
// itemError words the failure of one item of a batch, which the batch prefixes with the item, such
|
||||
// as "issue 9: not found in alice/x".
|
||||
func itemError(err error, scope string) error {
|
||||
if apiErr, ok := errors.AsType[*transport.APIError](err); ok && apiErr.Status == http.StatusNotFound {
|
||||
if services.IsNotFound(err) {
|
||||
return explain(err, "not found in %s", scope)
|
||||
}
|
||||
return err
|
||||
|
||||
@@ -125,13 +125,12 @@ func milestoneView(t target, now time.Time) func(services.Milestone) terminal.Vi
|
||||
}
|
||||
due := ""
|
||||
if item.Deadline != nil {
|
||||
deadline := terminal.ParseTime(*item.Deadline)
|
||||
due = terminal.Timestamp(deadline)
|
||||
meta = append([]string{"due " + deadline.Format("Jan 2, 2006") + " (" + terminal.RelativeTime(now, deadline) + ")"}, meta...)
|
||||
due = terminal.Timestamp(terminal.ParseTime(*item.Deadline))
|
||||
meta = append([]string{"due " + dueText(now, *item.Deadline)}, meta...)
|
||||
}
|
||||
return terminal.View{
|
||||
Title: item.Title, ID: "#" + strconv.FormatInt(item.ID, 10),
|
||||
Badges: []terminal.Cell{{Text: titleCase(item.State), Color: stateColors[item.State]}}, Meta: strings.Join(meta, " • "),
|
||||
Badges: []terminal.Cell{stateBadge(item.State)}, Meta: strings.Join(meta, " • "),
|
||||
Fields: []terminal.Field{
|
||||
{Label: "State", Value: item.State, PipedOnly: true},
|
||||
{Label: "Due", Value: due, PipedOnly: true},
|
||||
|
||||
@@ -90,7 +90,7 @@ is not on the remote yet is pushed first, with its upstream set.`, Args: argsExa
|
||||
return err
|
||||
}
|
||||
if milestone != "" && milestone != "0" {
|
||||
if options.Milestone, err = milestoneFilter(cmd.Context(), t, milestone); err != nil {
|
||||
if options.Milestone, err = milestoneID(cmd, t, milestone); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ pull request, or mark it as a draft or ready for review.`, Annotations: pullArgu
|
||||
var none int64
|
||||
opts.Milestone = &none
|
||||
case milestone != "":
|
||||
id, err := milestoneFilter(cmd.Context(), t, milestone)
|
||||
id, err := milestoneID(cmd, t, milestone)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+2
-38
@@ -4,9 +4,7 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -14,9 +12,7 @@ import (
|
||||
"gitea.dev/tea/internal/clierr"
|
||||
"gitea.dev/tea/internal/services"
|
||||
"gitea.dev/tea/internal/terminal"
|
||||
"gitea.dev/tea/internal/transport"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func (a *application) prCommand() *cobra.Command {
|
||||
@@ -71,7 +67,7 @@ each pull request, which tea only makes when --json names them.`, Args: argsExac
|
||||
return err
|
||||
}
|
||||
if milestone != "" {
|
||||
if options.Milestone, err = milestoneFilter(cmd.Context(), t, milestone); err != nil {
|
||||
if options.Milestone, err = milestoneID(cmd, t, milestone); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -91,7 +87,7 @@ each pull request, which tea only makes when --json names them.`, Args: argsExac
|
||||
noun := map[string]string{"open": "open pull requests", "closed": "closed pull requests", "merged": "merged pull requests", "all": "pull requests"}[state]
|
||||
what := noun + " in " + t.Repo
|
||||
empty := "no " + what
|
||||
if filtered(cmd, "state", "limit", "page", "sort") {
|
||||
if localFlagsChanged(cmd, "state", "limit", "page", "sort") {
|
||||
empty = "no " + noun + " match your filters in " + t.Repo
|
||||
}
|
||||
columns := []terminal.Column{{Header: "ID", Prefix: "#"}, {Header: "Title", Flex: true}, {Header: "Branch", Flex: true, Min: 12}, {Header: "State", PipedOnly: state == "open"}, {Header: "Author"}, {Header: "Created"}}
|
||||
@@ -137,22 +133,6 @@ $ tea pr list --json number,title,head --jq '.[] | select(.head == "feature/logi
|
||||
return cmd
|
||||
}
|
||||
|
||||
// changedFlags lists the names of the command's own flags that were given.
|
||||
func changedFlags(cmd *cobra.Command) []string {
|
||||
var names []string
|
||||
cmd.LocalNonPersistentFlags().VisitAll(func(flag *pflag.Flag) {
|
||||
if flag.Changed {
|
||||
names = append(names, flag.Name)
|
||||
}
|
||||
})
|
||||
return names
|
||||
}
|
||||
|
||||
// filtered reports whether a flag other than those named, which do not filter, was given.
|
||||
func filtered(cmd *cobra.Command, unfiltered ...string) bool {
|
||||
return slices.ContainsFunc(changedFlags(cmd), func(name string) bool { return !slices.Contains(unfiltered, name) })
|
||||
}
|
||||
|
||||
// jsonSelects reports whether --json names one of fields, rather than selecting every field.
|
||||
func (a *application) jsonSelects(fields ...string) bool {
|
||||
for field := range strings.SplitSeq(a.options.json, ",") {
|
||||
@@ -163,22 +143,6 @@ func (a *application) jsonSelects(fields ...string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// milestoneFilter resolves a milestone given as an ID or a title (D10): a number is an ID first,
|
||||
// then a title.
|
||||
func milestoneFilter(ctx context.Context, t target, raw string) (int64, error) {
|
||||
service := services.Milestones{Client: t.client, Repository: t.Repo}
|
||||
if id, err := strconv.ParseInt(raw, 10, 64); err == nil && id > 0 {
|
||||
_, err := service.View(ctx, id)
|
||||
if err == nil {
|
||||
return id, nil
|
||||
}
|
||||
if apiErr, ok := errors.AsType[*transport.APIError](err); !ok || apiErr.Status != http.StatusNotFound {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return service.ResolveName(ctx, raw)
|
||||
}
|
||||
|
||||
func (a *application) pullViewCommand() *cobra.Command {
|
||||
var web, comments bool
|
||||
cmd := &cobra.Command{Use: "view [<number> | <url> | <branch>]", Short: "View a pull request", Annotations: pullArgumentsAnnotation(nil), Args: usageArgs(cobra.MaximumNArgs(1)), RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
@@ -29,8 +29,8 @@ func (a *application) wikiCommand() *cobra.Command {
|
||||
// completeWikiPages completes the titles of the repository's wiki pages.
|
||||
func (a *application) completeWikiPages() cobra.CompletionFunc {
|
||||
return a.apiCompletion(true, func(ctx context.Context, t target, _ string) ([]cobra.Completion, error) {
|
||||
pages, err := services.Wiki{Client: t.client, Repository: t.Repo}.List(ctx, completionLimit, 1)
|
||||
return describe(pages, func(page services.WikiPage) (string, string) { return page.Title, "" }), err
|
||||
pages, err := services.Wiki{Client: t.client, Repository: t.Repo}.ListPage(ctx, completionLimit, 1)
|
||||
return describe(pages.Items, func(page services.WikiPage) (string, string) { return page.Title, "" }), err
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user