mirror of
https://github.com/owncloud/ocis
synced 2026-04-25 17:25:21 +02:00
* chore(deps): bump github.com/olekukonko/tablewriter from 0.0.5 to 1.0.9 Bumps [github.com/olekukonko/tablewriter](https://github.com/olekukonko/tablewriter) from 0.0.5 to 1.0.9. - [Commits](https://github.com/olekukonko/tablewriter/compare/v0.0.5...v1.0.9) --- updated-dependencies: - dependency-name: github.com/olekukonko/tablewriter dependency-version: 1.0.9 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> * fix: adjust to tablewriter 1.0.9 (AI-powered) Signed-off-by: Julian Koberg <julian.koberg@kiteworks.com> * update to NewTable * bring back a Footer * fix the tests --------- Signed-off-by: dependabot[bot] <support@github.com> Signed-off-by: Julian Koberg <julian.koberg@kiteworks.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Julian Koberg <julian.koberg@kiteworks.com> Co-authored-by: Roman Perekhod <rperekhod@owncloud.com>
232 lines
6.8 KiB
Go
232 lines
6.8 KiB
Go
// 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 (
|
|
"github.com/olekukonko/tablewriter/pkg/twwidth"
|
|
"math" // For mathematical operations like ceiling
|
|
"strconv" // For string-to-number conversions
|
|
"strings" // For string manipulation utilities
|
|
"unicode" // For Unicode character classification
|
|
"unicode/utf8" // 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 string, 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
|
|
}
|