-
Notifications
You must be signed in to change notification settings - Fork 415
Expand file tree
/
Copy pathstringutil.go
More file actions
164 lines (146 loc) · 4.74 KB
/
stringutil.go
File metadata and controls
164 lines (146 loc) · 4.74 KB
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
// Package stringutil provides utility functions for working with strings.
package stringutil
import (
"fmt"
"strconv"
"strings"
"github.com/github/gh-aw/pkg/logger"
)
var stringutilLog = logger.New("stringutil:stringutil")
// Truncate truncates a string to a maximum length, adding "..." if truncated.
// If maxLen is 3 or less, the string is truncated without "...".
//
// This is a general-purpose utility for truncating any string to a configurable
// length. For domain-specific workflow command identifiers with newline handling,
// see workflow.ShortenCommand instead.
func Truncate(s string, maxLen int) string {
if len(s) <= maxLen {
return s
}
if maxLen <= 3 {
stringutilLog.Printf("Truncate: hard-cut at maxLen=%d (no ellipsis)", maxLen)
return s[:maxLen]
}
stringutilLog.Printf("Truncate: shortened from %d to %d chars", len(s), maxLen)
return s[:maxLen-3] + "..."
}
// NormalizeWhitespace normalizes trailing whitespace and newlines to reduce spurious conflicts.
// It trims trailing whitespace from each line and ensures exactly one trailing newline.
func NormalizeWhitespace(content string) string {
// Split into lines and trim trailing whitespace from each line
lines := strings.Split(content, "\n")
for i, line := range lines {
lines[i] = strings.TrimRight(line, " \t")
}
// Join back and ensure exactly one trailing newline if content is not empty
normalized := strings.Join(lines, "\n")
normalized = strings.TrimRight(normalized, "\n")
if len(normalized) > 0 {
normalized += "\n"
}
return normalized
}
// ParseVersionValue converts version values of various types to strings.
// Supports string, int, int64, uint64, and float64 types.
// Returns empty string for unsupported types.
func ParseVersionValue(version any) string {
switch v := version.(type) {
case string:
return v
case int, int64, uint64:
return fmt.Sprintf("%d", v)
case float64:
return fmt.Sprintf("%g", v)
default:
stringutilLog.Printf("ParseVersionValue: unsupported type %T, returning empty string", version)
return ""
}
}
// FormatList formats a slice of strings as a natural-language comma-separated list
// with an Oxford comma and "and" before the final item.
//
// Examples:
//
// FormatList([]string{}) // returns ""
// FormatList([]string{"a"}) // returns "a"
// FormatList([]string{"a", "b"}) // returns "a and b"
// FormatList([]string{"a", "b", "c"}) // returns "a, b, and c"
func FormatList(items []string) string {
switch len(items) {
case 0:
return ""
case 1:
return items[0]
case 2:
return items[0] + " and " + items[1]
default:
return strings.Join(items[:len(items)-1], ", ") + ", and " + items[len(items)-1]
}
}
// NormalizeLeadingWhitespace removes consistent leading whitespace from all lines
// of a multi-line string. It finds the minimum indentation across all non-empty
// lines and strips that many leading whitespace characters (spaces or tabs) from
// every line.
//
// This is useful for cleaning up content generated with extra indentation,
// such as heredoc bodies.
func NormalizeLeadingWhitespace(content string) string {
lines := strings.Split(content, "\n")
if len(lines) == 0 {
return content
}
// Find minimum leading whitespace (excluding empty lines)
minLeading := -1
for _, line := range lines {
if strings.TrimSpace(line) == "" {
continue // Skip empty lines
}
leading := len(line) - len(strings.TrimLeft(line, " \t"))
if minLeading == -1 || leading < minLeading {
minLeading = leading
}
}
// If no content or no leading whitespace, return as-is
if minLeading <= 0 {
return content
}
stringutilLog.Printf("NormalizeLeadingWhitespace: stripping %d leading whitespace chars from %d lines", minLeading, len(lines))
// Remove the minimum leading whitespace from all lines
var result strings.Builder
for i, line := range lines {
if i > 0 {
result.WriteString("\n")
}
if strings.TrimSpace(line) == "" {
// Keep empty lines as empty
result.WriteString("")
} else if len(line) >= minLeading {
// Remove leading whitespace
result.WriteString(line[minLeading:])
} else {
result.WriteString(line)
}
}
return result.String()
}
// IsPositiveInteger checks if a string is a positive integer.
// Returns true for strings like "1", "123", "999" but false for:
// - Zero ("0")
// - Negative numbers ("-5")
// - Numbers with leading zeros ("007")
// - Floating point numbers ("3.14")
// - Non-numeric strings ("abc")
// - Empty strings ("")
func IsPositiveInteger(s string) bool {
// Must not be empty
if s == "" {
return false
}
// Must not have leading zeros (except "0" itself, but that's not positive)
if len(s) > 1 && s[0] == '0' {
return false
}
// Must be numeric and > 0
num, err := strconv.ParseInt(s, 10, 64)
return err == nil && num > 0
}