Transaction

7C58599E01E58E…0AFA923646FF

Block 274,199 · index 0 · indexed

Summary

Hash
7C58599E01E58EBD9F2568AC2FC1BFE3AB6F0C7625159E2ADD360AFA923646FF
Block
274,199
Size
29370 bytes
Gas used
36,813,209 / 99,033,400
Fee
297100 ugnot
Status
success

Messages

Attached funds
6000000 ugnot

Arguments · 9

  1. #1devtools
  2. #2README.md
  3. #3# `gno.land/r/moul/x/allinone/devtools` The worked example of wiring moul's developer packages into one realm: what a new realm should copy. Eight packages, each earning its place in the output: | package | what it contributes | |---|---| | [`r/moul/config`](../../../config) | the notice block, the pause guard, which explorer to link | | [`p/moul/mygnoscan`](../../../../p/moul/mygnoscan) | the explorer routes | | [`p/moul/debug`](../../../../p/moul/debug) | the `?debug=1` panel | | [`p/moul/realmpath`](../../../../p/moul/realmpath) | reading the render path | | [`p/moul/kit/ui`](../../../../p/moul/kit/ui) | escaping caller input, and the tables | | [`p/moul/md`](../../../../p/moul/md) | the markdown | | [`p/moul/txlink`](../../../../p/moul/txlink) | the call links | | [`p/moul/fifo`](../../../../p/moul/fifo) | the bounded note list | ## The three lines worth copying ```go func Render(path string) string { return config.TopBlockFor(realmPath) + body + config.BottomBlockFor(realmPath) } func Add(cur realm, body string) { config.AssertWritableFor(realmPath) // honours the pause switch notes.Append(ui.Inline(body)) // escapes what a caller typed } ``` The first gives the realm a place for a warning and a pause banner without shipping any banner code, and returns `""` while nothing is set, so a page that concatenates it unconditionally is unchanged by default. The second is two separate guards and both have to be there: the pause one so an incident can stop writes from outside this realm and without a redeploy, and the escaping one because a string a caller typed is the oldest way to break a page. ## Two views `Render("")` is the root and is free of anything that moves, so an example test can pin it. Everything live (the height, the chain id, the links built from them) is under `:chain`, which is also what keeps the pinned output stable. State is disposable: the note list is bounded by `fifo` at 10 entries and this realm is `private = true`, so a redeploy wipes it on purpose. <!-- BEGIN GNOCONTRACTS FOOTER (generated by `make readmes`; do not edit below) --> --- Part of **[moul/gno-contracts](https://github.com/moul/gno-contracts)** — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage. **Dependency graph:** ![gno.land/r/moul/x/allinone/devtools/v0 dependency graph](https://raw.githubusercontent.com/moul/gno-contracts/main/_assets/gno.land/r/moul/x/allinone/devtools/v0/deps.png) > 🧪 **Highly experimental — potentially vibe-coded.** Not audited; may break, change, or be removed at any time. Do not use with anything of value. Full disclaimer: [DISCLAIMER](https://github.com/moul/gno-contracts/blob/main/DISCLAIMER.md). <!-- END GNOCONTRACTS FOOTER -->
  4. #4devtools.gno
  5. #5// Package devtools is the worked example of wiring moul's developer packages // into one realm: what a new realm should copy. // // Everything here is a COMBINATION. Any one of these packages has its own demo // showing it alone; the thing that is hard to find is what they look like used // together, which is the only form a real realm ever meets them in. So this // realm imports eight of them and every one earns its place in the output: // // r/moul/config the notice block, the pause switch, the explorer to link // p/moul/mygnoscan the explorer routes themselves // p/moul/debug a metadata panel behind ?debug=1 // p/moul/realmpath reading the query string Render was handed // p/moul/kit/ui escaping what a caller typed, and the tables // p/moul/md the markdown, without hand-rolled strings // p/moul/txlink the "call this function" link // p/moul/fifo the bounded note list, so the state cannot grow forever // // # The three lines worth copying // // A realm that does nothing else should still do these: // // func Render(path string) string { // return config.TopBlockFor(realmPath) + body + config.BottomBlockFor(realmPath) // } // // func Mutate(cur realm, s string) { // config.AssertWritableFor(realmPath) // honours the pause switch // notes.Append(ui.Inline(s)) // escapes what a caller typed // } // // The first gives every realm a place to put a warning and a pause banner // without shipping banner code. The second is two separate guards that both // have to be there: the pause one so an incident can stop writes from outside // the realm, and the escaping one because a string a caller typed is the // oldest way to break a page. package devtools import ( "strconv" "strings" "chain/runtime" "gno.land/p/moul/debug/v0" "gno.land/p/moul/fifo/v0" "gno.land/p/moul/kit/ui/v0" "gno.land/p/moul/md/v0" "gno.land/p/moul/mygnoscan/v0" "gno.land/p/moul/realmpath/v0" "gno.land/p/moul/txlink/v0" "gno.land/r/moul/config/v1" ) // realmPath is this realm's own path. // // Passed explicitly to config rather than relying on its zero-argument helpers. // Both work (a plain read is borrowed, so config sees this realm as the // caller), but a constant cannot be wrong, and spelling it out is what a reader // copying this file needs to see. const realmPath = "gno.land/r/moul/x/allinone/devtools/v0" // maxNotes bounds the guest list. fifo drops the oldest rather than growing, // because storage on chain is paid for and never refunded: an unbounded list in // a demo anyone can write to is a bill with no ceiling. const maxNotes = 10 var notes = fifo.New(maxNotes) // Add appends a note. It is the only mutating function here, and it carries // both guards a real one should. func Add(cur realm, body string) { // 1. The pause switch. Set `pause@r/moul/x/allinone/devtools` on // r/moul/config and this aborts, with the reason, from outside this // realm and without a redeploy. config.AssertWritableFor(realmPath) body = strings.TrimSpace(body) if body == "" { panic("empty note") } if len(body) > 200 { panic("note too long: max 200 bytes, got " + strconv.Itoa(len(body))) } // 2. Escaping. body is a string a caller typed, and it is about to land in // a markdown list. ui.Inline is the repo's standard for exactly this. notes.Append(ui.Inline(body)) } // Reset empties the list, so the realm can be put back to a known state // without a redeploy. Not gated by the pause switch: clearing up is the thing // you still want to do while paused. func Reset(cur realm) { notes = fifo.New(maxNotes) } // Render shows every package at work. The root view is deliberately free of // anything that moves on its own, so an example test can pin it; the live // numbers live under "chain". func Render(path string) string { req := realmpath.Parse(path) var b strings.Builder b.WriteString(config.TopBlockFor(realmPath)) b.WriteString(md.H1("allinone: devtools")) b.WriteString("\nEight packages, used together rather than one at a time. ") b.WriteString(md.Link("source", config.MygnoscanFor(realmPath))) b.WriteString("\n\n") switch req.PathPart(0) { case "chain": b.WriteString(renderChain()) default: b.WriteString(renderHome(path)) } // debug prints its own panel only when ?debug=1 is set, and the empty // string otherwise, so this line is safe to leave in a deployed realm. b.WriteString(debug.Render(path)) b.WriteString(config.BottomBlockFor(realmPath)) return b.String() } func renderHome(path string) string { var b strings.Builder b.WriteString(md.H2("Notes")) b.WriteString("\n") // renderNotes returns no trailing newline of its own: md.BulletList and // ui.Empty already end in one, and adding a second here produced two // consecutive blank lines, which gno collapses inside an // Output: block // and which therefore no example test can ever pin. b.WriteString(renderNotes()) b.WriteString("\n") // txlink builds the "call this function" URL for the CURRENT realm, so it // keeps working after a version bump without anything here changing. b.WriteString(md.BulletList([]string{ md.Link("add a note", txlink.NewLink("Add").AddArgs("body", "hello").URL()), md.Link("clear them", txlink.Call("Reset")), md.Link("live chain values", "/r/moul/x/allinone/devtools/v0:chain"), md.Link("toggle the debug panel", debug.ToggleURL(path)), })) b.WriteString("\n") b.WriteString(md.H2("What each package does here")) b.WriteString("\n") t := ui.NewTable("package", "what it contributed") t.Row("`r/moul/config`", "the notice block above, the pause guard on `Add`, and which explorer to link") t.Row("`p/moul/mygnoscan`", "the explorer routes, so a link survives a route change") t.Row("`p/moul/debug`", "the `?debug=1` panel, empty otherwise") t.Row("`p/moul/realmpath`", "splitting the render path into a view and a query") t.Row("`p/moul/kit/ui`", "escaping a caller's note, and this table") t.Row("`p/moul/md`", "every heading, link and list on the page") t.Row("`p/moul/txlink`", "the call links above, relative to this realm") t.Row("`p/moul/fifo`", "the note list, bounded so storage cannot grow forever") b.WriteString(t.String()) return b.String() } // renderChain is everything that moves: the height, and the links built from // it. Kept off the root view so the root can be pinned by an example. func renderChain() string { s := config.Scanner() h := runtime.ChainHeight() var b strings.Builder b.WriteString(md.H2("Live")) b.WriteString("\n") t := ui.NewTable("what", "value") t.Row("chain id", ui.Cell(runtime.ChainID())) t.Row("height", strconv.FormatInt(h, 10)) t.Row("explorer", ui.Cell(s.Base())) t.Row("network", ui.Cell(s.Network())) b.WriteString(t.String()) b.WriteString("\n") // mygnoscan.Link used directly, rather than through config, to show that // the builder is an ordinary value: config only decides where it points. b.WriteString(md.BulletList([]string{ mygnoscan.Link("this block", s.Block(h)), mygnoscan.Link("this realm's transactions", s.Realm(realmPath, mygnoscan.TabCalls)), mygnoscan.Link("its dependency graph", s.Realm(realmPath, mygnoscan.TabDeps)), mygnoscan.Link("the parked deploy queue", s.Packages(mygnoscan.PackagesInert)), })) b.WriteString("\n") b.WriteString(md.Link("back", "/r/moul/x/allinone/devtools/v0")) b.WriteString("\n") return b.String() } func renderNotes() string { if notes.Size() == 0 { return ui.Empty("no notes yet") } items := []string{} for _, e := range notes.Entries() { items = append(items, e.(string)) } return md.BulletList(items) }
  6. #6devtools_test.gno
  7. #7package devtools import ( "testing" "gno.land/p/moul/fifo/v0" "gno.land/r/moul/config/v1" "gno.land/p/nt/uassert/v0" ) // reset puts the realm back to a just-deployed state. Realm globals live for // the whole test binary and gno runs every Example AFTER every Test, so without // this the pinned output would depend on which tests ran before it. func reset() { notes = fifo.New(maxNotes) } func TestAdd(cur realm, t *testing.T) { reset() Add(cross(cur), "hello") uassert.Equal(t, 1, notes.Size()) uassert.Equal(t, "hello", notes.Get(0).(string)) uassert.AbortsWithMessage(t, cur, "empty note", func() { Add(cross(cur), " ") }) uassert.AbortsContains(t, cur, "note too long", func() { long := "" for i := 0; i < 201; i++ { long += "x" } Add(cross(cur), long) }) } // TestAddEscapes is the guard that matters most in a realm anyone can write to: // a note is a string a caller typed, and it lands in a markdown list. Without // ui.Inline the first line below would close the list item and render as a // heading, and the second would become a link. func TestAddEscapes(cur realm, t *testing.T) { reset() Add(cross(cur), "# not a heading") Add(cross(cur), "[not](https://evil.example.com)") for _, got := range []string{notes.Get(0).(string), notes.Get(1).(string)} { uassert.False(t, got == "# not a heading", "markdown survived unescaped") uassert.False(t, got == "[not](https://evil.example.com)", "a link survived unescaped") } uassert.True(t, len(notes.Get(0).(string)) > len("# not a heading"), "escaping should have added characters") } // TestNotesAreBounded pins the reason fifo is here rather than a slice: storage // is paid for and never refunded, so a list anyone can append to needs a // ceiling in the type, not in a comment. func TestNotesAreBounded(cur realm, t *testing.T) { reset() for i := 0; i < maxNotes+5; i++ { Add(cross(cur), "note "+string(rune('a'+i))) } uassert.Equal(t, maxNotes, notes.Size(), "the list stops growing at maxNotes") uassert.Equal(t, maxNotes, notes.MaxSize()) } // TestAddHonoursThePause is the whole point of importing r/moul/config: this // realm can be stopped from outside, by a transaction against another realm, // with no redeploy here. It cannot set that from this package (config has its // own authority), so it asserts the wiring: Add goes through the pause check // and passes while nothing is set. func TestAddHonoursThePause(cur realm, t *testing.T) { reset() uassert.False(t, config.IsPausedFor(realmPath), "nothing configured means running") Add(cross(cur), "allowed while running") uassert.Equal(t, 1, notes.Size()) } func TestReset(cur realm, t *testing.T) { reset() Add(cross(cur), "one") Reset(cross(cur)) uassert.Equal(t, 0, notes.Size()) } // ExampleRender pins the root view. It is free of the height and the chain id // on purpose: those live under :chain, so this one is stable. func ExampleRender() { reset() print(Render("")) // Output: // # allinone: devtools // // Eight packages, used together rather than one at a time. [source](https://mygnoscan.moul.p2p.team/realm/r/moul/x/allinone/devtools/v0) // // ## Notes // // *no notes yet* // // - [add a note](/r/moul/x/allinone/devtools/v0$help&func=Add&body=hello) // - [clear them](/r/moul/x/allinone/devtools/v0$help&func=Reset) // - [live chain values](/r/moul/x/allinone/devtools/v0:chain) // - [toggle the debug panel](/r/moul/x/allinone/devtools/v0:?debug=1) // // ## What each package does here // // | package | what it contributed | // | --- | --- | // | `r/moul/config` | the notice block above, the pause guard on `Add`, and which explorer to link | // | `p/moul/mygnoscan` | the explorer routes, so a link survives a route change | // | `p/moul/debug` | the `?debug=1` panel, empty otherwise | // | `p/moul/realmpath` | splitting the render path into a view and a query | // | `p/moul/kit/ui` | escaping a caller's note, and this table | // | `p/moul/md` | every heading, link and list on the page | // | `p/moul/txlink` | the call links above, relative to this realm | // | `p/moul/fifo` | the note list, bounded so storage cannot grow forever | }
  8. #8gnomod.toml
  9. #9module = "gno.land/r/moul/x/allinone/devtools/v0" gno = "0.9" # Redeployable by its creator. A demo's state is disposable by design: its whole # job is to be redeployed whenever one of the packages it shows off changes, so # losing the notes people left is the intended trade rather than a regret. private = true
Attached funds
6000000 ugnot

Arguments · 9

  1. #1textlab
  2. #2README.md
  3. #3# `gno.land/r/moul/x/allinone/textlab` Every string algorithm in `p/moul` over one input at once, so they can be compared instead of read about one at a time. ``` /r/moul/x/allinone/textlab/v0:knight one word /r/moul/x/allinone/textlab/v0:knight/night compared with another /r/moul/x/allinone/textlab/v0:1987 read as a roman numeral ``` Eight packages: `soundex`, `levenshtein`, `rot13`, `piglatin`, `romannum`, plus `kit/ui`, `md`, `realmpath` and `r/moul/config`. ## What the combination shows soundex and levenshtein both claim to tell you whether two words are alike, and they are measuring different things. soundex keys on the **first letter** and then on consonant classes, so it is blind to how the rest is spelled. levenshtein counts edits and is blind to how it sounds. | pair | soundex | levenshtein | |---|---|---| | `knight` / `night` | K523 vs N230, **unrelated** | 1 edit, 83% similar | | `robert` / `rupert` | R163 vs R163, **identical** | 2 edits, 67% similar | The first is the clean case: one edit apart, and yet soundex says no, because the silent K changes the first character of the code. Measured 2026-09-23 against the packages in this tree and pinned by `TestTheDisagreement`, so the page cannot start saying something false without the suite going red. ## One defect worth keeping in mind `romannum.FromRoman` **panics** on a string that is not a roman numeral rather than returning zero. The input here comes out of a URL, so it is screened by `isRomanNumeral` first. Without that screen the default view aborted, because `knight` is not a roman numeral and neither is most of what anyone would type. No state at all: the input comes from the render path and nothing is stored. <!-- BEGIN GNOCONTRACTS FOOTER (generated by `make readmes`; do not edit below) --> --- Part of **[moul/gno-contracts](https://github.com/moul/gno-contracts)** — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage. **Dependency graph:** ![gno.land/r/moul/x/allinone/textlab/v0 dependency graph](https://raw.githubusercontent.com/moul/gno-contracts/main/_assets/gno.land/r/moul/x/allinone/textlab/v0/deps.png) > 🧪 **Highly experimental — potentially vibe-coded.** Not audited; may break, change, or be removed at any time. Do not use with anything of value. Full disclaimer: [DISCLAIMER](https://github.com/moul/gno-contracts/blob/main/DISCLAIMER.md). <!-- END GNOCONTRACTS FOOTER -->
  4. #4gnomod.toml
  5. #5module = "gno.land/r/moul/x/allinone/textlab/v0" gno = "0.9" # Redeployable by its creator. This realm holds no state worth keeping: it runs # algorithms over a string in the render path, so a redeploy loses nothing. private = true
  6. #6textlab.gno
  7. #7// Package textlab runs every string algorithm in p/moul over one input at // once, so they can be compared rather than read about one at a time. // // Each of these has its own demo realm showing it alone. What none of those can // show is how the answers differ for the SAME word, which is the only question // a reader actually has: soundex and levenshtein both claim to tell you whether // two words are alike, and they are measuring different things. // // soundex keys on the FIRST LETTER and then on consonant classes, so it is // blind to how the rest is spelled. levenshtein counts edits and is blind to // how it sounds. "knight" and "night" are the clean case: one edit apart (83% // similar) and yet soundex calls them unrelated, K523 against N230, because // the silent K changes the first character of the code. "robert" and "rupert" // go the other way, identical codes at two edits apart. Measured 2026-09-23 // against the packages in this tree, not quoted from a textbook. // // /r/moul/x/allinone/textlab/v0:knight // /r/moul/x/allinone/textlab/v0:knight/night compares two // // Eight packages, no state, nothing to redeploy carefully: the input comes out // of the render path and nothing is stored. package textlab import ( "strconv" "strings" "gno.land/p/moul/kit/ui/v0" "gno.land/p/moul/md/v0" "gno.land/p/moul/realmpath/v0" "gno.land/p/moul/x/daily/levenshtein/v0" "gno.land/p/moul/x/daily/piglatin/v0" "gno.land/p/moul/x/daily/romannum/v0" "gno.land/p/moul/x/daily/rot13/v0" "gno.land/p/moul/x/daily/soundex/v0" "gno.land/r/moul/config/v1" ) const realmPath = "gno.land/r/moul/x/allinone/textlab/v0" // maxInput bounds what a render path can carry into the algorithms below. // levenshtein.Matrix is O(len(a) * len(b)), so an unbounded pair of inputs is // a way to make a query expensive for whoever serves it. const maxInput = 64 // defaultWord is what the root view demonstrates, chosen because it is the // textbook case where the two similarity measures disagree: "knight" and // "night" sound identical to soundex and are one edit apart. const defaultWord = "knight" // Render takes its input from the path: one word, or two separated by "/". func Render(path string) string { req := realmpath.Parse(path) a := strings.TrimSpace(req.PathPart(0)) b := strings.TrimSpace(req.PathPart(1)) if a == "" { a = defaultWord } var out strings.Builder out.WriteString(config.TopBlockFor(realmPath)) out.WriteString(md.H1("allinone: textlab")) out.WriteString("\nEight packages over one input. ") out.WriteString(md.Link("source", config.MygnoscanFor(realmPath))) out.WriteString("\n\n") if tooLong(a) || tooLong(b) { out.WriteString(ui.Empty("input too long: max " + strconv.Itoa(maxInput) + " bytes")) out.WriteString("\n") out.WriteString(config.BottomBlockFor(realmPath)) return out.String() } out.WriteString(renderOne(a)) if b != "" { out.WriteString("\n") out.WriteString(renderPair(a, b)) } else { out.WriteString("\n") out.WriteString(renderTry()) } out.WriteString(config.BottomBlockFor(realmPath)) return out.String() } func tooLong(s string) bool { return len(s) > maxInput } // renderOne is every single-input algorithm, one row each. // // ui.Cell, not ui.Inline: these land in table cells, where an unescaped pipe // would silently eat a column. The input is whatever someone put in a URL. func renderOne(word string) string { t := ui.NewTable("package", "answer") t.Row("`x/daily/soundex`", ui.Cell(soundex.Encode(word))) t.Row("`x/daily/rot13`", ui.Cell(rot13.Rot13(word))) t.Row("`x/daily/piglatin`", ui.Cell(piglatin.Translate(word))) t.Row("`x/daily/romannum`", ui.Cell(romanOf(word))) return md.H2("Input: "+ui.Inline(word)) + "\n" + t.String() } // romanOf shows the round trip both ways, because romannum is the one package // here whose input is a number rather than a word. // // FromRoman PANICS on anything that is not a roman numeral rather than // returning zero, and the input here comes out of a URL, so it is screened // first. Without that screen the DEFAULT view aborts: "knight" is not a roman // numeral, and neither is most of what anyone would type. func romanOf(word string) string { if n, err := strconv.Atoi(word); err == nil { if n <= 0 || n > 3999 { return "out of range (1..3999)" } return romannum.ToRoman(n) } upper := strings.ToUpper(word) if !isRomanNumeral(upper) { return "not a number" } if n := romannum.FromRoman(upper); n > 0 { return strconv.Itoa(n) + " (read as roman)" } return "not a number" } // isRomanNumeral reports whether every byte is a roman digit. It is a screen // for romanOf, not a validity check: "IIII" passes here and FromRoman decides. func isRomanNumeral(s string) bool { if s == "" { return false } for i := 0; i < len(s); i++ { switch s[i] { case 'I', 'V', 'X', 'L', 'C', 'D', 'M': default: return false } } return true } // renderPair is the comparison, and the reason this realm exists: the two // similarity measures answer different questions, and a reader should watch // them disagree rather than be told they might. func renderPair(a, b string) string { sa, sb := soundex.Encode(a), soundex.Encode(b) t := ui.NewTable("measure", "answer") t.Row("`soundex` of "+ui.Cell(a), ui.Cell(sa)) t.Row("`soundex` of "+ui.Cell(b), ui.Cell(sb)) t.Row("sound alike", yesNo(soundex.Match(a, b))) t.Row("`levenshtein` distance", strconv.Itoa(levenshtein.Distance(a, b))) t.Row("`levenshtein` similarity", strconv.Itoa(levenshtein.Similarity(a, b))+"%") return md.H2("Compared with "+ui.Inline(b)) + "\n" + t.String() } func yesNo(b bool) string { if b { return "yes" } return "no" } func renderTry() string { return md.H2("Try two") + "\n" + md.BulletList([]string{ md.Link("knight vs night", "/r/moul/x/allinone/textlab/v0:knight/night") + " (one edit apart, yet soundex says unrelated)", md.Link("robert vs rupert", "/r/moul/x/allinone/textlab/v0:robert/rupert") + " (two edits apart, yet soundex says identical)", md.Link("1987 as a roman numeral", "/r/moul/x/allinone/textlab/v0:1987"), }) }
  8. #8textlab_test.gno
  9. #9package textlab import ( "testing" "gno.land/p/moul/x/daily/levenshtein/v0" "gno.land/p/moul/x/daily/soundex/v0" "gno.land/p/nt/uassert/v0" ) // TestRomanOf covers the one package here whose input is a number rather than // a word, in both directions plus the two ways it can be neither. func TestRomanOf(t *testing.T) { tests := []struct{ in, want string }{ {"1987", "MCMLXXXVII"}, {"1", "I"}, {"3999", "MMMCMXCIX"}, {"0", "out of range (1..3999)"}, {"-5", "out of range (1..3999)"}, {"4000", "out of range (1..3999)"}, {"MCMLXXXVII", "1987 (read as roman)"}, {"mcmlxxxvii", "1987 (read as roman)"}, {"knight", "not a number"}, } for _, tt := range tests { uassert.Equal(t, tt.want, romanOf(tt.in), "romanOf("+tt.in+")") } } // TestTheDisagreement is the claim the realm is built to show, pinned in both // directions. Measured 2026-09-23; the numbers in the package doc and on the // page come from here, so if either package changes its mind this fails rather // than the page quietly stating something false. func TestTheDisagreement(t *testing.T) { // One edit apart, and yet soundex says unrelated: it keys on the first // letter, and the silent K changes it. uassert.Equal(t, "K523", soundex.Encode("knight")) uassert.Equal(t, "N230", soundex.Encode("night")) uassert.False(t, soundex.Match("knight", "night"), "different soundex codes") uassert.Equal(t, 1, levenshtein.Distance("knight", "night")) uassert.Equal(t, 83, levenshtein.Similarity("knight", "night")) // And the other way: two edits apart, identical codes. uassert.Equal(t, "R163", soundex.Encode("robert")) uassert.Equal(t, "R163", soundex.Encode("rupert")) uassert.True(t, soundex.Match("robert", "rupert"), "same soundex code") uassert.Equal(t, 2, levenshtein.Distance("robert", "rupert")) } // TestRomanOfDoesNotAbortOnOrdinaryWords is the defect this realm shipped with // until a test found it: romannum.FromRoman PANICS on a non-numeral instead of // returning zero, so the DEFAULT view aborted. "knight" is not a roman numeral, // and neither is most of what anyone would put in the URL. func TestRomanOfDoesNotAbortOnOrdinaryWords(t *testing.T) { for _, w := range []string{"knight", "night", "a|b", "", "hello world", "MMM!"} { uassert.Equal(t, "not a number", romanOf(w), "romanOf("+w+")") } uassert.True(t, isRomanNumeral("MCMLXXXVII")) uassert.False(t, isRomanNumeral("KNIGHT")) uassert.False(t, isRomanNumeral("")) } // TestInputIsBounded pins the ceiling. levenshtein.Matrix is O(len(a)*len(b)), // so an unbounded pair out of a URL is a way to make a query expensive for // whoever serves it. func TestInputIsBounded(t *testing.T) { long := "" for i := 0; i < maxInput+1; i++ { long += "x" } uassert.True(t, tooLong(long)) uassert.False(t, tooLong("knight")) out := Render(long) uassert.True(t, len(out) < 600, "an over-long input renders a refusal, not a matrix") } // TestTableCellsAreEscaped: the input comes out of a URL and lands in a table // cell, where an unescaped pipe eats a column. func TestTableCellsAreEscaped(t *testing.T) { out := Render("a|b") uassert.False(t, contains(out, "| a|b |"), "a raw pipe reached a table cell") } func contains(h, n string) bool { for i := 0; i+len(n) <= len(h); i++ { if h[i:i+len(n)] == n { return true } } return false } // ExampleRender pins the root view, which is the default word and the "try // two" list. Nothing here moves with the chain. func ExampleRender() { print(Render("")) // Output: // # allinone: textlab // // Eight packages over one input. [source](https://mygnoscan.moul.p2p.team/realm/r/moul/x/allinone/textlab/v0) // // ## Input: knight // // | package | answer | // | --- | --- | // | `x/daily/soundex` | K523 | // | `x/daily/rot13` | xavtug | // | `x/daily/piglatin` | ightknay | // | `x/daily/romannum` | not a number | // // ## Try two // // - [knight vs night](/r/moul/x/allinone/textlab/v0:knight/night) (one edit apart, yet soundex says unrelated) // - [robert vs rupert](/r/moul/x/allinone/textlab/v0:robert/rupert) (two edits apart, yet soundex says identical) // - [1987 as a roman numeral](/r/moul/x/allinone/textlab/v0:1987) } // ExampleRender_pair pins the comparison view, which is the realm's whole // argument: the same pair, judged opposite ways by the two measures. func ExampleRender_pair() { print(Render("knight/night")) // Output: // # allinone: textlab // // Eight packages over one input. [source](https://mygnoscan.moul.p2p.team/realm/r/moul/x/allinone/textlab/v0) // // ## Input: knight // // | package | answer | // | --- | --- | // | `x/daily/soundex` | K523 | // | `x/daily/rot13` | xavtug | // | `x/daily/piglatin` | ightknay | // | `x/daily/romannum` | not a number | // // ## Compared with night // // | measure | answer | // | --- | --- | // | `soundex` of knight | K523 | // | `soundex` of night | N230 | // | sound alike | no | // | `levenshtein` distance | 1 | // | `levenshtein` similarity | 83% | }

Result log

msg:0,success:true,log:,events:[]
msg:1,success:true,log:,events:[]