package modules import ( "bytes" "net/http" "strings" "unicode/utf8" "gsg/internal/core" ) type Blob struct { Path string Size int Binary bool Lines []Line } type Line struct { N int Text string } func init() { core.Register("blob", blobMod) } // blob: file contents at Ctx.Path, split into numbered lines. // Binary detection is git's own heuristic: a NUL in the first 8000 bytes. func blobMod(c *core.Ctx, args ...string) (any, error) { path := c.Path if len(args) > 0 { path = args[0] } if path == "" { return nil, core.Errorf(http.StatusNotFound, "no path") } raw, err := c.Repo.Blob(c.Commit, path) if err != nil { return nil, core.Errorf(http.StatusNotFound, "no such file %q", path) } b := Blob{Path: path, Size: len(raw)} head := raw if len(head) > 8000 { head = head[:8000] } if bytes.IndexByte(head, 0) >= 0 || !utf8.Valid(head) { b.Binary = true return b, nil } for i, t := range strings.Split(strings.TrimRight(string(raw), "\n"), "\n") { b.Lines = append(b.Lines, Line{N: i + 1, Text: t}) } return b, nil } // RawBlob is the unmodified object, for the /raw/ route. Kept separate from // Blob so the line-split view and the byte stream cache independently. type RawBlob struct { Path string Data []byte Binary bool } func (r RawBlob) Bytes() []byte { return r.Data } func (r RawBlob) IsBinary() bool { return r.Binary } func init() { core.Register("blobraw", blobRawMod) } func blobRawMod(c *core.Ctx, args ...string) (any, error) { path := c.Path if len(args) > 0 { path = args[0] } if path == "" { return nil, core.Errorf(http.StatusNotFound, "no path") } raw, err := c.Repo.Blob(c.Commit, path) if err != nil { return nil, core.Errorf(http.StatusNotFound, "no such file %q", path) } head := raw if len(head) > 8000 { head = head[:8000] } return RawBlob{Path: path, Data: raw, Binary: bytes.IndexByte(head, 0) >= 0}, nil }