1// Package rot13demo is a small gnoweb demo of the ROT13 / Caesar cipher2// provided by the [p/moul/x/daily/rot13](/p/moul/x/daily/rot13/v0) library: it3// renders an explanation, a worked ROT13 example, and interactive path-driven4// ROT13 and Caesar transforms.5//6// It contains no cipher logic of its own — everything comes from `rot13.Rot13`7// and `rot13.Caesar`. Render reads its input straight from the gnoweb path.8package rot13demo910import (11 "strconv"12 "strings"1314 "gno.land/p/moul/x/daily/rot13/v0"15)1617// hexNibble decodes a single hex digit, returning -1 if invalid.18func hexNibble(b byte) int {19 switch {20 case b >= '0' && b <= '9':21 return int(b - '0')22 case b >= 'a' && b <= 'f':23 return int(b-'a') + 1024 case b >= 'A' && b <= 'F':25 return int(b-'A') + 1026 }27 return -128}2930// percentDecode turns "%20"/"+" style path escapes back into readable text so31// gnoweb links with spaces or punctuation round-trip nicely. Invalid escapes32// are left verbatim.33func percentDecode(s string) string {34 var sb strings.Builder35 for i := 0; i < len(s); i++ {36 c := s[i]37 switch {38 case c == '+':39 sb.WriteByte(' ')40 case c == '%' && i+2 < len(s):41 hi, lo := hexNibble(s[i+1]), hexNibble(s[i+2])42 if hi >= 0 && lo >= 0 {43 sb.WriteByte(byte(hi<<4 | lo))44 i += 245 } else {46 sb.WriteByte(c)47 }48 default:49 sb.WriteByte(c)50 }51 }52 return sb.String()53}5455// Render is the gnoweb entry point (not a crossing call).56//57// Render("") -> explanation + a worked example58// Render("/Hello, Gno!") -> the input and its ROT1359// Render("/caesar/3/attack") -> a Caesar shift of 3 applied to "attack"60func Render(path string) string {61 p := strings.TrimPrefix(path, "/")6263 // Caesar sub-path: /caesar/<shift>/<text>64 if rest, ok := strings.CutPrefix(p, "caesar/"); ok {65 shiftStr, text, found := strings.Cut(rest, "/")66 if !found {67 return renderRoot() + "\n> Usage: `/caesar/<shift>/<text>` — e.g. `/caesar/3/attack%20at%20dawn`\n"68 }69 shift, err := strconv.Atoi(shiftStr)70 if err != nil {71 return renderRoot() + "\n> `" + shiftStr + "` is not a valid integer shift.\n"72 }73 text = percentDecode(text)74 return renderCaesar(text, shift)75 }7677 if p == "" {78 return renderRoot()79 }8081 return renderRot13(percentDecode(p))82}8384func renderRoot() string {85 var sb strings.Builder86 sb.WriteString("# ROT13\n\n")87 sb.WriteString("Demo of the [`p/moul/x/daily/rot13`](/p/moul/x/daily/rot13/v0) library — ")88 sb.WriteString("an on-chain port of Go's classic **ROT13** cipher, the standard-library ")89 sb.WriteString("`strings.Map` / `io.Reader` teaching example. Each ASCII letter is rotated ")90 sb.WriteString("13 places through the alphabet; everything else is left alone.\n\n")91 sb.WriteString("Because the alphabet has 26 letters and 13 is exactly half, **ROT13 is its ")92 sb.WriteString("own inverse** — encoding twice returns the original text.\n\n")9394 const demo = "Hello, Gno!"95 enc := rot13.Rot13(demo)96 sb.WriteString("## Example\n\n")97 sb.WriteString("| stage | text |\n|---|---|\n")98 sb.WriteString("| input | `" + demo + "` |\n")99 sb.WriteString("| ROT13 | `" + enc + "` |\n")100 sb.WriteString("| ROT13 again | `" + rot13.Rot13(enc) + "` |\n\n")101102 sb.WriteString("## Try it\n\n")103 sb.WriteString("- `Render(\"/Uryyb, Tab!\")` — decode any text (append it to the path).\n")104 sb.WriteString("- `Render(\"/caesar/3/attack at dawn\")` — a general Caesar shift.\n\n")105 sb.WriteString("Or call the library functions directly:\n\n")106 sb.WriteString("```go\n")107 sb.WriteString("rot13.Rot13(\"Hello, Gno!\") // " + strconv.Quote(enc) + "\n")108 sb.WriteString("rot13.Caesar(\"attack\", 3) // " + strconv.Quote(rot13.Caesar("attack", 3)) + "\n")109 sb.WriteString("rot13.Caesar(\"Hello\", 13) // same as Rot13: " + strconv.Quote(rot13.Caesar("Hello", 13)) + "\n")110 sb.WriteString("```\n")111 return sb.String()112}113114func renderRot13(text string) string {115 enc := rot13.Rot13(text)116 var sb strings.Builder117 sb.WriteString("# ROT13\n\n")118 sb.WriteString("| stage | text |\n|---|---|\n")119 sb.WriteString("| input | `" + text + "` |\n")120 sb.WriteString("| ROT13 | `" + enc + "` |\n\n")121 sb.WriteString("_ROT13 is its own inverse — running it on the output above gives back the input._\n\n")122 sb.WriteString("[Back to overview](/r/moul/x/daily/rot13demo/v0:)\n")123 return sb.String()124}125126func renderCaesar(text string, shift int) string {127 enc := rot13.Caesar(text, shift)128 var sb strings.Builder129 sb.WriteString("# Caesar shift " + strconv.Itoa(shift) + "\n\n")130 sb.WriteString("| stage | text |\n|---|---|\n")131 sb.WriteString("| input | `" + text + "` |\n")132 sb.WriteString("| shifted | `" + enc + "` |\n\n")133 sb.WriteString("_Decode with the opposite shift: `/caesar/" + strconv.Itoa(-shift) + "/...`._\n\n")134 sb.WriteString("[Back to overview](/r/moul/x/daily/rot13demo/v0:)\n")135 return sb.String()136}137Render(path string) string
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
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.