This commit is contained in:
2026-06-23 05:02:15 +08:00
commit e6f1776d4f
264 changed files with 54215 additions and 0 deletions
+58
View File
@@ -0,0 +1,58 @@
// Package marker manages spx HTML-comment markers embedded in issue/PR bodies.
//
// Markers look like: <!-- spx:spec=docs/specs/foo.md -->
//
// They let spx and the surrounding tooling round-trip metadata (spec/plan
// paths, review flags, etc.) without dedicated Gitea fields.
package marker
import (
"fmt"
"regexp"
"strings"
)
// markerRegex builds a regex matching a full marker line of the given type.
//
// The "value" capture group accepts any sequence of non-whitespace, non-'>'
// characters, mirroring how the marker is written by UpsertMarker.
func markerRegex(markerType string) *regexp.Regexp {
return regexp.MustCompile(`<!--\s*spx:` + regexp.QuoteMeta(markerType) + `=([^\s>]*)\s*-->`)
}
// reviewMarkerRegex matches the review-comment lead marker.
var reviewMarkerRegex = regexp.MustCompile(`<!--\s*spx:review=1\s*-->`)
// UpsertMarker inserts or updates a marker of the given type in body.
//
// If a marker of this type already exists, the matching marker (just the tag,
// not the whole line) is replaced in place. Otherwise the marker is appended
// to the end of the body, separated by a newline so it sits on its own line.
//
// Other marker types in the body are preserved untouched.
func UpsertMarker(body, markerType, value string) string {
newMarker := fmt.Sprintf("<!-- spx:%s=%s -->", markerType, value)
re := markerRegex(markerType)
if re.MatchString(body) {
return re.ReplaceAllString(body, newMarker)
}
if body == "" {
return newMarker
}
// Ensure separation: end with at least one newline before the new marker.
if strings.HasSuffix(body, "\n") {
return body + newMarker
}
return body + "\n" + newMarker
}
// PrependReviewMarker prepends "<!-- spx:review=1 -->\n\n" to body if not
// already present. Idempotent: bodies already containing the marker are
// returned unchanged.
func PrependReviewMarker(body string) string {
if reviewMarkerRegex.MatchString(body) {
return body
}
const lead = "<!-- spx:review=1 -->\n\n"
return lead + body
}
+64
View File
@@ -0,0 +1,64 @@
package marker
import (
"strings"
"testing"
)
func TestUpsertMarkerAppendsWhenAbsent(t *testing.T) {
got := UpsertMarker("hello body", "spec", "docs/specs/x.md")
want := "hello body\n<!-- spx:spec=docs/specs/x.md -->"
if got != want {
t.Fatalf("got %q want %q", got, want)
}
}
func TestUpsertMarkerReplacesWhenPresent(t *testing.T) {
body := "intro\n<!-- spx:spec=old.md -->\nmore"
got := UpsertMarker(body, "spec", "new.md")
if strings.Contains(got, "old.md") {
t.Fatalf("old value should be gone: %q", got)
}
if !strings.Contains(got, "<!-- spx:spec=new.md -->") {
t.Fatalf("new marker missing: %q", got)
}
// Other content preserved.
if !strings.Contains(got, "intro") || !strings.Contains(got, "more") {
t.Fatalf("surrounding content lost: %q", got)
}
}
func TestUpsertMarkerLeavesOtherTypesAlone(t *testing.T) {
body := "<!-- spx:plan=p.md -->\n<!-- spx:spec=old.md -->"
got := UpsertMarker(body, "spec", "new.md")
if !strings.Contains(got, "<!-- spx:plan=p.md -->") {
t.Fatalf("plan marker should be preserved: %q", got)
}
if !strings.Contains(got, "<!-- spx:spec=new.md -->") {
t.Fatalf("spec marker should be updated: %q", got)
}
}
func TestUpsertMarkerEmptyBody(t *testing.T) {
got := UpsertMarker("", "plan", "p.md")
want := "<!-- spx:plan=p.md -->"
if got != want {
t.Fatalf("got %q want %q", got, want)
}
}
func TestPrependReviewMarkerAdds(t *testing.T) {
got := PrependReviewMarker("body")
want := "<!-- spx:review=1 -->\n\nbody"
if got != want {
t.Fatalf("got %q want %q", got, want)
}
}
func TestPrependReviewMarkerIdempotent(t *testing.T) {
body := "<!-- spx:review=1 -->\n\nbody"
got := PrependReviewMarker(body)
if got != body {
t.Fatalf("expected unchanged, got %q", got)
}
}