Detailed changes
@@ -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
+}
@@ -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(),
)
@@ -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
}
@@ -22,7 +22,6 @@ func NewBubble(items []string) *Bubble {
NormalStyle: normalStyle,
SelectedStyle: selectedStyle,
Items: items,
- selectedItem: -1,
}
}
@@ -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 {
@@ -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
+}