From ed5c998da5050109b3b873567f7a4e97cccbc65f Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Fri, 20 Aug 2021 22:28:49 -0300 Subject: [PATCH 01/10] wip: new list Signed-off-by: Carlos Alexandro Becker --- go.mod | 4 + go.sum | 14 ++- internal/ui/common.go | 61 +++-------- internal/ui/deleted.go | 104 +++++++++--------- internal/ui/deleting.go | 182 +++++++++++++++---------------- internal/ui/help.go | 82 +++++++------- internal/ui/initial.go | 235 ++++++++++++++++++++++++++++++++++------ internal/ui/list.go | 225 +++++++++++++++++++------------------- 8 files changed, 530 insertions(+), 377 deletions(-) diff --git a/go.mod b/go.mod index ab34272..48f926a 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,10 @@ module github.com/caarlos0/fork-cleaner/v2 go 1.16 require ( + github.com/caarlos0/timea.go v1.0.2 // indirect github.com/charmbracelet/bubbles v0.8.0 github.com/charmbracelet/bubbletea v0.14.1 + github.com/charmbracelet/lipgloss v0.2.2-0.20210525180645-66eb23093aa6 github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-github/v33 v33.0.0 @@ -14,3 +16,5 @@ require ( golang.org/x/oauth2 v0.0.0-20210810183815-faf39c7919d5 google.golang.org/appengine v1.6.7 // indirect ) + +replace github.com/charmbracelet/bubbles => ../../charm/bubbles-internal diff --git a/go.sum b/go.sum index 6546dc7..1b666a6 100644 --- a/go.sum +++ b/go.sum @@ -33,15 +33,16 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/atotto/clipboard v0.1.2 h1:YZCtFu5Ie8qX2VmVTBnrqLSiU9XOWwqNRmdT3gIQzbY= github.com/atotto/clipboard v0.1.2/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= +github.com/caarlos0/timea.go v1.0.2 h1:TTwrLOvn71SnLSq613h9Q9pdujOzrXXxMinNEqmpNso= +github.com/caarlos0/timea.go v1.0.2/go.mod h1:MyDHBpPAvgjxyCJDk1B/LWhBVCWoTrVhyZZ+rjAcxWA= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/charmbracelet/bubbles v0.8.0 h1:+l2op90Ag37Vn+30O1hbg/0wBl+e+sxHhgY1F/rvdHs= -github.com/charmbracelet/bubbles v0.8.0/go.mod h1:5WX1sSSjNCgCrzvRMN/z23HxvWaa+AI16Ch0KPZPeDs= github.com/charmbracelet/bubbletea v0.13.1/go.mod h1:tp9tr9Dadh0PLhgiwchE5zZJXm5543JYjHG9oY+5qSg= github.com/charmbracelet/bubbletea v0.14.1 h1:pD/bM5LBEH/nDo7nKcgNUgi4uRHQhpWTIHZbG5vuSlc= github.com/charmbracelet/bubbletea v0.14.1/go.mod h1:b5lOf5mLjMg1tRn1HVla54guZB+jvsyV0yYAQja95zE= -github.com/charmbracelet/lipgloss v0.1.2 h1:D+LUMg34W7n2pkuMrevKVxT7HXqnoRHm7IoomkX3/ZU= -github.com/charmbracelet/lipgloss v0.1.2/go.mod h1:5D8zradw52m7QmxRF6QgwbwJi9je84g8MkWiGN07uKg= +github.com/charmbracelet/lipgloss v0.2.2-0.20210525180645-66eb23093aa6 h1:lAHD8PDu2W7USlmKEt2v1/BCfmShVXrijjbCQcofOmg= +github.com/charmbracelet/lipgloss v0.2.2-0.20210525180645-66eb23093aa6/go.mod h1:uiZUfrHLQN14I0lJ5591WtcHyY1X76pOIPSaRKPY6dE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -124,9 +125,12 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +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.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 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/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA= github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= @@ -152,6 +156,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI= +github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= diff --git a/internal/ui/common.go b/internal/ui/common.go index 767c725..6a70c54 100644 --- a/internal/ui/common.go +++ b/internal/ui/common.go @@ -1,60 +1,33 @@ package ui import ( - "fmt" - - "github.com/muesli/termenv" + "github.com/charmbracelet/bubbles/list" + "github.com/charmbracelet/lipgloss" ) var ( - primary = termenv.ColorProfile().Color("205") - secondary = termenv.ColorProfile().Color("#89F0CB") - gray = termenv.ColorProfile().Color("#626262") - midGray = termenv.ColorProfile().Color("#4A4A4A") - red = termenv.ColorProfile().Color("#ED567A") + defaultStyles = list.NewDefaultItemStyles() + + primaryColor = defaultStyles.SelectedTitle.GetForeground() + secondaryColor = defaultStyles.NormalTitle.GetForeground() + errorColor = lipgloss.AdaptiveColor{ + Light: "#e94560", + Dark: "#f05945", + } + listStyle = lipgloss.NewStyle().Margin(2) + detailsStyle = lipgloss.NewStyle().PaddingLeft(2) + + boldPrimaryForeground = lipgloss.NewStyle().Foreground(primaryColor).Bold(true) + boldSecondaryForeground = lipgloss.NewStyle().Foreground(secondaryColor).Bold(true) + errorStyle = lipgloss.NewStyle().Foreground(errorColor) ) const ( iconSelected = "●" iconNotSelected = "○" + separator = " • " ) -func boldPrimaryForeground(s string) string { - return termenv.String(s).Foreground(primary).Bold().String() -} - -func boldSecondaryForeground(s string) string { - return termenv.String(s).Foreground(secondary).Bold().String() -} - -func boldRedForeground(s string) string { - return termenv.String(s).Foreground(red).Bold().String() -} - -func redForeground(s string) string { - return termenv.String(s).Foreground(red).String() -} - -func redFaintForeground(s string) string { - return termenv.String(s).Foreground(red).Faint().String() -} - -func grayForeground(s string) string { - return termenv.String(s).Foreground(gray).String() -} - -func midGrayForeground(s string) string { - return termenv.String(s).Foreground(midGray).String() -} - -func faint(s string) string { - return termenv.String(s).Faint().String() -} - type errMsg struct{ error } func (e errMsg) Error() string { return e.error.Error() } - -func errorView(action string, err error) string { - return redForeground(fmt.Sprintf(action+": %s.\nCheck the log file for more details.", err.Error())) + singleOptionHelp("q", "quit") -} diff --git a/internal/ui/deleted.go b/internal/ui/deleted.go index 74313bc..958ed48 100644 --- a/internal/ui/deleted.go +++ b/internal/ui/deleted.go @@ -1,54 +1,54 @@ package ui -import ( - "strconv" - - tea "github.com/charmbracelet/bubbletea" -) - -// NewDeleteEndModelSucceed creates a DeleteEndModel with a success result. -func NewDeleteEndModelSucceed(deleted int) DeleteEndModel { - return DeleteEndModel{ - deleted: deleted, - } -} - -// NewDeleteEndModelFailed creates a DeleteEndModel with a failed result. -func NewDeleteEndModelFailed(err error) DeleteEndModel { - return DeleteEndModel{ - err: err, - } -} - -// DeleteEndModel is the UI for when the forks were either deleted or failed -// to do so. -type DeleteEndModel struct { - err error - deleted int -} - -func (m DeleteEndModel) Init() tea.Cmd { - return nil -} - -func (m DeleteEndModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - switch msg := msg.(type) { - case tea.KeyMsg: - switch msg.String() { - case "ctrl+c", "q", "esc": - return m, tea.Quit - } - } - return m, nil -} - -func (m DeleteEndModel) View() string { - if m.deleted > 0 { - return redFaintForeground("Successfully deleted ") + redForeground(strconv.Itoa(m.deleted)) + redFaintForeground(" forks.") + - singleOptionHelp("q", "quit") - } - if m.err != nil { - return errorView("Error deleting repositories", m.err) - } - return "" -} +// import ( +// "strconv" + +// tea "github.com/charmbracelet/bubbletea" +// ) + +// // NewDeleteEndModelSucceed creates a DeleteEndModel with a success result. +// func NewDeleteEndModelSucceed(deleted int) DeleteEndModel { +// return DeleteEndModel{ +// deleted: deleted, +// } +// } + +// // NewDeleteEndModelFailed creates a DeleteEndModel with a failed result. +// func NewDeleteEndModelFailed(err error) DeleteEndModel { +// return DeleteEndModel{ +// err: err, +// } +// } + +// // DeleteEndModel is the UI for when the forks were either deleted or failed +// // to do so. +// type DeleteEndModel struct { +// err error +// deleted int +// } + +// func (m DeleteEndModel) Init() tea.Cmd { +// return nil +// } + +// func (m DeleteEndModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +// switch msg := msg.(type) { +// case tea.KeyMsg: +// switch msg.String() { +// case "ctrl+c", "q", "esc": +// return m, tea.Quit +// } +// } +// return m, nil +// } + +// func (m DeleteEndModel) View() string { +// if m.deleted > 0 { +// return redFaintForeground("Successfully deleted ") + redForeground(strconv.Itoa(m.deleted)) + redFaintForeground(" forks.") + +// singleOptionHelp("q", "quit") +// } +// if m.err != nil { +// return errorView("Error deleting repositories", m.err) +// } +// return "" +// } diff --git a/internal/ui/deleting.go b/internal/ui/deleting.go index abf605c..2198bb3 100644 --- a/internal/ui/deleting.go +++ b/internal/ui/deleting.go @@ -1,103 +1,103 @@ package ui -import ( - "context" +// import ( +// "context" - forkcleaner "github.com/caarlos0/fork-cleaner/v2" - "github.com/charmbracelet/bubbles/spinner" - tea "github.com/charmbracelet/bubbletea" - "github.com/google/go-github/v33/github" -) +// forkcleaner "github.com/caarlos0/fork-cleaner/v2" +// "github.com/charmbracelet/bubbles/spinner" +// tea "github.com/charmbracelet/bubbletea" +// "github.com/google/go-github/v33/github" +// ) -// NewDeletingModel creates a DeletingModel with required fields. -func NewDeletingModel(client *github.Client, repos []*forkcleaner.RepositoryWithDetails, previous ListModel) DeletingModel { - s := spinner.NewModel() - s.Spinner = spinner.MiniDot +// // NewDeletingModel creates a DeletingModel with required fields. +// func NewDeletingModel(client *github.Client, repos []*forkcleaner.RepositoryWithDetails, previous ListModel) DeletingModel { +// s := spinner.NewModel() +// s.Spinner = spinner.MiniDot - return DeletingModel{ - client: client, - repos: repos, - spinner: s, - previous: previous, - } -} +// return DeletingModel{ +// client: client, +// repos: repos, +// spinner: s, +// previous: previous, +// } +// } -// DeletingModel is the UI in which the user can review the repos they -// selected to be deleted and either finally delete them or cancel. -type DeletingModel struct { - client *github.Client - repos []*forkcleaner.RepositoryWithDetails - cursor int - spinner spinner.Model - loading bool - previous ListModel -} +// // DeletingModel is the UI in which the user can review the repos they +// // selected to be deleted and either finally delete them or cancel. +// type DeletingModel struct { +// client *github.Client +// repos []*forkcleaner.RepositoryWithDetails +// cursor int +// spinner spinner.Model +// loading bool +// previous ListModel +// } -func (m DeletingModel) Init() tea.Cmd { - return nil -} +// func (m DeletingModel) Init() tea.Cmd { +// return nil +// } -func (m DeletingModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - switch msg := msg.(type) { - case reposDeletedMsg: - return NewDeleteEndModelSucceed(msg.total), nil - case errMsg: - return NewDeleteEndModelFailed(msg.error), nil - case tea.KeyMsg: - switch msg.String() { - case "ctrl+c": - return m, tea.Quit - case "q", "esc", "n": - return m.previous, m.previous.Init() - case "up", "k": - if m.cursor > 0 { - m.cursor-- - } - case "down", "j": - if m.cursor < len(m.repos)-1 { - m.cursor++ - } - case "y": - m.loading = true - return m, tea.Batch(deleteRepos(m.client, m.repos), spinner.Tick) - } - default: - var cmd tea.Cmd - m.spinner, cmd = m.spinner.Update(msg) - return m, cmd - } - return m, nil -} +// func (m DeletingModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +// switch msg := msg.(type) { +// case reposDeletedMsg: +// return NewDeleteEndModelSucceed(msg.total), nil +// case errMsg: +// return NewDeleteEndModelFailed(msg.error), nil +// case tea.KeyMsg: +// switch msg.String() { +// case "ctrl+c": +// return m, tea.Quit +// case "q", "esc", "n": +// return m.previous, m.previous.Init() +// case "up", "k": +// if m.cursor > 0 { +// m.cursor-- +// } +// case "down", "j": +// if m.cursor < len(m.repos)-1 { +// m.cursor++ +// } +// case "y": +// m.loading = true +// return m, tea.Batch(deleteRepos(m.client, m.repos), spinner.Tick) +// } +// default: +// var cmd tea.Cmd +// m.spinner, cmd = m.spinner.Update(msg) +// return m, cmd +// } +// return m, nil +// } -func (m DeletingModel) View() string { - if m.loading { - return redFaintForeground(m.spinner.View()) + redForeground(" Deleting repositories...") - } +// func (m DeletingModel) View() string { +// if m.loading { +// return redFaintForeground(m.spinner.View()) + redForeground(" Deleting repositories...") +// } - s := redForeground("Are you sure you want to delete the selected repositories? (y/N)\n\n") - for i, repo := range m.repos { - line := faint(iconSelected+" "+repo.Name) + "\n" - if m.cursor == i { - line = "\n" + boldRedForeground(line) + viewRepositoryDetails(repo) - } - s += line - } - return s + helpView([]helpOption{ - {"q/esc/n", "abort", true}, - {"up/down", "navigate", false}, - {"y", "delete items", false}, - }) -} +// s := redForeground("Are you sure you want to delete the selected repositories? (y/N)\n\n") +// for i, repo := range m.repos { +// line := faint(iconSelected+" "+repo.Name) + "\n" +// if m.cursor == i { +// line = "\n" + boldRedForeground(line) + viewRepositoryDetails(repo) +// } +// s += line +// } +// return s + helpView([]helpOption{ +// {"q/esc/n", "abort", true}, +// {"up/down", "navigate", false}, +// {"y", "delete items", false}, +// }) +// } -type reposDeletedMsg struct { - total int -} +// type reposDeletedMsg struct { +// total int +// } -func deleteRepos(client *github.Client, repos []*forkcleaner.RepositoryWithDetails) tea.Cmd { - return func() tea.Msg { - if err := forkcleaner.Delete(context.Background(), client, repos); err != nil { - return errMsg{err} - } - return reposDeletedMsg{len(repos)} - } -} +// func deleteRepos(client *github.Client, repos []*forkcleaner.RepositoryWithDetails) tea.Cmd { +// return func() tea.Msg { +// if err := forkcleaner.Delete(context.Background(), client, repos); err != nil { +// return errMsg{err} +// } +// return reposDeletedMsg{len(repos)} +// } +// } diff --git a/internal/ui/help.go b/internal/ui/help.go index af88807..8115b45 100644 --- a/internal/ui/help.go +++ b/internal/ui/help.go @@ -1,43 +1,43 @@ package ui -import ( - "strings" - - "github.com/muesli/termenv" -) - -func singleOptionHelp(k, v string) string { - return helpView([]helpOption{ - {k, v, true}, - }) -} - -var separator = midGrayForeground(" • ") - -func helpView(options []helpOption) string { - var lines []string - - var line []string - for i, help := range options { - if help.primary { - line = append(line, grayForeground(help.key)+" "+termenv.String(help.help).Foreground(secondary).Faint().String()) - } else { - line = append(line, grayForeground(help.key)+" "+midGrayForeground(help.help)) - } - // splits in rows of 3 options max - if (i+1)%3 == 0 { - lines = append(lines, strings.Join(line, separator)) - line = []string{} - } - } - - // append remainder - lines = append(lines, strings.Join(line, separator)) - - return "\n\n" + strings.Join(lines, "\n") -} - -type helpOption struct { - key, help string - primary bool -} +// import ( +// "strings" + +// "github.com/muesli/termenv" +// ) + +// func singleOptionHelp(k, v string) string { +// return helpView([]helpOption{ +// {k, v, true}, +// }) +// } + +// var separator = midGrayForeground(" • ") + +// func helpView(options []helpOption) string { +// var lines []string + +// var line []string +// for i, help := range options { +// if help.primary { +// line = append(line, grayForeground(help.key)+" "+termenv.String(help.help).Foreground(secondary).Faint().String()) +// } else { +// line = append(line, grayForeground(help.key)+" "+midGrayForeground(help.help)) +// } +// // splits in rows of 3 options max +// if (i+1)%3 == 0 { +// lines = append(lines, strings.Join(line, separator)) +// line = []string{} +// } +// } + +// // append remainder +// lines = append(lines, strings.Join(line, separator)) + +// return "\n\n" + strings.Join(lines, "\n") +// } + +// type helpOption struct { +// key, help string +// primary bool +// } diff --git a/internal/ui/initial.go b/internal/ui/initial.go index ede9d77..c475d77 100644 --- a/internal/ui/initial.go +++ b/internal/ui/initial.go @@ -2,77 +2,220 @@ package ui import ( "context" + "fmt" + "log" forkcleaner "github.com/caarlos0/fork-cleaner/v2" + "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/list" "github.com/charmbracelet/bubbles/spinner" tea "github.com/charmbracelet/bubbletea" "github.com/google/go-github/v33/github" ) +var keySelectAll = key.NewBinding( + key.WithKeys("a"), + key.WithHelp("a", "select all"), +) + +var keySelectNone = key.NewBinding( + key.WithKeys("n"), + key.WithHelp("n", "select none"), +) + +var keySelectToggle = key.NewBinding( + key.WithKeys(" "), + key.WithHelp("space", "toggle selected item"), +) + +var keyDeletedSelected = key.NewBinding( + key.WithKeys("d"), + key.WithHelp("d", "delete selected forks"), +) + +var keyConfirmDelete = key.NewBinding( + key.WithKeys("y"), + key.WithHelp("y", "confirm deleting"), +) + +var keyCancelDelete = key.NewBinding( + key.WithKeys("n", "esc"), + key.WithHelp("n/esc", "go back"), +) + +var regularKeys = []key.Binding{ + keySelectToggle, + keySelectAll, + keySelectNone, + keyDeletedSelected, +} + +var confirmingDeleteKeys = []key.Binding{ + keyConfirmDelete, + keyCancelDelete, +} + // NewInitialModel creates a new InitialModel with required fields. func NewInitialModel(client *github.Client, login string) InitialModel { - s := spinner.NewModel() - s.Spinner = spinner.MiniDot - return InitialModel{ - client: client, - login: login, - spinner: s, - loading: true, + client: client, + login: login, + list: newList(false), + } +} + +func newList(confirmingDelete bool) list.Model { + list := list.NewModel([]list.Item{}, list.NewDefaultDelegate(), 0, 0) + list.Title = "Fork Cleaner" + list.SetSpinner(spinner.MiniDot) + list.AdditionalShortHelpKeys = func() []key.Binding { + if confirmingDelete { + return confirmingDeleteKeys + } + return regularKeys } + + return list } // InitialModel is the UI when the CLI starts, basically loading the repos. type InitialModel struct { - err error - login string - client *github.Client - spinner spinner.Model - loading bool + err error + login string + client *github.Client + list list.Model + confirmingDelete bool } func (m InitialModel) Init() tea.Cmd { - return tea.Batch(getRepos(m.client, m.login), spinner.Tick) + return enqueueGetReposCmd() } func (m InitialModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var cmds []tea.Cmd + var cmd tea.Cmd + switch msg := msg.(type) { + case tea.WindowSizeMsg: + log.Println("tea.WindowSizeMsg") + top, right, bottom, left := listStyle.GetMargin() + m.list.SetSize(msg.Width-left-right, msg.Height-top-bottom) case errMsg: - m.loading = false + log.Println("errMsg") m.err = msg.error - return m, nil + case getRepoListMsg: + log.Println("getRepoListMsg") + cmds = append(cmds, m.list.StartSpinner(), getReposCmd(m.client, m.login)) case gotRepoListMsg: - list := NewListModel(m.client, msg.repos) - return list, list.Init() + log.Println("gotRepoListMsg") + var items = make([]list.Item, 0, len(msg.repos)) + for _, repo := range msg.repos { + items = append(items, item{ + repo: repo, + }) + } + + m.list.StopSpinner() + cmds = append(cmds, m.list.SetItems(items)) + case askDeleteConfirmationMsg: + log.Println("askDeleteConfirmationMsg") + var items = make([]list.Item, 0, len(msg.repos)) + for _, repo := range msg.repos { + items = append(items, item{ + repo: repo, + selected: true, + }) + } + m.list = newList(true) + m.confirmingDelete = true + cmds = append(cmds, m.list.SetItems(items)) case tea.KeyMsg: - switch msg.String() { - case "ctrl+c", "q", "esc": - return m, tea.Quit + log.Println("tea.KeyMsg") + if !m.list.SettingFilter() { + log.Println("tea.KeyMsg -> !settingFilter") + + if key.Matches(msg, keySelectAll) { + log.Println("tea.KeyMsg -> !settingFilter -> selectAll") + for idx, i := range m.list.Items() { + item := i.(item) + item.selected = true + m.list.RemoveItem(idx) + cmds = append(cmds, m.list.InsertItem(item, idx)) + } + } + + if key.Matches(msg, keySelectNone) { + log.Println("tea.KeyMsg -> !settingFilter -> selectNone") + for idx, i := range m.list.Items() { + item := i.(item) + item.selected = false + m.list.RemoveItem(idx) + cmds = append(cmds, m.list.InsertItem(item, idx)) + } + } + + if key.Matches(msg, keySelectToggle) { + log.Println("tea.KeyMsg -> !settingFilter -> selectToggle") + item := m.list.SelectedItem().(item) + item.selected = !item.selected + idx := m.list.Index() + m.list.RemoveItem(idx) + cmds = append(cmds, m.list.InsertItem(item, idx)) + } + + if key.Matches(msg, keyDeletedSelected) { + log.Println("tea.KeyMsg -> !settingFilter -> deleteSelected") + var selected []*forkcleaner.RepositoryWithDetails + for _, i := range m.list.Items() { + item := i.(item) + if item.selected { + selected = append(selected, item.repo) + } + } + cmds = append(cmds, askDeleteConfirmationCmd(selected)) + } } - default: - var cmd tea.Cmd - m.spinner, cmd = m.spinner.Update(msg) - return m, cmd } - return m, nil + + log.Println("default") + m.list, cmd = m.list.Update(msg) + cmds = append(cmds, cmd) + return m, tea.Batch(cmds...) } func (m InitialModel) View() string { - if m.loading { - return boldPrimaryForeground(m.spinner.View()) + " Gathering repositories..." + singleOptionHelp("q", "quit") - } if m.err != nil { - return errorView("Error gathering the repository list", m.err) + return errorStyle.Bold(true).Render("Error gathering the repository list") + + "\n" + + errorStyle.Render(m.err.Error()) } - return "" + return m.list.View() } +// msgs + +type getRepoListMsg struct{} + type gotRepoListMsg struct { repos []*forkcleaner.RepositoryWithDetails } -func getRepos(client *github.Client, login string) tea.Cmd { +type askDeleteConfirmationMsg struct { + repos []*forkcleaner.RepositoryWithDetails +} + +// cmds + +func enqueueGetReposCmd() tea.Cmd { + return func() tea.Msg { + log.Println("enqueueGetReposCmd") + return getRepoListMsg{} + } +} + +func getReposCmd(client *github.Client, login string) tea.Cmd { return func() tea.Msg { + log.Println("getReposCmd") repos, err := forkcleaner.FindAllForks(context.Background(), client, login) if err != nil { return errMsg{err} @@ -80,3 +223,33 @@ func getRepos(client *github.Client, login string) tea.Cmd { return gotRepoListMsg{repos} } } + +func askDeleteConfirmationCmd(repos []*forkcleaner.RepositoryWithDetails) tea.Cmd { + return func() tea.Msg { + return askDeleteConfirmationMsg{repos} + } +} + +// models + +type item struct { + repo *forkcleaner.RepositoryWithDetails + selected bool +} + +func (i item) Title() string { + var forked string + if i.repo.ParentName != "" { + forked = fmt.Sprintf(" (forked from %s)", i.repo.ParentName) + } + if i.selected { + return iconSelected + " " + i.repo.Name + forked + } + return iconNotSelected + " " + i.repo.Name + forked +} + +func (i item) Description() string { + return detailsStyle.Render(viewRepositoryDetails(i.repo)) +} + +func (i item) FilterValue() string { return i.repo.Name } diff --git a/internal/ui/list.go b/internal/ui/list.go index b0559a2..fa233f8 100644 --- a/internal/ui/list.go +++ b/internal/ui/list.go @@ -2,150 +2,147 @@ package ui import ( "fmt" + "strings" "time" forkcleaner "github.com/caarlos0/fork-cleaner/v2" - tea "github.com/charmbracelet/bubbletea" - "github.com/google/go-github/v33/github" - "github.com/muesli/termenv" + timeago "github.com/caarlos0/timea.go" ) -// NewListModel creates a new ListModel with the required fields. -func NewListModel(client *github.Client, repos []*forkcleaner.RepositoryWithDetails) ListModel { - return ListModel{ - client: client, - repos: repos, - selected: map[int]struct{}{}, - } -} +// import ( +// "fmt" +// "time" -// ListModel is the UI in which the user can select which forks should be -// deleted if any, and see details on each of them. -type ListModel struct { - client *github.Client - repos []*forkcleaner.RepositoryWithDetails - cursor int - selected map[int]struct{} -} +// forkcleaner "github.com/caarlos0/fork-cleaner/v2" +// tea "github.com/charmbracelet/bubbletea" +// "github.com/google/go-github/v33/github" +// "github.com/muesli/termenv" +// ) -func (m ListModel) Init() tea.Cmd { - return nil -} +// // NewListModel creates a new ListModel with the required fields. +// func NewListModel(client *github.Client, repos []*forkcleaner.RepositoryWithDetails) ListModel { +// return ListModel{ +// client: client, +// repos: repos, +// selected: map[int]struct{}{}, +// } +// } -func (m ListModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - switch msg := msg.(type) { - case tea.KeyMsg: - switch msg.String() { - case "ctrl+c", "q", "esc": - return m, tea.Quit - case "up", "k": - if m.cursor > 0 { - m.cursor-- - } - case "down", "j": - if m.cursor < len(m.repos)-1 { - m.cursor++ - } - case "a": - for i := range m.repos { - m.selected[i] = struct{}{} - } - case "n": - for i := range m.selected { - delete(m.selected, i) - } - case " ": - _, ok := m.selected[m.cursor] - if ok { - delete(m.selected, m.cursor) - } else { - m.selected[m.cursor] = struct{}{} - } - case "d": - var deleteable []*forkcleaner.RepositoryWithDetails - for k := range m.selected { - deleteable = append(deleteable, m.repos[k]) - } - dm := NewDeletingModel(m.client, deleteable, m) - return dm, dm.Init() - } - } - return m, nil -} +// // ListModel is the UI in which the user can select which forks should be +// // deleted if any, and see details on each of them. +// type ListModel struct { +// client *github.Client +// repos []*forkcleaner.RepositoryWithDetails +// cursor int +// selected map[int]struct{} +// } -func (m ListModel) View() string { - s := boldSecondaryForeground("Which of these forks do you want to delete?\n\n") - - for i, repo := range m.repos { - line := repo.Name - if _, ok := m.selected[i]; ok { - line = iconSelected + " " + line - } else { - line = faint(iconNotSelected + " " + line) - } - line += "\n" - - if m.cursor == i { - nl := "" - if i > 0 { - nl = "\n" - } - line = nl + boldPrimaryForeground(line) + viewRepositoryDetails(repo) - } - - s += line - } +// func (m ListModel) Init() tea.Cmd { +// return nil +// } - return s + helpView([]helpOption{ - {"up/down", "navigate", false}, - {"space", "toggle selection", false}, - {"d", "delete selected", true}, - {"a", "select all", false}, - {"n", "deselect all", false}, - {"q/esc", "quit", false}, - }) -} +// func (m ListModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +// switch msg := msg.(type) { +// case tea.KeyMsg: +// switch msg.String() { +// case "ctrl+c", "q", "esc": +// return m, tea.Quit +// case "up", "k": +// if m.cursor > 0 { +// m.cursor-- +// } +// case "down", "j": +// if m.cursor < len(m.repos)-1 { +// m.cursor++ +// } +// case "a": +// for i := range m.repos { +// m.selected[i] = struct{}{} +// } +// case "n": +// for i := range m.selected { +// delete(m.selected, i) +// } +// case " ": +// _, ok := m.selected[m.cursor] +// if ok { +// delete(m.selected, m.cursor) +// } else { +// m.selected[m.cursor] = struct{}{} +// } +// case "d": +// var deleteable []*forkcleaner.RepositoryWithDetails +// for k := range m.selected { +// deleteable = append(deleteable, m.repos[k]) +// } +// dm := NewDeletingModel(m.client, deleteable, m) +// return dm, dm.Init() +// } +// } +// return m, nil +// } + +// func (m ListModel) View() string { +// s := boldSecondaryForeground("Which of these forks do you want to delete?\n\n") + +// for i, repo := range m.repos { +// line := repo.Name +// if _, ok := m.selected[i]; ok { +// line = iconSelected + " " + line +// } else { +// line = faint(iconNotSelected + " " + line) +// } +// line += "\n" + +// if m.cursor == i { +// nl := "" +// if i > 0 { +// nl = "\n" +// } +// line = nl + boldPrimaryForeground(line) + viewRepositoryDetails(repo) +// } + +// s += line +// } + +// return s + helpView([]helpOption{ +// {"up/down", "navigate", false}, +// {"space", "toggle selection", false}, +// {"d", "delete selected", true}, +// {"a", "select all", false}, +// {"n", "deselect all", false}, +// {"q/esc", "quit", false}, +// }) +// } func viewRepositoryDetails(repo *forkcleaner.RepositoryWithDetails) string { var details []string - if repo.ParentName != "" { - details = append(details, fmt.Sprintf("Forked from %s", repo.ParentName)) - } if repo.ParentDeleted { - details = append(details, "Parent repository was deleted") + details = append(details, "parent was deleted") } if repo.ParentDMCATakeDown { - details = append(details, "Parent repository was taken down by DMCA") + details = append(details, "parent was taken down by DMCA") } if repo.Private { - details = append(details, "Is private") + details = append(details, "is private") } if repo.CommitsAhead > 0 { - details = append(details, fmt.Sprintf("Has %d commit%s ahead of upstream", repo.CommitsAhead, maybePlural(repo.CommitsAhead))) + details = append(details, fmt.Sprintf("%d commit%s ahead", repo.CommitsAhead, maybePlural(repo.CommitsAhead))) } if repo.Forks > 0 { - details = append(details, fmt.Sprintf("Has %d fork%s", repo.Forks, maybePlural(repo.Forks))) + details = append(details, fmt.Sprintf("has %d fork%s", repo.Forks, maybePlural(repo.Forks))) } if repo.Stars > 0 { - details = append(details, fmt.Sprintf("Has %d star%s", repo.Stars, maybePlural(repo.Stars))) + details = append(details, fmt.Sprintf("has %d star%s", repo.Stars, maybePlural(repo.Stars))) } if repo.OpenPRs > 0 { - details = append(details, fmt.Sprintf("Has %d open PR%s to upstream", repo.OpenPRs, maybePlural(repo.OpenPRs))) + details = append(details, fmt.Sprintf("has %d open PR%s to upstream", repo.OpenPRs, maybePlural(repo.OpenPRs))) } if time.Now().Add(-30 * 24 * time.Hour).Before(repo.LastUpdate) { - details = append(details, fmt.Sprintf("Was updated recently (%s)", repo.LastUpdate)) + details = append(details, fmt.Sprintf("recently updated (%s)", timeago.Of(repo.LastUpdate))) } - if len(details) == 0 { - return "" - } - - var s string - for _, d := range details { - s += " * " + d + "\n" - } - s += "\n" - return termenv.String(s).Faint().Italic().String() + return strings.Join(details, separator) } func maybePlural(n int) string { From 0ff6bdd52a96ecbadbd32e1c7cc83b05de9cf6a5 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Fri, 20 Aug 2021 23:32:41 -0300 Subject: [PATCH 02/10] wip: new list Signed-off-by: Carlos Alexandro Becker --- cmd/fork-cleaner/main.go | 2 +- internal/ui/app.go | 133 ++++++++++++++++++++ internal/ui/commands.go | 46 +++++++ internal/ui/common.go | 33 ----- internal/ui/deleted.go | 54 --------- internal/ui/deleting.go | 103 ---------------- internal/ui/help.go | 43 ------- internal/ui/initial.go | 255 --------------------------------------- internal/ui/item.go | 88 ++++++++++++++ internal/ui/keys.go | 10 ++ internal/ui/list.go | 153 ----------------------- internal/ui/msgs.go | 17 +++ internal/ui/styles.go | 22 ++++ 13 files changed, 317 insertions(+), 642 deletions(-) create mode 100644 internal/ui/app.go create mode 100644 internal/ui/commands.go delete mode 100644 internal/ui/common.go delete mode 100644 internal/ui/deleted.go delete mode 100644 internal/ui/deleting.go delete mode 100644 internal/ui/help.go delete mode 100644 internal/ui/initial.go create mode 100644 internal/ui/item.go create mode 100644 internal/ui/keys.go delete mode 100644 internal/ui/list.go create mode 100644 internal/ui/msgs.go create mode 100644 internal/ui/styles.go diff --git a/cmd/fork-cleaner/main.go b/cmd/fork-cleaner/main.go index ea25459..34e1f41 100644 --- a/cmd/fork-cleaner/main.go +++ b/cmd/fork-cleaner/main.go @@ -62,7 +62,7 @@ func main() { return cli.NewExitError("missing github token", 1) } - p := tea.NewProgram(ui.NewInitialModel(client, login)) + p := tea.NewProgram(ui.NewAppModel(client, login)) p.EnterAltScreen() defer p.ExitAltScreen() if err = p.Start(); err != nil { diff --git a/internal/ui/app.go b/internal/ui/app.go new file mode 100644 index 0000000..7469aa6 --- /dev/null +++ b/internal/ui/app.go @@ -0,0 +1,133 @@ +package ui + +import ( + "log" + + "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/list" + "github.com/charmbracelet/bubbles/spinner" + tea "github.com/charmbracelet/bubbletea" + "github.com/google/go-github/v33/github" +) + +// AppModel is the UI when the CLI starts, basically loading the repos. +type AppModel struct { + err error + login string + client *github.Client + list list.Model +} + +// NewAppModel creates a new AppModel with required fields. +func NewAppModel(client *github.Client, login string) AppModel { + list := list.NewModel([]list.Item{}, list.NewDefaultDelegate(), 0, 0) + list.Title = "Fork Cleaner" + list.SetSpinner(spinner.MiniDot) + list.AdditionalShortHelpKeys = func() []key.Binding { + return []key.Binding{ + keySelectToggle, + keyDeletedSelected, + } + } + list.AdditionalFullHelpKeys = func() []key.Binding { + return []key.Binding{ + keySelectAll, + keySelectNone, + } + } + + return AppModel{ + client: client, + login: login, + list: list, + } +} + +func (m AppModel) Init() tea.Cmd { + return tea.Batch(enqueueGetReposCmd, m.list.StartSpinner()) +} + +func (m AppModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var cmds []tea.Cmd + var cmd tea.Cmd + + switch msg := msg.(type) { + case tea.WindowSizeMsg: + log.Println("tea.WindowSizeMsg") + top, right, bottom, left := listStyle.GetMargin() + m.list.SetSize(msg.Width-left-right, msg.Height-top-bottom) + case errMsg: + log.Println("errMsg") + m.err = msg.error + case getRepoListMsg: + log.Println("getRepoListMsg") + cmds = append(cmds, m.list.StartSpinner(), getReposCmd(m.client, m.login)) + case gotRepoListMsg: + log.Println("gotRepoListMsg") + m.list.StopSpinner() + cmds = append(cmds, m.list.SetItems(reposToItems(msg.repos))) + case reposDeletedMsg: + log.Println("reposDeletedMsg") + cmds = append(cmds, m.list.StartSpinner(), enqueueGetReposCmd) + case requestDeleteSelectedReposMsg: + log.Println("requestDeleteSelectedReposMsg") + cmds = append(cmds, deleteReposCmd(m.client, selectedRepos(m.list.Items()))) + + case tea.KeyMsg: + if m.list.SettingFilter() { + break + } + + if key.Matches(msg, keySelectAll) { + log.Println("tea.KeyMsg -> selectAll") + cmds = append(cmds, m.changeSelect(true)...) + } + + if key.Matches(msg, keySelectNone) { + log.Println("tea.KeyMsg -> selectNone") + cmds = append(cmds, m.changeSelect(false)...) + } + + if key.Matches(msg, keySelectToggle) { + log.Println("tea.KeyMsg -> selectToggle") + cmds = append(cmds, m.toggleSelection()) + } + + if key.Matches(msg, keyDeletedSelected) { + log.Println("tea.KeyMsg -> deleteSelected") + cmds = append(cmds, m.list.StartSpinner(), requestDeleteReposCmd) + } + } + + m.list, cmd = m.list.Update(msg) + cmds = append(cmds, cmd) + return m, tea.Batch(cmds...) +} + +func (m AppModel) View() string { + if m.err != nil { + return errorStyle.Bold(true).Render("Error gathering the repository list") + + "\n" + + errorStyle.Render(m.err.Error()) + } + return m.list.View() +} + +func (m AppModel) toggleSelection() tea.Cmd { + idx := m.list.Index() + item := m.list.SelectedItem().(item) + item.selected = !item.selected + m.list.RemoveItem(idx) + return m.list.InsertItem(item, idx) +} + +func (m AppModel) changeSelect(selected bool) []tea.Cmd { + var cmds []tea.Cmd + for idx, i := range m.list.Items() { + item := i.(item) + item.selected = selected + m.list.RemoveItem(idx) + cmds = append(cmds, m.list.InsertItem(item, idx)) + } + return cmds +} diff --git a/internal/ui/commands.go b/internal/ui/commands.go new file mode 100644 index 0000000..bb842ef --- /dev/null +++ b/internal/ui/commands.go @@ -0,0 +1,46 @@ +package ui + +import ( + "context" + "log" + "strings" + "time" + + forkcleaner "github.com/caarlos0/fork-cleaner/v2" + tea "github.com/charmbracelet/bubbletea" + "github.com/google/go-github/v33/github" +) + +func requestDeleteReposCmd() tea.Msg { + return requestDeleteSelectedReposMsg{} +} + +func deleteReposCmd(client *github.Client, repos []*forkcleaner.RepositoryWithDetails) tea.Cmd { + return func() tea.Msg { + var names []string + for _, r := range repos { + names = append(names, r.Name) + } + log.Println("deleteReposCmd", strings.Join(names, ", ")) + time.Sleep(time.Second) + // if err := forkcleaner.Delete(context.Background(), client, repos); err != nil { + // return errMsg{err} + // } + return reposDeletedMsg{} + } +} + +func enqueueGetReposCmd() tea.Msg { + return getRepoListMsg{} +} + +func getReposCmd(client *github.Client, login string) tea.Cmd { + return func() tea.Msg { + log.Println("getReposCmd") + repos, err := forkcleaner.FindAllForks(context.Background(), client, login) + if err != nil { + return errMsg{err} + } + return gotRepoListMsg{repos} + } +} diff --git a/internal/ui/common.go b/internal/ui/common.go deleted file mode 100644 index 6a70c54..0000000 --- a/internal/ui/common.go +++ /dev/null @@ -1,33 +0,0 @@ -package ui - -import ( - "github.com/charmbracelet/bubbles/list" - "github.com/charmbracelet/lipgloss" -) - -var ( - defaultStyles = list.NewDefaultItemStyles() - - primaryColor = defaultStyles.SelectedTitle.GetForeground() - secondaryColor = defaultStyles.NormalTitle.GetForeground() - errorColor = lipgloss.AdaptiveColor{ - Light: "#e94560", - Dark: "#f05945", - } - listStyle = lipgloss.NewStyle().Margin(2) - detailsStyle = lipgloss.NewStyle().PaddingLeft(2) - - boldPrimaryForeground = lipgloss.NewStyle().Foreground(primaryColor).Bold(true) - boldSecondaryForeground = lipgloss.NewStyle().Foreground(secondaryColor).Bold(true) - errorStyle = lipgloss.NewStyle().Foreground(errorColor) -) - -const ( - iconSelected = "●" - iconNotSelected = "○" - separator = " • " -) - -type errMsg struct{ error } - -func (e errMsg) Error() string { return e.error.Error() } diff --git a/internal/ui/deleted.go b/internal/ui/deleted.go deleted file mode 100644 index 958ed48..0000000 --- a/internal/ui/deleted.go +++ /dev/null @@ -1,54 +0,0 @@ -package ui - -// import ( -// "strconv" - -// tea "github.com/charmbracelet/bubbletea" -// ) - -// // NewDeleteEndModelSucceed creates a DeleteEndModel with a success result. -// func NewDeleteEndModelSucceed(deleted int) DeleteEndModel { -// return DeleteEndModel{ -// deleted: deleted, -// } -// } - -// // NewDeleteEndModelFailed creates a DeleteEndModel with a failed result. -// func NewDeleteEndModelFailed(err error) DeleteEndModel { -// return DeleteEndModel{ -// err: err, -// } -// } - -// // DeleteEndModel is the UI for when the forks were either deleted or failed -// // to do so. -// type DeleteEndModel struct { -// err error -// deleted int -// } - -// func (m DeleteEndModel) Init() tea.Cmd { -// return nil -// } - -// func (m DeleteEndModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { -// switch msg := msg.(type) { -// case tea.KeyMsg: -// switch msg.String() { -// case "ctrl+c", "q", "esc": -// return m, tea.Quit -// } -// } -// return m, nil -// } - -// func (m DeleteEndModel) View() string { -// if m.deleted > 0 { -// return redFaintForeground("Successfully deleted ") + redForeground(strconv.Itoa(m.deleted)) + redFaintForeground(" forks.") + -// singleOptionHelp("q", "quit") -// } -// if m.err != nil { -// return errorView("Error deleting repositories", m.err) -// } -// return "" -// } diff --git a/internal/ui/deleting.go b/internal/ui/deleting.go deleted file mode 100644 index 2198bb3..0000000 --- a/internal/ui/deleting.go +++ /dev/null @@ -1,103 +0,0 @@ -package ui - -// import ( -// "context" - -// forkcleaner "github.com/caarlos0/fork-cleaner/v2" -// "github.com/charmbracelet/bubbles/spinner" -// tea "github.com/charmbracelet/bubbletea" -// "github.com/google/go-github/v33/github" -// ) - -// // NewDeletingModel creates a DeletingModel with required fields. -// func NewDeletingModel(client *github.Client, repos []*forkcleaner.RepositoryWithDetails, previous ListModel) DeletingModel { -// s := spinner.NewModel() -// s.Spinner = spinner.MiniDot - -// return DeletingModel{ -// client: client, -// repos: repos, -// spinner: s, -// previous: previous, -// } -// } - -// // DeletingModel is the UI in which the user can review the repos they -// // selected to be deleted and either finally delete them or cancel. -// type DeletingModel struct { -// client *github.Client -// repos []*forkcleaner.RepositoryWithDetails -// cursor int -// spinner spinner.Model -// loading bool -// previous ListModel -// } - -// func (m DeletingModel) Init() tea.Cmd { -// return nil -// } - -// func (m DeletingModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { -// switch msg := msg.(type) { -// case reposDeletedMsg: -// return NewDeleteEndModelSucceed(msg.total), nil -// case errMsg: -// return NewDeleteEndModelFailed(msg.error), nil -// case tea.KeyMsg: -// switch msg.String() { -// case "ctrl+c": -// return m, tea.Quit -// case "q", "esc", "n": -// return m.previous, m.previous.Init() -// case "up", "k": -// if m.cursor > 0 { -// m.cursor-- -// } -// case "down", "j": -// if m.cursor < len(m.repos)-1 { -// m.cursor++ -// } -// case "y": -// m.loading = true -// return m, tea.Batch(deleteRepos(m.client, m.repos), spinner.Tick) -// } -// default: -// var cmd tea.Cmd -// m.spinner, cmd = m.spinner.Update(msg) -// return m, cmd -// } -// return m, nil -// } - -// func (m DeletingModel) View() string { -// if m.loading { -// return redFaintForeground(m.spinner.View()) + redForeground(" Deleting repositories...") -// } - -// s := redForeground("Are you sure you want to delete the selected repositories? (y/N)\n\n") -// for i, repo := range m.repos { -// line := faint(iconSelected+" "+repo.Name) + "\n" -// if m.cursor == i { -// line = "\n" + boldRedForeground(line) + viewRepositoryDetails(repo) -// } -// s += line -// } -// return s + helpView([]helpOption{ -// {"q/esc/n", "abort", true}, -// {"up/down", "navigate", false}, -// {"y", "delete items", false}, -// }) -// } - -// type reposDeletedMsg struct { -// total int -// } - -// func deleteRepos(client *github.Client, repos []*forkcleaner.RepositoryWithDetails) tea.Cmd { -// return func() tea.Msg { -// if err := forkcleaner.Delete(context.Background(), client, repos); err != nil { -// return errMsg{err} -// } -// return reposDeletedMsg{len(repos)} -// } -// } diff --git a/internal/ui/help.go b/internal/ui/help.go deleted file mode 100644 index 8115b45..0000000 --- a/internal/ui/help.go +++ /dev/null @@ -1,43 +0,0 @@ -package ui - -// import ( -// "strings" - -// "github.com/muesli/termenv" -// ) - -// func singleOptionHelp(k, v string) string { -// return helpView([]helpOption{ -// {k, v, true}, -// }) -// } - -// var separator = midGrayForeground(" • ") - -// func helpView(options []helpOption) string { -// var lines []string - -// var line []string -// for i, help := range options { -// if help.primary { -// line = append(line, grayForeground(help.key)+" "+termenv.String(help.help).Foreground(secondary).Faint().String()) -// } else { -// line = append(line, grayForeground(help.key)+" "+midGrayForeground(help.help)) -// } -// // splits in rows of 3 options max -// if (i+1)%3 == 0 { -// lines = append(lines, strings.Join(line, separator)) -// line = []string{} -// } -// } - -// // append remainder -// lines = append(lines, strings.Join(line, separator)) - -// return "\n\n" + strings.Join(lines, "\n") -// } - -// type helpOption struct { -// key, help string -// primary bool -// } diff --git a/internal/ui/initial.go b/internal/ui/initial.go deleted file mode 100644 index c475d77..0000000 --- a/internal/ui/initial.go +++ /dev/null @@ -1,255 +0,0 @@ -package ui - -import ( - "context" - "fmt" - "log" - - forkcleaner "github.com/caarlos0/fork-cleaner/v2" - "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/bubbles/list" - "github.com/charmbracelet/bubbles/spinner" - tea "github.com/charmbracelet/bubbletea" - "github.com/google/go-github/v33/github" -) - -var keySelectAll = key.NewBinding( - key.WithKeys("a"), - key.WithHelp("a", "select all"), -) - -var keySelectNone = key.NewBinding( - key.WithKeys("n"), - key.WithHelp("n", "select none"), -) - -var keySelectToggle = key.NewBinding( - key.WithKeys(" "), - key.WithHelp("space", "toggle selected item"), -) - -var keyDeletedSelected = key.NewBinding( - key.WithKeys("d"), - key.WithHelp("d", "delete selected forks"), -) - -var keyConfirmDelete = key.NewBinding( - key.WithKeys("y"), - key.WithHelp("y", "confirm deleting"), -) - -var keyCancelDelete = key.NewBinding( - key.WithKeys("n", "esc"), - key.WithHelp("n/esc", "go back"), -) - -var regularKeys = []key.Binding{ - keySelectToggle, - keySelectAll, - keySelectNone, - keyDeletedSelected, -} - -var confirmingDeleteKeys = []key.Binding{ - keyConfirmDelete, - keyCancelDelete, -} - -// NewInitialModel creates a new InitialModel with required fields. -func NewInitialModel(client *github.Client, login string) InitialModel { - return InitialModel{ - client: client, - login: login, - list: newList(false), - } -} - -func newList(confirmingDelete bool) list.Model { - list := list.NewModel([]list.Item{}, list.NewDefaultDelegate(), 0, 0) - list.Title = "Fork Cleaner" - list.SetSpinner(spinner.MiniDot) - list.AdditionalShortHelpKeys = func() []key.Binding { - if confirmingDelete { - return confirmingDeleteKeys - } - return regularKeys - } - - return list -} - -// InitialModel is the UI when the CLI starts, basically loading the repos. -type InitialModel struct { - err error - login string - client *github.Client - list list.Model - confirmingDelete bool -} - -func (m InitialModel) Init() tea.Cmd { - return enqueueGetReposCmd() -} - -func (m InitialModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - var cmds []tea.Cmd - var cmd tea.Cmd - - switch msg := msg.(type) { - case tea.WindowSizeMsg: - log.Println("tea.WindowSizeMsg") - top, right, bottom, left := listStyle.GetMargin() - m.list.SetSize(msg.Width-left-right, msg.Height-top-bottom) - case errMsg: - log.Println("errMsg") - m.err = msg.error - case getRepoListMsg: - log.Println("getRepoListMsg") - cmds = append(cmds, m.list.StartSpinner(), getReposCmd(m.client, m.login)) - case gotRepoListMsg: - log.Println("gotRepoListMsg") - var items = make([]list.Item, 0, len(msg.repos)) - for _, repo := range msg.repos { - items = append(items, item{ - repo: repo, - }) - } - - m.list.StopSpinner() - cmds = append(cmds, m.list.SetItems(items)) - case askDeleteConfirmationMsg: - log.Println("askDeleteConfirmationMsg") - var items = make([]list.Item, 0, len(msg.repos)) - for _, repo := range msg.repos { - items = append(items, item{ - repo: repo, - selected: true, - }) - } - m.list = newList(true) - m.confirmingDelete = true - cmds = append(cmds, m.list.SetItems(items)) - case tea.KeyMsg: - log.Println("tea.KeyMsg") - if !m.list.SettingFilter() { - log.Println("tea.KeyMsg -> !settingFilter") - - if key.Matches(msg, keySelectAll) { - log.Println("tea.KeyMsg -> !settingFilter -> selectAll") - for idx, i := range m.list.Items() { - item := i.(item) - item.selected = true - m.list.RemoveItem(idx) - cmds = append(cmds, m.list.InsertItem(item, idx)) - } - } - - if key.Matches(msg, keySelectNone) { - log.Println("tea.KeyMsg -> !settingFilter -> selectNone") - for idx, i := range m.list.Items() { - item := i.(item) - item.selected = false - m.list.RemoveItem(idx) - cmds = append(cmds, m.list.InsertItem(item, idx)) - } - } - - if key.Matches(msg, keySelectToggle) { - log.Println("tea.KeyMsg -> !settingFilter -> selectToggle") - item := m.list.SelectedItem().(item) - item.selected = !item.selected - idx := m.list.Index() - m.list.RemoveItem(idx) - cmds = append(cmds, m.list.InsertItem(item, idx)) - } - - if key.Matches(msg, keyDeletedSelected) { - log.Println("tea.KeyMsg -> !settingFilter -> deleteSelected") - var selected []*forkcleaner.RepositoryWithDetails - for _, i := range m.list.Items() { - item := i.(item) - if item.selected { - selected = append(selected, item.repo) - } - } - cmds = append(cmds, askDeleteConfirmationCmd(selected)) - } - } - } - - log.Println("default") - m.list, cmd = m.list.Update(msg) - cmds = append(cmds, cmd) - return m, tea.Batch(cmds...) -} - -func (m InitialModel) View() string { - if m.err != nil { - return errorStyle.Bold(true).Render("Error gathering the repository list") + - "\n" + - errorStyle.Render(m.err.Error()) - } - return m.list.View() -} - -// msgs - -type getRepoListMsg struct{} - -type gotRepoListMsg struct { - repos []*forkcleaner.RepositoryWithDetails -} - -type askDeleteConfirmationMsg struct { - repos []*forkcleaner.RepositoryWithDetails -} - -// cmds - -func enqueueGetReposCmd() tea.Cmd { - return func() tea.Msg { - log.Println("enqueueGetReposCmd") - return getRepoListMsg{} - } -} - -func getReposCmd(client *github.Client, login string) tea.Cmd { - return func() tea.Msg { - log.Println("getReposCmd") - repos, err := forkcleaner.FindAllForks(context.Background(), client, login) - if err != nil { - return errMsg{err} - } - return gotRepoListMsg{repos} - } -} - -func askDeleteConfirmationCmd(repos []*forkcleaner.RepositoryWithDetails) tea.Cmd { - return func() tea.Msg { - return askDeleteConfirmationMsg{repos} - } -} - -// models - -type item struct { - repo *forkcleaner.RepositoryWithDetails - selected bool -} - -func (i item) Title() string { - var forked string - if i.repo.ParentName != "" { - forked = fmt.Sprintf(" (forked from %s)", i.repo.ParentName) - } - if i.selected { - return iconSelected + " " + i.repo.Name + forked - } - return iconNotSelected + " " + i.repo.Name + forked -} - -func (i item) Description() string { - return detailsStyle.Render(viewRepositoryDetails(i.repo)) -} - -func (i item) FilterValue() string { return i.repo.Name } diff --git a/internal/ui/item.go b/internal/ui/item.go new file mode 100644 index 0000000..452a3e5 --- /dev/null +++ b/internal/ui/item.go @@ -0,0 +1,88 @@ +package ui + +import ( + "fmt" + "strings" + "time" + + forkcleaner "github.com/caarlos0/fork-cleaner/v2" + timeago "github.com/caarlos0/timea.go" + "github.com/charmbracelet/bubbles/list" +) + +type item struct { + repo *forkcleaner.RepositoryWithDetails + selected bool +} + +func (i item) Title() string { + var forked string + if i.repo.ParentName != "" { + forked = fmt.Sprintf(" (forked from %s)", i.repo.ParentName) + } + if i.selected { + return iconSelected + " " + i.repo.Name + forked + } + return iconNotSelected + " " + i.repo.Name + forked +} + +func (i item) Description() string { + repo := i.repo + var details []string + if repo.ParentDeleted { + details = append(details, "parent was deleted") + } + if repo.ParentDMCATakeDown { + details = append(details, "parent was taken down by DMCA") + } + if repo.Private { + details = append(details, "is private") + } + if repo.CommitsAhead > 0 { + details = append(details, fmt.Sprintf("%d commit%s ahead", repo.CommitsAhead, maybePlural(repo.CommitsAhead))) + } + if repo.Forks > 0 { + details = append(details, fmt.Sprintf("has %d fork%s", repo.Forks, maybePlural(repo.Forks))) + } + if repo.Stars > 0 { + details = append(details, fmt.Sprintf("has %d star%s", repo.Stars, maybePlural(repo.Stars))) + } + if repo.OpenPRs > 0 { + details = append(details, fmt.Sprintf("has %d open PR%s to upstream", repo.OpenPRs, maybePlural(repo.OpenPRs))) + } + if time.Now().Add(-30 * 24 * time.Hour).Before(repo.LastUpdate) { + details = append(details, fmt.Sprintf("recently updated (%s)", timeago.Of(repo.LastUpdate))) + } + + return detailsStyle.Render(strings.Join(details, separator)) +} + +func maybePlural(n int) string { + if n == 1 { + return "" + } + return "s" +} + +func (i item) FilterValue() string { return " " + i.repo.Name } + +func selectedRepos(items []list.Item) []*forkcleaner.RepositoryWithDetails { + var result []*forkcleaner.RepositoryWithDetails + for _, it := range items { + item := it.(item) + if item.selected { + result = append(result, item.repo) + } + } + return result +} + +func reposToItems(repos []*forkcleaner.RepositoryWithDetails) []list.Item { + var items = make([]list.Item, 0, len(repos)) + for _, repo := range repos { + items = append(items, item{ + repo: repo, + }) + } + return items +} diff --git a/internal/ui/keys.go b/internal/ui/keys.go new file mode 100644 index 0000000..c93f1ce --- /dev/null +++ b/internal/ui/keys.go @@ -0,0 +1,10 @@ +package ui + +import "github.com/charmbracelet/bubbles/key" + +var ( + keySelectAll = key.NewBinding(key.WithKeys("a"), key.WithHelp("a", "select all")) + keySelectNone = key.NewBinding(key.WithKeys("n"), key.WithHelp("n", "select none")) + keySelectToggle = key.NewBinding(key.WithKeys(" "), key.WithHelp("space", "toggle selected item")) + keyDeletedSelected = key.NewBinding(key.WithKeys("d"), key.WithHelp("d", "delete selected forks")) +) diff --git a/internal/ui/list.go b/internal/ui/list.go deleted file mode 100644 index fa233f8..0000000 --- a/internal/ui/list.go +++ /dev/null @@ -1,153 +0,0 @@ -package ui - -import ( - "fmt" - "strings" - "time" - - forkcleaner "github.com/caarlos0/fork-cleaner/v2" - timeago "github.com/caarlos0/timea.go" -) - -// import ( -// "fmt" -// "time" - -// forkcleaner "github.com/caarlos0/fork-cleaner/v2" -// tea "github.com/charmbracelet/bubbletea" -// "github.com/google/go-github/v33/github" -// "github.com/muesli/termenv" -// ) - -// // NewListModel creates a new ListModel with the required fields. -// func NewListModel(client *github.Client, repos []*forkcleaner.RepositoryWithDetails) ListModel { -// return ListModel{ -// client: client, -// repos: repos, -// selected: map[int]struct{}{}, -// } -// } - -// // ListModel is the UI in which the user can select which forks should be -// // deleted if any, and see details on each of them. -// type ListModel struct { -// client *github.Client -// repos []*forkcleaner.RepositoryWithDetails -// cursor int -// selected map[int]struct{} -// } - -// func (m ListModel) Init() tea.Cmd { -// return nil -// } - -// func (m ListModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { -// switch msg := msg.(type) { -// case tea.KeyMsg: -// switch msg.String() { -// case "ctrl+c", "q", "esc": -// return m, tea.Quit -// case "up", "k": -// if m.cursor > 0 { -// m.cursor-- -// } -// case "down", "j": -// if m.cursor < len(m.repos)-1 { -// m.cursor++ -// } -// case "a": -// for i := range m.repos { -// m.selected[i] = struct{}{} -// } -// case "n": -// for i := range m.selected { -// delete(m.selected, i) -// } -// case " ": -// _, ok := m.selected[m.cursor] -// if ok { -// delete(m.selected, m.cursor) -// } else { -// m.selected[m.cursor] = struct{}{} -// } -// case "d": -// var deleteable []*forkcleaner.RepositoryWithDetails -// for k := range m.selected { -// deleteable = append(deleteable, m.repos[k]) -// } -// dm := NewDeletingModel(m.client, deleteable, m) -// return dm, dm.Init() -// } -// } -// return m, nil -// } - -// func (m ListModel) View() string { -// s := boldSecondaryForeground("Which of these forks do you want to delete?\n\n") - -// for i, repo := range m.repos { -// line := repo.Name -// if _, ok := m.selected[i]; ok { -// line = iconSelected + " " + line -// } else { -// line = faint(iconNotSelected + " " + line) -// } -// line += "\n" - -// if m.cursor == i { -// nl := "" -// if i > 0 { -// nl = "\n" -// } -// line = nl + boldPrimaryForeground(line) + viewRepositoryDetails(repo) -// } - -// s += line -// } - -// return s + helpView([]helpOption{ -// {"up/down", "navigate", false}, -// {"space", "toggle selection", false}, -// {"d", "delete selected", true}, -// {"a", "select all", false}, -// {"n", "deselect all", false}, -// {"q/esc", "quit", false}, -// }) -// } - -func viewRepositoryDetails(repo *forkcleaner.RepositoryWithDetails) string { - var details []string - if repo.ParentDeleted { - details = append(details, "parent was deleted") - } - if repo.ParentDMCATakeDown { - details = append(details, "parent was taken down by DMCA") - } - if repo.Private { - details = append(details, "is private") - } - if repo.CommitsAhead > 0 { - details = append(details, fmt.Sprintf("%d commit%s ahead", repo.CommitsAhead, maybePlural(repo.CommitsAhead))) - } - if repo.Forks > 0 { - details = append(details, fmt.Sprintf("has %d fork%s", repo.Forks, maybePlural(repo.Forks))) - } - if repo.Stars > 0 { - details = append(details, fmt.Sprintf("has %d star%s", repo.Stars, maybePlural(repo.Stars))) - } - if repo.OpenPRs > 0 { - details = append(details, fmt.Sprintf("has %d open PR%s to upstream", repo.OpenPRs, maybePlural(repo.OpenPRs))) - } - if time.Now().Add(-30 * 24 * time.Hour).Before(repo.LastUpdate) { - details = append(details, fmt.Sprintf("recently updated (%s)", timeago.Of(repo.LastUpdate))) - } - - return strings.Join(details, separator) -} - -func maybePlural(n int) string { - if n == 1 { - return "" - } - return "s" -} diff --git a/internal/ui/msgs.go b/internal/ui/msgs.go new file mode 100644 index 0000000..9d0aaed --- /dev/null +++ b/internal/ui/msgs.go @@ -0,0 +1,17 @@ +package ui + +import forkcleaner "github.com/caarlos0/fork-cleaner/v2" + +type errMsg struct{ error } + +func (e errMsg) Error() string { return e.error.Error() } + +type getRepoListMsg struct{} + +type gotRepoListMsg struct { + repos []*forkcleaner.RepositoryWithDetails +} + +type reposDeletedMsg struct{} + +type requestDeleteSelectedReposMsg struct{} diff --git a/internal/ui/styles.go b/internal/ui/styles.go new file mode 100644 index 0000000..f410c90 --- /dev/null +++ b/internal/ui/styles.go @@ -0,0 +1,22 @@ +package ui + +import ( + "github.com/charmbracelet/lipgloss" +) + +var ( + errorColor = lipgloss.AdaptiveColor{ + Light: "#e94560", + Dark: "#f05945", + } + listStyle = lipgloss.NewStyle().Margin(2) + detailsStyle = lipgloss.NewStyle().PaddingLeft(2) + + errorStyle = lipgloss.NewStyle().Foreground(errorColor) +) + +const ( + iconSelected = "●" + iconNotSelected = "○" + separator = " • " +) From 24849cd3d2abed5cf22e6b433509654f27fc2def Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Fri, 20 Aug 2021 23:33:55 -0300 Subject: [PATCH 03/10] fix: import Signed-off-by: Carlos Alexandro Becker --- internal/ui/styles.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/internal/ui/styles.go b/internal/ui/styles.go index f410c90..8b205cb 100644 --- a/internal/ui/styles.go +++ b/internal/ui/styles.go @@ -1,8 +1,6 @@ package ui -import ( - "github.com/charmbracelet/lipgloss" -) +import "github.com/charmbracelet/lipgloss" var ( errorColor = lipgloss.AdaptiveColor{ From 88d9e87975b7d6f694f14ca150e5fd702e8232fe Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Thu, 26 Aug 2021 23:15:44 -0300 Subject: [PATCH 04/10] fix: improve remove Signed-off-by: Carlos Alexandro Becker --- go.mod | 10 ++++------ go.sum | 18 ++++++++---------- internal/ui/app.go | 11 ++++++++--- internal/ui/item.go | 10 ++++++---- 4 files changed, 26 insertions(+), 23 deletions(-) diff --git a/go.mod b/go.mod index 48f926a..780e1ba 100644 --- a/go.mod +++ b/go.mod @@ -3,18 +3,16 @@ module github.com/caarlos0/fork-cleaner/v2 go 1.16 require ( - github.com/caarlos0/timea.go v1.0.2 // indirect - github.com/charmbracelet/bubbles v0.8.0 + github.com/caarlos0/timea.go v1.0.2 + github.com/charmbracelet/bubbles v0.8.1-0.20210823213054-d987ef84f266 github.com/charmbracelet/bubbletea v0.14.1 - github.com/charmbracelet/lipgloss v0.2.2-0.20210525180645-66eb23093aa6 + github.com/charmbracelet/lipgloss v0.3.0 github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-github/v33 v33.0.0 - github.com/muesli/termenv v0.9.0 github.com/urfave/cli v1.22.5 + golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 // indirect golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d // indirect golang.org/x/oauth2 v0.0.0-20210810183815-faf39c7919d5 google.golang.org/appengine v1.6.7 // indirect ) - -replace github.com/charmbracelet/bubbles => ../../charm/bubbles-internal diff --git a/go.sum b/go.sum index 1b666a6..c1c79bf 100644 --- a/go.sum +++ b/go.sum @@ -38,11 +38,13 @@ github.com/atotto/clipboard v0.1.2/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn github.com/caarlos0/timea.go v1.0.2 h1:TTwrLOvn71SnLSq613h9Q9pdujOzrXXxMinNEqmpNso= github.com/caarlos0/timea.go v1.0.2/go.mod h1:MyDHBpPAvgjxyCJDk1B/LWhBVCWoTrVhyZZ+rjAcxWA= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/charmbracelet/bubbletea v0.13.1/go.mod h1:tp9tr9Dadh0PLhgiwchE5zZJXm5543JYjHG9oY+5qSg= +github.com/charmbracelet/bubbles v0.8.1-0.20210823213054-d987ef84f266 h1:uE0NRa15pVNdnF0EUIwK48ARdRdZkEZ4wLnP/GuoHkI= +github.com/charmbracelet/bubbles v0.8.1-0.20210823213054-d987ef84f266/go.mod h1:NWT/c+0rYEnYChz5qCyX4Lj6fDw9gGToh9EFJPajghU= github.com/charmbracelet/bubbletea v0.14.1 h1:pD/bM5LBEH/nDo7nKcgNUgi4uRHQhpWTIHZbG5vuSlc= github.com/charmbracelet/bubbletea v0.14.1/go.mod h1:b5lOf5mLjMg1tRn1HVla54guZB+jvsyV0yYAQja95zE= -github.com/charmbracelet/lipgloss v0.2.2-0.20210525180645-66eb23093aa6 h1:lAHD8PDu2W7USlmKEt2v1/BCfmShVXrijjbCQcofOmg= -github.com/charmbracelet/lipgloss v0.2.2-0.20210525180645-66eb23093aa6/go.mod h1:uiZUfrHLQN14I0lJ5591WtcHyY1X76pOIPSaRKPY6dE= +github.com/charmbracelet/harmonica v0.1.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= +github.com/charmbracelet/lipgloss v0.3.0 h1:5MysOD6sHr4RP4jkZNWGVIul5GKoOsP12NgbgXPvAlA= +github.com/charmbracelet/lipgloss v0.3.0/go.mod h1:VkhdBS2eNAmRkTwRKLJCFhCOVkjntMusBDxv7TXahuk= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -103,7 +105,6 @@ github.com/google/go-github/v33 v33.0.0 h1:qAf9yP0qc54ufQxzwv+u9H0tiVOnPJxo0lI/J github.com/google/go-github/v33 v33.0.0/go.mod h1:GMdDnVZY/2TsWgp/lkYnpSAh6TrzhANBBwm6k6TTEXg= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= -github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -127,21 +128,20 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 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.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 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/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA= github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68 h1:y1p/ycavWjGT9FnmSjdbWUlLGvcxrY0Rw3ATltrxOhk= github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ= -github.com/muesli/termenv v0.7.2/go.mod h1:ct2L5N2lmix82RaY3bMWwVu/jUFc9Ule0KGDCiKYPh8= +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.8.1/go.mod h1:kzt/D/4a88RoheZmwfqorY3A+tnsSMA9HJC/fQSFKo0= github.com/muesli/termenv v0.9.0 h1:wnbOaGz+LUR3jNT0zOzinPnyDaCZUQRZj9GxK8eRVl8= github.com/muesli/termenv v0.9.0/go.mod h1:R/LzAKf+suGs4IsO95y7+7DpFHO0KABgnZqtlyx2mBw= @@ -176,7 +176,6 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -279,7 +278,6 @@ golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c= diff --git a/internal/ui/app.go b/internal/ui/app.go index 7469aa6..69721b6 100644 --- a/internal/ui/app.go +++ b/internal/ui/app.go @@ -71,7 +71,12 @@ func (m AppModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { cmds = append(cmds, m.list.StartSpinner(), enqueueGetReposCmd) case requestDeleteSelectedReposMsg: log.Println("requestDeleteSelectedReposMsg") - cmds = append(cmds, deleteReposCmd(m.client, selectedRepos(m.list.Items()))) + selected, unselected := splitBySelection(m.list.Items()) + cmds = append( + cmds, + m.list.SetItems(reposToItems(unselected)), + deleteReposCmd(m.client, selected), + ) case tea.KeyMsg: if m.list.SettingFilter() { @@ -118,7 +123,7 @@ func (m AppModel) toggleSelection() tea.Cmd { item := m.list.SelectedItem().(item) item.selected = !item.selected m.list.RemoveItem(idx) - return m.list.InsertItem(item, idx) + return m.list.InsertItem(idx, item) } func (m AppModel) changeSelect(selected bool) []tea.Cmd { @@ -127,7 +132,7 @@ func (m AppModel) changeSelect(selected bool) []tea.Cmd { item := i.(item) item.selected = selected m.list.RemoveItem(idx) - cmds = append(cmds, m.list.InsertItem(item, idx)) + cmds = append(cmds, m.list.InsertItem(idx, item)) } return cmds } diff --git a/internal/ui/item.go b/internal/ui/item.go index 452a3e5..5057882 100644 --- a/internal/ui/item.go +++ b/internal/ui/item.go @@ -66,15 +66,17 @@ func maybePlural(n int) string { func (i item) FilterValue() string { return " " + i.repo.Name } -func selectedRepos(items []list.Item) []*forkcleaner.RepositoryWithDetails { - var result []*forkcleaner.RepositoryWithDetails +func splitBySelection(items []list.Item) ([]*forkcleaner.RepositoryWithDetails, []*forkcleaner.RepositoryWithDetails) { + var selected, unselected []*forkcleaner.RepositoryWithDetails for _, it := range items { item := it.(item) if item.selected { - result = append(result, item.repo) + selected = append(selected, item.repo) + } else { + unselected = append(unselected, item.repo) } } - return result + return selected, unselected } func reposToItems(repos []*forkcleaner.RepositoryWithDetails) []list.Item { From b47423d626545435e3694b6323532002a3dc0931 Mon Sep 17 00:00:00 2001 From: Carlos A Becker Date: Mon, 15 Nov 2021 16:37:52 -0300 Subject: [PATCH 05/10] fix: use go 1.17 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index abeef98..55ed912 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,7 +19,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v2 with: - go-version: 1.16 + go-version: 1.17 - name: Cache Go modules uses: actions/cache@v1 with: From 5d43c21a35e88f03693936ff7be0208734583572 Mon Sep 17 00:00:00 2001 From: Carlos A Becker Date: Mon, 15 Nov 2021 16:38:53 -0300 Subject: [PATCH 06/10] fix: actually delete stuff --- internal/ui/commands.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/internal/ui/commands.go b/internal/ui/commands.go index bb842ef..d1774e0 100644 --- a/internal/ui/commands.go +++ b/internal/ui/commands.go @@ -4,7 +4,6 @@ import ( "context" "log" "strings" - "time" forkcleaner "github.com/caarlos0/fork-cleaner/v2" tea "github.com/charmbracelet/bubbletea" @@ -22,10 +21,9 @@ func deleteReposCmd(client *github.Client, repos []*forkcleaner.RepositoryWithDe names = append(names, r.Name) } log.Println("deleteReposCmd", strings.Join(names, ", ")) - time.Sleep(time.Second) - // if err := forkcleaner.Delete(context.Background(), client, repos); err != nil { - // return errMsg{err} - // } + if err := forkcleaner.Delete(context.Background(), client, repos); err != nil { + return errMsg{err} + } return reposDeletedMsg{} } } From 8c802f4be1e5c99e9d7c0f53728bf53e18359348 Mon Sep 17 00:00:00 2001 From: Carlos A Becker Date: Wed, 15 Jun 2022 09:35:03 -0300 Subject: [PATCH 07/10] feat: update deps Signed-off-by: Carlos A Becker --- cmd/fork-cleaner/main.go | 44 ++++++++++++--------- fork-cleaner.go | 2 +- go.mod | 30 +++++++------- go.sum | 84 +++++++++++++++++++++------------------- internal/ui/app.go | 2 +- internal/ui/commands.go | 2 +- 6 files changed, 88 insertions(+), 76 deletions(-) diff --git a/cmd/fork-cleaner/main.go b/cmd/fork-cleaner/main.go index 34e1f41..a318e83 100644 --- a/cmd/fork-cleaner/main.go +++ b/cmd/fork-cleaner/main.go @@ -7,8 +7,8 @@ import ( "github.com/caarlos0/fork-cleaner/v2/internal/ui" tea "github.com/charmbracelet/bubbletea" - "github.com/google/go-github/v33/github" - "github.com/urfave/cli" + "github.com/google/go-github/v45/github" + "github.com/urfave/cli/v2" "golang.org/x/oauth2" ) @@ -18,23 +18,29 @@ func main() { app := cli.NewApp() app.Name = "fork-cleaner" app.Version = version - app.Author = "Carlos Alexandro Becker (caarlos0@gmail.com)" + app.Authors = []*cli.Author{{ + Name: "Carlos Alexandro Becker", + Email: "carlos@becker.software", + }} app.Usage = "Delete old, unused forks" app.Flags = []cli.Flag{ - cli.StringFlag{ - EnvVar: "GITHUB_TOKEN", - Name: "token, t", - Usage: "Your GitHub token", + &cli.StringFlag{ + EnvVars: []string{"GITHUB_TOKEN"}, + Name: "token", + Usage: "Your GitHub token", + Aliases: []string{"t"}, }, - cli.StringFlag{ - EnvVar: "GITHUB_URL", - Name: "github-url, g", - Usage: "Base GitHub URL", - Value: "https://api.github.com/", + &cli.StringFlag{ + EnvVars: []string{"GITHUB_URL"}, + Name: "github-url", + Aliases: []string{"g"}, + Usage: "Base GitHub URL", + Value: "https://api.github.com/", }, - cli.StringFlag{ - Name: "user, u", - Usage: "GitHub username or organization name. Defaults to current user.", + &cli.StringFlag{ + Name: "user", + Aliases: []string{"u"}, + Usage: "GitHub username or organization name. Defaults to current user.", }, } @@ -42,7 +48,7 @@ func main() { log.SetFlags(0) f, err := tea.LogToFile("fork-cleaner.log", "") if err != nil { - return cli.NewExitError(err.Error(), 1) + return cli.Exit(err.Error(), 1) } defer func() { _ = f.Close() }() @@ -55,18 +61,18 @@ func main() { tc := oauth2.NewClient(ctx, ts) client, err := github.NewEnterpriseClient(ghurl, ghurl, tc) if err != nil { - return cli.NewExitError(err.Error(), 1) + return cli.Exit(err.Error(), 1) } if token == "" { - return cli.NewExitError("missing github token", 1) + return cli.Exit("missing github token", 1) } p := tea.NewProgram(ui.NewAppModel(client, login)) p.EnterAltScreen() defer p.ExitAltScreen() if err = p.Start(); err != nil { - return cli.NewExitError(err.Error(), 1) + return cli.Exit(err.Error(), 1) } return nil } diff --git a/fork-cleaner.go b/fork-cleaner.go index 4b8b2a5..e094f60 100644 --- a/fork-cleaner.go +++ b/fork-cleaner.go @@ -9,7 +9,7 @@ import ( "strings" "time" - "github.com/google/go-github/v33/github" + "github.com/google/go-github/v45/github" ) const pageSize = 100 diff --git a/go.mod b/go.mod index 0d8bbc3..5c954e2 100644 --- a/go.mod +++ b/go.mod @@ -4,33 +4,35 @@ go 1.17 require ( github.com/caarlos0/timea.go v1.0.2 - github.com/charmbracelet/bubbles v0.9.0 - github.com/charmbracelet/bubbletea v0.15.0 - github.com/charmbracelet/lipgloss v0.3.0 - github.com/google/go-github/v33 v33.0.0 - github.com/urfave/cli v1.22.5 + github.com/charmbracelet/bubbles v0.11.0 + github.com/charmbracelet/bubbletea v0.21.0 + github.com/charmbracelet/lipgloss v0.5.0 + github.com/google/go-github/v45 v45.1.0 + github.com/urfave/cli/v2 v2.8.1 golang.org/x/oauth2 v0.0.0-20210810183815-faf39c7919d5 ) require ( - github.com/atotto/clipboard v0.1.2 // indirect - github.com/containerd/console v1.0.2 // indirect + github.com/atotto/clipboard v0.1.4 // indirect + github.com/containerd/console v1.0.3 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect github.com/golang/protobuf v1.5.2 // indirect - github.com/google/go-querystring v1.0.0 // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect - github.com/mattn/go-isatty v0.0.13 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect + github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect + github.com/muesli/cancelreader v0.2.0 // indirect github.com/muesli/reflow v0.3.0 // indirect - github.com/muesli/termenv v0.9.0 // indirect - github.com/pkg/errors v0.9.1 // indirect + github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sahilm/fuzzy v0.1.0 // indirect - golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 // indirect + github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect + golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d // indirect - golang.org/x/sys v0.0.0-20210423082822-04245dca01da // indirect - golang.org/x/term v0.0.0-20210422114643-f5beecf764ed // indirect + golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect + golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.26.0 // indirect ) diff --git a/go.sum b/go.sum index fbd5b49..c858600 100644 --- a/go.sum +++ b/go.sum @@ -32,29 +32,27 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/atotto/clipboard v0.1.2 h1:YZCtFu5Ie8qX2VmVTBnrqLSiU9XOWwqNRmdT3gIQzbY= -github.com/atotto/clipboard v0.1.2/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= +github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= +github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/caarlos0/timea.go v1.0.2 h1:TTwrLOvn71SnLSq613h9Q9pdujOzrXXxMinNEqmpNso= github.com/caarlos0/timea.go v1.0.2/go.mod h1:MyDHBpPAvgjxyCJDk1B/LWhBVCWoTrVhyZZ+rjAcxWA= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/charmbracelet/bubbles v0.9.0 h1:lqJ8FXwoLceQF2J0A+dWo1Cuu1dNyjbW4Opgdi2vkhw= -github.com/charmbracelet/bubbles v0.9.0/go.mod h1:NWT/c+0rYEnYChz5qCyX4Lj6fDw9gGToh9EFJPajghU= -github.com/charmbracelet/bubbletea v0.14.1/go.mod h1:b5lOf5mLjMg1tRn1HVla54guZB+jvsyV0yYAQja95zE= -github.com/charmbracelet/bubbletea v0.15.0 h1:7++QPke7CsjBs+tZl49x7KXTHsof+NUMhreAtwBXygE= -github.com/charmbracelet/bubbletea v0.15.0/go.mod h1:YTZSs2p3odhwYZdhqJheYHVUjU37c9OLgS85kw6NGQY= -github.com/charmbracelet/harmonica v0.1.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= -github.com/charmbracelet/lipgloss v0.3.0 h1:5MysOD6sHr4RP4jkZNWGVIul5GKoOsP12NgbgXPvAlA= -github.com/charmbracelet/lipgloss v0.3.0/go.mod h1:VkhdBS2eNAmRkTwRKLJCFhCOVkjntMusBDxv7TXahuk= +github.com/charmbracelet/bubbles v0.11.0 h1:fBLyY0PvJnd56Vlu5L84JJH6f4axhgIJ9P3NET78f0Q= +github.com/charmbracelet/bubbles v0.11.0/go.mod h1:bbeTiXwPww4M031aGi8UK2HT9RDWoiNibae+1yCMtcc= +github.com/charmbracelet/bubbletea v0.21.0 h1:f3y+kanzgev5PA916qxmDybSHU3N804uOnKnhRPXTcI= +github.com/charmbracelet/bubbletea v0.21.0/go.mod h1:GgmJMec61d08zXsOhqRC/AiOx4K4pmz+VIcRIm1FKr4= +github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= +github.com/charmbracelet/lipgloss v0.5.0 h1:lulQHuVeodSgDez+3rGiuxlPVXSnhth442DATR2/8t8= +github.com/charmbracelet/lipgloss v0.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0xFhg5SaqxScmgs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= -github.com/containerd/console v1.0.2 h1:Pi6D+aZXM+oUw1czuKgH5IJ+y0jhYcwBJfx5/Ghn9dE= -github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -101,12 +99,14 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-github/v33 v33.0.0 h1:qAf9yP0qc54ufQxzwv+u9H0tiVOnPJxo0lI/JXqw3ZM= -github.com/google/go-github/v33 v33.0.0/go.mod h1:GMdDnVZY/2TsWgp/lkYnpSAh6TrzhANBBwm6k6TTEXg= -github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-github/v45 v45.1.0 h1:SbUjHMRiCe9cHfu6Me4idWxLQEV8ZW9DLPz69zopyWo= +github.com/google/go-github/v45 v45.1.0/go.mod h1:FObaZJEDSTa/WGCzZ2Z3eoCDXWJKMenWWTrd8jrta28= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -134,37 +134,38 @@ github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69 github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA= -github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/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/muesli/cancelreader v0.2.0 h1:SOpr+CfyVNce341kKqvbhhzQhBPyJRXQaCtn03Pae1Q= +github.com/muesli/cancelreader v0.2.0/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ= 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.8.1/go.mod h1:kzt/D/4a88RoheZmwfqorY3A+tnsSMA9HJC/fQSFKo0= -github.com/muesli/termenv v0.9.0 h1:wnbOaGz+LUR3jNT0zOzinPnyDaCZUQRZj9GxK8eRVl8= -github.com/muesli/termenv v0.9.0/go.mod h1:R/LzAKf+suGs4IsO95y7+7DpFHO0KABgnZqtlyx2mBw= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= +github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739 h1:QANkGiGr39l1EESqrE0gZw0/AJNYzIvoGLhIoVYtluI= +github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 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/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI= github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU= -github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli/v2 v2.8.1 h1:CGuYNZF9IKZY/rfBe3lJpccSoIY1ytfvmgQT90cNOl4= +github.com/urfave/cli/v2 v2.8.1/go.mod h1:Z41J9TPoffeoqP0Iza0YbAhGvymRdZAd2uPmZ5JxRdY= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -178,8 +179,8 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E= -golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -236,6 +237,7 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -266,7 +268,6 @@ golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -279,21 +280,24 @@ golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210422114643-f5beecf764ed h1:Ei4bQjjpYUsS4efOUz+5Nz++IVkHk87n2zBA0NxBWc0= -golang.org/x/term v0.0.0-20210422114643-f5beecf764ed/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -340,7 +344,6 @@ golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= @@ -424,6 +427,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/ui/app.go b/internal/ui/app.go index 69721b6..6b28467 100644 --- a/internal/ui/app.go +++ b/internal/ui/app.go @@ -7,7 +7,7 @@ import ( "github.com/charmbracelet/bubbles/list" "github.com/charmbracelet/bubbles/spinner" tea "github.com/charmbracelet/bubbletea" - "github.com/google/go-github/v33/github" + "github.com/google/go-github/v45/github" ) // AppModel is the UI when the CLI starts, basically loading the repos. diff --git a/internal/ui/commands.go b/internal/ui/commands.go index d1774e0..5dbd74c 100644 --- a/internal/ui/commands.go +++ b/internal/ui/commands.go @@ -7,7 +7,7 @@ import ( forkcleaner "github.com/caarlos0/fork-cleaner/v2" tea "github.com/charmbracelet/bubbletea" - "github.com/google/go-github/v33/github" + "github.com/google/go-github/v45/github" ) func requestDeleteReposCmd() tea.Msg { From dd03748a2693e9609437b53d97bad4ed3e4a3843 Mon Sep 17 00:00:00 2001 From: Carlos A Becker Date: Wed, 15 Jun 2022 09:35:58 -0300 Subject: [PATCH 08/10] fix: 1.18 Signed-off-by: Carlos A Becker --- go.mod | 2 +- go.sum | 9 --------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 5c954e2..dbc1e6f 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/caarlos0/fork-cleaner/v2 -go 1.17 +go 1.18 require ( github.com/caarlos0/timea.go v1.0.2 diff --git a/go.sum b/go.sum index c858600..0f9b36a 100644 --- a/go.sum +++ b/go.sum @@ -32,7 +32,6 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= @@ -102,7 +101,6 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github/v45 v45.1.0 h1:SbUjHMRiCe9cHfu6Me4idWxLQEV8ZW9DLPz69zopyWo= github.com/google/go-github/v45 v45.1.0/go.mod h1:FObaZJEDSTa/WGCzZ2Z3eoCDXWJKMenWWTrd8jrta28= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= @@ -237,7 +235,6 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -280,15 +277,12 @@ golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -296,8 +290,6 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -427,7 +419,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From 00a0c21d36f0656016d463e1fd8ade52aed6a31a Mon Sep 17 00:00:00 2001 From: Carlos A Becker Date: Sun, 2 Apr 2023 20:02:42 +0000 Subject: [PATCH 09/10] fix: merge issues Signed-off-by: Carlos A Becker --- go.mod | 7 +- go.sum | 120 +++++++++++++++++++++++++++++++ internal/ui/app.go | 2 +- internal/ui/commands.go | 2 +- internal/ui/initial.go | 82 --------------------- internal/ui/list.go | 156 ---------------------------------------- 6 files changed, 127 insertions(+), 242 deletions(-) delete mode 100644 internal/ui/initial.go delete mode 100644 internal/ui/list.go diff --git a/go.mod b/go.mod index 68ce86f..107fe26 100644 --- a/go.mod +++ b/go.mod @@ -3,18 +3,19 @@ module github.com/caarlos0/fork-cleaner/v2 go 1.20 require ( + github.com/caarlos0/timea.go v1.0.2 github.com/charmbracelet/bubbles v0.15.0 github.com/charmbracelet/bubbletea v0.23.2 + github.com/charmbracelet/lipgloss v0.7.1 github.com/google/go-github/v50 v50.2.0 - github.com/muesli/termenv v0.15.1 github.com/urfave/cli/v2 v2.25.1 golang.org/x/oauth2 v0.6.0 ) require ( github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect + github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect - github.com/charmbracelet/lipgloss v0.7.1 // indirect github.com/cloudflare/circl v1.1.0 // indirect github.com/containerd/console v1.0.3 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect @@ -27,8 +28,10 @@ require ( 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.1 // indirect github.com/rivo/uniseg v0.4.4 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/sahilm/fuzzy v0.1.0 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect golang.org/x/crypto v0.7.0 // indirect golang.org/x/net v0.8.0 // indirect diff --git a/go.sum b/go.sum index e69de29..8b610ad 100644 --- a/go.sum +++ b/go.sum @@ -0,0 +1,120 @@ +github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA= +github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= +github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= +github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= +github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= +github.com/aymanbagabas/go-osc52 v1.2.1/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/caarlos0/timea.go v1.0.2 h1:TTwrLOvn71SnLSq613h9Q9pdujOzrXXxMinNEqmpNso= +github.com/caarlos0/timea.go v1.0.2/go.mod h1:MyDHBpPAvgjxyCJDk1B/LWhBVCWoTrVhyZZ+rjAcxWA= +github.com/charmbracelet/bubbles v0.15.0 h1:c5vZ3woHV5W2b8YZI1q7v4ZNQaPetfHuoHzx+56Z6TI= +github.com/charmbracelet/bubbles v0.15.0/go.mod h1:Y7gSFbBzlMpUDR/XM9MhZI374Q+1p1kluf1uLl8iK74= +github.com/charmbracelet/bubbletea v0.23.1/go.mod h1:JAfGK/3/pPKHTnAS8JIE2u9f61BjWTQY57RbT25aMXU= +github.com/charmbracelet/bubbletea v0.23.2 h1:vuUJ9HJ7b/COy4I30e8xDVQ+VRDUEFykIjryPfgsdps= +github.com/charmbracelet/bubbletea v0.23.2/go.mod h1:FaP3WUivcTM0xOKNmhciz60M6I+weYLF76mr1JyI7sM= +github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= +github.com/charmbracelet/lipgloss v0.6.0/go.mod h1:tHh2wr34xcHjC2HCXIlGSG1jaDF0S0atAUvBMP6Ppuk= +github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E= +github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c= +github.com/cloudflare/circl v1.1.0 h1:bZgT/A+cikZnKIwn7xL2OBj012Bmvho/o6RpRvv3GKY= +github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= +github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-github/v50 v50.2.0 h1:j2FyongEHlO9nxXLc+LP3wuBSVU9mVxfpdYUexMpIfk= +github.com/google/go-github/v50 v50.2.0/go.mod h1:VBY8FB6yPIjrtKhozXv4FQupxKLS6H4m6xFZlT43q8Q= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +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/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +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-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.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +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/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= +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.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ= +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.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= +github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc= +github.com/muesli/termenv v0.14.0/go.mod h1:kG/pF1E7fh949Xhe156crRUrHNyK221IuGO7Ez60Uc8= +github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs= +github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +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.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI= +github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= +github.com/urfave/cli/v2 v2.25.1 h1:zw8dSP7ghX0Gmm8vugrs6q9Ku0wzweqPyshy+syu9Gw= +github.com/urfave/cli/v2 v2.25.1/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw= +golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= +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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= diff --git a/internal/ui/app.go b/internal/ui/app.go index 6b28467..4366344 100644 --- a/internal/ui/app.go +++ b/internal/ui/app.go @@ -7,7 +7,7 @@ import ( "github.com/charmbracelet/bubbles/list" "github.com/charmbracelet/bubbles/spinner" tea "github.com/charmbracelet/bubbletea" - "github.com/google/go-github/v45/github" + "github.com/google/go-github/v50/github" ) // AppModel is the UI when the CLI starts, basically loading the repos. diff --git a/internal/ui/commands.go b/internal/ui/commands.go index 5dbd74c..0cbeb27 100644 --- a/internal/ui/commands.go +++ b/internal/ui/commands.go @@ -7,7 +7,7 @@ import ( forkcleaner "github.com/caarlos0/fork-cleaner/v2" tea "github.com/charmbracelet/bubbletea" - "github.com/google/go-github/v45/github" + "github.com/google/go-github/v50/github" ) func requestDeleteReposCmd() tea.Msg { diff --git a/internal/ui/initial.go b/internal/ui/initial.go deleted file mode 100644 index 3f0803e..0000000 --- a/internal/ui/initial.go +++ /dev/null @@ -1,82 +0,0 @@ -package ui - -import ( - "context" - - forkcleaner "github.com/caarlos0/fork-cleaner/v2" - "github.com/charmbracelet/bubbles/spinner" - tea "github.com/charmbracelet/bubbletea" - "github.com/google/go-github/v50/github" -) - -// NewInitialModel creates a new InitialModel with required fields. -func NewInitialModel(client *github.Client, login string) InitialModel { - s := spinner.New() - s.Spinner = spinner.MiniDot - - return InitialModel{ - client: client, - login: login, - spinner: s, - loading: true, - } -} - -// InitialModel is the UI when the CLI starts, basically loading the repos. -type InitialModel struct { - err error - login string - client *github.Client - spinner spinner.Model - loading bool -} - -func (m InitialModel) Init() tea.Cmd { - return tea.Batch(getRepos(m.client, m.login), m.spinner.Tick) -} - -func (m InitialModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - switch msg := msg.(type) { - case errMsg: - m.loading = false - m.err = msg.error - return m, nil - case gotRepoListMsg: - list := NewListModel(m.client, msg.repos) - return list, list.Init() - case tea.KeyMsg: - switch msg.String() { - case "ctrl+c", "q", "esc": - return m, tea.Quit - } - default: - var cmd tea.Cmd - m.spinner, cmd = m.spinner.Update(msg) - return m, cmd - } - return m, nil -} - -func (m InitialModel) View() string { - if m.loading { - return boldPrimaryForeground(m.spinner.View()) + " Gathering repositories..." + singleOptionHelp("q", "quit") - } - if m.err != nil { - return errorView("Error gathering the repository list", m.err) - } - return "" -} - -type gotRepoListMsg struct { - repos []*forkcleaner.RepositoryWithDetails -} - -func getRepos(client *github.Client, login string) tea.Cmd { - return func() tea.Msg { - repos, err := forkcleaner.FindAllForks(context.Background(), client, login) - if err != nil { - return errMsg{err} - } - return gotRepoListMsg{repos} - } -} diff --git a/internal/ui/list.go b/internal/ui/list.go deleted file mode 100644 index af7b2db..0000000 --- a/internal/ui/list.go +++ /dev/null @@ -1,156 +0,0 @@ -package ui - -import ( - "fmt" - "time" - - forkcleaner "github.com/caarlos0/fork-cleaner/v2" - tea "github.com/charmbracelet/bubbletea" - "github.com/google/go-github/v50/github" - "github.com/muesli/termenv" -) - -// NewListModel creates a new ListModel with the required fields. -func NewListModel(client *github.Client, repos []*forkcleaner.RepositoryWithDetails) ListModel { - return ListModel{ - client: client, - repos: repos, - selected: map[int]struct{}{}, - } -} - -// ListModel is the UI in which the user can select which forks should be -// deleted if any, and see details on each of them. -type ListModel struct { - client *github.Client - repos []*forkcleaner.RepositoryWithDetails - cursor int - selected map[int]struct{} -} - -func (m ListModel) Init() tea.Cmd { - return nil -} - -func (m ListModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - switch msg := msg.(type) { - case tea.KeyMsg: - switch msg.String() { - case "ctrl+c", "q", "esc": - return m, tea.Quit - case "up", "k": - if m.cursor > 0 { - m.cursor-- - } - case "down", "j": - if m.cursor < len(m.repos)-1 { - m.cursor++ - } - case "a": - for i := range m.repos { - m.selected[i] = struct{}{} - } - case "n": - for i := range m.selected { - delete(m.selected, i) - } - case " ": - _, ok := m.selected[m.cursor] - if ok { - delete(m.selected, m.cursor) - } else { - m.selected[m.cursor] = struct{}{} - } - case "d": - var deleteable []*forkcleaner.RepositoryWithDetails - for k := range m.selected { - deleteable = append(deleteable, m.repos[k]) - } - dm := NewDeletingModel(m.client, deleteable, m) - return dm, dm.Init() - } - } - return m, nil -} - -func (m ListModel) View() string { - s := boldSecondaryForeground("Which of these forks do you want to delete?\n\n") - - for i, repo := range m.repos { - line := repo.Name - if _, ok := m.selected[i]; ok { - line = iconSelected + " " + line - } else { - line = faint(iconNotSelected + " " + line) - } - line += "\n" - - if m.cursor == i { - nl := "" - if i > 0 { - nl = "\n" - } - line = nl + boldPrimaryForeground(line) + viewRepositoryDetails(repo) - } - - s += line - } - - return s + helpView([]helpOption{ - {"up/down", "navigate", false}, - {"space", "toggle selection", false}, - {"d", "delete selected", true}, - {"a", "select all", false}, - {"n", "deselect all", false}, - {"q/esc", "quit", false}, - }) -} - -func viewRepositoryDetails(repo *forkcleaner.RepositoryWithDetails) string { - var details []string - if repo.ParentName != "" { - details = append(details, fmt.Sprintf("Forked from %s", repo.ParentName)) - } - if repo.ParentDeleted { - details = append(details, "Parent repository was deleted or there are no common ancestors") - } - if repo.ParentDMCATakeDown { - details = append(details, "Parent repository was taken down by DMCA") - } - if repo.Private { - details = append(details, "Is private") - } - if repo.CommitsAhead > 0 { - details = append(details, fmt.Sprintf("Has %d commit%s ahead of parent", repo.CommitsAhead, maybePlural(repo.CommitsAhead))) - } - if repo.Forks > 0 { - details = append(details, fmt.Sprintf("Has %d fork%s", repo.Forks, maybePlural(repo.Forks))) - } - if repo.Stars > 0 { - details = append(details, fmt.Sprintf("Has %d star%s", repo.Stars, maybePlural(repo.Stars))) - } - if repo.OpenPRs > 0 { - details = append(details, fmt.Sprintf("Has %d open PR%s on parent", repo.OpenPRs, maybePlural(repo.OpenPRs))) - } - if time.Now().Add(-30 * 24 * time.Hour).Before(repo.LastUpdate) { - details = append(details, fmt.Sprintf("Was updated recently (%s)", repo.LastUpdate)) - } - - if len(details) == 0 { - return "" - } - - var s string - for _, d := range details { - s += " * " + d + "\n" - } - s += "\n" - return termenv.String(s).Faint().Italic().String() -} - -func maybePlural(n int) string { - if n == 1 { - return "" - } - return "s" -} From 3561e39388c0666969af391636f0a11849e374d3 Mon Sep 17 00:00:00 2001 From: Carlos A Becker Date: Sun, 2 Apr 2023 20:03:58 +0000 Subject: [PATCH 10/10] fix: lint Signed-off-by: Carlos A Becker --- internal/ui/app.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/ui/app.go b/internal/ui/app.go index 4366344..dcab6cc 100644 --- a/internal/ui/app.go +++ b/internal/ui/app.go @@ -20,7 +20,7 @@ type AppModel struct { // NewAppModel creates a new AppModel with required fields. func NewAppModel(client *github.Client, login string) AppModel { - list := list.NewModel([]list.Item{}, list.NewDefaultDelegate(), 0, 0) + list := list.New([]list.Item{}, list.NewDefaultDelegate(), 0, 0) list.Title = "Fork Cleaner" list.SetSpinner(spinner.MiniDot) list.AdditionalShortHelpKeys = func() []key.Binding {