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

render.gno

4.56 Kb · 148 lines
  1package home
  2
  3import (
  4	"strconv"
  5	"strings"
  6
  7	"chain/runtime"
  8
  9	"gno.land/p/moul/dynreplacer/v0"
 10)
 11
 12// defaultLayout is what renders before a layout slot exists, i.e. right after
 13// the very first deploy. It uses only computed placeholders, so it never shows
 14// an unresolved :slug: of its own.
 15const defaultLayout = "# gno.land/r/moul/home\n\n" +
 16	"No layout slot yet. Set one, then the page is whatever you make it:\n\n" +
 17	"    gnokey maketx call -pkgpath gno.land/r/moul/home -func Set \\\n" +
 18	"      -args layout -args \"$(/bin/cat content/layout.md)\" \\\n" +
 19	"      -gas-fee 1000000ugnot -gas-wanted 20000000 \\\n" +
 20	"      -broadcast -chainid gnoland-1 -remote https://rpc.gno.land:443 moul\n\n" +
 21	"## Slots\n\n:slots:\n\n---\n\nrev :rev: · height :height: · :chainid:\n"
 22
 23func chainHeight() int64 { return runtime.ChainHeight() }
 24
 25// Render serves three views:
 26//
 27//	""            the assembled page, what https://gno.land/u/moul shows
 28//	"slots"       the slot index: name, size, revision, last write
 29//	"slots/<slug>" one slot's raw markdown, fenced
 30func Render(path string) string {
 31	switch {
 32	case path == "":
 33		return renderPage()
 34	case path == "slots":
 35		return renderIndex()
 36	case strings.HasPrefix(path, "slots/"):
 37		return renderSlot(strings.TrimPrefix(path, "slots/"))
 38	default:
 39		return "# Not found\n\nNo such path: " + strconv.Quote(path) +
 40			"\n\nTry [the slot index](/r/moul/home:slots).\n"
 41	}
 42}
 43
 44// renderPage fills the layout. dynreplacer only invokes the callbacks whose
 45// placeholder actually occurs in the layout, so an unused slot is never even
 46// read out of the tree.
 47func renderPage() string {
 48	layout := Get(layoutSlug)
 49	if layout == "" {
 50		layout = defaultLayout
 51	}
 52
 53	r := dynreplacer.New()
 54
 55	// Computed from chain state. Registered first; assertWritableSlug keeps a
 56	// slot from ever taking one of these names, so nothing shadows them.
 57	r.RegisterCallback(":owner:", func() string { return admin.String() })
 58	r.RegisterCallback(":realm:", func() string { return "gno.land/r/moul/home" })
 59	r.RegisterCallback(":chainid:", func() string { return runtime.ChainID() })
 60	r.RegisterCallback(":height:", func() string { return strconv.FormatInt(runtime.ChainHeight(), 10) })
 61	r.RegisterCallback(":rev:", func() string { return strconv.Itoa(rev) })
 62	r.RegisterCallback(":slots:", func() string { return slotLinks() })
 63
 64	// One callback per slot. The closure captures s, which is re-bound on each
 65	// iteration, so every callback sees its own slot.
 66	slots.Iterate("", "", func(key string, value any) bool {
 67		s := value.(*slot)
 68		r.RegisterCallback(":"+key+":", func() string { return s.body })
 69		return false
 70	})
 71
 72	return r.Replace(layout)
 73}
 74
 75// slotLinks is the :slots: placeholder: a bullet list of every slot, linking to
 76// its raw view.
 77func slotLinks() string {
 78	if slots.Size() == 0 {
 79		return "_no slots yet_"
 80	}
 81	var b strings.Builder
 82	slots.Iterate("", "", func(key string, value any) bool {
 83		b.WriteString("- [")
 84		b.WriteString(key)
 85		b.WriteString("](/r/moul/home:slots/")
 86		b.WriteString(key)
 87		b.WriteString(")\n")
 88		return false
 89	})
 90	return strings.TrimSuffix(b.String(), "\n")
 91}
 92
 93func renderIndex() string {
 94	var b strings.Builder
 95	b.WriteString("# Slots\n\n")
 96	b.WriteString("rev ")
 97	b.WriteString(strconv.Itoa(rev))
 98	b.WriteString(" · ")
 99	b.WriteString(strconv.Itoa(slots.Size()))
100	b.WriteString(" slot(s)\n\n")
101	if slots.Size() == 0 {
102		b.WriteString("_no slots yet_\n")
103		return b.String()
104	}
105	b.WriteString("| slot | bytes | rev | height |\n")
106	b.WriteString("| --- | ---: | ---: | ---: |\n")
107	slots.Iterate("", "", func(key string, value any) bool {
108		s := value.(*slot)
109		b.WriteString("| [")
110		b.WriteString(key)
111		b.WriteString("](/r/moul/home:slots/")
112		b.WriteString(key)
113		b.WriteString(") | ")
114		b.WriteString(strconv.Itoa(len(s.body)))
115		b.WriteString(" | ")
116		b.WriteString(strconv.Itoa(s.rev))
117		b.WriteString(" | ")
118		b.WriteString(strconv.FormatInt(s.updated, 10))
119		b.WriteString(" |\n")
120		return false
121	})
122	return b.String()
123}
124
125func renderSlot(slug string) string {
126	v := slots.Get(slug)
127	if v == nil {
128		return "# Not found\n\nNo slot named " + strconv.Quote(slug) +
129			".\n\nTry [the slot index](/r/moul/home:slots).\n"
130	}
131	s := v.(*slot)
132	var b strings.Builder
133	b.WriteString("# ")
134	b.WriteString(slug)
135	b.WriteString("\n\n")
136	b.WriteString(strconv.Itoa(len(s.body)))
137	b.WriteString(" bytes · rev ")
138	b.WriteString(strconv.Itoa(s.rev))
139	b.WriteString(" · written at height ")
140	b.WriteString(strconv.FormatInt(s.updated, 10))
141	b.WriteString("\n\n```\n")
142	b.WriteString(s.body)
143	if !strings.HasSuffix(s.body, "\n") {
144		b.WriteString("\n")
145	}
146	b.WriteString("```\n")
147	return b.String()
148}