1package tui
2
3import (
4 "bytes"
5 "fmt"
6 "text/template"
7
8 tea "github.com/charmbracelet/bubbletea"
9 "github.com/charmbracelet/lipgloss"
10 gitypes "github.com/charmbracelet/soft-serve/internal/tui/bubbles/git/types"
11 "github.com/charmbracelet/soft-serve/internal/tui/bubbles/repo"
12 "github.com/charmbracelet/soft-serve/internal/tui/bubbles/selection"
13 gm "github.com/charmbracelet/wish/git"
14)
15
16type errMsg struct{ err error }
17
18func (e errMsg) Error() string {
19 return e.err.Error()
20}
21
22func (b *Bubble) setupCmd() tea.Msg {
23 if b.config == nil || b.config.Source == nil {
24 return errMsg{err: fmt.Errorf("config not set")}
25 }
26 mes, err := b.menuEntriesFromSource()
27 if err != nil {
28 return errMsg{err}
29 }
30 if len(mes) == 0 {
31 return errMsg{fmt.Errorf("no repos found")}
32 }
33 b.repoMenu = mes
34 rs := make([]string, 0)
35 for _, m := range mes {
36 rs = append(rs, m.Name)
37 }
38 b.repoSelect = selection.NewBubble(rs, b.styles)
39 b.boxes[0] = b.repoSelect
40
41 // Jump to an initial repo
42 ir := -1
43 if b.initialRepo != "" {
44 for i, me := range b.repoMenu {
45 if me.Repo == b.initialRepo {
46 ir = i
47 }
48 }
49 }
50 if ir == -1 {
51 b.boxes[1] = b.repoMenu[0].bubble
52 b.activeBox = 0
53 } else {
54 b.boxes[1] = b.repoMenu[ir].bubble
55 b.repoSelect.SelectedItem = ir
56 b.activeBox = 1
57 }
58
59 b.state = loadedState
60 return nil
61}
62
63func (b *Bubble) menuEntriesFromSource() ([]MenuEntry, error) {
64 mes := make([]MenuEntry, 0)
65 for _, cr := range b.config.Repos {
66 acc := b.config.AuthRepo(cr.Repo, b.session.PublicKey())
67 if acc == gm.NoAccess && cr.Repo != "config" {
68 continue
69 }
70 me, err := b.newMenuEntry(cr.Name, cr.Repo)
71 if err != nil {
72 return nil, err
73 }
74 mes = append(mes, me)
75 }
76 for _, r := range b.config.Source.AllRepos() {
77 var found bool
78 for _, me := range mes {
79 if me.Repo == r.Name {
80 found = true
81 }
82 }
83 if !found {
84 acc := b.config.AuthRepo(r.Name, b.session.PublicKey())
85 if acc == gm.NoAccess {
86 continue
87 }
88 me, err := b.newMenuEntry(r.Name, r.Name)
89 if err != nil {
90 return nil, err
91 }
92 mes = append(mes, me)
93 }
94 }
95 return mes, nil
96}
97
98func (b *Bubble) newMenuEntry(name string, rn string) (MenuEntry, error) {
99 gr := &Repo{
100 name: rn,
101 }
102 me := MenuEntry{Name: name, Repo: rn}
103 r, err := b.config.Source.GetRepo(rn)
104 if err != nil {
105 return me, err
106 }
107 if rn == "config" {
108 md, err := templatize(r.Readme, b.config)
109 if err != nil {
110 return me, err
111 }
112 r.Readme = md
113 }
114 gr.repo = r.Repository
115 gr.readme = r.Readme
116 gr.ref, err = r.Repository.Head()
117 if err != nil {
118 return me, err
119 }
120 boxLeftWidth := b.styles.Menu.GetWidth() + b.styles.Menu.GetHorizontalFrameSize()
121 // TODO: also send this along with a tea.WindowSizeMsg
122 var heightMargin = lipgloss.Height(b.headerView()) +
123 lipgloss.Height(b.footerView()) +
124 b.styles.RepoBody.GetVerticalFrameSize() +
125 b.styles.App.GetVerticalMargins()
126 rb := repo.NewBubble(rn, b.config.Host, b.config.Port, gr, b.styles, b.width, boxLeftWidth, b.height, heightMargin)
127 initCmd := rb.Init()
128 msg := initCmd()
129 switch msg := msg.(type) {
130 case gitypes.ErrMsg:
131 return me, fmt.Errorf("missing %s: %s", me.Repo, msg.Err.Error())
132 }
133 me.bubble = rb
134 return me, nil
135}
136
137func templatize(mdt string, tmpl interface{}) (string, error) {
138 t, err := template.New("readme").Parse(mdt)
139 if err != nil {
140 return "", err
141 }
142 buf := &bytes.Buffer{}
143 err = t.Execute(buf, tmpl)
144 if err != nil {
145 return "", err
146 }
147 return buf.String(), nil
148}