68feddf0916b6b716c4654535360a38f0abdab0f
- Author
- Ayman Bagabas <ayman.bagabas@gmail.com>
- Committer
- Ayman Bagabas <ayman.bagabas@gmail.com>
- Date
Message
Diff
This diff is truncated to protect this page.
1diff --git a/go.mod b/go.mod
2index d5f7682a9923cb74f8bca15d843e3081c3d6d8f1..cf1b09a59d8632c429f7c463253b5f089fdeebf6 100644
3--- a/go.mod
4+++ b/go.mod
5@@ -23,6 +23,7 @@ require (
6 )
7
8 require (
9+ github.com/gobwas/glob v0.2.3
10 github.com/muesli/mango v0.1.0
11 github.com/muesli/roff v0.1.0
12 )
13diff --git a/go.sum b/go.sum
14index cee667d313c1218357ce9eccd76647357f1d76e0..28b4b112f5b43f968ef2cc8e825ee12b8cdc241b 100644
15--- a/go.sum
16+++ b/go.sum
17@@ -61,6 +61,8 @@ github.com/go-git/go-git-fixtures/v4 v4.2.1 h1:n9gGL1Ct/yIw+nfsfr8s4+sbhT+Ncu2Su
18 github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0=
19 github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4=
20 github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc=
21+github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
22+github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
23 github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
24 github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
25 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
26diff --git a/internal/config/config.go b/internal/config/config.go
27index cf2f58f922f4003e57ef2dbdca28ec3aa0a26960..972fe1e4cc2efadea699d971c0bcb96df04828d2 100644
28--- a/internal/config/config.go
29+++ b/internal/config/config.go
30@@ -1,10 +1,10 @@
31 package config
32
33 import (
34- "os/exec"
35- "path/filepath"
36+ "bytes"
37 "strings"
38 "sync"
39+ "text/template"
40
41 "golang.org/x/crypto/ssh"
42 "gopkg.in/yaml.v2"
43@@ -46,6 +46,7 @@ type Repo struct {
44 Repo string `yaml:"repo"`
45 Note string `yaml:"note"`
46 Private bool `yaml:"private"`
47+ Readme string `yaml:"readme"`
48 }
49
50 // NewConfig creates a new internal Config struct.
51@@ -127,6 +128,41 @@ func (cfg *Config) Reload() error {
52 if err != nil {
53 return fmt.Errorf("bad yaml in config.yaml: %s", err)
54 }
55+ for _, r := range cfg.Source.AllRepos() {
56+ name := r.Name()
57+ pat := "README*"
58+ rp := ""
59+ for _, rr := range cfg.Repos {
60+ if name == rr.Repo {
61+ rp = rr.Readme
62+ break
63+ }
64+ }
65+ if rp != "" {
66+ pat = rp
67+ }
68+ rm := ""
69+ f, err := r.FindLatestFile(pat)
70+ if err != nil && err != object.ErrFileNotFound {
71+ return err
72+ }
73+ if err == nil {
74+ fc, err := f.Contents()
75+ if err != nil {
76+ return err
77+ }
78+ rm = fc
79+ r.ReadmePath = f.Name
80+ }
81+ if name == "config" {
82+ md, err := templatize(rm, cfg)
83+ if err != nil {
84+ return err
85+ }
86+ rm = md
87+ }
88+ r.Readme = rm
89+ }
90 return nil
91 }
92
93@@ -150,7 +186,7 @@ func (cfg *Config) createDefaultConfigRepo(yaml string) error {
94 if err != nil {
95 return err
96 }
97- _, err = rs.GetRepo(cn)
98+ r, err := rs.GetRepo(cn)
99 if err == git.ErrMissingRepo {
100 cr, err := rs.InitRepo(cn, true)
101 if err != nil {
102@@ -218,3 +254,16 @@ func (cfg *Config) isPrivate(repo string) bool {
103 }
104 return false
105 }
106+
107+func templatize(mdt string, tmpl interface{}) (string, error) {
108+ t, err := template.New("readme").Parse(mdt)
109+ if err != nil {
110+ return "", err
111+ }
112+ buf := &bytes.Buffer{}
113+ err = t.Execute(buf, tmpl)
114+ if err != nil {
115+ return "", err
116+ }
117+ return buf.String(), nil
118+}
119diff --git a/internal/config/defaults.go b/internal/config/defaults.go
120index 30ed4ec86f375ebd219cead9ae1c32fb93f4f4b1..60a93b36dfb3cccd4b8dad0c4502476ae83b5e3c 100644
121--- a/internal/config/defaults.go
122+++ b/internal/config/defaults.go
123@@ -19,13 +19,13 @@ anon-access: %s
124 # will be accepted.
125 allow-keyless: false
126
127-# Customize repo display in the menu. Only repos in this list will appear in
128-# the TUI.
129+# Customize repo display in the menu.
130 repos:
131 - name: Home
132 repo: config
133 private: true
134 note: "Configuration and content repo for this server"
135+ readme: README.md
136 `
137
138 const hasKeyUserConfig = `
139diff --git a/internal/git/git.go b/internal/git/git.go
140index 010e22f16014112e617eefa867f64b7d9fa9c002..5008477728b9e31f3d636212ca7879bc782910e4 100644
141--- a/internal/git/git.go
142+++ b/internal/git/git.go
143@@ -14,8 +14,10 @@ import (
144 "github.com/go-git/go-git/v5"
145 "github.com/go-git/go-git/v5/plumbing"
146 "github.com/go-git/go-git/v5/plumbing/object"
147+ "github.com/go-git/go-git/v5/plumbing/storer"
148 "github.com/go-git/go-git/v5/plumbing/transport"
149 "github.com/go-git/go-git/v5/storage/memory"
150+ "github.com/gobwas/glob"
151 )
152
153 // ErrMissingRepo indicates that the requested repository could not be found.
154@@ -26,6 +28,7 @@ type Repo struct {
155 path string
156 repository *git.Repository
157 Readme string
158+ ReadmePath string
159 refCommits map[plumbing.Hash]gitypes.Commits
160 head *plumbing.Reference
161 refs []*plumbing.Reference
162@@ -107,6 +110,7 @@ func (r *Repo) commitForHash(hash plumbing.Hash) (*object.Commit, error) {
163 return co, nil
164 }
165
166+// PatchCtx returns the patch for a given commit.
167 func (r *Repo) PatchCtx(ctx context.Context, commit *object.Commit) (*object.Patch, error) {
168 hash := commit.Hash
169 p, ok := r.patch[hash]
170@@ -199,14 +203,12 @@ func (r *Repo) targetHash(ref *plumbing.Reference) (plumbing.Hash, error) {
171
172 // GetReadme returns the readme for a repository.
173 func (r *Repo) GetReadme() string {
174- if r.Readme != "" {
175- return r.Readme
176- }
177- md, err := r.LatestFile("README.md")
178- if err != nil {
179- return ""
180- }
181- return md
182+ return r.Readme
183+}
184+
185+// GetReadmePath returns the path to the readme for a repository.
186+func (r *Repo) GetReadmePath() string {
187+ return r.ReadmePath
188 }
189
190 // RepoSource is a reference to an on-disk repositories.
191@@ -310,13 +312,6 @@ func (rs *RepoSource) loadRepo(path string, rg *git.Repository) (*Repo, error) {
192 return nil, err
193 }
194 r.head = ref
195- rm, err := r.LatestFile("README.md")
196- if err == object.ErrFileNotFound {
197- rm = ""
198- } else if err != nil {
199- return nil, err
200- }
201- r.Readme = rm
202 l, err := r.repository.Log(&git.LogOptions{All: true})
203 if err != nil {
204 return nil, err
205@@ -341,13 +336,40 @@ func (rs *RepoSource) loadRepo(path string, rg *git.Repository) (*Repo, error) {
206 return r, nil
207 }
208
209-// LatestFile returns the latest file at the specified path in the repository.
210-func (r *Repo) LatestFile(path string) (string, error) {
211+// FindLatestFile returns the latest file for a given path.
212+func (r *Repo) FindLatestFile(pattern string) (*object.File, error) {
213+ g, err := glob.Compile(pattern)
214+ if err != nil {
215+ return nil, err
216+ }
217 c, err := r.commitForHash(r.head.Hash())
218 if err != nil {
219- return "", err
220+ return nil, err
221 }
222- f, err := c.File(path)
223+ fi, err := c.Files()
224+ if err != nil {
225+ return nil, err
226+ }
227+ var f *object.File
228+ err = fi.ForEach(func(ff *object.File) error {
229+ if g.Match(ff.Name) {
230+ f = ff
231+ return storer.ErrStop
232+ }
233+ return nil
234+ })
235+ if err != nil {
236+ return nil, err
237+ }
238+ if f == nil {
239+ return nil, object.ErrFileNotFound
240+ }
241+ return f, nil
242+}
243diff --git a/internal/tui/bubbles/git/about/bubble.go b/internal/tui/bubbles/git/about/bubble.go
244index 4740246b670a001a93b59c4425a34cc4820877c2..2c957397d0bde01e0911261bb5dd5d4cfefc1efc 100644
245--- a/internal/tui/bubbles/git/about/bubble.go
246+++ b/internal/tui/bubbles/git/about/bubble.go
247@@ -8,6 +8,7 @@ import (
248 vp "github.com/charmbracelet/soft-serve/internal/tui/bubbles/git/viewport"
249 "github.com/charmbracelet/soft-serve/internal/tui/style"
250 "github.com/go-git/go-git/v5/plumbing"
251+ "github.com/muesli/reflow/wrap"
252 )
253
254 type Bubble struct {
255@@ -92,7 +93,18 @@ func (b *Bubble) glamourize() (string, error) {
256 if rm == "" {
257 return b.styles.AboutNoReadme.Render("No readme found."), nil
258 }
259- return types.Glamourize(w, rm)
260+ f, err := types.RenderFile(b.repo.GetReadmePath(), rm, w)
261+ if err != nil {
262+ return "", err
263+ }
264+ // For now, hard-wrap long lines in Glamour that would otherwise break the
265+ // layout when wrapping. This may be due to #43 in Reflow, which has to do
266+ // with a bug in the way lines longer than the given width are wrapped.
267+ //
268+ // https://github.com/muesli/reflow/issues/43
269+ //
270+ // TODO: solve this upstream in Glamour/Reflow.
271+ return wrap.String(f, w), nil
272 }
273
274 func (b *Bubble) setupCmd() tea.Msg {
275diff --git a/internal/tui/bubbles/git/tree/bubble.go b/internal/tui/bubbles/git/tree/bubble.go
276index 1876df204b6901eeed52f690d665fa8ef69f4d85..e6c2a22d4698cb964352b1468a33569a80b7b775 100644
277--- a/internal/tui/bubbles/git/tree/bubble.go
278+++ b/internal/tui/bubbles/git/tree/bubble.go
279@@ -7,11 +7,9 @@ import (
280 "sort"
281 "strings"
282
283- "github.com/alecthomas/chroma/lexers"
284 "github.com/charmbracelet/bubbles/list"
285 "github.com/charmbracelet/bubbles/viewport"
286 tea "github.com/charmbracelet/bubbletea"
287- gansi "github.com/charmbracelet/glamour/ansi"
288 "github.com/charmbracelet/lipgloss"
289 "github.com/charmbracelet/soft-serve/internal/tui/bubbles/git/refs"
290 "github.com/charmbracelet/soft-serve/internal/tui/bubbles/git/types"
291@@ -336,31 +334,12 @@ func (b *Bubble) renderFile(m fileMsg) string {
292 if len(strings.Split(c, "\n")) > types.MaxDiffLines {
293 s.WriteString(types.ErrFileTooLarge.Error())
294 } else {
295- lexer := lexers.Match(b.path)
296- lang := ""
297- if lexer != nil && lexer.Config() != nil {
298- lang = lexer.Config().Name
299- }
300- formatter := &gansi.CodeBlockElement{
301- Code: c,
302- Language: lang,
303- }
304- if lang == "markdown" {
305- w := b.width - b.widthMargin - b.style.RepoBody.GetHorizontalFrameSize()
306- md, err := types.Glamourize(w, c)
307- if err != nil {
308- s.WriteString(err.Error())
309- } else {
310- s.WriteString(md)
311- }
312+ w := b.width - b.widthMargin - b.style.RepoBody.GetHorizontalFrameSize()
313+ f, err := types.RenderFile(b.path, m.content, w)
314+ if err != nil {
315+ s.WriteString(err.Error())
316 } else {
317- r := strings.Builder{}
318- err := formatter.Render(&r, types.RenderCtx)
319- if err != nil {
320- s.WriteString(err.Error())
321- } else {
322- s.WriteString(r.String())
323- }
324+ s.WriteString(f)
325 }
326 }
327 return b.style.TreeFileContent.Copy().Width(b.width - b.widthMargin).Render(s.String())
328diff --git a/internal/tui/bubbles/git/types/formatter.go b/internal/tui/bubbles/git/types/formatter.go
329index ea3197a22e08a7c687b6720e6efcf8817efb480d..a7ef850e834284ddb0a41807ccd059429260d4a0 100644
330--- a/internal/tui/bubbles/git/types/formatter.go
331+++ b/internal/tui/bubbles/git/types/formatter.go
332@@ -1,9 +1,11 @@
333 package types
334
335 import (
336+ "strings"
337+
338+ "github.com/alecthomas/chroma/lexers"
339 "github.com/charmbracelet/glamour"
340 gansi "github.com/charmbracelet/glamour/ansi"
341- "github.com/muesli/reflow/wrap"
342 "github.com/muesli/termenv"
343 )
344
345@@ -52,12 +54,35 @@ func Glamourize(w int, md string) (string, error) {
346 if err != nil {
347 return "", err
348 }
349- // For now, hard-wrap long lines in Glamour that would otherwise break the
350- // layout when wrapping. This may be due to #43 in Reflow, which has to do
351- // with a bug in the way lines longer than the given width are wrapped.
352- //
353- // https://github.com/muesli/reflow/issues/43
354- //
355- // TODO: solve this upstream in Glamour/Reflow.
356- return wrap.String(mdt, w), nil
357+ return mdt, nil
358+}
359+
360+func RenderFile(path, content string, width int) (string, error) {
361+ lexer := lexers.Fallback
362+ if path == "" {
363+ lexer = lexers.Analyse(content)
364+ } else {
365+ lexer = lexers.Match(path)
366+ }
367+ lang := ""
368+ if lexer != nil && lexer.Config() != nil {
369+ lang = lexer.Config().Name
370+ }
371+ formatter := &gansi.CodeBlockElement{
372+ Code: content,
373+ Language: lang,
374+ }
375+ if lang == "markdown" {
376+ md, err := Glamourize(width, content)
377+ if err != nil {
378+ return "", err
379+ }
380+ return md, nil
381+ }
382+ r := strings.Builder{}
383+ err := formatter.Render(&r, RenderCtx)
384+ if err != nil {
385+ return "", err
386+ }
387+ return r.String(), nil
388 }
389diff --git a/internal/tui/bubbles/git/types/git.go b/internal/tui/bubbles/git/types/git.go
390index 05a1c26114c178ee6e08286c3687c7a187c6b95c..90e5436e98a21a06413dd177dd5f085c59eac91b 100644
391--- a/internal/tui/bubbles/git/types/git.go
392+++ b/internal/tui/bubbles/git/types/git.go
393@@ -14,6 +14,7 @@ type Repo interface {
394 SetHEAD(*plumbing.Reference) error
395 GetReferences() []*plumbing.Reference
396 GetReadme() string
397+ GetReadmePath() string
398 GetCommits(*plumbing.Reference) (Commits, error)
399 Repository() *git.Repository
400 Tree(*plumbing.Reference, string) (*object.Tree, error)
401diff --git a/internal/tui/commands.go b/internal/tui/commands.go
402index 20d3b16aaa4a541033f7af8c01d1f0d686d5907f..2008ae811db8f59d58152979d133c03aea521fd4 100644
403--- a/internal/tui/commands.go
404+++ b/internal/tui/commands.go
405@@ -1,9 +1,7 @@
406 package tui
407
408 import (
409- "bytes"
410 "fmt"
411- "text/template"
412
413 tea "github.com/charmbracelet/bubbletea"
414 "github.com/charmbracelet/lipgloss"
415@@ -102,13 +100,6 @@ func (b *Bubble) newMenuEntry(name string, rn string) (MenuEntry, error) {
416 if err != nil {
417 return me, err
418 }
419- if rn == "config" {
420- md, err := templatize(r.Readme, b.config)
421- if err != nil {
422- return me, err
423- }
424- r.Readme = md
425- }
426 boxLeftWidth := b.styles.Menu.GetWidth() + b.styles.Menu.GetHorizontalFrameSize()
427 // TODO: also send this along with a tea.WindowSizeMsg
428 var heightMargin = lipgloss.Height(b.headerView()) +
429@@ -125,16 +116,3 @@ func (b *Bubble) newMenuEntry(name string, rn string) (MenuEntry, error) {
430 me.bubble = rb
431 return me, nil
432 }
433-
434-func templatize(mdt string, tmpl interface{}) (string, error) {
435- t, err := template.New("readme").Parse(mdt)
436- if err != nil {
437- return "", err
438- }
439- buf := &bytes.Buffer{}
440- err = t.Execute(buf, tmpl)
441- if err != nil {
442- return "", err
443- }
444- return buf.String(), nil
445-}