render.gno
2.97 Kb · 123 lines
1package home
2
3import (
4 "strconv"
5 "strings"
6
7 "chain/runtime"
8)
9
10const (
11 realmPath = "gno.land/r/samcrew/home"
12 webPath = "/r/samcrew/home"
13)
14
15const fallbackLayout = "# samcrew\n\n:intro:\n\n## The crew\n\n:crew:\n"
16
17// Render serves:
18//
19// "" the team page
20// "slots" the slot index
21// "slots/<slug>" one slot's raw markdown
22func Render(path string) string {
23 switch {
24 case path == "":
25 return renderPage()
26 case path == "slots":
27 return renderIndex()
28 case strings.HasPrefix(path, "slots/"):
29 return renderSlot(strings.TrimPrefix(path, "slots/"))
30 }
31 // The path is not echoed: it comes from the visitor's URL.
32 return "# Not found\n\nThis page does not exist.\n\n[Back home](" + webPath + ")\n"
33}
34
35func renderPage() string {
36 layout := Get(layoutSlug)
37 if layout == "" {
38 layout = fallbackLayout
39 }
40 return fill(layout)
41}
42
43// fill replaces every :slug: in s, in one left-to-right pass. Replacement text
44// is never rescanned, and an unknown placeholder stays verbatim.
45func fill(s string) string {
46 var b strings.Builder
47 for {
48 i := strings.IndexByte(s, ':')
49 if i < 0 {
50 b.WriteString(s)
51 return b.String()
52 }
53 b.WriteString(s[:i])
54 s = s[i:]
55 j := strings.IndexByte(s[1:], ':')
56 if j < 0 {
57 b.WriteString(s)
58 return b.String()
59 }
60 if v, ok := lookup(s[1 : j+1]); ok {
61 b.WriteString(v)
62 s = s[j+2:]
63 continue
64 }
65 b.WriteString(":")
66 s = s[1:]
67 }
68}
69
70func lookup(slug string) (string, bool) {
71 if !validSlug(slug) {
72 return "", false
73 }
74 switch slug {
75 case "chainid":
76 return runtime.ChainID(), true
77 case "crew":
78 return crewTable(), true
79 case "hero":
80 return heroImage(), true
81 case "height":
82 return strconv.FormatInt(runtime.ChainHeight(), 10), true
83 case "realm":
84 return realmPath, true
85 case "rev":
86 return strconv.Itoa(rev), true
87 }
88 if strings.HasPrefix(slug, galleryPrefix) {
89 return renderGallery(strings.TrimPrefix(slug, galleryPrefix))
90 }
91 if v := slots.Get(slug); v != nil {
92 return v.(*slot).body, true
93 }
94 return "", false
95}
96
97func renderIndex() string {
98 var b strings.Builder
99 b.WriteString("# Slots\n\nrev " + strconv.Itoa(rev) + " · " + strconv.Itoa(slots.Size()) + " slot(s) · admin " + admin.String() + "\n\n")
100 b.WriteString("| slot | bytes | rev | height |\n| --- | ---: | ---: | ---: |\n")
101 slots.Iterate("", "", func(key string, value any) bool {
102 s := value.(*slot)
103 b.WriteString("| [" + key + "](" + webPath + ":slots/" + key + ") | " +
104 strconv.Itoa(len(s.body)) + " | " + strconv.Itoa(s.rev) + " | " +
105 strconv.FormatInt(s.updated, 10) + " |\n")
106 return false
107 })
108 return b.String()
109}
110
111func renderSlot(slug string) string {
112 v := slots.Get(slug)
113 if v == nil {
114 return "# Not found\n\nNo such slot.\n\n[Slot index](" + webPath + ":slots)\n"
115 }
116 s := v.(*slot)
117 body := s.body
118 if !strings.HasSuffix(body, "\n") {
119 body += "\n"
120 }
121 return "# " + slug + "\n\n" + strconv.Itoa(len(s.body)) + " bytes · rev " + strconv.Itoa(s.rev) +
122 " · written at height " + strconv.FormatInt(s.updated, 10) + "\n\n````\n" + body + "````\n"
123}