text table, telegram html message

This commit is contained in:
2025-11-28 15:43:07 +03:00
parent 68310ac711
commit 5727c7a60c
6 changed files with 179 additions and 4 deletions

112
tools.go Normal file
View File

@@ -0,0 +1,112 @@
package notify
import (
"html"
"regexp"
"strings"
"github.com/mattn/go-runewidth"
)
// Проверка, что строка — число (целое или с точкой, +,-, пробелы)
var numberRe = regexp.MustCompile(`^[+-]?[\d\s_]*\.?\d+$`)
// Выравнивание текста по центру с учётом визуальной ширины
func centerPad(s string, width int) string {
w := runewidth.StringWidth(s)
if w >= width {
return s
}
left := (width - w) / 2
right := width - w - left
return strings.Repeat(" ", left) + s + strings.Repeat(" ", right)
}
// Выравнивание текста по левому краю с учётом ширины
func padRightVisual(s string, width int) string {
w := runewidth.StringWidth(s)
if w >= width {
return s
}
return s + strings.Repeat(" ", width-w)
}
// Выравнивание текста по правому краю с учётом ширины
func padLeftVisual(s string, width int) string {
w := runewidth.StringWidth(s)
if w >= width {
return s
}
return strings.Repeat(" ", width-w) + s
}
// BuildTelegramBoxTable строит ASCII-таблицу с рамками для Telegram HTML <pre>
func BuildTelegramHTMLTable(rows [][]string) string {
if len(rows) == 0 {
return ""
}
// Экранируем HTML-символы
for i := range rows {
for j := range rows[i] {
rows[i][j] = html.EscapeString(rows[i][j])
}
}
// Определяем визуальную ширину колонок
colWidths := make([]int, len(rows[0]))
for _, row := range rows {
for i, col := range row {
w := runewidth.StringWidth(col)
if w > colWidths[i] {
colWidths[i] = w
}
}
}
// Функция для линий-разделителей
buildSep := func() string {
var s strings.Builder
s.WriteString("+")
for _, w := range colWidths {
s.WriteString(strings.Repeat("-", w+2))
s.WriteString("+")
}
return s.String()
}
var b strings.Builder
b.WriteString(buildSep() + "\n")
for ri, row := range rows {
b.WriteString("|")
for i, col := range row {
w := colWidths[i]
var cell string
switch {
case ri == 0:
// Заголовок — по центру
cell = centerPad(col, w)
case numberRe.MatchString(col):
// Число — по правому краю
cell = padLeftVisual(col, w)
default:
// Текст — по левому краю
cell = padRightVisual(col, w)
}
b.WriteString(" " + cell + " |")
}
b.WriteString("\n")
// Разделитель после заголовка
if ri == 0 {
b.WriteString(buildSep() + "\n")
}
}
b.WriteString(buildSep() + "\n")
return b.String()
}