rledemo.gno
2.88 Kb · 100 lines
1// Package rledemo is a small gnoweb demo of the run-length codec provided by
2// the [p/moul/x/daily/rle](/p/moul/x/daily/rle/v0) library: it encodes a few
3// sample strings, round-trips them, and reports the size ratio — including the
4// case where the "compression" makes things bigger.
5//
6// It contains no codec logic of its own. Stateless, so Render is deterministic.
7package rledemo
8
9import (
10 "strconv"
11 "strings"
12
13 "gno.land/p/g1magld5ptgzaktqcexxq0pgq5lmdf0kz6re8hlv/md/v0"
14 "gno.land/p/g1magld5ptgzaktqcexxq0pgq5lmdf0kz6re8hlv/x/daily/rle/v0"
15)
16
17// samples are picked to show both outcomes: runny data compresses, data
18// without runs expands.
19var samples = []string{
20 "aaaaaaaaaabbbbbbbbbb",
21 "aaabbc",
22 "abcdef",
23}
24
25// Render renders the demo for gnoweb.
26//
27// Render("") / Render("/") -> the samples table
28// Render("/<text>") -> encode that text
29func Render(path string) string {
30 var b strings.Builder
31 b.WriteString("# Run-Length Encoding\n\n")
32 b.WriteString("`<count><char>` pairs, demoing the ")
33 b.WriteString("[`p/moul/x/daily/rle`](/p/moul/x/daily/rle/v0) library.\n\n")
34
35 if in := parseArg(path); in != "" {
36 return b.String() + one(in)
37 }
38
39 b.WriteString("| input | encoded | size | round-trips |\n|---|---|---|---|\n")
40 for _, s := range samples {
41 enc, err := rle.Encode(s)
42 if err != nil {
43 continue
44 }
45 dec, _ := rle.Decode(enc)
46 b.WriteString("| `")
47 b.WriteString(s)
48 b.WriteString("` | `")
49 b.WriteString(enc)
50 b.WriteString("` | ")
51 b.WriteString(strconv.Itoa(rle.Ratio(s, enc)))
52 b.WriteString("% | ")
53 if dec == s {
54 b.WriteString("✅")
55 } else {
56 b.WriteString("❌")
57 }
58 b.WriteString(" |\n")
59 }
60 b.WriteString("\n> Over 100% means the encoding made the data **bigger**. ")
61 b.WriteString("RLE only wins on runny input, and `abcdef` is the honest counter-example.\n\n")
62 b.WriteString("> Append text to the path to encode it — digits are rejected, ")
63 b.WriteString("since they would be ambiguous with a run count.\n")
64 return b.String()
65}
66
67// one encodes a caller's text. The encoder runs first, so a rejected input is
68// never echoed, and an accepted one still goes through md.InlineCode: rle
69// accepts a backtick, and its encoding carries it through.
70func one(in string) string {
71 var b strings.Builder
72 enc, err := rle.Encode(in)
73 if err != nil {
74 b.WriteString("_")
75 b.WriteString(err.Error())
76 b.WriteString("_\n")
77 return b.String()
78 }
79 b.WriteString("## " + md.InlineCode(in) + "\n\n")
80 dec, _ := rle.Decode(enc)
81 b.WriteString("- encoded: " + md.InlineCode(enc))
82 b.WriteString("\n- size: **")
83 b.WriteString(strconv.Itoa(rle.Ratio(in, enc)))
84 b.WriteString("%**\n- round-trips: ")
85 if dec == in {
86 b.WriteString("✅\n")
87 } else {
88 b.WriteString("❌\n")
89 }
90 return b.String()
91}
92
93func parseArg(path string) string {
94 s := strings.TrimSpace(path)
95 s = strings.TrimPrefix(s, "/")
96 if i := strings.IndexByte(s, '/'); i >= 0 {
97 s = s[:i]
98 }
99 return s
100}