Skip to content
quarry Artifact [b875ab2e0f]
Login

Artifact [b875ab2e0f]

Artifact b875ab2e0f0f483dbbdc26acb671f69915ea069590c03f0b6d83dc3c682406df:


package index

import (
	"fmt"
	"strconv"
	"strings"
)

// A LINE NOTE is a landing remark pinned to one line of the diff. It is
// an ordinary reply in the record's thread, so it is history like every
// other remark and a clone reads it without asking the forge; the body
// opens with a short header naming the line, then a blank line, then
// what the reviewer wrote:
//
//	zit-line: 1
//	file: internal/web/web.go
//	side: new
//	line: 2251
//	at: 8a0ba3d4805e…
//
//	This should be recorded before the tickets close.
//
// side is "new" for a line the branch has (an addition or context, by
// its number in the branch) and "old" for a line it removed (by its
// number in the target). at is the branch tip the reviewer was looking
// at, so a page can say when the line has moved on since.
const lineNoteMarker = "zit-line: 1"

// LineNote is where a line note points.
type LineNote struct {
	File string `json:"file"`
	Side string `json:"side"`
	Line int    `json:"line"`
	At   string `json:"at,omitempty"`
}

// Valid reports whether n names a line a diff can have.
func (n LineNote) Valid() error {
	switch {
	case n.File == "" || len(n.File) > 400 || strings.ContainsAny(n.File, "\n\r"):
		return fmt.Errorf("a line note needs a file path")
	case n.Side != "new" && n.Side != "old":
		return fmt.Errorf("a line note's side is new or old, not %q", n.Side)
	case n.Line < 1 || n.Line > 10_000_000:
		return fmt.Errorf("a line note's line is a positive number")
	case len(n.At) > 64 || strings.Trim(n.At, "0123456789abcdef") != "":
		return fmt.Errorf("a line note's at is a check-in hash")
	}
	return nil
}

// FormatLineNote writes a note's body: the header, a blank line, prose.
func FormatLineNote(n LineNote, prose string) string {
	var b strings.Builder
	fmt.Fprintf(&b, "%s\nfile: %s\nside: %s\nline: %d\n", lineNoteMarker, n.File, n.Side, n.Line)
	if n.At != "" {
		fmt.Fprintf(&b, "at: %s\n", n.At)
	}
	b.WriteString("\n")
	b.WriteString(prose)
	return b.String()
}

// ParseLineNote reads a body written by FormatLineNote: where it points,
// and the prose under the header. ok is false for any other body.
func ParseLineNote(body string) (n LineNote, prose string, ok bool) {
	head, rest, found := strings.Cut(strings.ReplaceAll(body, "\r\n", "\n"), "\n\n")
	if !found {
		head, rest = body, ""
	}
	lines := strings.Split(head, "\n")
	if len(lines) == 0 || strings.TrimSpace(lines[0]) != lineNoteMarker {
		return LineNote{}, "", false
	}
	for _, l := range lines[1:] {
		k, v, ok := strings.Cut(l, ":")
		if !ok {
			return LineNote{}, "", false
		}
		v = strings.TrimSpace(v)
		switch strings.TrimSpace(k) {
		case "file":
			n.File = v
		case "side":
			n.Side = v
		case "line":
			i, err := strconv.Atoi(v)
			if err != nil {
				return LineNote{}, "", false
			}
			n.Line = i
		case "at":
			n.At = v
		}
	}
	if n.Valid() != nil {
		return LineNote{}, "", false
	}
	return n, rest, true
}