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

blocks.gno

3.24 Kb · 85 lines
 1package config
 2
 3import "strings"
 4
 5// The notice blocks: two strings a realm drops at the top and the bottom of its
 6// Render, empty by default, so moul can put a warning, a changelog line or a
 7// bit of news on every realm at once or on one of them.
 8//
 9//	func Render(path string) string {
10//		return config.TopBlock() + body + config.BottomBlock()
11//	}
12//
13// Both are set with the ordinary Set, which is what keeps this realm's surface
14// from growing a function per idea:
15//
16//	Set block.top            "> Chain migration on Tuesday."   # every realm
17//	Set block.top@r/moul/gns "> v2 shipped, see the changelog" # this one only
18const (
19	// KeyBlockTop is the notice rendered above a realm's content.
20	KeyBlockTop = "block.top"
21	// KeyBlockBottom is the notice rendered below it.
22	KeyBlockBottom = "block.bottom"
23)
24
25// TopBlock returns the notice for the realm calling in, ready to concatenate
26// in front of its content. Empty when there is nothing to say, which is the
27// default and the usual case.
28//
29// Three things can appear, in this order, separated by blank lines:
30//
31//  1. the pause banner, when this realm or every realm is paused
32//  2. the global block.top
33//  3. this realm's own block.top
34//
35// The pause banner comes first because it is the one a reader has to see, and
36// it is included here rather than left to the realm so that guarding writes
37// with AssertWritable is enough to also explain the refusal on the page.
38//
39// Global and scoped are BOTH shown rather than one overriding the other: a
40// chain-wide warning and a per-realm changelog are different messages, and
41// dropping either because the other exists is the surprising behaviour.
42func TopBlock() string { return TopBlockFor(caller()) }
43
44// TopBlockFor is TopBlock for a named realm. Use it from a crossing function,
45// where the caller cannot be read off the stack, or to render another realm's
46// notice.
47func TopBlockFor(pkgPath string) string {
48	global, scoped := scopedPair(KeyBlockTop, pkgPath)
49	return joinBlocks(PauseFor(pkgPath).Notice(), global, scoped)
50}
51
52// BottomBlock returns the notice for the realm calling in, ready to
53// concatenate after its content.
54//
55// No pause banner here: one is enough, and the top is where it is read.
56func BottomBlock() string { return BottomBlockFor(caller()) }
57
58// BottomBlockFor is BottomBlock for a named realm.
59func BottomBlockFor(pkgPath string) string {
60	global, scoped := scopedPair(KeyBlockBottom, pkgPath)
61	return joinBlocks(global, scoped)
62}
63
64// joinBlocks assembles the non-empty parts into one markdown fragment.
65//
66// Each part is separated by a blank line, because two markdown blocks with
67// only a newline between them are one paragraph, and a blockquote followed
68// directly by text swallows the text into the quote.
69//
70// The result ends with a blank line when there is anything at all, so a realm
71// can write config.TopBlock() + body without a separator of its own and get
72// the same output whether or not a notice is set. Nothing to say means the
73// empty string, and the page is byte-for-byte what it was before.
74func joinBlocks(parts ...string) string {
75	var kept []string
76	for _, p := range parts {
77		if p = strings.TrimSpace(p); p != "" {
78			kept = append(kept, p)
79		}
80	}
81	if len(kept) == 0 {
82		return ""
83	}
84	return strings.Join(kept, "\n\n") + "\n\n"
85}