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
  • links.gnogno
    1package wiki23import (4	"strings"56	"gno.land/p/nt/markdown/sanitize/v0"7)8

    Functions

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

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

    9// Link is one wikilink occurrence in a body.
    10type Link struct {
    11 Target string // target text as written, before ParseTitle
    12 Label string // display text; "" means render the target
    13 Explicit bool // written [[:Category:X]]: link to the category, do not join it
    14 Start int // byte offset of the opening delimiter
    15 End int // byte offset just past the closing delimiter
    16}
    17
    18// ScanLinks finds every [[target|label]] occurrence delimited by open and
    19// close.
    20//
    21// The delimiters are parameters because the same syntax has to be found twice
    22// with different bytes. Indexing reads the raw body, where a link is
    23// "[[X]]". Rendering reads the body after sanitize.BlockRich, where the very
    24// same link is "\[\[X\]\]" because the sanitizer escapes every "[". Rendering
    25// must sanitize first and rewrite second: a rewriter that ran first would
    26// hand the markdown links it just generated to the escaper, and every link on
    27// the wiki would render as literal text.
    28//
    29// A link whose inner text spans a newline or is empty is not a link. When a
    30// nearer opener appears inside the inner text, scanning restarts from it, so
    31// "[[a [[b]]" yields b rather than a mis-parsed a.
    32func ScanLinks(s, openTok, closeTok string) []Link {
    33 out := []Link{}
    34 i := 0
    35 for i < len(s) {
    36 a := strings.Index(s[i:], openTok)
    37 if a < 0 {
    38 break
    39 }
    40 a += i
    41 rest := a + len(openTok)
    42 b := strings.Index(s[rest:], closeTok)
    43 if b < 0 {
    44 break
    45 }
    46 b += rest
    47
    48 inner := s[rest:b]
    49 if n := strings.Index(inner, openTok); n >= 0 {
    50 i = rest + n
    51 continue
    52 }
    53 i = b + len(closeTok)
    54 if inner == "" || strings.Contains(inner, "\n") {
    55 continue
    56 }
    57
    58 l := Link{Start: a, End: i}
    59 if p := strings.Index(inner, "|"); p >= 0 {
    60 l.Target = strings.TrimSpace(inner[:p])
    61 l.Label = strings.TrimSpace(inner[p+1:])
    62 } else {
    63 l.Target = strings.TrimSpace(inner)
    64 }
    65 if strings.HasPrefix(l.Target, ":") {
    66 l.Explicit = true
    67 l.Target = strings.TrimSpace(l.Target[1:])
    68 }
    69 if l.Target == "" {
    70 continue
    71 }
    72 out = append(out, l)
    73 }
    74 return out
    75}
    76
    77// redirectTarget returns the canonical title a body redirects to, or "".
    78// The syntax is MediaWiki's: "#REDIRECT [[Target]]" on the first line.
    79func redirectTarget(body string) string {
    80 line := body
    81 if i := strings.Index(line, "\n"); i >= 0 {
    82 line = line[:i]
    83 }
    84 line = strings.TrimSpace(line)
    85 if len(line) < len("#REDIRECT") || !strings.EqualFold(line[:len("#REDIRECT")], "#REDIRECT") {
    86 return ""
    87 }
    88 links := ScanLinks(line, "[[", "]]")
    89 if len(links) == 0 {
    90 return ""
    91 }
    92 t, err := ParseTitle(links[0].Target)
    93 if err != nil {
    94 return ""
    95 }
    96 return t.String()
    97}
    98
    99// Ctx carries what rendering needs from the realm: where the realm lives, and
    100// which titles exist. Its function fields are read during a single Render call
    101// and never stored, so no closure is ever persisted.
    102type Ctx struct {
    103 Base string // realm path prefix, e.g. "/r/moul/x/wiki/v0"
    104 Exists func(Title) bool // nil treats every title as existing
    105}
    106
    107func (c Ctx) exists(t Title) bool {
    108 if c.Exists == nil {
    109 return true
    110 }
    111 return c.Exists(t)
    112}
    113
    114// URL is the render path of a title under this realm.
    115func (c Ctx) URL(t Title) string { return c.Sub(t, "") }
    116
    117// Sub is the render path of a sub-route of a title, e.g. "history".
    118func (c Ctx) Sub(t Title, route string) string {
    119 u := c.Base + ":" + t.Slug()
    120 if route != "" {
    121 u += "/" + route
    122 }
    123 return escapeURL(u)
    124}
    125
    126// SpecialURL is the render path of a Special: page.
    127func (c Ctx) SpecialURL(name, query string) string {
    128 u := c.Base + ":Special:" + name
    129 if query != "" {
    130 u += "?" + query
    131 }
    132 return escapeURL(u)
    133}
    134
    135// escapeURL percent-encodes the characters a title may contain that would
    136// otherwise terminate a markdown link destination or split a path. Titles
    137// admit "(", ")", " ", "'" and "," (see validTitleRune), and an unencoded ")"
    138// ends the "(...)" of a markdown link at the first occurrence, which turns
    139// [[Mercury (planet)]] into a broken link plus stray text.
    140func escapeURL(u string) string {
    141 r := strings.NewReplacer(
    142 " ", "%20",
    143 "(", "%28",
    144 ")", "%29",
    145 "'", "%27",
    146 ",", "%2C",
    147 )
    148 return sanitize.URL(r.Replace(u))
    149}
    150
    151// RewriteLinks turns the wikilinks of an already-sanitized body into markdown
    152// links. Category declarations are removed from the flow: membership is shown
    153// by the rendered footer, not inline, which is also what MediaWiki does.
    154//
    155// s MUST be the output of sanitize.BlockRich or sanitize.Block, and open/close
    156// MUST be the escaped delimiters. Passing a raw body here would emit links
    157// built from unsanitized bytes.
    158func RewriteLinks(c Ctx, s string) string {
    159 links := ScanLinks(s, `\[\[`, `\]\]`)
    160 if len(links) == 0 {
    161 return s
    162 }
    163 var out strings.Builder
    164 prev := 0
    165 for _, l := range links {
    166 out.WriteString(s[prev:l.Start])
    167 prev = l.End
    168
    169 t, err := ParseTitle(unescapeInline(l.Target))
    170 if err != nil {
    171 // Not a usable title: leave the sanitized text in place. It is
    172 // already escaped, so it renders as the literal brackets the
    173 // author typed.
    174 out.WriteString(s[l.Start:l.End])
    175 continue
    176 }
    177 if t.NS == NSCategory && !l.Explicit {
    178 continue
    179 }
    180 label := l.Label
    181 if label == "" {
    182 label = t.String()
    183 }
    184 if c.exists(t) {
    185 out.WriteString("[" + label + "](" + c.URL(t) + ")")
    186 } else {
    187 // A red link: the page does not exist yet. Point at the same
    188 // path, which renders the "create this page" stub.
    189 out.WriteString("[" + label + "](" + c.URL(t) + ") ⁺")
    190 }
    191 }
    192 out.WriteString(s[prev:])
    193 return out.String()
    194}
    195
    196// unescapeInline undoes the backslash escaping the sanitizer applies inside a
    197// link's inner text, so ParseTitle sees the title the author typed.
    198func unescapeInline(s string) string {
    199 if !strings.Contains(s, `\`) {
    200 return s
    201 }
    202 var b strings.Builder
    203 for i := 0; i < len(s); i++ {
    204 if s[i] == '\\' && i+1 < len(s) {
    205 i++
    206 }
    207 b.WriteByte(s[i])
    208 }
    209 return b.String()
    210}
    211