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

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

semverdemo.gno

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