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.

render.gno

6.33 Kb · 177 lines
  1package home
  2
  3import (
  4	"strconv"
  5	"strings"
  6
  7	"chain/runtime"
  8
  9	"gno.land/p/nym-moultest123/dynreplacer/v0"
 10	"gno.land/r/nym-moultest123/config/v1"
 11)
 12
 13// defaultLayout is what renders before a layout slot exists, which is not only
 14// the very first deploy: a redeploy re-runs init() and wipes every slot, so
 15// this is also the page at the exact moment someone is restoring it. It uses
 16// only computed placeholders, so it never shows an unresolved :slug: of its own.
 17//
 18// It prints no gnokey command and no gas numbers, on purpose. The one it used
 19// to print offered 1000000ugnot against 20000000 gas: a ratio of 0.05 against a
 20// mainnet floor of 0.001, and gas_fee is deducted in full as offered and never
 21// refunded, so it asked for about fifty times what the write costs. The tooling
 22// was corrected and this string was not, which is the argument against having
 23// it at all. A command baked into a realm cannot be fixed without a redeploy,
 24// and the redeploy is the thing that wipes the slots. gnohome recomputes it
 25// from content/ and from the chain, so it cannot go stale.
 26const defaultLayout = "# gno.land/r/nym-moultest123/home\n\n" +
 27	"No layout slot yet. A redeploy wipes every slot and `content/` is the source\n" +
 28	"of truth, so push them back with the tool rather than by hand:\n\n" +
 29	"    go -C tools tool gnohome tx -all\n\n" +
 30	"No gas numbers here on purpose: a command written into a realm goes stale and\n" +
 31	"cannot be corrected without another redeploy.\n\n" +
 32	"## Slots\n\n:slots:\n\n---\n\nrev :rev: · height :height: · :chainid:\n"
 33
 34func chainHeight() int64 { return runtime.ChainHeight() }
 35
 36// Render serves three views:
 37//
 38//	""            the assembled page, what https://gno.land/u/moul shows
 39//	"slots"       the slot index: name, size, revision, last write
 40//	"slots/<slug>" one slot's raw markdown, fenced
 41func Render(path string) string {
 42	switch {
 43	case path == "":
 44		return renderPage()
 45	case path == "slots":
 46		return renderIndex()
 47	case strings.HasPrefix(path, "slots/"):
 48		return renderSlot(strings.TrimPrefix(path, "slots/"))
 49	default:
 50		return "# Not found\n\nNo such path: " + strconv.Quote(path) +
 51			"\n\nTry [the slot index](/r/nym-moultest123/home:slots).\n"
 52	}
 53}
 54
 55// renderPage fills the layout. dynreplacer only invokes the callbacks whose
 56// placeholder actually occurs in the layout, so an unused slot is never even
 57// read out of the tree.
 58func renderPage() string {
 59	layout := Get(layoutSlug)
 60	if layout == "" {
 61		layout = defaultLayout
 62	}
 63
 64	r := dynreplacer.New()
 65
 66	// Computed from chain state. Registered first; assertWritableSlug keeps a
 67	// slot from ever taking one of these names, so nothing shadows them.
 68	r.RegisterCallback(":owner:", func() string { return admin.String() })
 69	r.RegisterCallback(":realm:", func() string { return realmPath })
 70	r.RegisterCallback(":chainid:", func() string { return runtime.ChainID() })
 71	r.RegisterCallback(":height:", func() string { return strconv.FormatInt(runtime.ChainHeight(), 10) })
 72	r.RegisterCallback(":rev:", func() string { return strconv.Itoa(rev) })
 73	r.RegisterCallback(":slots:", func() string { return slotLinks() })
 74
 75	// The explorer links, which are computed the same way but come from
 76	// another realm's configuration. See scan.gno.
 77	registerScanCallbacks(r)
 78
 79	// The reaction block, which is the one thing on this page a reader can
 80	// act on. See reactions.gno.
 81	registerReactionCallbacks(r)
 82
 83	// One callback per slot. The closure captures s, which is re-bound on each
 84	// iteration, so every callback sees its own slot.
 85	slots.Iterate("", "", func(key string, value any) bool {
 86		s := value.(*slot)
 87		r.RegisterCallback(":"+key+":", func() string { return s.body })
 88		return false
 89	})
 90
 91	// The notice blocks, from gno.land/r/nym-moultest123/config: a warning, a changelog
 92	// line or a bit of news, set either on every realm of moul's at once or on
 93	// this one alone, and empty by default. A pause banner rides along at the
 94	// top, so the page explains itself if this realm is ever held back.
 95	//
 96	// The path is passed explicitly rather than using config's zero-argument
 97	// TopBlock(). That form reads the caller off the stack, which is correct
 98	// here and is covered by config's own tests, but this realm's Render IS
 99	// gno.land/u/moul and it already states its own path: passing the constant
100	// cannot be wrong, at no cost.
101	return config.TopBlockFor(realmPath) + r.Replace(layout) + config.BottomBlockFor(realmPath)
102}
103
104// slotLinks is the :slots: placeholder: a bullet list of every slot, linking to
105// its raw view.
106func slotLinks() string {
107	if slots.Size() == 0 {
108		return "_no slots yet_"
109	}
110	var b strings.Builder
111	slots.Iterate("", "", func(key string, value any) bool {
112		b.WriteString("- [")
113		b.WriteString(key)
114		b.WriteString("](/r/nym-moultest123/home:slots/")
115		b.WriteString(key)
116		b.WriteString(")\n")
117		return false
118	})
119	return strings.TrimSuffix(b.String(), "\n")
120}
121
122func renderIndex() string {
123	var b strings.Builder
124	b.WriteString("# Slots\n\n")
125	b.WriteString("rev ")
126	b.WriteString(strconv.Itoa(rev))
127	b.WriteString(" · ")
128	b.WriteString(strconv.Itoa(slots.Size()))
129	b.WriteString(" slot(s)\n\n")
130	if slots.Size() == 0 {
131		b.WriteString("_no slots yet_\n")
132		return b.String()
133	}
134	b.WriteString("| slot | bytes | rev | height |\n")
135	b.WriteString("| --- | ---: | ---: | ---: |\n")
136	slots.Iterate("", "", func(key string, value any) bool {
137		s := value.(*slot)
138		b.WriteString("| [")
139		b.WriteString(key)
140		b.WriteString("](/r/nym-moultest123/home:slots/")
141		b.WriteString(key)
142		b.WriteString(") | ")
143		b.WriteString(strconv.Itoa(len(s.body)))
144		b.WriteString(" | ")
145		b.WriteString(strconv.Itoa(s.rev))
146		b.WriteString(" | ")
147		b.WriteString(strconv.FormatInt(s.updated, 10))
148		b.WriteString(" |\n")
149		return false
150	})
151	return b.String()
152}
153
154func renderSlot(slug string) string {
155	v := slots.Get(slug)
156	if v == nil {
157		return "# Not found\n\nNo slot named " + strconv.Quote(slug) +
158			".\n\nTry [the slot index](/r/nym-moultest123/home:slots).\n"
159	}
160	s := v.(*slot)
161	var b strings.Builder
162	b.WriteString("# ")
163	b.WriteString(slug)
164	b.WriteString("\n\n")
165	b.WriteString(strconv.Itoa(len(s.body)))
166	b.WriteString(" bytes · rev ")
167	b.WriteString(strconv.Itoa(s.rev))
168	b.WriteString(" · written at height ")
169	b.WriteString(strconv.FormatInt(s.updated, 10))
170	b.WriteString("\n\n```\n")
171	b.WriteString(s.body)
172	if !strings.HasSuffix(s.body, "\n") {
173		b.WriteString("\n")
174	}
175	b.WriteString("```\n")
176	return b.String()
177}