middleware.go

  1package server
  2
  3import (
  4	"fmt"
  5	"path/filepath"
  6	"strings"
  7
  8	"github.com/alecthomas/chroma/lexers"
  9	gansi "github.com/charmbracelet/glamour/ansi"
 10	"github.com/charmbracelet/lipgloss"
 11	appCfg "github.com/charmbracelet/soft-serve/internal/config"
 12	"github.com/charmbracelet/soft-serve/internal/tui/bubbles/git/types"
 13	"github.com/charmbracelet/wish"
 14	"github.com/charmbracelet/wish/git"
 15	"github.com/gliderlabs/ssh"
 16	gg "github.com/go-git/go-git/v5"
 17	"github.com/muesli/termenv"
 18)
 19
 20// softServeMiddleware is a middleware that handles displaying files with the
 21// option of syntax highlighting and line numbers.
 22func softServeMiddleware(ac *appCfg.Config) wish.Middleware {
 23	return func(sh ssh.Handler) ssh.Handler {
 24		return func(s ssh.Session) {
 25			_, _, active := s.Pty()
 26			cmds := s.Command()
 27			if !active && len(cmds) > 0 {
 28				func() {
 29					color := false
 30					lineno := false
 31					fp := filepath.Clean(cmds[0])
 32					ps := strings.Split(fp, "/")
 33					repo := ps[0]
 34					if repo == "config" {
 35						return
 36					}
 37					repoExists := false
 38					for _, rp := range ac.Source.AllRepos() {
 39						if rp.Name == repo {
 40							repoExists = true
 41						}
 42					}
 43					if !repoExists {
 44						return
 45					}
 46					auth := ac.AuthRepo(repo, s.PublicKey())
 47					if auth < git.ReadOnlyAccess {
 48						s.Write([]byte("unauthorized"))
 49						s.Exit(1)
 50						return
 51					}
 52					for _, op := range cmds[1:] {
 53						if op == "-c" || op == "--color" {
 54							color = true
 55						} else if op == "-l" || op == "--lineno" || op == "--linenumber" {
 56							lineno = true
 57						}
 58					}
 59					rs, err := ac.Source.GetRepo(repo)
 60					if err != nil {
 61						_, _ = s.Write([]byte(err.Error()))
 62						_ = s.Exit(1)
 63						return
 64					}
 65					fc, err := readFile(rs.Repository, strings.Join(ps[1:], "/"))
 66					if err != nil {
 67						_, _ = s.Write([]byte(err.Error()))
 68						_ = s.Exit(1)
 69						return
 70					}
 71					if color {
 72						ffc, err := withFormatting(fp, fc)
 73						if err != nil {
 74							s.Write([]byte(err.Error()))
 75							s.Exit(1)
 76							return
 77						}
 78						fc = ffc
 79					}
 80					if lineno {
 81						fc = withLineNumber(fc, color)
 82					}
 83					s.Write([]byte(fc))
 84				}()
 85			}
 86			sh(s)
 87		}
 88	}
 89}
 90
 91func readFile(r *gg.Repository, fp string) (string, error) {
 92	l, err := r.Log(&gg.LogOptions{})
 93	if err != nil {
 94		return "", err
 95	}
 96	c, err := l.Next()
 97	if err != nil {
 98		return "", err
 99	}
100	f, err := c.File(fp)
101	if err != nil {
102		return "", err
103	}
104	fc, err := f.Contents()
105	if err != nil {
106		return "", err
107	}
108	return fc, nil
109}
110
111func withLineNumber(s string, color bool) string {
112	st := lipgloss.NewStyle().Foreground(lipgloss.Color("15"))
113	lines := strings.Split(s, "\n")
114	mll := fmt.Sprintf("%d", len(fmt.Sprintf("%d", len(lines))))
115	for i, l := range lines {
116		lines[i] = fmt.Sprintf("%-"+mll+"d │ %s", i+1, l)
117		if color {
118			lines[i] = st.Render(lines[i])
119		}
120	}
121	return strings.Join(lines, "\n")
122}
123
124func withFormatting(p, c string) (string, error) {
125	zero := uint(0)
126	lang := ""
127	lexer := lexers.Match(p)
128	if lexer != nil && lexer.Config() != nil {
129		lang = lexer.Config().Name
130	}
131	formatter := &gansi.CodeBlockElement{
132		Code:     c,
133		Language: lang,
134	}
135	r := strings.Builder{}
136	styles := types.DefaultStyles()
137	styles.CodeBlock.Margin = &zero
138	rctx := gansi.NewRenderContext(gansi.Options{
139		Styles:       styles,
140		ColorProfile: termenv.TrueColor,
141	})
142	err := formatter.Render(&r, rctx)
143	if err != nil {
144		return "", err
145	}
146	return r.String(), nil
147}