From 2dc38fb92aa56dc037cc1e75cf6eaa9f4cf79676 Mon Sep 17 00:00:00 2001 From: John McBride Date: Wed, 25 Oct 2023 18:57:17 -0600 Subject: [PATCH] wip: Add "pizza insights user-contributions" command Signed-off-by: John McBride --- cmd/insights/contributors.go | 16 -- cmd/insights/insights.go | 1 + cmd/insights/user-contributions.go | 246 +++++++++++++++++++++++++++++ cmd/insights/utils.go | 25 +++ go.mod | 26 +-- go.sum | 53 ++++--- 6 files changed, 315 insertions(+), 52 deletions(-) create mode 100644 cmd/insights/user-contributions.go create mode 100644 cmd/insights/utils.go diff --git a/cmd/insights/contributors.go b/cmd/insights/contributors.go index 55d2e63..4757dc1 100644 --- a/cmd/insights/contributors.go +++ b/cmd/insights/contributors.go @@ -6,7 +6,6 @@ import ( "encoding/csv" "errors" "fmt" - "net/http" "strconv" "strings" "sync" @@ -206,21 +205,6 @@ func (cis contributorsInsightsSlice) OutputTable() (string, error) { return strings.Join(tables, separator), nil } -func findRepositoryByOwnerAndRepoName(ctx context.Context, apiClient *client.APIClient, repoURL string) (*client.DbRepo, error) { - owner, repoName, err := utils.GetOwnerAndRepoFromURL(repoURL) - if err != nil { - return nil, fmt.Errorf("could not extract owner and repo from url: %w", err) - } - repo, response, err := apiClient.RepositoryServiceAPI.FindOneByOwnerAndRepo(ctx, owner, repoName).Execute() - if err != nil { - if response != nil && response.StatusCode == http.StatusNotFound { - return nil, fmt.Errorf("repository %s is either non-existent, private, or has not been indexed yet", repoURL) - } - return nil, fmt.Errorf("error while calling 'RepositoryServiceAPI.FindOneByOwnerAndRepo' with owner %q and repo %q: %w", owner, repoName, err) - } - return repo, nil -} - func findAllContributorsInsights(ctx context.Context, opts *contributorsOptions, repoURL string) (*contributorsInsights, error) { repo, err := findRepositoryByOwnerAndRepoName(ctx, opts.APIClient, repoURL) if err != nil { diff --git a/cmd/insights/insights.go b/cmd/insights/insights.go index e09539d..b2ff7ee 100644 --- a/cmd/insights/insights.go +++ b/cmd/insights/insights.go @@ -16,5 +16,6 @@ func NewInsightsCommand() *cobra.Command { } cmd.AddCommand(NewContributorsCommand()) cmd.AddCommand(NewRepositoriesCommand()) + cmd.AddCommand(NewUserContributionsCommand()) return cmd } diff --git a/cmd/insights/user-contributions.go b/cmd/insights/user-contributions.go new file mode 100644 index 0000000..03a2182 --- /dev/null +++ b/cmd/insights/user-contributions.go @@ -0,0 +1,246 @@ +package insights + +import ( + "bytes" + "context" + "encoding/csv" + "errors" + "fmt" + "strconv" + "sync" + + bubblesTable "github.com/charmbracelet/bubbles/table" + "github.com/open-sauced/go-api/client" + "github.com/open-sauced/pizza-cli/pkg/api" + "github.com/open-sauced/pizza-cli/pkg/constants" + "github.com/open-sauced/pizza-cli/pkg/utils" + "github.com/spf13/cobra" +) + +type userContributionsOptions struct { + // APIClient is the http client for making calls to the open-sauced api + APIClient *client.APIClient + + // Repos is the array of git repository urls + Repos []string + + // FilePath is the path to yaml file containing an array of git repository urls + FilePath string + + // Period is the number of days, used for query filtering + Period int32 + + // Output is the formatting style for command output + Output string +} + +// NewUserContributionsCommand +func NewUserContributionsCommand() *cobra.Command { + opts := &userContributionsOptions{} + cmd := &cobra.Command{ + Use: "user-contributions url... [flags]", + Short: "", + Long: "", + Args: func(cmd *cobra.Command, args []string) error { + fileFlag := cmd.Flags().Lookup(constants.FlagNameFile) + if !fileFlag.Changed && len(args) == 0 { + return fmt.Errorf("must specify git repository url argument(s) or provide %s flag", fileFlag.Name) + } + opts.Repos = append(opts.Repos, args...) + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + endpointURL, _ := cmd.Flags().GetString(constants.FlagNameEndpoint) + opts.APIClient = api.NewGoClient(endpointURL) + output, _ := cmd.Flags().GetString(constants.FlagNameOutput) + opts.Output = output + return opts.run(context.TODO()) + }, + } + cmd.Flags().StringVarP(&opts.FilePath, constants.FlagNameFile, "f", "", "Path to yaml file containing an array of git repository urls") + cmd.Flags().Int32VarP(&opts.Period, constants.FlagNamePeriod, "p", 30, "Number of days, used for query filtering") + return cmd +} + +func (opts *userContributionsOptions) run(ctx context.Context) error { + repositories, err := utils.HandleRepositoryValues(opts.Repos, opts.FilePath) + if err != nil { + return err + } + + var ( + waitGroup = new(sync.WaitGroup) + errorChan = make(chan error, len(repositories)) + insightsChan = make(chan *userContributionsInsightGroup, len(repositories)) + doneChan = make(chan struct{}) + insights = make([]*userContributionsInsightGroup, 0, len(repositories)) + allErrors error + ) + + go func() { + for url := range repositories { + repoURL := url + waitGroup.Add(1) + go func() { + defer waitGroup.Done() + data, err := findAllUserContributionsInsights(ctx, opts, repoURL) + if err != nil { + errorChan <- err + return + } + if data == nil { + return + } + insightsChan <- data + }() + } + + waitGroup.Wait() + close(doneChan) + }() + + for { + select { + case err = <-errorChan: + allErrors = errors.Join(allErrors, err) + case data := <-insightsChan: + insights = append(insights, data) + case <-doneChan: + if allErrors != nil { + return allErrors + } + for _, insight := range insights { + output, err := insight.BuildOutput(opts.Output) + if err != nil { + return err + } + fmt.Println(output) + } + + return nil + } + } +} + +type userContributionsInsights struct { + Login string `json:"login" yaml:"login"` + Commits int `json:"commits" yaml:"commits"` + PrsCreated int `json:"prs_created" yaml:"prs_created"` + TotalContributions int `json:"total_contributions" yaml:"total_contributions"` +} + +type userContributionsInsightGroup struct { + RepoURL string `json:"repo_url" yaml:"repo_url"` + Insights []userContributionsInsights +} + +func (ucis userContributionsInsightGroup) BuildOutput(format string) (string, error) { + switch format { + case constants.OutputTable: + return ucis.OutputTable() + case constants.OutputJSON: + return utils.OutputJSON(ucis) + case constants.OutputYAML: + return utils.OutputYAML(ucis) + case constants.OuputCSV: + return ucis.OutputCSV() + default: + return "", fmt.Errorf("unknown output format %s", format) + } +} + +func (ucis userContributionsInsightGroup) OutputCSV() (string, error) { + if len(ucis.Insights) == 0 { + return "", fmt.Errorf("repository is either non-existent or has not been indexed yet") + } + b := new(bytes.Buffer) + writer := csv.NewWriter(b) + + // write headers + err := writer.WriteAll([][]string{ + {ucis.RepoURL}, + {"User", "Total", "Commits", "PRs Created"}, + }) + if err != nil { + return "", err + } + + // write records + for _, uci := range ucis.Insights { + err := writer.WriteAll([][]string{ + { + uci.Login, + strconv.Itoa(uci.Commits + uci.PrsCreated), + strconv.Itoa(uci.Commits), + strconv.Itoa(uci.PrsCreated), + }, + }) + + if err != nil { + return "", err + } + } + + return b.String(), nil +} + +func (ucis userContributionsInsightGroup) OutputTable() (string, error) { + rows := []bubblesTable.Row{} + + for _, uci := range ucis.Insights { + rows = append(rows, bubblesTable.Row{ + uci.Login, + strconv.Itoa(uci.TotalContributions), + strconv.Itoa(uci.Commits), + strconv.Itoa(uci.PrsCreated), + }) + } + + columns := []bubblesTable.Column{ + { + Title: "User", + Width: utils.GetMaxTableRowWidth(rows), + }, + { + Title: "Total", + Width: 10, + }, + { + Title: "Commits", + Width: 10, + }, + { + Title: "PRs Created", + Width: 15, + }, + } + + return fmt.Sprintf("%s\n%s\n", ucis.RepoURL, utils.OutputTable(rows, columns)), nil +} + +func findAllUserContributionsInsights(ctx context.Context, opts *userContributionsOptions, repoURL string) (*userContributionsInsightGroup, error) { + owner, name, err := utils.GetOwnerAndRepoFromURL(repoURL) + if err != nil { + return nil, err + } + + repoUserContributionsInsightGroup := &userContributionsInsightGroup{ + RepoURL: repoURL, + } + + dataPoints, _, err := opts.APIClient.RepositoryServiceAPI.FindAllContributorsByRepoId(ctx, owner, name).Range_(opts.Period).Execute() + if err != nil { + return nil, fmt.Errorf("error while calling 'RepositoryServiceAPI.FindAllContributorsByRepoId' with repository %s/%s': %w", owner, name, err) + } + + for _, data := range dataPoints { + repoUserContributionsInsightGroup.Insights = append(repoUserContributionsInsightGroup.Insights, userContributionsInsights{ + Login: *data.Login, + Commits: int(data.Commits), + PrsCreated: int(data.PrsCreated), + TotalContributions: int(data.Commits) + int(data.PrsCreated), + }) + } + + return repoUserContributionsInsightGroup, nil +} diff --git a/cmd/insights/utils.go b/cmd/insights/utils.go new file mode 100644 index 0000000..0a1dba1 --- /dev/null +++ b/cmd/insights/utils.go @@ -0,0 +1,25 @@ +package insights + +import ( + "context" + "fmt" + "net/http" + + "github.com/open-sauced/go-api/client" + "github.com/open-sauced/pizza-cli/pkg/utils" +) + +func findRepositoryByOwnerAndRepoName(ctx context.Context, apiClient *client.APIClient, repoURL string) (*client.DbRepo, error) { + owner, repoName, err := utils.GetOwnerAndRepoFromURL(repoURL) + if err != nil { + return nil, fmt.Errorf("could not extract owner and repo from url: %w", err) + } + repo, response, err := apiClient.RepositoryServiceAPI.FindOneByOwnerAndRepo(ctx, owner, repoName).Execute() + if err != nil { + if response != nil && response.StatusCode == http.StatusNotFound { + return nil, fmt.Errorf("repository %s is either non-existent, private, or has not been indexed yet", repoURL) + } + return nil, fmt.Errorf("error while calling 'RepositoryServiceAPI.FindOneByOwnerAndRepo' with owner %q and repo %q: %w", owner, repoName, err) + } + return repo, nil +} diff --git a/go.mod b/go.mod index e09d154..12817ed 100644 --- a/go.mod +++ b/go.mod @@ -5,33 +5,33 @@ go 1.21 require ( github.com/charmbracelet/bubbles v0.16.1 github.com/charmbracelet/bubbletea v0.24.2 - github.com/charmbracelet/lipgloss v0.8.0 - github.com/cli/browser v1.2.0 - github.com/open-sauced/go-api/client v0.0.0-20230925192938-8a8b1fa31f60 + github.com/charmbracelet/lipgloss v0.9.1 + github.com/cli/browser v1.3.0 + github.com/open-sauced/go-api/client v0.0.0-20231025234817-a8f01f3b26d8 github.com/posthog/posthog-go v0.0.0-20230801140217-d607812dee69 github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 - golang.org/x/term v0.12.0 + golang.org/x/term v0.13.0 gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect - github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect - github.com/google/uuid v1.3.0 // indirect + github.com/containerd/console v1.0.4-0.20230706203907-8f6c4e4faef5 // indirect + github.com/google/uuid v1.3.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect - github.com/mattn/go-isatty v0.0.18 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-localereader v0.0.1 // indirect - github.com/mattn/go-runewidth v0.0.14 // indirect - github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/reflow v0.3.0 // indirect github.com/muesli/termenv v0.15.2 // indirect - github.com/rivo/uniseg v0.2.0 // indirect + github.com/rivo/uniseg v0.4.4 // indirect github.com/sahilm/fuzzy v0.1.0 // indirect - golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.12.0 // indirect - golang.org/x/text v0.3.8 // indirect + golang.org/x/sync v0.4.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect ) diff --git a/go.sum b/go.sum index fa4ea40..9e024aa 100644 --- a/go.sum +++ b/go.sum @@ -7,45 +7,53 @@ github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5 github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc= github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY= github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg= -github.com/charmbracelet/lipgloss v0.8.0 h1:IS00fk4XAHcf8uZKc3eHeMUTCxUH6NkaTrdyCQk84RU= -github.com/charmbracelet/lipgloss v0.8.0/go.mod h1:p4eYUZZJ/0oXTuCQKFF8mqyKCz0ja6y+7DniDDw5KKU= -github.com/cli/browser v1.2.0 h1:yvU7e9qf97kZqGFX6n2zJPHsmSObY9ske+iCvKelvXg= -github.com/cli/browser v1.2.0/go.mod h1:xFFnXLVcAyW9ni0cuo6NnrbCP75JxJ0RO7VtCBiH/oI= +github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg= +github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I= +github.com/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo= +github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk= github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY= github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= +github.com/containerd/console v1.0.4-0.20230706203907-8f6c4e4faef5 h1:Ig+OPkE3XQrrl+SKsOqAjlkrBN/zrr+Qpw7rCuDjRCE= +github.com/containerd/console v1.0.4-0.20230706203907-8f6c4e4faef5/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= -github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= -github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34= -github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= -github.com/open-sauced/go-api/client v0.0.0-20230925192938-8a8b1fa31f60 h1:JqzkafXPIqfn88GkVuVkN3HSWRvBzYLpn7LyVZq+fSg= -github.com/open-sauced/go-api/client v0.0.0-20230925192938-8a8b1fa31f60/go.mod h1:W/TRuLUqYpMvkmElDUQvQ07xlxhK8TOfpwRh8SCAuNA= +github.com/open-sauced/go-api/client v0.0.0-20231023202852-271ed316d5fd h1:xWarN75bBSNVdXJ9DkTRwWnmDG/uAooasLp1IOakIPI= +github.com/open-sauced/go-api/client v0.0.0-20231023202852-271ed316d5fd/go.mod h1:W/TRuLUqYpMvkmElDUQvQ07xlxhK8TOfpwRh8SCAuNA= +github.com/open-sauced/go-api/client v0.0.0-20231024233005-61e58f577005 h1:qrsKqDy8Duw/MNPoaAyD3R/kZScSYosWKBI5jIR6Iwk= +github.com/open-sauced/go-api/client v0.0.0-20231024233005-61e58f577005/go.mod h1:W/TRuLUqYpMvkmElDUQvQ07xlxhK8TOfpwRh8SCAuNA= +github.com/open-sauced/go-api/client v0.0.0-20231025234817-a8f01f3b26d8 h1:qzSaxN4BdovOr2DjXcYJz4LH7RSXEdhw98zAdnJVqJU= +github.com/open-sauced/go-api/client v0.0.0-20231025234817-a8f01f3b26d8/go.mod h1:W/TRuLUqYpMvkmElDUQvQ07xlxhK8TOfpwRh8SCAuNA= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posthog/posthog-go v0.0.0-20230801140217-d607812dee69 h1:01dHVodha5BzrMtVmcpPeA4VYbZEsTXQ6m4123zQXJk= github.com/posthog/posthog-go v0.0.0-20230801140217-d607812dee69/go.mod h1:migYMxlAqcnQy+3eN8mcL0b2tpKy6R+8Zc0lxwk4dKM= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI= @@ -56,17 +64,16 @@ github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRM github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU= -golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= -golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=