romannumdemo.gno
3.72 Kb · 111 lines
1// Package romannumdemo is a small gnoweb demo of the Roman-numeral converter
2// provided by the [p/moul/x/daily/romannum](/p/moul/x/daily/romannum/v0)
3// library: it renders a table of worked examples and interactive
4// integer→Roman and Roman→integer conversions.
5//
6// It contains no conversion logic of its own — everything comes from
7// romannum.ToRoman and romannum.FromRoman.
8package romannumdemo
9
10import (
11 "strconv"
12 "strings"
13
14 "gno.land/p/g1magld5ptgzaktqcexxq0pgq5lmdf0kz6re8hlv/md/v0"
15 "gno.land/p/g1magld5ptgzaktqcexxq0pgq5lmdf0kz6re8hlv/x/daily/romannum/v0"
16)
17
18// examples are the classic milestones shown by the root Render.
19var examples = []int{1, 4, 9, 14, 40, 49, 90, 400, 900, 1994, 2024, 3888, 3999}
20
21// Render drives gnoweb:
22// - "" or "/" → a table of example conversions
23// - "/<n>" → integer → Roman
24// - "/r/<roman>" → Roman → integer
25func Render(path string) string {
26 arg := strings.Trim(strings.TrimSpace(path), "/")
27
28 switch {
29 case arg == "":
30 return renderTable()
31 case strings.HasPrefix(strings.ToLower(arg), "r/"):
32 return renderFromRoman(arg[2:])
33 default:
34 return renderToRoman(arg)
35 }
36}
37
38func renderToRoman(arg string) string {
39 n, err := strconv.Atoi(strings.TrimSpace(arg))
40 if err != nil {
41 return "# Roman numeral converter\n\nCould not parse " + md.InlineCode(arg) +
42 "` as an integer.\n\n" + usage()
43 }
44 if n < 1 || n > 3999 {
45 return "# Roman numeral converter\n\n`" + strconv.Itoa(n) +
46 "` is out of range — Roman numerals here cover **1..3999**.\n\n" + usage()
47 }
48 var b strings.Builder
49 b.WriteString("# " + strconv.Itoa(n) + " → " + romannum.ToRoman(n) + "\n\n")
50 b.WriteString("| decimal | roman |\n|---:|:---|\n")
51 b.WriteString("| " + strconv.Itoa(n) + " | **" + romannum.ToRoman(n) + "** |\n\n")
52 if n > 1 {
53 b.WriteString("[« " + strconv.Itoa(n-1) + "](/r/moul/x/daily/romannumdemo/v0:" +
54 strconv.Itoa(n-1) + ") · ")
55 }
56 if n < 3999 {
57 b.WriteString("[" + strconv.Itoa(n+1) + " »](/r/moul/x/daily/romannumdemo/v0:" +
58 strconv.Itoa(n+1) + ")")
59 }
60 b.WriteString("\n\n" + usage())
61 return b.String()
62}
63
64func renderFromRoman(arg string) string {
65 arg = strings.TrimSpace(arg)
66 n, ok := tryFromRoman(arg)
67 if !ok {
68 return "# Roman numeral converter\n\n" + md.InlineCode(arg) +
69 " is not a valid Roman numeral (1..3999).\n\n" + usage()
70 }
71 up := strings.ToUpper(arg)
72 var b strings.Builder
73 b.WriteString("# " + up + " → " + strconv.Itoa(n) + "\n\n")
74 b.WriteString("| roman | decimal |\n|:---|---:|\n")
75 b.WriteString("| **" + up + "** | " + strconv.Itoa(n) + " |\n\n")
76 b.WriteString(usage())
77 return b.String()
78}
79
80// tryFromRoman wraps the panic-on-invalid romannum.FromRoman in a panic-free
81// (value, ok) shape, so the demo can report a friendly error for bad input.
82func tryFromRoman(s string) (n int, ok bool) {
83 defer func() {
84 if recover() != nil {
85 n, ok = 0, false
86 }
87 }()
88 return romannum.FromRoman(s), true
89}
90
91func renderTable() string {
92 var b strings.Builder
93 b.WriteString("# Roman numeral converter\n\n")
94 b.WriteString("A tiny demo of the [`p/moul/x/daily/romannum`](/p/moul/x/daily/romannum/v0) ")
95 b.WriteString("library — the classic `ToRoman` / `FromRoman` kata. ")
96 b.WriteString("Pure and deterministic.\n\n")
97 b.WriteString("## Examples\n\n")
98 b.WriteString("| decimal | roman |\n|---:|:---|\n")
99 for _, n := range examples {
100 b.WriteString("| [" + strconv.Itoa(n) + "](/r/moul/x/daily/romannumdemo/v0:" +
101 strconv.Itoa(n) + ") | " + romannum.ToRoman(n) + " |\n")
102 }
103 b.WriteString("\n" + usage())
104 return b.String()
105}
106
107func usage() string {
108 return "## Try it\n\n" +
109 "- `:/<n>` — integer → Roman, e.g. `:/2024` → " + romannum.ToRoman(2024) + "\n" +
110 "- `:/r/<roman>` — Roman → integer, e.g. `:/r/MMXXIV` → 2024\n"
111}