Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions 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}