Initial QSfera import
This commit is contained in:
+237
@@ -0,0 +1,237 @@
|
||||
// Package tw provides utility functions for text formatting, width calculation, and string manipulation
|
||||
// specifically tailored for table rendering, including handling ANSI escape codes and Unicode text.
|
||||
package tw
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/olekukonko/tablewriter/pkg/twwidth"
|
||||
// For mathematical operations like ceiling
|
||||
// For string-to-number conversions
|
||||
// For string manipulation utilities
|
||||
// For Unicode character classification
|
||||
// For UTF-8 rune handling
|
||||
)
|
||||
|
||||
// Title normalizes and uppercases a label string for use in headers.
|
||||
// It replaces underscores and certain dots with spaces and trims whitespace.
|
||||
func Title(name string) string {
|
||||
origLen := len(name)
|
||||
rs := []rune(name)
|
||||
for i, r := range rs {
|
||||
switch r {
|
||||
case '_':
|
||||
rs[i] = ' ' // Replace underscores with spaces
|
||||
case '.':
|
||||
// Replace dots with spaces unless they are between numeric or space characters
|
||||
if (i != 0 && !IsIsNumericOrSpace(rs[i-1])) || (i != len(rs)-1 && !IsIsNumericOrSpace(rs[i+1])) {
|
||||
rs[i] = ' '
|
||||
}
|
||||
}
|
||||
}
|
||||
name = string(rs)
|
||||
name = strings.TrimSpace(name)
|
||||
// If the input was non-empty but trimmed to empty, return a single space
|
||||
if len(name) == 0 && origLen > 0 {
|
||||
name = " "
|
||||
}
|
||||
// Convert to uppercase for header formatting
|
||||
return strings.ToUpper(name)
|
||||
}
|
||||
|
||||
// PadCenter centers a string within a specified width using a padding character.
|
||||
// Extra padding is split between left and right, with slight preference to left if uneven.
|
||||
func PadCenter(s, pad string, width int) string {
|
||||
gap := width - twwidth.Width(s)
|
||||
if gap > 0 {
|
||||
// Calculate left and right padding; ceil ensures left gets extra if gap is odd
|
||||
gapLeft := int(math.Ceil(float64(gap) / 2))
|
||||
gapRight := gap - gapLeft
|
||||
return strings.Repeat(pad, gapLeft) + s + strings.Repeat(pad, gapRight)
|
||||
}
|
||||
// If no padding needed or string is too wide, return as is
|
||||
return s
|
||||
}
|
||||
|
||||
// PadRight left-aligns a string within a specified width, filling remaining space on the right with padding.
|
||||
func PadRight(s, pad string, width int) string {
|
||||
gap := width - twwidth.Width(s)
|
||||
if gap > 0 {
|
||||
// Append padding to the right
|
||||
return s + strings.Repeat(pad, gap)
|
||||
}
|
||||
// If no padding needed or string is too wide, return as is
|
||||
return s
|
||||
}
|
||||
|
||||
// PadLeft right-aligns a string within a specified width, filling remaining space on the left with padding.
|
||||
func PadLeft(s, pad string, width int) string {
|
||||
gap := width - twwidth.Width(s)
|
||||
if gap > 0 {
|
||||
// Prepend padding to the left
|
||||
return strings.Repeat(pad, gap) + s
|
||||
}
|
||||
// If no padding needed or string is too wide, return as is
|
||||
return s
|
||||
}
|
||||
|
||||
// Pad aligns a string within a specified width using a padding character.
|
||||
// It truncates if the string is wider than the target width.
|
||||
func Pad(s, padChar string, totalWidth int, alignment Align) string {
|
||||
sDisplayWidth := twwidth.Width(s)
|
||||
if sDisplayWidth > totalWidth {
|
||||
return twwidth.Truncate(s, totalWidth) // Only truncate if necessary
|
||||
}
|
||||
switch alignment {
|
||||
case AlignLeft:
|
||||
return PadRight(s, padChar, totalWidth)
|
||||
case AlignRight:
|
||||
return PadLeft(s, padChar, totalWidth)
|
||||
case AlignCenter:
|
||||
return PadCenter(s, padChar, totalWidth)
|
||||
default:
|
||||
return PadRight(s, padChar, totalWidth)
|
||||
}
|
||||
}
|
||||
|
||||
// IsIsNumericOrSpace checks if a rune is a digit or space character.
|
||||
// Used in formatting logic to determine safe character replacements.
|
||||
func IsIsNumericOrSpace(r rune) bool {
|
||||
return ('0' <= r && r <= '9') || r == ' '
|
||||
}
|
||||
|
||||
// IsNumeric checks if a string represents a valid integer or floating-point number.
|
||||
func IsNumeric(s string) bool {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
return false
|
||||
}
|
||||
// Try parsing as integer first
|
||||
if _, err := strconv.Atoi(s); err == nil {
|
||||
return true
|
||||
}
|
||||
// Then try parsing as float
|
||||
_, err := strconv.ParseFloat(s, 64)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// SplitCamelCase splits a camelCase or PascalCase or snake_case string into separate words.
|
||||
// It detects transitions between uppercase, lowercase, digits, and other characters.
|
||||
func SplitCamelCase(src string) (entries []string) {
|
||||
// Validate UTF-8 input; return as single entry if invalid
|
||||
if !utf8.ValidString(src) {
|
||||
return []string{src}
|
||||
}
|
||||
entries = []string{}
|
||||
var runes [][]rune
|
||||
lastClass := 0
|
||||
class := 0
|
||||
// Classify each rune into categories: lowercase (1), uppercase (2), digit (3), other (4)
|
||||
for _, r := range src {
|
||||
switch {
|
||||
case unicode.IsLower(r):
|
||||
class = 1
|
||||
case unicode.IsUpper(r):
|
||||
class = 2
|
||||
case unicode.IsDigit(r):
|
||||
class = 3
|
||||
default:
|
||||
class = 4
|
||||
}
|
||||
// Group consecutive runes of the same class together
|
||||
if class == lastClass {
|
||||
runes[len(runes)-1] = append(runes[len(runes)-1], r)
|
||||
} else {
|
||||
runes = append(runes, []rune{r})
|
||||
}
|
||||
lastClass = class
|
||||
}
|
||||
// Adjust for cases where an uppercase letter is followed by lowercase (e.g., CamelCase)
|
||||
for i := 0; i < len(runes)-1; i++ {
|
||||
if unicode.IsUpper(runes[i][0]) && unicode.IsLower(runes[i+1][0]) {
|
||||
// Move the last uppercase rune to the next group for proper word splitting
|
||||
runes[i+1] = append([]rune{runes[i][len(runes[i])-1]}, runes[i+1]...)
|
||||
runes[i] = runes[i][:len(runes[i])-1]
|
||||
}
|
||||
}
|
||||
// Convert rune groups to strings, excluding empty, underscore or whitespace-only groups
|
||||
for _, s := range runes {
|
||||
str := string(s)
|
||||
if len(s) > 0 && strings.TrimSpace(str) != "" && str != "_" {
|
||||
entries = append(entries, str)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Or provides a ternary-like operation for strings, returning 'valid' if cond is true, else 'inValid'.
|
||||
func Or(cond bool, valid, inValid string) string {
|
||||
if cond {
|
||||
return valid
|
||||
}
|
||||
return inValid
|
||||
}
|
||||
|
||||
// Max returns the greater of two integers.
|
||||
func Max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Min returns the smaller of two integers.
|
||||
func Min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// BreakPoint finds the rune index where the display width of a string first exceeds the specified limit.
|
||||
// It returns the number of runes if the entire string fits, or 0 if nothing fits.
|
||||
func BreakPoint(s string, limit int) int {
|
||||
// If limit is 0 or negative, nothing can fit
|
||||
if limit <= 0 {
|
||||
return 0
|
||||
}
|
||||
// Empty string has a breakpoint of 0
|
||||
if s == "" {
|
||||
return 0
|
||||
}
|
||||
|
||||
currentWidth := 0
|
||||
runeCount := 0
|
||||
// Iterate over runes, accumulating display width
|
||||
for _, r := range s {
|
||||
runeWidth := twwidth.Width(string(r)) // Calculate width of individual rune
|
||||
if currentWidth+runeWidth > limit {
|
||||
// Adding this rune would exceed the limit; breakpoint is before this rune
|
||||
if currentWidth == 0 {
|
||||
// First rune is too wide; allow breaking after it if limit > 0
|
||||
if runeWidth > limit && limit > 0 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
return runeCount
|
||||
}
|
||||
currentWidth += runeWidth
|
||||
runeCount++
|
||||
}
|
||||
|
||||
// Entire string fits within the limit
|
||||
return runeCount
|
||||
}
|
||||
|
||||
func MakeAlign(l int, align Align) Alignment {
|
||||
aa := make(Alignment, l)
|
||||
for i := 0; i < l; i++ {
|
||||
aa[i] = align
|
||||
}
|
||||
return aa
|
||||
}
|
||||
Reference in New Issue
Block a user