build(deps): bump github.com/olekukonko/tablewriter from 1.0.7 to 1.0.8
Bumps [github.com/olekukonko/tablewriter](https://github.com/olekukonko/tablewriter) from 1.0.7 to 1.0.8. - [Commits](https://github.com/olekukonko/tablewriter/compare/v1.0.7...v1.0.8) --- updated-dependencies: - dependency-name: github.com/olekukonko/tablewriter dependency-version: 1.0.8 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
+16
-13
@@ -8,12 +8,13 @@
|
||||
package twwarp
|
||||
|
||||
import (
|
||||
"github.com/rivo/uniseg"
|
||||
"math"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/mattn/go-runewidth"
|
||||
"github.com/olekukonko/tablewriter/pkg/twwidth" // IMPORT YOUR NEW PACKAGE
|
||||
"github.com/rivo/uniseg"
|
||||
// "github.com/mattn/go-runewidth" // This can be removed if all direct uses are gone
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -59,7 +60,8 @@ func WrapString(s string, lim int) ([]string, int) {
|
||||
var lines []string
|
||||
max := 0
|
||||
for _, v := range words {
|
||||
max = runewidth.StringWidth(v)
|
||||
// max = runewidth.StringWidth(v) // OLD
|
||||
max = twwidth.Width(v) // NEW: Use twdw.Width
|
||||
if max > lim {
|
||||
lim = max
|
||||
}
|
||||
@@ -82,12 +84,13 @@ func WrapStringWithSpaces(s string, lim int) ([]string, int) {
|
||||
return []string{""}, lim
|
||||
}
|
||||
if strings.TrimSpace(s) == "" { // All spaces
|
||||
if runewidth.StringWidth(s) <= lim {
|
||||
return []string{s}, runewidth.StringWidth(s)
|
||||
// if runewidth.StringWidth(s) <= lim { // OLD
|
||||
if twwidth.Width(s) <= lim { // NEW: Use twdw.Width
|
||||
// return []string{s}, runewidth.StringWidth(s) // OLD
|
||||
return []string{s}, twwidth.Width(s) // NEW: Use twdw.Width
|
||||
}
|
||||
// For very long all-space strings, "wrap" by truncating to the limit.
|
||||
if lim > 0 {
|
||||
// Use our new helper function to get a substring of the correct display width
|
||||
substring, _ := stringToDisplayWidth(s, lim)
|
||||
return []string{substring}, lim
|
||||
}
|
||||
@@ -96,7 +99,6 @@ func WrapStringWithSpaces(s string, lim int) ([]string, int) {
|
||||
|
||||
var leadingSpaces, trailingSpaces, coreContent string
|
||||
firstNonSpace := strings.IndexFunc(s, func(r rune) bool { return !unicode.IsSpace(r) })
|
||||
// firstNonSpace will not be -1 due to TrimSpace check above.
|
||||
leadingSpaces = s[:firstNonSpace]
|
||||
lastNonSpace := strings.LastIndexFunc(s, func(r rune) bool { return !unicode.IsSpace(r) })
|
||||
trailingSpaces = s[lastNonSpace+1:]
|
||||
@@ -116,7 +118,8 @@ func WrapStringWithSpaces(s string, lim int) ([]string, int) {
|
||||
|
||||
maxCoreWordWidth := 0
|
||||
for _, v := range words {
|
||||
w := runewidth.StringWidth(v)
|
||||
// w := runewidth.StringWidth(v) // OLD
|
||||
w := twwidth.Width(v) // NEW: Use twdw.Width
|
||||
if w > maxCoreWordWidth {
|
||||
maxCoreWordWidth = w
|
||||
}
|
||||
@@ -153,15 +156,14 @@ func stringToDisplayWidth(s string, targetWidth int) (substring string, actualWi
|
||||
g := uniseg.NewGraphemes(s)
|
||||
for g.Next() {
|
||||
grapheme := g.Str()
|
||||
graphemeWidth := runewidth.StringWidth(grapheme) // Get width of the current grapheme cluster
|
||||
// graphemeWidth := runewidth.StringWidth(grapheme) // OLD
|
||||
graphemeWidth := twwidth.Width(grapheme) // NEW: Use twdw.Width
|
||||
|
||||
if currentWidth+graphemeWidth > targetWidth {
|
||||
// Adding this grapheme would exceed the target width
|
||||
break
|
||||
}
|
||||
|
||||
currentWidth += graphemeWidth
|
||||
// Get the end byte position of the current grapheme cluster
|
||||
_, e := g.Positions()
|
||||
endIndex = e
|
||||
}
|
||||
@@ -186,14 +188,15 @@ func WrapWords(words []string, spc, lim, pen int) [][]string {
|
||||
}
|
||||
lengths := make([]int, n)
|
||||
for i := 0; i < n; i++ {
|
||||
lengths[i] = runewidth.StringWidth(words[i])
|
||||
// lengths[i] = runewidth.StringWidth(words[i]) // OLD
|
||||
lengths[i] = twwidth.Width(words[i]) // NEW: Use twdw.Width
|
||||
}
|
||||
nbrk := make([]int, n)
|
||||
cost := make([]int, n)
|
||||
for i := range cost {
|
||||
cost[i] = math.MaxInt32
|
||||
}
|
||||
remainderLen := lengths[n-1]
|
||||
remainderLen := lengths[n-1] // Uses updated lengths
|
||||
for i := n - 1; i >= 0; i-- {
|
||||
if i < n-1 {
|
||||
remainderLen += spc + lengths[i]
|
||||
|
||||
+321
@@ -0,0 +1,321 @@
|
||||
package twwidth
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/mattn/go-runewidth"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// condition holds the global runewidth configuration, including East Asian width settings.
|
||||
var condition *runewidth.Condition
|
||||
|
||||
// mu protects access to condition and widthCache for thread safety.
|
||||
var mu sync.Mutex
|
||||
|
||||
// ansi is a compiled regular expression for stripping ANSI escape codes from strings.
|
||||
var ansi = Filter()
|
||||
|
||||
func init() {
|
||||
condition = runewidth.NewCondition()
|
||||
widthCache = make(map[cacheKey]int)
|
||||
}
|
||||
|
||||
// cacheKey is used as a key for memoizing string width results in widthCache.
|
||||
type cacheKey struct {
|
||||
str string // Input string
|
||||
eastAsianWidth bool // East Asian width setting
|
||||
}
|
||||
|
||||
// widthCache stores memoized results of Width calculations to improve performance.
|
||||
var widthCache map[cacheKey]int
|
||||
|
||||
// Filter compiles and returns a regular expression for matching ANSI escape sequences,
|
||||
// including CSI (Control Sequence Introducer) and OSC (Operating System Command) sequences.
|
||||
// The returned regex can be used to strip ANSI codes from strings.
|
||||
func Filter() *regexp.Regexp {
|
||||
var regESC = "\x1b" // ASCII escape character
|
||||
var regBEL = "\x07" // ASCII bell character
|
||||
|
||||
// ANSI string terminator: either ESC+\ or BEL
|
||||
var regST = "(" + regexp.QuoteMeta(regESC+"\\") + "|" + regexp.QuoteMeta(regBEL) + ")"
|
||||
// Control Sequence Introducer (CSI): ESC[ followed by parameters and a final byte
|
||||
var regCSI = regexp.QuoteMeta(regESC+"[") + "[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]"
|
||||
// Operating System Command (OSC): ESC] followed by arbitrary content until a terminator
|
||||
var regOSC = regexp.QuoteMeta(regESC+"]") + ".*?" + regST
|
||||
|
||||
// Combine CSI and OSC patterns into a single regex
|
||||
return regexp.MustCompile("(" + regCSI + "|" + regOSC + ")")
|
||||
}
|
||||
|
||||
// SetEastAsian enables or disables East Asian width handling for width calculations.
|
||||
// When the setting changes, the width cache is cleared to ensure accuracy.
|
||||
// This function is thread-safe.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// twdw.SetEastAsian(true) // Enable East Asian width handling
|
||||
func SetEastAsian(enable bool) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if condition.EastAsianWidth != enable {
|
||||
condition.EastAsianWidth = enable
|
||||
widthCache = make(map[cacheKey]int) // Clear cache on setting change
|
||||
}
|
||||
}
|
||||
|
||||
// SetCondition updates the global runewidth.Condition used for width calculations.
|
||||
// When the condition is changed, the width cache is cleared.
|
||||
// This function is thread-safe.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// newCond := runewidth.NewCondition()
|
||||
// newCond.EastAsianWidth = true
|
||||
// twdw.SetCondition(newCond)
|
||||
func SetCondition(newCond *runewidth.Condition) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
condition = newCond
|
||||
widthCache = make(map[cacheKey]int) // Clear cache on setting change
|
||||
}
|
||||
|
||||
// Width calculates the visual width of a string, excluding ANSI escape sequences,
|
||||
// using the go-runewidth package for accurate Unicode handling. It accounts for the
|
||||
// current East Asian width setting and caches results for performance.
|
||||
// This function is thread-safe.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// width := twdw.Width("Hello\x1b[31mWorld") // Returns 10
|
||||
func Width(str string) int {
|
||||
mu.Lock()
|
||||
key := cacheKey{str: str, eastAsianWidth: condition.EastAsianWidth}
|
||||
if w, found := widthCache[key]; found {
|
||||
mu.Unlock()
|
||||
return w
|
||||
}
|
||||
mu.Unlock()
|
||||
|
||||
// Use a temporary condition to avoid holding the lock during calculation
|
||||
tempCond := runewidth.NewCondition()
|
||||
tempCond.EastAsianWidth = key.eastAsianWidth
|
||||
|
||||
stripped := ansi.ReplaceAllLiteralString(str, "")
|
||||
calculatedWidth := tempCond.StringWidth(stripped)
|
||||
|
||||
mu.Lock()
|
||||
widthCache[key] = calculatedWidth
|
||||
mu.Unlock()
|
||||
|
||||
return calculatedWidth
|
||||
}
|
||||
|
||||
// WidthNoCache calculates the visual width of a string without using or
|
||||
// updating the global cache. It uses the current global East Asian width setting.
|
||||
// This function is intended for internal use (e.g., benchmarking) and is thread-safe.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// width := twdw.WidthNoCache("Hello\x1b[31mWorld") // Returns 10
|
||||
func WidthNoCache(str string) int {
|
||||
mu.Lock()
|
||||
currentEA := condition.EastAsianWidth
|
||||
mu.Unlock()
|
||||
|
||||
tempCond := runewidth.NewCondition()
|
||||
tempCond.EastAsianWidth = currentEA
|
||||
|
||||
stripped := ansi.ReplaceAllLiteralString(str, "")
|
||||
return tempCond.StringWidth(stripped)
|
||||
}
|
||||
|
||||
// Display calculates the visual width of a string, excluding ANSI escape sequences,
|
||||
// using the provided runewidth condition. Unlike Width, it does not use caching
|
||||
// and is intended for cases where a specific condition is required.
|
||||
// This function is thread-safe with respect to the provided condition.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// cond := runewidth.NewCondition()
|
||||
// width := twdw.Display(cond, "Hello\x1b[31mWorld") // Returns 10
|
||||
func Display(cond *runewidth.Condition, str string) int {
|
||||
return cond.StringWidth(ansi.ReplaceAllLiteralString(str, ""))
|
||||
}
|
||||
|
||||
// Truncate shortens a string to fit within a specified visual width, optionally
|
||||
// appending a suffix (e.g., "..."). It preserves ANSI escape sequences and adds
|
||||
// a reset sequence (\x1b[0m) if needed to prevent formatting bleed. The function
|
||||
// respects the global East Asian width setting and is thread-safe.
|
||||
//
|
||||
// If maxWidth is negative, an empty string is returned. If maxWidth is zero and
|
||||
// a suffix is provided, the suffix is returned. If the string's visual width is
|
||||
// less than or equal to maxWidth, the string (and suffix, if provided and fits)
|
||||
// is returned unchanged.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// s := twdw.Truncate("Hello\x1b[31mWorld", 5, "...") // Returns "Hello..."
|
||||
// s = twdw.Truncate("Hello", 10) // Returns "Hello"
|
||||
func Truncate(s string, maxWidth int, suffix ...string) string {
|
||||
if maxWidth < 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
suffixStr := strings.Join(suffix, "")
|
||||
sDisplayWidth := Width(s) // Uses global cached Width
|
||||
suffixDisplayWidth := Width(suffixStr) // Uses global cached Width
|
||||
|
||||
// Case 1: Original string is visually empty.
|
||||
if sDisplayWidth == 0 {
|
||||
// If suffix is provided and fits within maxWidth (or if maxWidth is generous)
|
||||
if len(suffixStr) > 0 && suffixDisplayWidth <= maxWidth {
|
||||
return suffixStr
|
||||
}
|
||||
// If s has ANSI codes (len(s)>0) but maxWidth is 0, can't display them.
|
||||
if maxWidth == 0 && len(s) > 0 {
|
||||
return ""
|
||||
}
|
||||
return s // Returns "" or original ANSI codes
|
||||
}
|
||||
|
||||
// Case 2: maxWidth is 0, but string has content. Cannot display anything.
|
||||
if maxWidth == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Case 3: String fits completely or fits with suffix.
|
||||
// Here, maxWidth is the total budget for the line.
|
||||
if sDisplayWidth <= maxWidth {
|
||||
if len(suffixStr) == 0 { // No suffix.
|
||||
return s
|
||||
}
|
||||
// Suffix is provided. Check if s + suffix fits.
|
||||
if sDisplayWidth+suffixDisplayWidth <= maxWidth {
|
||||
return s + suffixStr
|
||||
}
|
||||
// s fits, but s + suffix is too long. Return s.
|
||||
return s
|
||||
}
|
||||
|
||||
// Case 4: String needs truncation (sDisplayWidth > maxWidth).
|
||||
// maxWidth is the total budget for the final string (content + suffix).
|
||||
|
||||
// Capture the global EastAsianWidth setting once for consistent use
|
||||
mu.Lock()
|
||||
currentGlobalEastAsianWidth := condition.EastAsianWidth
|
||||
mu.Unlock()
|
||||
|
||||
// Special case for EastAsian true: if only suffix fits, return suffix.
|
||||
// This was derived from previous test behavior.
|
||||
if len(suffixStr) > 0 && currentGlobalEastAsianWidth {
|
||||
provisionalContentWidth := maxWidth - suffixDisplayWidth
|
||||
if provisionalContentWidth == 0 { // Exactly enough space for suffix only
|
||||
return suffixStr // <<<< MODIFIED: No ANSI reset here
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the budget for the content part, reserving space for the suffix.
|
||||
targetContentForIteration := maxWidth
|
||||
if len(suffixStr) > 0 {
|
||||
targetContentForIteration -= suffixDisplayWidth
|
||||
}
|
||||
|
||||
// If content budget is negative, means not even suffix fits (or no suffix and no space).
|
||||
// However, if only suffix fits, it should be handled.
|
||||
if targetContentForIteration < 0 {
|
||||
// Can we still fit just the suffix?
|
||||
if len(suffixStr) > 0 && suffixDisplayWidth <= maxWidth {
|
||||
if strings.Contains(s, "\x1b[") {
|
||||
return "\x1b[0m" + suffixStr
|
||||
}
|
||||
return suffixStr
|
||||
}
|
||||
return "" // Cannot fit anything.
|
||||
}
|
||||
// If targetContentForIteration is 0, loop won't run, result will be empty string, then suffix is added.
|
||||
|
||||
var contentBuf bytes.Buffer
|
||||
var currentContentDisplayWidth int
|
||||
var ansiSeqBuf bytes.Buffer
|
||||
inAnsiSequence := false
|
||||
ansiWrittenToContent := false
|
||||
|
||||
localRunewidthCond := runewidth.NewCondition()
|
||||
localRunewidthCond.EastAsianWidth = currentGlobalEastAsianWidth
|
||||
|
||||
for _, r := range s {
|
||||
if r == '\x1b' {
|
||||
inAnsiSequence = true
|
||||
ansiSeqBuf.Reset()
|
||||
ansiSeqBuf.WriteRune(r)
|
||||
} else if inAnsiSequence {
|
||||
ansiSeqBuf.WriteRune(r)
|
||||
seqBytes := ansiSeqBuf.Bytes()
|
||||
seqLen := len(seqBytes)
|
||||
terminated := false
|
||||
if seqLen >= 2 {
|
||||
introducer := seqBytes[1]
|
||||
if introducer == '[' {
|
||||
if seqLen >= 3 && r >= 0x40 && r <= 0x7E {
|
||||
terminated = true
|
||||
}
|
||||
} else if introducer == ']' {
|
||||
if r == '\x07' {
|
||||
terminated = true
|
||||
} else if seqLen > 1 && seqBytes[seqLen-2] == '\x1b' && r == '\\' { // Check for ST: \x1b\
|
||||
terminated = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if terminated {
|
||||
inAnsiSequence = false
|
||||
contentBuf.Write(ansiSeqBuf.Bytes())
|
||||
ansiWrittenToContent = true
|
||||
ansiSeqBuf.Reset()
|
||||
}
|
||||
} else { // Normal character
|
||||
runeDisplayWidth := localRunewidthCond.RuneWidth(r)
|
||||
if targetContentForIteration == 0 { // No budget for content at all
|
||||
break
|
||||
}
|
||||
if currentContentDisplayWidth+runeDisplayWidth > targetContentForIteration {
|
||||
break
|
||||
}
|
||||
contentBuf.WriteRune(r)
|
||||
currentContentDisplayWidth += runeDisplayWidth
|
||||
}
|
||||
}
|
||||
|
||||
result := contentBuf.String()
|
||||
|
||||
// Suffix is added if:
|
||||
// 1. A suffix string is provided.
|
||||
// 2. Truncation actually happened (sDisplayWidth > maxWidth originally)
|
||||
// OR if the content part is empty but a suffix is meant to be shown
|
||||
// (e.g. targetContentForIteration was 0).
|
||||
if len(suffixStr) > 0 {
|
||||
// Add suffix if we are in the truncation path (sDisplayWidth > maxWidth)
|
||||
// OR if targetContentForIteration was 0 (meaning only suffix should be shown)
|
||||
// but we must ensure we don't exceed original maxWidth.
|
||||
// The logic above for targetContentForIteration already ensures space.
|
||||
|
||||
needsReset := false
|
||||
// Condition for reset: if styling was active in 's' and might affect suffix
|
||||
if (ansiWrittenToContent || (inAnsiSequence && strings.Contains(s, "\x1b["))) && (currentContentDisplayWidth > 0 || ansiWrittenToContent) {
|
||||
if !strings.HasSuffix(result, "\x1b[0m") {
|
||||
needsReset = true
|
||||
}
|
||||
} else if currentContentDisplayWidth > 0 && strings.Contains(result, "\x1b[") && !strings.HasSuffix(result, "\x1b[0m") && strings.Contains(s, "\x1b[") {
|
||||
// If result has content and ANSI, and original had ANSI, and result not already reset
|
||||
needsReset = true
|
||||
}
|
||||
|
||||
if needsReset {
|
||||
result += "\x1b[0m"
|
||||
}
|
||||
result += suffixStr
|
||||
}
|
||||
return result
|
||||
}
|
||||
Reference in New Issue
Block a user