Files
notify/tools.go
Maksimov V Vladimir 8331f1a95b исправление багов, документация
- telegram: флаг disableIPV6 не работал — IPv6 был отключён всегда
- telegram: log.Fatalf заменён на возврат ошибки
- tools: BuildTelegramTable не падает при строках разной длины
- README.md с примерами использования
- .gitignore обновлён
2026-04-01 16:45:17 +03:00

128 lines
3.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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])
}
}
// Определяем число колонок по максимальной строке
numCols := len(rows[0])
for _, row := range rows {
if len(row) > numCols {
numCols = len(row)
}
}
// Дополняем короткие строки пустыми ячейками
for i := range rows {
for len(rows[i]) < numCols {
rows[i] = append(rows[i], "")
}
}
// Определяем визуальную ширину колонок
colWidths := make([]int, numCols)
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()
}