triedemo.gno
3.95 Kb · 125 lines
1// Package triedemo is a small gnoweb demo of the prefix tree provided by the
2// [p/moul/x/daily/trie](/p/moul/x/daily/trie/v0) library: type a prefix in the
3// URL and it lists every dictionary word that completes it.
4//
5// It contains no trie logic of its own — the tree, the ordering and the limit
6// all come from the library. The dictionary is a fixed, sorted word list so the
7// realm has no mutable state and Render is fully deterministic.
8package triedemo
9
10import (
11 "strconv"
12 "strings"
13
14 "gno.land/p/moul/x/daily/trie/v0"
15)
16
17// maxResults caps how many completions a single Render lists.
18const maxResults = 25
19
20// dictionary is the demo corpus: gno/Go vocabulary, deliberately clustered on
21// a few prefixes ("co", "gn", "re", "tr") so completions are fun to explore.
22var dictionary = []string{
23 "chain", "coin", "collection", "commit", "compile", "complete", "concurrent",
24 "consensus", "constant", "contract", "counter", "crossing", "deploy", "gas",
25 "genesis", "gno", "gnodev", "gnokey", "gnoland", "gnoweb", "goroutine",
26 "grc20", "hash", "interface", "keeper", "ledger", "mempool", "merkle",
27 "module", "namespace", "package", "panic", "pointer", "prefix", "prove",
28 "realm", "receiver", "recover", "reflect", "render", "replay", "rollback",
29 "slice", "stdlib", "struct", "transaction", "transfer", "traverse", "tree",
30 "trie", "type", "validator", "vault", "vm", "wallet",
31}
32
33// dict is built once at init; the library keeps it sorted internally.
34var dict = trie.FromWords(dictionary)
35
36// Render renders the autocomplete for gnoweb.
37//
38// Render("") / Render("/") -> the whole dictionary + usage
39// Render("/<prefix>") -> every word completing <prefix>
40func Render(path string) string {
41 prefix := parsePrefix(path)
42
43 var b strings.Builder
44 b.WriteString("# Trie Autocomplete\n\n")
45 b.WriteString("A prefix tree over a ")
46 b.WriteString(strconv.Itoa(dict.Len()))
47 b.WriteString("-word dictionary, demoing the ")
48 b.WriteString("[`p/moul/x/daily/trie`](/p/moul/x/daily/trie/v0) library.\n\n")
49
50 if prefix == "" {
51 b.WriteString("Append a prefix to the path to complete it.\n\n")
52 b.WriteString("## Try one\n\n")
53 // Listing all 55 words here would bury the page (and the example test
54 // that pins it); the interesting part is the prefix walk, so show a few
55 // live counts and let /<prefix> do the listing.
56 for _, p := range []string{"co", "gno", "re", "tr"} {
57 b.WriteString("- [`/")
58 b.WriteString(p)
59 b.WriteString("`](/r/moul/x/daily/triedemo/v0:")
60 b.WriteString(p)
61 b.WriteString(") — ")
62 b.WriteString(strconv.Itoa(len(dict.Complete(p, 0))))
63 b.WriteString(" words\n")
64 }
65 return b.String()
66 }
67
68 b.WriteString("## Completions for `")
69 b.WriteString(prefix)
70 b.WriteString("`\n\n")
71
72 if !dict.HasPrefix(prefix) {
73 b.WriteString("_No word starts with `")
74 b.WriteString(prefix)
75 b.WriteString("`._\n\n> Try `/co`, `/gno`, `/tr` or `/re`.\n")
76 return b.String()
77 }
78
79 words := dict.Complete(prefix, maxResults)
80 b.WriteString("**")
81 b.WriteString(strconv.Itoa(len(words)))
82 b.WriteString("** match")
83 if len(words) != 1 {
84 b.WriteString("es")
85 }
86 if len(words) == maxResults {
87 b.WriteString(" (capped at ")
88 b.WriteString(strconv.Itoa(maxResults))
89 b.WriteString(")")
90 }
91 b.WriteString("\n\n")
92 b.WriteString(list(words))
93
94 if dict.Contains(prefix) {
95 b.WriteString("\n> `")
96 b.WriteString(prefix)
97 b.WriteString("` is itself a word in the dictionary.\n")
98 }
99 return b.String()
100}
101
102// parsePrefix extracts the prefix from a Render path like "/gno", keeping only
103// the first segment. Returns "" for the empty/root path.
104func parsePrefix(path string) string {
105 s := strings.TrimSpace(path)
106 s = strings.TrimPrefix(s, "/")
107 if i := strings.IndexByte(s, '/'); i >= 0 {
108 s = s[:i]
109 }
110 return s
111}
112
113// list renders words as a Markdown bullet list.
114func list(words []string) string {
115 if len(words) == 0 {
116 return "_none_\n"
117 }
118 var b strings.Builder
119 for _, w := range words {
120 b.WriteString("- `")
121 b.WriteString(w)
122 b.WriteString("`\n")
123 }
124 return b.String()
125}