diff --git a/git/git.go b/git/git.go index 3996165a5..5858e9c30 100644 --- a/git/git.go +++ b/git/git.go @@ -142,3 +142,23 @@ func (rs *RepoSource) loadRepo(name string, rg *git.Repository) (*Repo, error) { sort.Sort(rs.commits) return r, nil } + +func (r *Repo) LatestFile(path string) (string, error) { + lg, err := r.Repository.Log(&git.LogOptions{}) + if err != nil { + return "", err + } + c, err := lg.Next() + if err != nil { + return "", err + } + f, err := c.File(path) + if err != nil { + return "", nil + } + content, err := f.Contents() + if err != nil { + return "", nil + } + return content, nil +} diff --git a/main.go b/main.go index 537c061f1..fae768a15 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,7 @@ import ( gm "smoothie/server/middleware/git" lm "smoothie/server/middleware/logging" "smoothie/tui" + "time" "github.com/meowgorithm/babyenv" ) @@ -27,7 +28,7 @@ func main() { s, err := server.NewServer( cfg.Port, cfg.KeyPath, - bm.Middleware(tui.SessionHandler(cfg.RepoPath)), + bm.Middleware(tui.SessionHandler(cfg.RepoPath, time.Second*5)), gm.Middleware(cfg.RepoPath, cfg.RepoAuthPath), lm.Middleware(), ) diff --git a/tui/bubble.go b/tui/bubble.go index 4bc443084..8c8c810c7 100644 --- a/tui/bubble.go +++ b/tui/bubble.go @@ -1,11 +1,13 @@ package tui import ( + "encoding/json" "fmt" "log" "smoothie/git" "smoothie/tui/bubbles/commits" "smoothie/tui/bubbles/selection" + "time" "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" @@ -24,14 +26,33 @@ const ( quitState ) +type MenuEntry struct { + Name string `json:"name"` + Repo string `json:"repo"` +} + +type Config struct { + Name string `json:"name"` + ShowAllRepos bool `json:"show_all_repos"` + Menu []MenuEntry `json:"menu"` + RepoSource *git.RepoSource +} + +type SessionConfig struct { + Width int + Height int + WindowChanges <-chan ssh.Window +} + type Bubble struct { + config *Config state sessionState error string width int height int - session ssh.Session windowChanges <-chan ssh.Window repoSource *git.RepoSource + repoMenu []MenuEntry repos []*git.Repo boxes []tea.Model activeBox int @@ -40,25 +61,18 @@ type Bubble struct { readmeViewport *ViewportBubble } -type Config struct { - Width int - Height int - Session ssh.Session - WindowChanges <-chan ssh.Window - RepoSource *git.RepoSource -} - -func NewBubble(cfg Config) *Bubble { +func NewBubble(cfg *Config, sCfg *SessionConfig) *Bubble { b := &Bubble{ - width: cfg.Width, - height: cfg.Height, - windowChanges: cfg.WindowChanges, + config: cfg, + width: sCfg.Width, + height: sCfg.Height, + windowChanges: sCfg.WindowChanges, repoSource: cfg.RepoSource, boxes: make([]tea.Model, 2), readmeViewport: &ViewportBubble{ Viewport: &viewport.Model{ Width: boxRightWidth - horizontalPadding - 2, - Height: cfg.Height - verticalPadding - viewportHeightConstant, + Height: sCfg.Height - verticalPadding - viewportHeightConstant, }, }, } @@ -91,7 +105,7 @@ func (b *Bubble) Update(msg tea.Msg) (tea.Model, tea.Cmd) { b.width = msg.Width b.height = msg.Height case selection.SelectedMsg: - cmds = append(cmds, b.getRepoCmd(b.repos[msg.Index].Name)) + cmds = append(cmds, b.getRepoCmd(b.repoMenu[msg.Index].Repo)) } if b.state == loadedState { ab, cmd := b.boxes[b.activeBox].Update(msg) @@ -114,7 +128,7 @@ func (b *Bubble) viewForBox(i int, width int) string { } func (b *Bubble) View() string { - h := headerStyle.Width(b.width - horizontalPadding).Render("Charm Beta") + h := headerStyle.Width(b.width - horizontalPadding).Render(b.config.Name) f := footerStyle.Render("") s := "" content := "" @@ -147,22 +161,41 @@ func glamourReadme(md string) string { return mdt } -func SessionHandler(reposPath string) func(ssh.Session) (tea.Model, []tea.ProgramOption) { +func SessionHandler(reposPath string, repoPoll time.Duration) func(ssh.Session) (tea.Model, []tea.ProgramOption) { + appCfg := &Config{} rs := git.NewRepoSource(reposPath, glamourReadme) + appCfg.RepoSource = rs + go func() { + for { + _ = rs.LoadRepos() + cr, err := rs.GetRepo("config") + if err != nil { + log.Fatalf("cannot load config repo: %s", err) + } + cs, err := cr.LatestFile("config.json") + err = json.Unmarshal([]byte(cs), appCfg) + time.Sleep(repoPoll) + } + }() + err := createDefaultConfigRepo(rs) + if err != nil { + if err != nil { + log.Fatalf("cannot create config repo: %s", err) + } + } + return func(s ssh.Session) (tea.Model, []tea.ProgramOption) { if len(s.Command()) == 0 { pty, changes, active := s.Pty() if !active { return nil, nil } - cfg := Config{ + cfg := &SessionConfig{ Width: pty.Window.Width, Height: pty.Window.Height, WindowChanges: changes, - RepoSource: rs, - Session: s, } - return NewBubble(cfg), []tea.ProgramOption{tea.WithAltScreen()} + return NewBubble(appCfg, cfg), []tea.ProgramOption{tea.WithAltScreen()} } return nil, nil } diff --git a/tui/bubbles/selection/bubble.go b/tui/bubbles/selection/bubble.go index c052723ed..ab4f0c6bd 100644 --- a/tui/bubbles/selection/bubble.go +++ b/tui/bubbles/selection/bubble.go @@ -22,7 +22,6 @@ func NewBubble(items []string) *Bubble { NormalStyle: normalStyle, SelectedStyle: selectedStyle, Items: items, - selectedItem: -1, } } diff --git a/tui/commands.go b/tui/commands.go index 704f9072e..a32ad7d4c 100644 --- a/tui/commands.go +++ b/tui/commands.go @@ -1,15 +1,10 @@ package tui import ( - "os" - "path/filepath" - "smoothie/git" "smoothie/tui/bubbles/commits" "smoothie/tui/bubbles/selection" tea "github.com/charmbracelet/bubbletea" - gg "github.com/go-git/go-git/v5" - "github.com/go-git/go-git/v5/plumbing/object" ) type windowMsg struct{} @@ -27,78 +22,26 @@ func (b *Bubble) windowChangesCmd() tea.Msg { } func (b *Bubble) loadGitCmd() tea.Msg { - cn := "config" - err := b.repoSource.LoadRepos() - cr, err := b.repoSource.GetRepo(cn) - if err == git.ErrMissingRepo { - cr, err = b.repoSource.InitRepo(cn, false) - if err != nil { - return errMsg{err} - } - - // Add default README and config - rp := filepath.Join(b.repoSource.Path, cn, "README.md") - rf, err := os.Create(rp) - if err != nil { - return errMsg{err} - } - defer rf.Close() - _, err = rf.WriteString(defaultReadme) - if err != nil { - return errMsg{err} - } - err = rf.Sync() - if err != nil { - return errMsg{err} - } - cp := filepath.Join(b.repoSource.Path, cn, "config.json") - cf, err := os.Create(cp) - if err != nil { - return errMsg{err} - } - defer cf.Close() - _, err = cf.WriteString(defaultConfig) - if err != nil { - return errMsg{err} - } - err = cf.Sync() - if err != nil { - return errMsg{err} - } - wt, err := cr.Repository.Worktree() - if err != nil { - return errMsg{err} - } - _, err = wt.Add("README.md") - if err != nil { - return errMsg{err} - } - _, err = wt.Add("config.json") - if err != nil { - return errMsg{err} - } - _, err = wt.Commit("Default init", &gg.CommitOptions{ - All: true, - Author: &object.Signature{ - Name: "Smoothie Server", - Email: "vt100@charm.sh", - }, - }) - if err != nil { - return errMsg{err} - } - err = b.repoSource.LoadRepos() - if err != nil { - return errMsg{err} - } - } else if err != nil { - return errMsg{err} - } b.repos = b.repoSource.AllRepos() - + mes := make([]MenuEntry, 0) rs := make([]string, 0) - for _, r := range b.repos { - rs = append(rs, r.Name) + for _, me := range b.config.Menu { + mes = append(mes, me) + } + if b.config.ShowAllRepos { + OUTER: + for _, r := range b.repos { + for _, me := range mes { + if r.Name == me.Repo { + continue OUTER + } + } + mes = append(mes, MenuEntry{Name: r.Name, Repo: r.Name}) + } + } + b.repoMenu = mes + for _, me := range mes { + rs = append(rs, me.Name) } b.repoSelect = selection.NewBubble(rs) b.boxes[0] = b.repoSelect @@ -107,10 +50,8 @@ func (b *Bubble) loadGitCmd() tea.Msg { boxRightWidth-horizontalPadding-2, b.repoSource.GetCommits(200), ) - b.boxes[1] = b.commitsLog - b.activeBox = 0 b.state = loadedState - return nil + return b.getRepoCmd("config")() } func (b *Bubble) getRepoCmd(name string) tea.Cmd { diff --git a/tui/defaults.go b/tui/defaults.go index e6200c18a..6724b8101 100644 --- a/tui/defaults.go +++ b/tui/defaults.go @@ -1,5 +1,14 @@ package tui +import ( + "os" + "path/filepath" + "smoothie/git" + + gg "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing/object" +) + const defaultReadme = "# Smoothie\nWelcome to Smoothie. To setup your own configuration, please clone this repo." const defaultConfig = `{ @@ -13,3 +22,68 @@ const defaultConfig = `{ } ] }` + +func createFile(path string, content string) error { + f, err := os.Create(path) + if err != nil { + return err + } + defer f.Close() + _, err = f.WriteString(content) + if err != nil { + return err + } + return f.Sync() +} + +func createDefaultConfigRepo(rs *git.RepoSource) error { + cn := "config" + err := rs.LoadRepos() + cr, err := rs.GetRepo(cn) + if err == git.ErrMissingRepo { + cr, err = rs.InitRepo(cn, false) + if err != nil { + return err + } + + rp := filepath.Join(rs.Path, cn, "README.md") + err = createFile(rp, defaultReadme) + if err != nil { + return err + } + cp := filepath.Join(rs.Path, cn, "config.json") + err = createFile(cp, defaultConfig) + if err != nil { + return err + } + wt, err := cr.Repository.Worktree() + if err != nil { + return err + } + _, err = wt.Add("README.md") + if err != nil { + return err + } + _, err = wt.Add("config.json") + if err != nil { + return err + } + _, err = wt.Commit("Default init", &gg.CommitOptions{ + All: true, + Author: &object.Signature{ + Name: "Smoothie Server", + Email: "vt100@charm.sh", + }, + }) + if err != nil { + return err + } + err = rs.LoadRepos() + if err != nil { + return err + } + } else if err != nil { + return err + } + return nil +}