mirror of https://git.jolheiser.com/ugit.git
169 lines
3.1 KiB
Go
169 lines
3.1 KiB
Go
package tui
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"sort"
|
|
"strings"
|
|
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
"github.com/charmbracelet/huh"
|
|
"github.com/charmbracelet/ssh"
|
|
"go.jolheiser.com/ugit/internal/git"
|
|
)
|
|
|
|
type tui struct {
|
|
s ssh.Session
|
|
repoDir string
|
|
note *string
|
|
repo *git.Repo
|
|
nextCmd func() error
|
|
}
|
|
|
|
func New(s ssh.Session, repoDir string) error {
|
|
t := tui{
|
|
s: s,
|
|
repoDir: repoDir,
|
|
}
|
|
t.nextCmd = t.repoList
|
|
return t.run()
|
|
}
|
|
|
|
func (t *tui) run() error {
|
|
for {
|
|
if t.nextCmd == nil {
|
|
break
|
|
}
|
|
if err := t.nextCmd(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (t *tui) repoList() error {
|
|
repoPaths, err := os.ReadDir(t.repoDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
repos := make([]*git.Repo, 0, len(repoPaths))
|
|
for _, repoName := range repoPaths {
|
|
if !strings.HasSuffix(repoName.Name(), ".git") {
|
|
continue
|
|
}
|
|
repo, err := git.NewRepo(t.repoDir, repoName.Name())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
repos = append(repos, repo)
|
|
}
|
|
sort.Slice(repos, func(i, j int) bool {
|
|
return repos[i].Name() < repos[j].Name()
|
|
})
|
|
|
|
opts := make([]huh.Option[*git.Repo], 0, len(repos))
|
|
for _, repo := range repos {
|
|
opts = append(opts, huh.NewOption(repo.Name(), repo))
|
|
}
|
|
|
|
if err := t.form(huh.NewSelect[*git.Repo]().
|
|
Title("Repo Selection").
|
|
Options(opts...).
|
|
Value(&t.repo),
|
|
).Run(); err != nil {
|
|
return err
|
|
}
|
|
|
|
t.nextCmd = t.repoMenu
|
|
return nil
|
|
}
|
|
|
|
func (t *tui) repoMenu() error {
|
|
var sel string
|
|
if err := t.form(huh.NewSelect[string]().
|
|
Title(t.repo.Name()).
|
|
Options(
|
|
huh.NewOption("edit", "edit"),
|
|
huh.NewOption("delete", "delete"),
|
|
huh.NewOption("back", "back"),
|
|
).
|
|
Value(&sel),
|
|
).Run(); err != nil {
|
|
return err
|
|
}
|
|
|
|
switch sel {
|
|
case "edit":
|
|
t.nextCmd = t.repoEdit
|
|
case "delete":
|
|
t.nextCmd = t.repoDelete
|
|
case "back":
|
|
t.nextCmd = t.repoList
|
|
default:
|
|
t.nextCmd = nil
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (t *tui) repoEdit() error {
|
|
form := git.RepoMeta{
|
|
Description: t.repo.Meta.Description,
|
|
Private: t.repo.Meta.Private,
|
|
}
|
|
if err := t.form(
|
|
huh.NewText().
|
|
Title("Description").
|
|
Value(&form.Description),
|
|
huh.NewConfirm().
|
|
Title("Visibility").
|
|
Affirmative("Private").
|
|
Negative("Public").
|
|
Value(&form.Private),
|
|
).Run(); err != nil {
|
|
return err
|
|
}
|
|
|
|
t.repo.Meta = form
|
|
if err := t.repo.SaveMeta(); err != nil {
|
|
return err
|
|
}
|
|
|
|
t.note = note(fmt.Sprintf("%q edited", t.repo.Name()))
|
|
t.nextCmd = t.repoList
|
|
return nil
|
|
}
|
|
|
|
func (t *tui) repoDelete() error {
|
|
var del bool
|
|
if err := t.form(huh.NewConfirm().
|
|
Title(fmt.Sprintf("Are you sure you want to delete %q?", t.repo.Name())).
|
|
Value(&del),
|
|
).Run(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if del {
|
|
if err := os.RemoveAll(t.repo.Path()); err != nil {
|
|
return err
|
|
}
|
|
t.note = note(fmt.Sprintf("%q deleted", t.repo.Name()))
|
|
}
|
|
t.nextCmd = t.repoList
|
|
return nil
|
|
}
|
|
|
|
func (t *tui) form(fields ...huh.Field) *huh.Form {
|
|
if t.note != nil {
|
|
fields = append([]huh.Field{huh.NewNote().Title(*t.note)}, fields...)
|
|
t.note = nil
|
|
}
|
|
return huh.NewForm(
|
|
huh.NewGroup(fields...),
|
|
).WithProgramOptions(tea.WithAltScreen()).WithInput(t.s).WithOutput(t.s).WithTheme(huh.ThemeCatppuccin())
|
|
}
|
|
|
|
func note(msg string) *string {
|
|
return &msg
|
|
}
|