1// Package romannumdemo is a small gnoweb demo of the Roman-numeral converter2// provided by the [p/moul/x/daily/romannum](/p/moul/x/daily/romannum/v0)3// library: it renders a table of worked examples and interactive4// integer→Roman and Roman→integer conversions.5//6// It contains no conversion logic of its own — everything comes from7// romannum.ToRoman and romannum.FromRoman.8package romannumdemo910import (11 "strconv"12 "strings"1314 "gno.land/p/moul/x/daily/romannum/v0"15)1617// examples are the classic milestones shown by the root Render.18var examples = []int{1, 4, 9, 14, 40, 49, 90, 400, 900, 1994, 2024, 3888, 3999}1920// Render drives gnoweb:21// - "" or "/" → a table of example conversions22// - "/<n>" → integer → Roman23// - "/r/<roman>" → Roman → integer24func Render(path string) string {25 arg := strings.Trim(strings.TrimSpace(path), "/")2627 switch {28 case arg == "":29 return renderTable()30 case strings.HasPrefix(strings.ToLower(arg), "r/"):31 return renderFromRoman(arg[2:])32 default:33 return renderToRoman(arg)34 }35}3637func renderToRoman(arg string) string {38 n, err := strconv.Atoi(strings.TrimSpace(arg))39 if err != nil {40 return "# Roman numeral converter\n\nCould not parse `" + arg +41 "` as an integer.\n\n" + usage()42 }43 if n < 1 || n > 3999 {44 return "# Roman numeral converter\n\n`" + strconv.Itoa(n) +45 "` is out of range — Roman numerals here cover **1..3999**.\n\n" + usage()46 }47 var b strings.Builder48 b.WriteString("# " + strconv.Itoa(n) + " → " + romannum.ToRoman(n) + "\n\n")49 b.WriteString("| decimal | roman |\n|---:|:---|\n")50 b.WriteString("| " + strconv.Itoa(n) + " | **" + romannum.ToRoman(n) + "** |\n\n")51 if n > 1 {52 b.WriteString("[« " + strconv.Itoa(n-1) + "](/r/moul/x/daily/romannumdemo/v0:" +53 strconv.Itoa(n-1) + ") · ")54 }55 if n < 3999 {56 b.WriteString("[" + strconv.Itoa(n+1) + " »](/r/moul/x/daily/romannumdemo/v0:" +57 strconv.Itoa(n+1) + ")")58 }59 b.WriteString("\n\n" + usage())60 return b.String()61}6263func renderFromRoman(arg string) string {64 arg = strings.TrimSpace(arg)65 n, ok := tryFromRoman(arg)66 if !ok {67 return "# Roman numeral converter\n\n`" + arg +68 "` is not a valid Roman numeral (1..3999).\n\n" + usage()69 }70 up := strings.ToUpper(arg)71 var b strings.Builder72 b.WriteString("# " + up + " → " + strconv.Itoa(n) + "\n\n")73 b.WriteString("| roman | decimal |\n|:---|---:|\n")74 b.WriteString("| **" + up + "** | " + strconv.Itoa(n) + " |\n\n")75 b.WriteString(usage())76 return b.String()77}7879// tryFromRoman wraps the panic-on-invalid romannum.FromRoman in a panic-free80// (value, ok) shape, so the demo can report a friendly error for bad input.81func tryFromRoman(s string) (n int, ok bool) {82 defer func() {83 if recover() != nil {84 n, ok = 0, false85 }86 }()87 return romannum.FromRoman(s), true88}8990func renderTable() string {91 var b strings.Builder92 b.WriteString("# Roman numeral converter\n\n")93 b.WriteString("A tiny demo of the [`p/moul/x/daily/romannum`](/p/moul/x/daily/romannum/v0) ")94 b.WriteString("library — the classic `ToRoman` / `FromRoman` kata. ")95 b.WriteString("Pure and deterministic.\n\n")96 b.WriteString("## Examples\n\n")97 b.WriteString("| decimal | roman |\n|---:|:---|\n")98 for _, n := range examples {99 b.WriteString("| [" + strconv.Itoa(n) + "](/r/moul/x/daily/romannumdemo/v0:" +100 strconv.Itoa(n) + ") | " + romannum.ToRoman(n) + " |\n")101 }102 b.WriteString("\n" + usage())103 return b.String()104}105106func usage() string {107 return "## Try it\n\n" +108 "- `:/<n>` — integer → Roman, e.g. `:/2024` → " + romannum.ToRoman(2024) + "\n" +109 "- `:/r/<roman>` — Roman → integer, e.g. `:/r/MMXXIV` → 2024\n"110}111Render(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.