File: fn.go

package info (click to toggle)
golang-github-olekukonko-tablewriter 1.0.9-1
  • links: PTS, VCS
  • area: main
  • in suites: experimental, forky, sid
  • size: 1,380 kB
  • sloc: makefile: 4
file content (231 lines) | stat: -rw-r--r-- 6,915 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
// 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
}