PathrockNetwork Gno Explorer
HomeBlocksTransactionsTokensRealmsPackagesValidatorsAnalytics

PathrockNetwork Gno Explorer — an independent explorer for Gno.land Mainnet (gnoland-1), operated by PathrockNetwork. Not an official Gno.land service.

gnowebarchive RPC

gno.land/p/moul/kit/ui/v0

Package
Open in gnoweb ↗

Overview

Kind
Pure package
Name
v0
Namespace
moul / kit / ui
Files
4 (README)(gnomod.toml)
Exported functions
n/a — not supported for pure packages by the node (vm/qfuncs)
Module
gno.land/p/moul/kit/ui/v0
gno
0.9

Files (4)

  • README.mdmarkdown
  • gnomod.tomltoml
  • ui.gnogno
  • ui_test.gnogno
ui.gnogno
1// Package ui is the shared display vocabulary for r/moul realms: the small set2// of rendering decisions that every realm was making on its own, made once.3//4// It deliberately does NOT re-export markdown primitives. Headings, bold,5// lists, code blocks and links already have an owner in6// [p/moul/md](/p/moul/md/v0); import that alongside this package. What lives7// here is only what had no owner and was therefore copy-pasted:8//9//   - Addr: one address shortening for the whole namespace. Eleven realms had10//     their own shortAddr with four different truncation rules, so the same11//     account rendered differently depending on which realm you opened.12//   - Inline / Cell / Excerpt: markdown escaping, delegated to13//     [p/nt/markdown/sanitize](/p/nt/markdown/sanitize/v0). Eight realms14//     hand-rolled a seven-pair strings.NewReplacer that misses most of what15//     the real escaper handles. Excerpt adds the length cap those previews16//     also need, in the order that is safe: cut, then escape.17//   - Action: a clickable call link instead of prose telling the reader to go18//     type a function name.19//   - Table, Empty, Podium: the last recurring scraps.20//21// # Escaping contract22//23// Table cells and Action titles are markdown, not plain text. Anything that24// came from a user must go through [Cell] (inside a table) or [Inline]25// (anywhere else) before it is handed to this package. Output of [Addr],26// [AddrFull] and [Podium] is already safe and must NOT be escaped again.27package ui2829import (30	"strings"3132	"gno.land/p/moul/helplink/v0"33	"gno.land/p/moul/md/v0"34	"gno.land/p/nt/markdown/sanitize/v0"35)3637// Ellipsis is the character placed between the kept head and tail of a38// shortened string.39const Ellipsis = "…"4041// Head and Tail are how many characters [Addr] and [Short] keep on each side.42//43// A gno.land address is 40 characters ("g1" plus 38 of bech32 data), so44// 8+1+4 renders it as g1manfre…dlf5: enough of the head to recognise a45// familiar account, enough of the tail to tell two similar ones apart.46const (47	Head = 848	Tail = 449)5051// minShorten is the length below which shortening cannot save a character:52// Head + len(Ellipsis) + Tail. Strings at or under it are returned unchanged,53// which is why the rule never produces output longer than its input.54const minShorten = Head + 1 + Tail5556// Addr renders an address shortened and in backticks: `g1manfre…dlf5`.57//58// This is the default way to show an address. It is monospace (an address is59// opaque data, not prose), and the backticks make it inert markdown, so it is60// safe in a table cell or anywhere else without further escaping.61func Addr(a address) string {62	return md.InlineCode(AddrText(a))63}6465// AddrFull renders an address in full, in backticks. Use it where the reader66// needs to copy the value; use [Addr] everywhere else.67func AddrFull(a address) string {68	return md.InlineCode(a.String())69}7071// AddrText renders an address shortened, with no backticks. Use it inside a72// link title or another construct where backticks would not render.73func AddrText(a address) string {74	return Short(a.String())75}7677// AddrOf is [Addr] for an address already in its string form, which is how78// realms hold one when it is an avl key or a stored field.79func AddrOf(s string) string {80	return md.InlineCode(Short(s))81}8283// Short shortens any string with the house rule: unchanged when shortening84// would not save a character, otherwise head + ellipsis + tail.85//86// It is the same rule [Addr] uses, exposed for the non-address strings realms87// also truncate: URLs, handles, commitment hashes.88func Short(s string) string {89	return ShortN(s, Head, Tail)90}9192// ShortN is [Short] with an explicit head and tail. A negative head or tail is93// treated as zero. When head+tail cannot save a character against the input,94// the input is returned unchanged.95func ShortN(s string, head, tail int) string {96	if head < 0 {97		head = 098	}99	if tail < 0 {100		tail = 0101	}102	// Count runes, not bytes: slicing a multi-byte string by byte offset103	// splits a rune and emits replacement characters.104	r := []rune(s)105	if len(r) <= head+1+tail {106		return s107	}108	return string(r[:head]) + Ellipsis + string(r[len(r)-tail:])109}110111// Inline escapes user-supplied text for an inline markdown context: a112// sentence, a list item, a link title.113//114// It delegates to sanitize.InlineText, which strips bidi and zero-width115// characters, folds newlines to spaces so the text cannot escape its line,116// and applies the full CommonMark inline escape. Never concatenate117// user-supplied text into rendered output without this.118func Inline(s string) string {119	return sanitize.InlineText(s)120}121122// Cell escapes user-supplied text for a markdown table cell: [Inline] plus123// tab and pipe handling, so the value cannot open a new column.124func Cell(s string) string {125	return sanitize.TableCell(s)126}127128// Excerpt is [Inline] for a string too long to show whole: it keeps the first129// width runes, appends [Ellipsis], and escapes what it kept.130//131// Use it for a preview of user-written prose where only the beginning carries132// meaning: the first line of a post in an index, a note on a board, a comment133// in a list. For an identifier whose tail has to stay recognisable, an134// address, a hash, a URL, use [Short] instead, which keeps both ends.135//136// The order is the whole point, and it is what a call site gets wrong.137// Escaping inserts backslashes, so cutting an ALREADY-escaped string can138// strand a trailing lone backslash that escapes whatever chrome follows it.139// Excerpt cuts first, on a rune boundary so a multi-byte character is never140// split, then escapes. [Ellipsis] stays outside the escaper: it is this141// package's chrome, not the user's text.142func Excerpt(s string, width int) string {143	if width < 0 {144		width = 0145	}146	r := []rune(s)147	// The same threshold as [ShortN] with no tail: cutting is only worth doing148	// when it saves a character, so a string one rune over is kept whole.149	if len(r) <= width+1 {150		return Inline(s)151	}152	return Inline(string(r[:width])) + Ellipsis153}154155// Action renders a clickable call to a function of the current realm.156//157//	ui.Action("Play cell 4", "Move", "gameID", "7", "cell", "4")158//159// Arguments are key/value pairs, as in p/moul/txlink. The title is escaped,160// which is the one thing helplink.Func does not do.161func Action(title, fn string, args ...string) string {162	return helplink.Func(Inline(title), fn, args...)163}164165// ActionIn is [Action] against another realm, given as the full package path166// that realm declares on the module line of its gnomod.toml.167//168// This doc deliberately contains no example path, not even a placeholder one.169// gnopm scans doc comments the way it scans imports, so any package path170// spelled out in a comment becomes a dependency of the package holding it: one171// that is neither live nor in the workspace BLOCKS publishing this package and172// everything that imports it, and naming a live one is no better, since it173// drags an unrelated realm into every publish plan. This comment previously174// named a path that deversioning had already removed, which blocked the whole175// ui dependency tree on every chain.176func ActionIn(pkgPath, title, fn string, args ...string) string {177	return helplink.Realm(pkgPath).Func(Inline(title), fn, args...)178}179180// Empty renders the placeholder shown where a list would be: an italic line,181// newline-terminated, so it drops into a Render body as-is.182//183//	ui.Empty("No games yet.")  // "_No games yet._\n"184//185// The message is realm chrome, not user input, and is not escaped.186func Empty(msg string) string {187	return md.Italic(msg) + "\n"188}189190// Podium returns the medal for a zero-based rank, or "" past third place.191func Podium(rank int) string {192	switch rank {193	case 0:194		return "\U0001F947" // 🥇195	case 1:196		return "\U0001F948" // 🥈197	case 2:198		return "\U0001F949" // 🥉199	default:200		return ""201	}202}203204// Table accumulates markdown table rows and renders them.205//206// Cells are markdown, not plain text: pass [Addr] and friends straight207// through, and wrap anything user-supplied in [Cell] first.208//209// Table renders the GFM table itself rather than delegating to210// [p/moul/mdtable](/p/moul/mdtable/v0), which unconditionally rewrites "|" to211// "&#124;" in every cell. Stacked on [Cell], which already emits the GFM212// escape "\|", that double-escapes into a stray backslash ("a\&#124;b"). One213// escaping stage is the only way to get this right, and it has to be the one214// that knows whether the text is user input.215//216//	t := ui.NewTable("#", "X", "O", "Status")217//	t.Row("7", ui.Addr(x), ui.Addr(o), "Turn: X")218//	return t.String()219type Table struct {220	headers []string221	rows    [][]string222}223224// NewTable starts a table with the given header cells.225func NewTable(headers ...string) *Table {226	return &Table{headers: headers}227}228229// Row appends a row and returns the table, so calls can be chained.230//231// A row with fewer cells than there are headers is padded with empty cells; a232// longer row is kept as-is, which renders as a ragged table rather than233// silently dropping data.234func (t *Table) Row(cells ...string) *Table {235	if n := len(t.headers); len(cells) < n {236		padded := make([]string, n)237		copy(padded, cells)238		cells = padded239	}240	t.rows = append(t.rows, cells)241	return t242}243244// Len reports how many rows have been added.245func (t *Table) Len() int { return len(t.rows) }246247// String renders the table. A table with no rows renders as "", so a caller248// can fall back to [Empty] with a single check on [Table.Len].249func (t *Table) String() string {250	if len(t.rows) == 0 {251		return ""252	}253	var sb strings.Builder254	sb.WriteString("| " + strings.Join(t.headers, " | ") + " |\n")255	sb.WriteString("|" + strings.Repeat(" --- |", len(t.headers)) + "\n")256	for _, r := range t.rows {257		sb.WriteString("| " + strings.Join(r, " | ") + " |\n")258	}259	return sb.String()260}261262// OrEmpty renders the table, or the placeholder when it has no rows.263func (t *Table) OrEmpty(msg string) string {264	if len(t.rows) == 0 {265		return Empty(msg)266	}267	return t.String()268}269270// Join concatenates parts, skipping empty ones, with sep between them. It is271// the small piece of glue every Render needs to assemble optional sections272// without emitting stray separators.273func Join(sep string, parts ...string) string {274	kept := make([]string, 0, len(parts))275	for _, p := range parts {276		if p != "" {277			kept = append(kept, p)278		}279	}280	return strings.Join(kept, sep)281}282

Functions

not supported for pure packages by the node (vm/qfuncs)

Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.