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/r/moul/x/allinone/textlab/v0

Realm
Open in gnoweb ↗

Overview

Kind
Realm (renderable)
Name
v0
Namespace
moul / x / allinone / textlab
Files
4 (README)(gnomod.toml)
Exported functions
1
Module
gno.land/r/moul/x/allinone/textlab/v0
gno
0.9

Files (4)

  • README.mdmarkdown
  • gnomod.tomltoml
  • textlab.gnogno
  • textlab_test.gnogno
textlab.gnogno
1// Package textlab runs every string algorithm in p/moul over one input at2// once, so they can be compared rather than read about one at a time.3//4// Each of these has its own demo realm showing it alone. What none of those can5// show is how the answers differ for the SAME word, which is the only question6// a reader actually has: soundex and levenshtein both claim to tell you whether7// two words are alike, and they are measuring different things.8//9// soundex keys on the FIRST LETTER and then on consonant classes, so it is10// blind to how the rest is spelled. levenshtein counts edits and is blind to11// how it sounds. "knight" and "night" are the clean case: one edit apart (83%12// similar) and yet soundex calls them unrelated, K523 against N230, because13// the silent K changes the first character of the code. "robert" and "rupert"14// go the other way, identical codes at two edits apart. Measured 2026-09-2315// against the packages in this tree, not quoted from a textbook.16//17//	/r/moul/x/allinone/textlab/v0:knight18//	/r/moul/x/allinone/textlab/v0:knight/night   compares two19//20// Eight packages, no state, nothing to redeploy carefully: the input comes out21// of the render path and nothing is stored.22package textlab2324import (25	"strconv"26	"strings"2728	"gno.land/p/moul/kit/ui/v0"29	"gno.land/p/moul/md/v0"30	"gno.land/p/moul/realmpath/v0"31	"gno.land/p/moul/x/daily/levenshtein/v0"32	"gno.land/p/moul/x/daily/piglatin/v0"33	"gno.land/p/moul/x/daily/romannum/v0"34	"gno.land/p/moul/x/daily/rot13/v0"35	"gno.land/p/moul/x/daily/soundex/v0"36	"gno.land/r/moul/config/v1"37)3839const realmPath = "gno.land/r/moul/x/allinone/textlab/v0"4041// maxInput bounds what a render path can carry into the algorithms below.42// levenshtein.Matrix is O(len(a) * len(b)), so an unbounded pair of inputs is43// a way to make a query expensive for whoever serves it.44const maxInput = 644546// defaultWord is what the root view demonstrates, chosen because it is the47// textbook case where the two similarity measures disagree: "knight" and48// "night" sound identical to soundex and are one edit apart.49const defaultWord = "knight"5051// Render takes its input from the path: one word, or two separated by "/".52func Render(path string) string {53	req := realmpath.Parse(path)5455	a := strings.TrimSpace(req.PathPart(0))56	b := strings.TrimSpace(req.PathPart(1))57	if a == "" {58		a = defaultWord59	}6061	var out strings.Builder62	out.WriteString(config.TopBlockFor(realmPath))63	out.WriteString(md.H1("allinone: textlab"))64	out.WriteString("\nEight packages over one input. ")65	out.WriteString(md.Link("source", config.MygnoscanFor(realmPath)))66	out.WriteString("\n\n")6768	if tooLong(a) || tooLong(b) {69		out.WriteString(ui.Empty("input too long: max " + strconv.Itoa(maxInput) + " bytes"))70		out.WriteString("\n")71		out.WriteString(config.BottomBlockFor(realmPath))72		return out.String()73	}7475	out.WriteString(renderOne(a))76	if b != "" {77		out.WriteString("\n")78		out.WriteString(renderPair(a, b))79	} else {80		out.WriteString("\n")81		out.WriteString(renderTry())82	}8384	out.WriteString(config.BottomBlockFor(realmPath))85	return out.String()86}8788func tooLong(s string) bool { return len(s) > maxInput }8990// renderOne is every single-input algorithm, one row each.91//92// ui.Cell, not ui.Inline: these land in table cells, where an unescaped pipe93// would silently eat a column. The input is whatever someone put in a URL.94func renderOne(word string) string {95	t := ui.NewTable("package", "answer")96	t.Row("`x/daily/soundex`", ui.Cell(soundex.Encode(word)))97	t.Row("`x/daily/rot13`", ui.Cell(rot13.Rot13(word)))98	t.Row("`x/daily/piglatin`", ui.Cell(piglatin.Translate(word)))99	t.Row("`x/daily/romannum`", ui.Cell(romanOf(word)))100101	return md.H2("Input: "+ui.Inline(word)) + "\n" + t.String()102}103104// romanOf shows the round trip both ways, because romannum is the one package105// here whose input is a number rather than a word.106//107// FromRoman PANICS on anything that is not a roman numeral rather than108// returning zero, and the input here comes out of a URL, so it is screened109// first. Without that screen the DEFAULT view aborts: "knight" is not a roman110// numeral, and neither is most of what anyone would type.111func romanOf(word string) string {112	if n, err := strconv.Atoi(word); err == nil {113		if n <= 0 || n > 3999 {114			return "out of range (1..3999)"115		}116		return romannum.ToRoman(n)117	}118	upper := strings.ToUpper(word)119	if !isRomanNumeral(upper) {120		return "not a number"121	}122	if n := romannum.FromRoman(upper); n > 0 {123		return strconv.Itoa(n) + " (read as roman)"124	}125	return "not a number"126}127128// isRomanNumeral reports whether every byte is a roman digit. It is a screen129// for romanOf, not a validity check: "IIII" passes here and FromRoman decides.130func isRomanNumeral(s string) bool {131	if s == "" {132		return false133	}134	for i := 0; i < len(s); i++ {135		switch s[i] {136		case 'I', 'V', 'X', 'L', 'C', 'D', 'M':137		default:138			return false139		}140	}141	return true142}143144// renderPair is the comparison, and the reason this realm exists: the two145// similarity measures answer different questions, and a reader should watch146// them disagree rather than be told they might.147func renderPair(a, b string) string {148	sa, sb := soundex.Encode(a), soundex.Encode(b)149150	t := ui.NewTable("measure", "answer")151	t.Row("`soundex` of "+ui.Cell(a), ui.Cell(sa))152	t.Row("`soundex` of "+ui.Cell(b), ui.Cell(sb))153	t.Row("sound alike", yesNo(soundex.Match(a, b)))154	t.Row("`levenshtein` distance", strconv.Itoa(levenshtein.Distance(a, b)))155	t.Row("`levenshtein` similarity", strconv.Itoa(levenshtein.Similarity(a, b))+"%")156157	return md.H2("Compared with "+ui.Inline(b)) + "\n" + t.String()158}159160func yesNo(b bool) string {161	if b {162		return "yes"163	}164	return "no"165}166167func renderTry() string {168	return md.H2("Try two") + "\n" +169		md.BulletList([]string{170			md.Link("knight vs night", "/r/moul/x/allinone/textlab/v0:knight/night") +171				" (one edit apart, yet soundex says unrelated)",172			md.Link("robert vs rupert", "/r/moul/x/allinone/textlab/v0:robert/rupert") +173				" (two edits apart, yet soundex says identical)",174			md.Link("1987 as a roman numeral", "/r/moul/x/allinone/textlab/v0:1987"),175		})176}177

Functions

  • Render(path string) string

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

Rendered

RenderedRawgnoweb ↗

vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.