45ec4a89b24f7fbeeda727dbcb8a3db59c4efd63

Author
Toby Padilla <toby@charm.sh>
Committer
Toby Padilla <toby@charm.sh>
Date

Message

Create a config repo with defaults

Diff

This diff is truncated to protect this page.

  1diff --git a/git/git.go b/git/git.go
  2index 87a9658ef1e2816928674ef2737e6e5983366793..3996165a5cde47ed3cbfcae7f5054d64dd9a7257 100644
  3--- a/git/git.go
  4+++ b/git/git.go
  5@@ -37,21 +37,19 @@ func (cl CommitLog) Less(i, j int) bool {
  6 }
  7 
  8 type RepoSource struct {
  9+	Path            string
 10 	mtx             sync.Mutex
 11-	path            string
 12 	repos           []*Repo
 13 	commits         CommitLog
 14 	readmeTransform ReadmeTransform
 15 }
 16 
 17-func NewRepoSource(repoPath string, poll time.Duration, rf ReadmeTransform) *RepoSource {
 18-	rs := &RepoSource{path: repoPath, readmeTransform: rf}
 19-	go func() {
 20-		for {
 21-			rs.loadRepos()
 22-			time.Sleep(poll)
 23-		}
 24-	}()
 25+func NewRepoSource(repoPath string, rf ReadmeTransform) *RepoSource {
 26+	err := os.MkdirAll(repoPath, os.ModeDir|os.FileMode(0700))
 27+	if err != nil {
 28+		log.Fatal(err)
 29+	}
 30+	rs := &RepoSource{Path: repoPath, readmeTransform: rf}
 31 	return rs
 32 }
 33 
 34@@ -72,6 +70,21 @@ func (rs *RepoSource) GetRepo(name string) (*Repo, error) {
 35 	return nil, ErrMissingRepo
 36 }
 37 
 38+func (rs *RepoSource) InitRepo(name string, bare bool) (*Repo, error) {
 39+	rs.mtx.Lock()
 40+	defer rs.mtx.Unlock()
 41+	rg, err := git.PlainInit(rs.Path+string(os.PathSeparator)+name, bare)
 42+	if err != nil {
 43+		return nil, err
 44+	}
 45+	r := &Repo{
 46+		Name:       name,
 47+		Repository: rg,
 48+	}
 49+	rs.repos = append(rs.repos, r)
 50+	return r, nil
 51+}
 52+
 53 func (rs *RepoSource) GetCommits(limit int) []RepoCommit {
 54 	rs.mtx.Lock()
 55 	defer rs.mtx.Unlock()
 56@@ -81,42 +94,51 @@ func (rs *RepoSource) GetCommits(limit int) []RepoCommit {
 57 	return rs.commits[:limit]
 58 }
 59 
 60-func (rs *RepoSource) loadRepos() {
 61+func (rs *RepoSource) LoadRepos() error {
 62 	rs.mtx.Lock()
 63 	defer rs.mtx.Unlock()
 64-	rd, err := os.ReadDir(rs.path)
 65+	rd, err := os.ReadDir(rs.Path)
 66 	if err != nil {
 67-		return
 68+		return err
 69 	}
 70 	rs.repos = make([]*Repo, 0)
 71 	rs.commits = make([]RepoCommit, 0)
 72 	for _, de := range rd {
 73 		rn := de.Name()
 74-		r := &Repo{Name: rn}
 75-		rg, err := git.PlainOpen(rs.path + string(os.PathSeparator) + rn)
 76+		rg, err := git.PlainOpen(rs.Path + string(os.PathSeparator) + rn)
 77 		if err != nil {
 78-			log.Fatal(err)
 79+			return err
 80 		}
 81-		r.Repository = rg
 82-		l, err := rg.Log(&git.LogOptions{All: true})
 83-		if err != nil {
 84-			log.Fatal(err)
 85-		}
 86-		l.ForEach(func(c *object.Commit) error {
 87-			if r.LastUpdated == nil {
 88-				r.LastUpdated = &c.Author.When
 89-				rf, err := c.File("README.md")
 90+		r, err := rs.loadRepo(rn, rg)
 91+		rs.repos = append(rs.repos, r)
 92+	}
 93+	return nil
 94+}
 95+
 96+func (rs *RepoSource) loadRepo(name string, rg *git.Repository) (*Repo, error) {
 97+	r := &Repo{Name: name}
 98+	r.Repository = rg
 99+	l, err := rg.Log(&git.LogOptions{All: true})
100+	if err != nil {
101+		return nil, err
102+	}
103+	err = l.ForEach(func(c *object.Commit) error {
104+		if r.LastUpdated == nil {
105diff --git a/tui/bubble.go b/tui/bubble.go
106index d16ca4bc0411eb73d18bfd73c9c4c93b5bdf8086..4bc44308435a7803317896a1e4b6dc0b4e70277c 100644
107--- a/tui/bubble.go
108+++ b/tui/bubble.go
109@@ -6,7 +6,6 @@ import (
110 	"smoothie/git"
111 	"smoothie/tui/bubbles/commits"
112 	"smoothie/tui/bubbles/selection"
113-	"time"
114 
115 	"github.com/charmbracelet/bubbles/viewport"
116 	tea "github.com/charmbracelet/bubbletea"
117@@ -30,6 +29,7 @@ type Bubble struct {
118 	error          string
119 	width          int
120 	height         int
121+	session        ssh.Session
122 	windowChanges  <-chan ssh.Window
123 	repoSource     *git.RepoSource
124 	repos          []*git.Repo
125@@ -40,17 +40,25 @@ type Bubble struct {
126 	readmeViewport *ViewportBubble
127 }
128 
129-func NewBubble(width int, height int, windowChanges <-chan ssh.Window, repoSource *git.RepoSource) *Bubble {
130+type Config struct {
131+	Width         int
132+	Height        int
133+	Session       ssh.Session
134+	WindowChanges <-chan ssh.Window
135+	RepoSource    *git.RepoSource
136+}
137+
138+func NewBubble(cfg Config) *Bubble {
139 	b := &Bubble{
140-		width:         width,
141-		height:        height,
142-		windowChanges: windowChanges,
143-		repoSource:    repoSource,
144+		width:         cfg.Width,
145+		height:        cfg.Height,
146+		windowChanges: cfg.WindowChanges,
147+		repoSource:    cfg.RepoSource,
148 		boxes:         make([]tea.Model, 2),
149 		readmeViewport: &ViewportBubble{
150 			Viewport: &viewport.Model{
151 				Width:  boxRightWidth - horizontalPadding - 2,
152-				Height: height - verticalPadding - viewportHeightConstant,
153+				Height: cfg.Height - verticalPadding - viewportHeightConstant,
154 			},
155 		},
156 	}
157@@ -140,19 +148,21 @@ func glamourReadme(md string) string {
158 }
159 
160 func SessionHandler(reposPath string) func(ssh.Session) (tea.Model, []tea.ProgramOption) {
161-	rs := git.NewRepoSource(reposPath, time.Second*10, glamourReadme)
162+	rs := git.NewRepoSource(reposPath, glamourReadme)
163 	return func(s ssh.Session) (tea.Model, []tea.ProgramOption) {
164 		if len(s.Command()) == 0 {
165 			pty, changes, active := s.Pty()
166 			if !active {
167 				return nil, nil
168 			}
169-			return NewBubble(
170-					pty.Window.Width,
171-					pty.Window.Height,
172-					changes,
173-					rs),
174-				[]tea.ProgramOption{tea.WithAltScreen()}
175+			cfg := Config{
176+				Width:         pty.Window.Width,
177+				Height:        pty.Window.Height,
178+				WindowChanges: changes,
179+				RepoSource:    rs,
180+				Session:       s,
181+			}
182+			return NewBubble(cfg), []tea.ProgramOption{tea.WithAltScreen()}
183 		}
184 		return nil, nil
185 	}
186diff --git a/tui/bubbles/selection/bubble.go b/tui/bubbles/selection/bubble.go
187index b957872e07b5d28a266b29fb9c280f182db0f8f7..c052723ed6af6d6c62a8cc832442b4f03e986611 100644
188--- a/tui/bubbles/selection/bubble.go
189+++ b/tui/bubbles/selection/bubble.go
190@@ -63,8 +63,11 @@ func (b *Bubble) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
191 }
192 
193 func (b *Bubble) sendMessage() tea.Msg {
194-	return SelectedMsg{
195-		Name:  b.Items[b.selectedItem],
196-		Index: b.selectedItem,
197+	if b.selectedItem >= 0 && b.selectedItem < len(b.Items) {
198+		return SelectedMsg{
199+			Name:  b.Items[b.selectedItem],
200+			Index: b.selectedItem,
201+		}
202 	}
203+	return nil
204 }
205diff --git a/tui/commands.go b/tui/commands.go
206index d1749ae0e9d103ca481a8f58e036212492abb642..704f9072e3958fdeb014e7eea7a6abd092649fb7 100644
207--- a/tui/commands.go
208+++ b/tui/commands.go
209@@ -1,10 +1,15 @@
210 package tui
211 
212 import (
213+	"os"
214+	"path/filepath"
215+	"smoothie/git"
216 	"smoothie/tui/bubbles/commits"
217 	"smoothie/tui/bubbles/selection"
218 
219 	tea "github.com/charmbracelet/bubbletea"
220+	gg "github.com/go-git/go-git/v5"
221+	"github.com/go-git/go-git/v5/plumbing/object"
222 )
223 
224 type windowMsg struct{}
225@@ -21,24 +26,76 @@ func (b *Bubble) windowChangesCmd() tea.Msg {
226 	return windowMsg{}
227 }
228 
229-func (b *Bubble) getRepoCmd(name string) tea.Cmd {
230-	return func() tea.Msg {
231-		r, err := b.repoSource.GetRepo(name)
232+func (b *Bubble) loadGitCmd() tea.Msg {
233+	cn := "config"
234+	err := b.repoSource.LoadRepos()
235+	cr, err := b.repoSource.GetRepo(cn)
236+	if err == git.ErrMissingRepo {
237+		cr, err = b.repoSource.InitRepo(cn, false)
238 		if err != nil {
239 			return errMsg{err}
240 		}
241-		b.readmeViewport.Viewport.GotoTop()
242-		b.readmeViewport.Viewport.Height = b.height - verticalPadding - viewportHeightConstant
243-		b.readmeViewport.Viewport.Width = boxLeftWidth - 2
244-		b.readmeViewport.Viewport.SetContent(r.Readme)
245-		b.boxes[1] = b.readmeViewport
246-		b.activeBox = 1
247-		return nil
248-	}
249-}
250 
251-func (b *Bubble) loadGitCmd() tea.Msg {
252+		// Add default README and config
253+		rp := filepath.Join(b.repoSource.Path, cn, "README.md")
254+		rf, err := os.Create(rp)
255+		if err != nil {
256+			return errMsg{err}
257+		}
258+		defer rf.Close()
259+		_, err = rf.WriteString(defaultReadme)
260+		if err != nil {
261+			return errMsg{err}
262+		}
263+		err = rf.Sync()
264+		if err != nil {
265+			return errMsg{err}
266+		}
267+		cp := filepath.Join(b.repoSource.Path, cn, "config.json")
268+		cf, err := os.Create(cp)
269+		if err != nil {
270+			return errMsg{err}
271+		}
272+		defer cf.Close()
273+		_, err = cf.WriteString(defaultConfig)
274+		if err != nil {
275+			return errMsg{err}
276+		}
277+		err = cf.Sync()
278+		if err != nil {
279+			return errMsg{err}
280+		}
281+		wt, err := cr.Repository.Worktree()
282+		if err != nil {
283+			return errMsg{err}
284+		}
285+		_, err = wt.Add("README.md")
286+		if err != nil {
287+			return errMsg{err}
288+		}
289+		_, err = wt.Add("config.json")
290+		if err != nil {
291+			return errMsg{err}
292+		}
293+		_, err = wt.Commit("Default init", &gg.CommitOptions{
294+			All: true,
295+			Author: &object.Signature{
296+				Name:  "Smoothie Server",
297+				Email: "vt100@charm.sh",
298+			},
299+		})
300+		if err != nil {
301+			return errMsg{err}
302+		}
303+		err = b.repoSource.LoadRepos()
304+		if err != nil {
305+			return errMsg{err}
306+		}
307+	} else if err != nil {
308+		return errMsg{err}
309diff --git a/tui/defaults.go b/tui/defaults.go
310new file mode 100644
311index 0000000000000000000000000000000000000000..e6200c18a72275b8a9e0195b0b1ac54eeb3e384d
312--- /dev/null
313+++ b/tui/defaults.go
314@@ -0,0 +1,15 @@
315+package tui
316+
317+const defaultReadme = "# Smoothie\nWelcome to Smoothie. To setup your own configuration, please clone this repo."
318+
319+const defaultConfig = `{
320+	"name": "Smoothie",
321+	"show_all_repos": true,
322+	"menu": [
323+	  {
324+			"name": "Home",
325+			"repo": "config",
326+			"note": ""
327+		}
328+	]
329+}`