Detailed changes
@@ -37,21 +37,19 @@ func (cl CommitLog) Less(i, j int) bool {
}
type RepoSource struct {
+ Path string
mtx sync.Mutex
- path string
repos []*Repo
commits CommitLog
readmeTransform ReadmeTransform
}
-func NewRepoSource(repoPath string, poll time.Duration, rf ReadmeTransform) *RepoSource {
- rs := &RepoSource{path: repoPath, readmeTransform: rf}
- go func() {
- for {
- rs.loadRepos()
- time.Sleep(poll)
- }
- }()
+func NewRepoSource(repoPath string, rf ReadmeTransform) *RepoSource {
+ err := os.MkdirAll(repoPath, os.ModeDir|os.FileMode(0700))
+ if err != nil {
+ log.Fatal(err)
+ }
+ rs := &RepoSource{Path: repoPath, readmeTransform: rf}
return rs
}
@@ -72,6 +70,21 @@ func (rs *RepoSource) GetRepo(name string) (*Repo, error) {
return nil, ErrMissingRepo
}
+func (rs *RepoSource) InitRepo(name string, bare bool) (*Repo, error) {
+ rs.mtx.Lock()
+ defer rs.mtx.Unlock()
+ rg, err := git.PlainInit(rs.Path+string(os.PathSeparator)+name, bare)
+ if err != nil {
+ return nil, err
+ }
+ r := &Repo{
+ Name: name,
+ Repository: rg,
+ }
+ rs.repos = append(rs.repos, r)
+ return r, nil
+}
+
func (rs *RepoSource) GetCommits(limit int) []RepoCommit {
rs.mtx.Lock()
defer rs.mtx.Unlock()
@@ -81,42 +94,51 @@ func (rs *RepoSource) GetCommits(limit int) []RepoCommit {
return rs.commits[:limit]
}
-func (rs *RepoSource) loadRepos() {
+func (rs *RepoSource) LoadRepos() error {
rs.mtx.Lock()
defer rs.mtx.Unlock()
- rd, err := os.ReadDir(rs.path)
+ rd, err := os.ReadDir(rs.Path)
if err != nil {
- return
+ return err
}
rs.repos = make([]*Repo, 0)
rs.commits = make([]RepoCommit, 0)
for _, de := range rd {
rn := de.Name()
- r := &Repo{Name: rn}
- rg, err := git.PlainOpen(rs.path + string(os.PathSeparator) + rn)
+ rg, err := git.PlainOpen(rs.Path + string(os.PathSeparator) + rn)
if err != nil {
- log.Fatal(err)
+ return err
}
- r.Repository = rg
- l, err := rg.Log(&git.LogOptions{All: true})
- if err != nil {
- log.Fatal(err)
- }
- l.ForEach(func(c *object.Commit) error {
- if r.LastUpdated == nil {
- r.LastUpdated = &c.Author.When
- rf, err := c.File("README.md")
+ r, err := rs.loadRepo(rn, rg)
+ rs.repos = append(rs.repos, r)
+ }
+ return nil
+}
+
+func (rs *RepoSource) loadRepo(name string, rg *git.Repository) (*Repo, error) {
+ r := &Repo{Name: name}
+ r.Repository = rg
+ l, err := rg.Log(&git.LogOptions{All: true})
+ if err != nil {
+ return nil, err
+ }
+ err = l.ForEach(func(c *object.Commit) error {
+ if r.LastUpdated == nil {
+ r.LastUpdated = &c.Author.When
+ rf, err := c.File("README.md")
+ if err == nil {
+ rmd, err := rf.Contents()
if err == nil {
- rmd, err := rf.Contents()
- if err == nil {
- r.Readme = rs.readmeTransform(rmd)
- }
+ r.Readme = rs.readmeTransform(rmd)
}
}
- rs.commits = append(rs.commits, RepoCommit{Name: rn, Commit: c})
- return nil
- })
- sort.Sort(rs.commits)
- rs.repos = append(rs.repos, r)
+ }
+ rs.commits = append(rs.commits, RepoCommit{Name: name, Commit: c})
+ return nil
+ })
+ if err != nil {
+ return nil, err
}
+ sort.Sort(rs.commits)
+ return r, nil
}
@@ -6,7 +6,6 @@ import (
"smoothie/git"
"smoothie/tui/bubbles/commits"
"smoothie/tui/bubbles/selection"
- "time"
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
@@ -30,6 +29,7 @@ type Bubble struct {
error string
width int
height int
+ session ssh.Session
windowChanges <-chan ssh.Window
repoSource *git.RepoSource
repos []*git.Repo
@@ -40,17 +40,25 @@ type Bubble struct {
readmeViewport *ViewportBubble
}
-func NewBubble(width int, height int, windowChanges <-chan ssh.Window, repoSource *git.RepoSource) *Bubble {
+type Config struct {
+ Width int
+ Height int
+ Session ssh.Session
+ WindowChanges <-chan ssh.Window
+ RepoSource *git.RepoSource
+}
+
+func NewBubble(cfg Config) *Bubble {
b := &Bubble{
- width: width,
- height: height,
- windowChanges: windowChanges,
- repoSource: repoSource,
+ width: cfg.Width,
+ height: cfg.Height,
+ windowChanges: cfg.WindowChanges,
+ repoSource: cfg.RepoSource,
boxes: make([]tea.Model, 2),
readmeViewport: &ViewportBubble{
Viewport: &viewport.Model{
Width: boxRightWidth - horizontalPadding - 2,
- Height: height - verticalPadding - viewportHeightConstant,
+ Height: cfg.Height - verticalPadding - viewportHeightConstant,
},
},
}
@@ -140,19 +148,21 @@ func glamourReadme(md string) string {
}
func SessionHandler(reposPath string) func(ssh.Session) (tea.Model, []tea.ProgramOption) {
- rs := git.NewRepoSource(reposPath, time.Second*10, glamourReadme)
+ rs := git.NewRepoSource(reposPath, glamourReadme)
return func(s ssh.Session) (tea.Model, []tea.ProgramOption) {
if len(s.Command()) == 0 {
pty, changes, active := s.Pty()
if !active {
return nil, nil
}
- return NewBubble(
- pty.Window.Width,
- pty.Window.Height,
- changes,
- rs),
- []tea.ProgramOption{tea.WithAltScreen()}
+ cfg := Config{
+ Width: pty.Window.Width,
+ Height: pty.Window.Height,
+ WindowChanges: changes,
+ RepoSource: rs,
+ Session: s,
+ }
+ return NewBubble(cfg), []tea.ProgramOption{tea.WithAltScreen()}
}
return nil, nil
}
@@ -63,8 +63,11 @@ func (b *Bubble) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
func (b *Bubble) sendMessage() tea.Msg {
- return SelectedMsg{
- Name: b.Items[b.selectedItem],
- Index: b.selectedItem,
+ if b.selectedItem >= 0 && b.selectedItem < len(b.Items) {
+ return SelectedMsg{
+ Name: b.Items[b.selectedItem],
+ Index: b.selectedItem,
+ }
}
+ return nil
}
@@ -1,10 +1,15 @@
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{}
@@ -21,24 +26,76 @@ func (b *Bubble) windowChangesCmd() tea.Msg {
return windowMsg{}
}
-func (b *Bubble) getRepoCmd(name string) tea.Cmd {
- return func() tea.Msg {
- r, err := b.repoSource.GetRepo(name)
+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}
}
- b.readmeViewport.Viewport.GotoTop()
- b.readmeViewport.Viewport.Height = b.height - verticalPadding - viewportHeightConstant
- b.readmeViewport.Viewport.Width = boxLeftWidth - 2
- b.readmeViewport.Viewport.SetContent(r.Readme)
- b.boxes[1] = b.readmeViewport
- b.activeBox = 1
- return nil
- }
-}
-func (b *Bubble) loadGitCmd() tea.Msg {
+ // 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()
+
rs := make([]string, 0)
for _, r := range b.repos {
rs = append(rs, r.Name)
@@ -55,3 +112,19 @@ func (b *Bubble) loadGitCmd() tea.Msg {
b.state = loadedState
return nil
}
+
+func (b *Bubble) getRepoCmd(name string) tea.Cmd {
+ return func() tea.Msg {
+ r, err := b.repoSource.GetRepo(name)
+ if err != nil {
+ return errMsg{err}
+ }
+ b.readmeViewport.Viewport.GotoTop()
+ b.readmeViewport.Viewport.Height = b.height - verticalPadding - viewportHeightConstant
+ b.readmeViewport.Viewport.Width = boxLeftWidth - 2
+ b.readmeViewport.Viewport.SetContent(r.Readme)
+ b.boxes[1] = b.readmeViewport
+ b.activeBox = 1
+ return nil
+ }
+}
@@ -0,0 +1,15 @@
+package tui
+
+const defaultReadme = "# Smoothie\nWelcome to Smoothie. To setup your own configuration, please clone this repo."
+
+const defaultConfig = `{
+ "name": "Smoothie",
+ "show_all_repos": true,
+ "menu": [
+ {
+ "name": "Home",
+ "repo": "config",
+ "note": ""
+ }
+ ]
+}`