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}