romannumdemo.gno
3.60 Kb · 110 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/moul/x/daily/romannum/v0"
15)
16
17// 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}
19
20// Render drives gnoweb:
21// - "" or "/" → a table of example conversions
22// - "/<n>" → integer → Roman
23// - "/r/<roman>" → Roman → integer
24func Render(path string) string {
25 arg := strings.Trim(strings.TrimSpace(path), "/")
26
27 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}
36
37func 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.Builder
48 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}
62
63func 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.Builder
72 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}
78
79// tryFromRoman wraps the panic-on-invalid romannum.FromRoman in a panic-free
80// (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, false
85 }
86 }()
87 return romannum.FromRoman(s), true
88}
89
90func renderTable() string {
91 var b strings.Builder
92 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}
105
106func 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}