feat(portfolio): nest asset positions

This commit is contained in:
2026-07-21 16:42:33 +07:00
parent 1c6e0ad8a0
commit 6fe91f0e4e
24 changed files with 830 additions and 979 deletions
@@ -6,6 +6,7 @@ package chathelper
import (
"context"
"html"
"math"
"strconv"
"strings"
@@ -15,6 +16,45 @@ import (
"github.com/go-telegram/bot/models"
)
// MonospaceTable renders an HTML-safe, left-aligned table for Telegram's
// <pre> mode. Callers should send the result with ReplyHTML.
func MonospaceTable(headers []string, rows [][]string) string {
widths := make([]int, len(headers))
for index, header := range headers {
widths[index] = len([]rune(header))
}
for _, row := range rows {
for index := range headers {
if index < len(row) && len([]rune(row[index])) > widths[index] {
widths[index] = len([]rune(row[index]))
}
}
}
var lines []string
appendRow := func(row []string) {
cells := make([]string, len(headers))
for index := range headers {
if index < len(row) {
cells[index] = row[index]
}
if index < len(headers)-1 {
cells[index] += strings.Repeat(" ", widths[index]-len([]rune(cells[index])))
}
}
lines = append(lines, strings.Join(cells, " "))
}
appendRow(headers)
separator := make([]string, len(headers))
for index := range headers {
separator[index] = strings.Repeat("-", widths[index])
}
appendRow(separator)
for _, row := range rows {
appendRow(row)
}
return "<pre>" + html.EscapeString(strings.Join(lines, "\n")) + "</pre>"
}
// SubjectFor returns the identity key per-module state should be scoped by:
// group/supergroup → chat ID (shared game state), otherwise → user ID.
// Returns "" when no usable id is present (caller should reply with a
@@ -2,6 +2,7 @@ package chathelper
import (
"context"
"strings"
"testing"
"time"
@@ -268,3 +269,12 @@ func TestWinRate(t *testing.T) {
}
}
}
func TestMonospaceTableAlignsAndEscapesHTML(t *testing.T) {
got := MonospaceTable([]string{"Asset", "Qty"}, [][]string{{"<TCB>", "10"}, {"BTC", "2"}})
for _, want := range []string{"<pre>", "Asset", "&lt;TCB&gt;", "BTC 2", "</pre>"} {
if !strings.Contains(got, want) {
t.Fatalf("table missing %q in %q", want, got)
}
}
}