113 lines
2.6 KiB
Go
113 lines
2.6 KiB
Go
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 BuildTelegramTable(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()
|
||
}
|