Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

semverdemo.gno

5.68 Kb · 192 lines
  1// Package semverdemo is a small on-chain demo of the semantic-versioning
  2// parser/comparator provided by the
  3// [p/moul/x/daily/semver](/p/moul/x/daily/semver/v0) library.
  4//
  5// It holds the realm state the pure library deliberately does not: a
  6// "submitted versions" board (an avl tree of version string -> submitter
  7// address) that anyone can post to with [Submit], plus a gnoweb [Render] view
  8// that lists them in SemVer precedence order and compares arbitrary pairs. All
  9// parsing and comparison math lives in the library; this realm is just the
 10// on-chain wiring and the view.
 11package semverdemo
 12
 13import (
 14	"sort"
 15	"strconv"
 16	"strings"
 17
 18	"chain"
 19	"chain/runtime/unsafe"
 20
 21	"gno.land/p/moul/x/daily/semver/v0"
 22	"gno.land/p/nt/avl/v0"
 23)
 24
 25// submitted holds versions users have posted on-chain: version string -> submitter.
 26var submitted = avl.NewTree()
 27
 28// Submit stores a valid version on-chain, tagged with the caller's address, so
 29// Render("/sorted") can list everyone's submissions in precedence order. It
 30// validates the input with [semver.Parse] and panics on anything malformed.
 31func Submit(cur realm, version string) {
 32	if _, err := semver.Parse(version); err != nil {
 33		panic("invalid semantic version: " + version)
 34	}
 35	who := unsafe.PreviousRealm().Address()
 36	submitted.Set(version, who.String())
 37	chain.Emit("VersionSubmitted", "version", version, "by", who.String())
 38}
 39
 40// ---- Render ----------------------------------------------------------------
 41
 42func Render(path string) string {
 43	path = strings.TrimPrefix(path, "/")
 44	switch {
 45	case path == "":
 46		return renderHome()
 47	case path == "sorted":
 48		return renderSorted()
 49	default:
 50		parts := strings.SplitN(path, "/", 2)
 51		if len(parts) != 2 {
 52			return renderHome()
 53		}
 54		return renderCompare(parts[0], parts[1])
 55	}
 56}
 57
 58func renderHome() string {
 59	var b strings.Builder
 60	b.WriteString("# semver — Semantic Versioning on-chain\n\n")
 61	b.WriteString("Demo of the [`p/moul/x/daily/semver`](/p/moul/x/daily/semver/v0) ")
 62	b.WriteString("library — a port of `golang.org/x/mod/semver`. Parse ")
 63	b.WriteString("`vMAJOR.MINOR.PATCH[-prerelease][+build]` and compare with ")
 64	b.WriteString("SemVer 2.0.0 precedence.\n\n")
 65
 66	b.WriteString("## Examples\n\n")
 67	b.WriteString("| a | rel | b | note |\n|---|:---:|---|---|\n")
 68	examples := [][2]string{
 69		{"1.2.3", "1.2.10"},
 70		{"1.0.0-alpha", "1.0.0"},
 71		{"1.0.0-alpha.1", "1.0.0-alpha.beta"},
 72		{"1.0.0-rc.1", "1.0.0"},
 73		{"2.0.0", "2.0.0+build.5"},
 74	}
 75	notes := []string{
 76		"numeric patch, not lexical",
 77		"pre-release < release",
 78		"numeric id < alphanumeric id",
 79		"release candidate < final",
 80		"build metadata ignored",
 81	}
 82	for i, ex := range examples {
 83		b.WriteString("| `" + ex[0] + "` | " + relSym(semver.Compare(ex[0], ex[1])) +
 84			" | `" + ex[1] + "` | " + notes[i] + " |\n")
 85	}
 86
 87	b.WriteString("\n## Try it\n\n")
 88	b.WriteString("- Compare two versions: [`/1.2.3/1.2.10`](/r/moul/x/daily/semverdemo/v0:1.2.3/1.2.10)\n")
 89	b.WriteString("- Submitted list: [`/sorted`](/r/moul/x/daily/semverdemo/v0:sorted)\n")
 90	b.WriteString("- Submit on-chain: `Submit(\"v1.4.2\")`\n")
 91	return b.String()
 92}
 93
 94func renderCompare(a, b string) string {
 95	var sb strings.Builder
 96	sb.WriteString("# Compare\n\n")
 97	sb.WriteString(describe("A", a))
 98	sb.WriteString(describe("B", b))
 99
100	_, ea := semver.Parse(a)
101	_, eb := semver.Parse(b)
102	if ea != nil || eb != nil {
103		sb.WriteString("\n> One or both inputs are not valid semver.\n")
104		return sb.String()
105	}
106	c := semver.Compare(a, b)
107	sb.WriteString("\n## Result\n\n")
108	sb.WriteString("`" + a + "` **" + word(c) + "** `" + b + "`  ")
109	sb.WriteString("→ `Compare = " + strconv.Itoa(c) + "`\n")
110	return sb.String()
111}
112
113func describe(label, s string) string {
114	v, err := semver.Parse(s)
115	if err != nil {
116		return "## " + label + ": `" + s + "`\n\n> invalid\n\n"
117	}
118	pre := "—"
119	if len(v.Pre) > 0 {
120		pre = "`" + strings.Join(v.Pre, ".") + "`"
121	}
122	build := "—"
123	if v.Build != "" {
124		build = "`" + v.Build + "`"
125	}
126	return "## " + label + ": `" + v.Canonical() + "`\n\n" +
127		"| major | minor | patch | pre | build |\n|---|---|---|---|---|\n" +
128		"| " + strconv.Itoa(v.Major) + " | " + strconv.Itoa(v.Minor) + " | " + strconv.Itoa(v.Patch) +
129		" | " + pre + " | " + build + " |\n\n"
130}
131
132func renderSorted() string {
133	var es []entry
134	// Capture the submitter straight from Iterate's callback value, avoiding a
135	// separate avl.Get whose arity differs across gno versions.
136	submitted.Iterate("", "", func(key string, value any) bool {
137		if v, err := semver.Parse(key); err == nil {
138			who, _ := value.(string)
139			es = append(es, entry{v: v, who: who})
140		}
141		return false
142	})
143	if len(es) == 0 {
144		return "# Submitted versions\n\n_None yet._ Call `Submit(\"v1.0.0\")` to add one.\n"
145	}
146	sort.Stable(byPrecedence(es))
147
148	var b strings.Builder
149	b.WriteString("# Submitted versions (lowest → highest)\n\n")
150	b.WriteString("| # | version | submitter |\n|---|---|---|\n")
151	for i, e := range es {
152		b.WriteString("| " + strconv.Itoa(i+1) + " | `" + e.v.Canonical() + "` | `" + e.who + "` |\n")
153	}
154	return b.String()
155}
156
157// entry pairs a parsed version with the address that submitted it.
158type entry struct {
159	v   semver.Version
160	who string
161}
162
163// byPrecedence sorts entries ascending. `sort` on-chain has no Slice helper,
164// so we implement sort.Interface; ordering defers to semver.Compare on the
165// original strings.
166type byPrecedence []entry
167
168func (p byPrecedence) Len() int { return len(p) }
169func (p byPrecedence) Less(i, j int) bool {
170	return semver.Compare(p[i].v.Orig, p[j].v.Orig) < 0
171}
172func (p byPrecedence) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
173
174func relSym(c int) string {
175	switch {
176	case c < 0:
177		return "&lt;"
178	case c > 0:
179		return "&gt;"
180	}
181	return "="
182}
183
184func word(c int) string {
185	switch {
186	case c < 0:
187		return "<"
188	case c > 0:
189		return ">"
190	}
191	return "=="
192}