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/x/wiki/v0

Package
Open in gnoweb ↗

Overview

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

Files (18)

  • README.mdmarkdown
  • gnomod.tomltoml
  • diff.gnogno
  • doc.gnogno
  • links.gnogno
  • page.gnogno
  • render.gnogno
  • revision.gnogno
  • talk.gnogno
  • title.gnogno
  • wiki.gnogno
  • diff_test.gnogno
  • limits_test.gnogno
  • links_test.gnogno
  • render_test.gnogno
  • talk_test.gnogno
  • title_test.gnogno
  • wiki_test.gnogno
  • title.gnogno
    1package wiki23import (4	"errors"5	"strings"6	"unicode"78

    Functions

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

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

    "gno.land/p/nt/markdown/sanitize/v0"
    9)
    10
    11// Namespace partitions the title space the way MediaWiki does: the same name
    12// can exist once per namespace, and each namespace gets its own rendering and
    13// its own listing.
    14type Namespace uint8
    15
    16// There is deliberately no Talk namespace. Discussion is not an article with
    17// a prefix: it is an append-only thread attached to a page, which threads
    18// properly, moderates per message, and cannot be silently rewritten by whoever
    19// edits last. See talk.gno.
    20const (
    21 NSMain Namespace = iota // articles
    22 NSUser // per-address pages
    23 NSCategory // category descriptions; membership is an index
    24 NSTemplate // reusable fragments
    25 NSHelp // documentation about the wiki itself
    26 NSSpecial // generated listings; never stored
    27)
    28
    29// namespaces is the canonical table. The two-character code is the avl key
    30// prefix: it is fixed-width on purpose, because ufmt in gno supports no width
    31// or padding flags (ufmt.Sprintf("%02d", 7) returns "7"), so a numeric key
    32// would sort "0","1","10","2" and silently interleave namespaces past nine.
    33var namespaces = [...]struct {
    34 prefix string // canonical display prefix, "" for main
    35 code string // fixed-width avl key prefix
    36}{
    37 NSMain: {"", "00"},
    38 NSUser: {"User", "01"},
    39 NSCategory: {"Category", "02"},
    40 NSTemplate: {"Template", "03"},
    41 NSHelp: {"Help", "04"},
    42 NSSpecial: {"Special", "05"},
    43}
    44
    45// Title is a normalized (namespace, name) pair. The zero Title is invalid;
    46// build one with ParseTitle.
    47type Title struct {
    48 NS Namespace
    49 Name string
    50}
    51
    52// MaxTitleLen bounds a title name in bytes, matching MediaWiki's limit.
    53const MaxTitleLen = 255
    54
    55var (
    56 ErrEmptyTitle = errors.New("wiki: empty title")
    57 ErrTitleTooLong = errors.New("wiki: title too long")
    58 ErrTitleChar = errors.New("wiki: illegal character in title")
    59)
    60
    61// ParseTitle normalizes a user-supplied title.
    62//
    63// The normalization is a deliberate subset of MediaWiki's: underscores become
    64// spaces, runs of whitespace collapse, the first letter is upper-cased, and a
    65// recognized "Namespace:" prefix (case-insensitive) selects the namespace.
    66// "Talk:gno land" and "talk:Gno_land" both parse to Talk:Gno land.
    67//
    68// The accepted character set is narrower than MediaWiki's on purpose. A title
    69// is rendered inside markdown and inside a URL path, so anything that is
    70// markdown-significant ("*", "`", "[", "|", "#", "<"), path-significant ("/",
    71// "?", "%", "&") or invisible (bidi and zero-width controls) is rejected
    72// rather than escaped. Rejecting keeps Title.String, Title.Slug and the
    73// sanitized render byte-identical, which is what TestTitleCharsSurviveSanitize
    74// pins.
    75func ParseTitle(raw string) (Title, error) {
    76 s := sanitize.StripBidiAndZeroWidth(raw)
    77 s = strings.ReplaceAll(s, "_", " ")
    78 s = strings.Join(strings.Fields(s), " ")
    79 if s == "" {
    80 return Title{}, ErrEmptyTitle
    81 }
    82
    83 ns := NSMain
    84 if i := strings.Index(s, ":"); i > 0 {
    85 if n, ok := namespaceByPrefix(s[:i]); ok {
    86 ns = n
    87 s = strings.TrimSpace(s[i+1:])
    88 if s == "" {
    89 return Title{}, ErrEmptyTitle
    90 }
    91 }
    92 }
    93
    94 if len(s) > MaxTitleLen {
    95 return Title{}, ErrTitleTooLong
    96 }
    97 for _, r := range s {
    98 if !validTitleRune(r) {
    99 return Title{}, ErrTitleChar
    100 }
    101 }
    102 return Title{NS: ns, Name: upperFirst(s)}, nil
    103}
    104
    105// MustParseTitle is ParseTitle for titles known at development time.
    106func MustParseTitle(raw string) Title {
    107 t, err := ParseTitle(raw)
    108 if err != nil {
    109 panic(err)
    110 }
    111 return t
    112}
    113
    114func namespaceByPrefix(p string) (Namespace, bool) {
    115 p = strings.TrimSpace(p)
    116 for i, e := range namespaces {
    117 if e.prefix != "" && strings.EqualFold(e.prefix, p) {
    118 return Namespace(i), true
    119 }
    120 }
    121 return NSMain, false
    122}
    123
    124// validTitleRune reports whether r may appear in a title name. ASCII is an
    125// explicit allowlist; above ASCII only letters and digits are accepted, which
    126// admits "Élysée" and "東京" while excluding every invisible control.
    127func validTitleRune(r rune) bool {
    128 switch {
    129 case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z', r >= '0' && r <= '9':
    130 return true
    131 case r == ' ' || r == '-' || r == '.' || r == ',' || r == '\'' || r == '(' || r == ')' || r == ':':
    132 return true
    133 case r < 0x80:
    134 return false
    135 default:
    136 return unicode.IsLetter(r) || unicode.IsDigit(r)
    137 }
    138}
    139
    140func upperFirst(s string) string {
    141 rs := []rune(s)
    142 if len(rs) == 0 {
    143 return s
    144 }
    145 rs[0] = unicode.ToUpper(rs[0])
    146 return string(rs)
    147}
    148
    149// String is the canonical display form, "Talk:Gno land".
    150func (t Title) String() string {
    151 if t.NS == NSMain {
    152 return t.Name
    153 }
    154 return namespaces[t.NS].prefix + ":" + t.Name
    155}
    156
    157// Slug is the URL form used in render paths, "Talk:Gno_land". Titles cannot
    158// contain "/", so a slug is always exactly one path segment and the segment
    159// after it is unambiguously a sub-route.
    160func (t Title) Slug() string {
    161 return strings.ReplaceAll(t.String(), " ", "_")
    162}
    163
    164// Key is the avl key: a fixed-width namespace code followed by the name, so
    165// iteration yields main-namespace articles first, then Talk, and so on, each
    166// group in name order.
    167func (t Title) Key() string {
    168 return namespaces[t.NS].code + ":" + t.Name
    169}
    170
    171// Prefix returns the avl key prefix that selects a whole namespace.
    172func (ns Namespace) Prefix() string { return namespaces[ns].code + ":" }
    173
    174// String returns the namespace's display prefix, "" for the main namespace.
    175func (ns Namespace) String() string { return namespaces[ns].prefix }
    176