Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

rot13demo.gno

4.74 Kb · 136 lines
  1// Package rot13demo is a small gnoweb demo of the ROT13 / Caesar cipher
  2// provided by the [p/moul/x/daily/rot13](/p/moul/x/daily/rot13/v0) library: it
  3// renders an explanation, a worked ROT13 example, and interactive path-driven
  4// 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 rot13demo
  9
 10import (
 11	"strconv"
 12	"strings"
 13
 14	"gno.land/p/moul/x/daily/rot13/v0"
 15)
 16
 17// 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') + 10
 24	case b >= 'A' && b <= 'F':
 25		return int(b-'A') + 10
 26	}
 27	return -1
 28}
 29
 30// percentDecode turns "%20"/"+" style path escapes back into readable text so
 31// gnoweb links with spaces or punctuation round-trip nicely. Invalid escapes
 32// are left verbatim.
 33func percentDecode(s string) string {
 34	var sb strings.Builder
 35	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 += 2
 45			} else {
 46				sb.WriteByte(c)
 47			}
 48		default:
 49			sb.WriteByte(c)
 50		}
 51	}
 52	return sb.String()
 53}
 54
 55// Render is the gnoweb entry point (not a crossing call).
 56//
 57//	Render("")                 -> explanation + a worked example
 58//	Render("/Hello, Gno!")     -> the input and its ROT13
 59//	Render("/caesar/3/attack") -> a Caesar shift of 3 applied to "attack"
 60func Render(path string) string {
 61	p := strings.TrimPrefix(path, "/")
 62
 63	// 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	}
 76
 77	if p == "" {
 78		return renderRoot()
 79	}
 80
 81	return renderRot13(percentDecode(p))
 82}
 83
 84func renderRoot() string {
 85	var sb strings.Builder
 86	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")
 93
 94	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")
101
102	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}
113
114func renderRot13(text string) string {
115	enc := rot13.Rot13(text)
116	var sb strings.Builder
117	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}
125
126func renderCaesar(text string, shift int) string {
127	enc := rot13.Caesar(text, shift)
128	var sb strings.Builder
129	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}