Search Apps Documentation Source Content File Folder Download Copy Actions Download 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}