1// Package semverdemo is a small on-chain demo of the semantic-versioning2// parser/comparator provided by the3// [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: a6// "submitted versions" board (an avl tree of version string -> submitter7// address) that anyone can post to with [Submit], plus a gnoweb [Render] view8// that lists them in SemVer precedence order and compares arbitrary pairs. All9// parsing and comparison math lives in the library; this realm is just the10// on-chain wiring and the view.11package semverdemo1213import (14 "sort"15 "strconv"16 "strings"1718 "chain"19 "chain/runtime/unsafe"2021 "gno.land/p/moul/x/daily/semver/v0"22 "gno.land/p/nt/avl/v0"23)2425// submitted holds versions users have posted on-chain: version string -> submitter.26var submitted = avl.NewTree()2728// Submit stores a valid version on-chain, tagged with the caller's address, so29// Render("/sorted") can list everyone's submissions in precedence order. It30// 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}3940// ---- Render ----------------------------------------------------------------4142func 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}5758func renderHome() string {59 var b strings.Builder60 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")6566 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 }8687 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}9394func renderCompare(a, b string) string {95 var sb strings.Builder96 sb.WriteString("# Compare\n\n")97 sb.WriteString(describe("A", a))98 sb.WriteString(describe("B", b))99100 _, 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}112113func 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}131132func renderSorted() string {133 var es []entry134 // Capture the submitter straight from Iterate's callback value, avoiding a135 // 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 false142 })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))147148 var b strings.Builder149 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}156157// entry pairs a parsed version with the address that submitted it.158type entry struct {159 v semver.Version160 who string161}162163// byPrecedence sorts entries ascending. `sort` on-chain has no Slice helper,164// so we implement sort.Interface; ordering defers to semver.Compare on the165// original strings.166type byPrecedence []entry167168func (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) < 0171}172func (p byPrecedence) Swap(i, j int) { p[i], p[j] = p[j], p[i] }173174func relSym(c int) string {175 switch {176 case c < 0:177 return "<"178 case c > 0:179 return ">"180 }181 return "="182}183184func word(c int) string {185 switch {186 case c < 0:187 return "<"188 case c > 0:189 return ">"190 }191 return "=="192}193Render(path string) string
Submit(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, version string)
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.