diff --git a/go.mod b/go.mod index 79e7ce786..6009ac3ae 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/containerd/errdefs v1.0.0 github.com/google/go-containerregistry v0.21.6 github.com/google/uuid v1.6.0 - github.com/jedib0t/go-pretty/v6 v6.7.10 + github.com/jedib0t/go-pretty/v6 v6.8.2 github.com/neuvector/neuvector v0.0.0-20260708144319-b63e0c3435fe github.com/opencontainers/go-digest v1.0.0 github.com/quay/clair/v2 v2.1.8 diff --git a/go.sum b/go.sum index 062760e63..73c4c0a3a 100644 --- a/go.sum +++ b/go.sum @@ -367,8 +367,8 @@ github.com/in-toto/in-toto-golang v0.11.0 h1:nfidMYBFx+E0lnmX5KUnN2Pdm8zdNKal1ay github.com/in-toto/in-toto-golang v0.11.0/go.mod h1:u3PjTnwFKjp5a1YCcw8SJg0G+tMeKfVoWsWeFMDCMtw= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jedib0t/go-pretty/v6 v6.7.10 h1:B/2qW2Bkv2L6n14PP8o1kx75kWzHOQ3YTluWzg9icac= -github.com/jedib0t/go-pretty/v6 v6.7.10/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU= +github.com/jedib0t/go-pretty/v6 v6.8.2 h1:FmKNr1GOyot/zqNQplE8HLhFguJaeHJTCArntnI4uxE= +github.com/jedib0t/go-pretty/v6 v6.8.2/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU= github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 h1:TMtDYDHKYY15rFihtRfck/bfFqNfvcabqvXAFQfAUpY= github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267/go.mod h1:h1nSAbGFqGVzn6Jyl1R/iCcBUHN4g+gW1u9CoBTrb9E= github.com/jellydator/ttlcache/v3 v3.4.0 h1:YS4P125qQS0tNhtL6aeYkheEaB/m8HCqdMMP4mnWdTY= diff --git a/vendor/github.com/jedib0t/go-pretty/v6/table/filter.go b/vendor/github.com/jedib0t/go-pretty/v6/table/filter.go index 7462cfc89..b4b6f19bf 100644 --- a/vendor/github.com/jedib0t/go-pretty/v6/table/filter.go +++ b/vendor/github.com/jedib0t/go-pretty/v6/table/filter.go @@ -42,6 +42,11 @@ type FilterBy struct { // // Use this when the default filtering logic is not sufficient. CustomFilter func(cellValue string) bool + + // compiledRegex caches the compiled pattern for RegexMatch and + // RegexNotMatch; populated once by parseFilterBy instead of compiling + // the pattern again for every row + compiledRegex *regexp.Regexp } // FilterOperator defines how to filter. @@ -91,12 +96,13 @@ func (t *Table) parseFilterBy(filterBy []FilterBy) []FilterBy { } if colNum > 0 { resFilterBy = append(resFilterBy, FilterBy{ - Name: filter.Name, - Number: colNum, - Operator: filter.Operator, - Value: filter.Value, - IgnoreCase: filter.IgnoreCase, - CustomFilter: filter.CustomFilter, + Name: filter.Name, + Number: colNum, + Operator: filter.Operator, + Value: filter.Value, + IgnoreCase: filter.IgnoreCase, + CustomFilter: filter.CustomFilter, + compiledRegex: compileFilterRegex(filter), }) } } @@ -154,9 +160,9 @@ func (t *Table) matchesOperator(cellValue string, filter FilterBy) bool { case EndsWith: return t.compareEndsWith(cellValue, filter.Value, filter.IgnoreCase) case RegexMatch: - return t.compareRegexMatch(cellValue, filter.Value, filter.IgnoreCase) + return t.compareRegexMatch(cellValue, filter) case RegexNotMatch: - return !t.compareRegexMatch(cellValue, filter.Value, filter.IgnoreCase) + return !t.compareRegexMatch(cellValue, filter) default: return false } @@ -229,22 +235,33 @@ func (t *Table) compareEndsWith(cellValue string, filterValue interface{}, ignor return strings.HasSuffix(cellValue, filterStr) } -func (t *Table) compareRegexMatch(cellValue string, filterValue interface{}, ignoreCase bool) bool { - filterStr := fmt.Sprint(filterValue) - - // Compile the regex pattern - var pattern *regexp.Regexp - var err error - if ignoreCase { - pattern, err = regexp.Compile("(?i)" + filterStr) - } else { - pattern, err = regexp.Compile(filterStr) +func (t *Table) compareRegexMatch(cellValue string, filter FilterBy) bool { + pattern := filter.compiledRegex + if pattern == nil { + pattern = compileFilterRegex(filter) } - - if err != nil { + if pattern == nil { // If regex compilation fails, fall back to simple string matching - return t.compareEqual(cellValue, filterValue, ignoreCase) + return t.compareEqual(cellValue, filter.Value, filter.IgnoreCase) } return pattern.MatchString(cellValue) } + +// compileFilterRegex compiles the pattern for a RegexMatch/RegexNotMatch +// filter; returns nil for other operators or when the pattern is invalid. +func compileFilterRegex(filter FilterBy) *regexp.Regexp { + if filter.Operator != RegexMatch && filter.Operator != RegexNotMatch { + return nil + } + + filterStr := fmt.Sprint(filter.Value) + if filter.IgnoreCase { + filterStr = "(?i)" + filterStr + } + pattern, err := regexp.Compile(filterStr) + if err != nil { + return nil + } + return pattern +} diff --git a/vendor/github.com/jedib0t/go-pretty/v6/table/render.go b/vendor/github.com/jedib0t/go-pretty/v6/table/render.go index a1b6cfe96..37c4b6bd5 100644 --- a/vendor/github.com/jedib0t/go-pretty/v6/table/render.go +++ b/vendor/github.com/jedib0t/go-pretty/v6/table/render.go @@ -107,7 +107,9 @@ func (t *Table) renderColumn(out *strings.Builder, row rowStr, colIdx int, maxCo func (t *Table) renderColumnAutoIndex(out *strings.Builder, hint renderHint) { var outAutoIndex strings.Builder - outAutoIndex.Grow(t.maxColumnLengths[0]) + if len(t.maxColumnLengths) > 0 { + outAutoIndex.Grow(t.maxColumnLengths[0]) + } if hint.isSeparatorRow { numChars := t.autoIndexVIndexMaxLength + utf8.RuneCountInString(t.style.Box.PaddingLeft) + diff --git a/vendor/github.com/jedib0t/go-pretty/v6/table/render_csv.go b/vendor/github.com/jedib0t/go-pretty/v6/table/render_csv.go index 897456571..eac582477 100644 --- a/vendor/github.com/jedib0t/go-pretty/v6/table/render_csv.go +++ b/vendor/github.com/jedib0t/go-pretty/v6/table/render_csv.go @@ -10,7 +10,7 @@ import ( // // #,First Name,Last Name,Salary, // 1,Arya,Stark,3000, -// 20,Jon,Snow,2000,"You know nothing\, Jon Snow!" +// 20,Jon,Snow,2000,"You know nothing, Jon Snow!" // 300,Tyrion,Lannister,5000, // ,,Total,10000, func (t *Table) RenderCSV() string { @@ -18,6 +18,7 @@ func (t *Table) RenderCSV() string { var out strings.Builder if t.numColumns > 0 { + out.Grow(t.estimatedRenderLength()) if t.title != "" { out.WriteString(t.title) } @@ -35,12 +36,21 @@ func (t *Table) RenderCSV() string { return t.render(&out) } -func (t *Table) csvFixCommas(str string) string { - return strings.ReplaceAll(str, ",", "\\,") +// csvEscapeDoubleQuotes escapes double-quotes inside a quoted field by +// doubling them, as mandated by RFC 4180. +func (t *Table) csvEscapeDoubleQuotes(str string) string { + return strings.ReplaceAll(str, "\"", "\"\"") } -func (t *Table) csvFixDoubleQuotes(str string) string { - return strings.ReplaceAll(str, "\"", "\\\"") +// csvProtectField neutralizes fields that spreadsheet applications would +// interpret as formulas (=, +, -, @, tab or CR prefix) by prefixing them with +// a single-quote. +func (t *Table) csvProtectField(str string) string { + switch str[0] { + case '=', '+', '-', '@', '\t', '\r': + return "'" + str + } + return str } func (t *Table) csvRenderRow(out *strings.Builder, row rowStr, hint renderHint) { @@ -61,9 +71,12 @@ func (t *Table) csvRenderRow(out *strings.Builder, row rowStr, hint renderHint) if colIdx > 0 { out.WriteRune(',') } + if t.style.CSV.FieldProtection && colStr != "" { + colStr = t.csvProtectField(colStr) + } if strings.ContainsAny(colStr, "\",\n") { out.WriteRune('"') - out.WriteString(t.csvFixCommas(t.csvFixDoubleQuotes(colStr))) + out.WriteString(t.csvEscapeDoubleQuotes(colStr)) out.WriteRune('"') } else if utf8.RuneCountInString(colStr) > 0 { out.WriteString(colStr) diff --git a/vendor/github.com/jedib0t/go-pretty/v6/table/render_html.go b/vendor/github.com/jedib0t/go-pretty/v6/table/render_html.go index 07b18501c..e138fafdc 100644 --- a/vendor/github.com/jedib0t/go-pretty/v6/table/render_html.go +++ b/vendor/github.com/jedib0t/go-pretty/v6/table/render_html.go @@ -64,11 +64,12 @@ func (t *Table) RenderHTML() string { var out strings.Builder if t.numColumns > 0 { + out.Grow(t.estimatedRenderLength()) out.WriteString("\n") t.htmlRenderTitle(&out) @@ -99,8 +100,12 @@ func (t *Table) htmlGetColStrAndTag(row rowStr, colIdx int, hint renderHint) (st func (t *Table) htmlRenderCaption(out *strings.Builder) { if t.caption != "" { + caption := t.caption + if t.style.HTML.EscapeText { + caption = html.EscapeString(caption) + } out.WriteString(" \n") } } @@ -263,6 +268,9 @@ func (t *Table) htmlRenderTitle(out *strings.Builder) { align := t.style.Title.Align.HTMLProperty() colors := t.style.Title.Colors.HTMLProperty() title := t.style.Title.Format.Apply(t.title) + if t.style.HTML.EscapeText { + title = html.EscapeString(title) + } out.WriteString("
") - out.WriteString(t.caption) + out.WriteString(caption) out.WriteString(" 0 { + out.Grow(t.estimatedRenderLength()) t.markdownRenderTitle(&out) t.markdownRenderRowsHeader(&out) t.markdownRenderRows(&out, t.rows, renderHint{}) diff --git a/vendor/github.com/jedib0t/go-pretty/v6/table/render_tsv.go b/vendor/github.com/jedib0t/go-pretty/v6/table/render_tsv.go index 9e0c4dd50..81022d994 100644 --- a/vendor/github.com/jedib0t/go-pretty/v6/table/render_tsv.go +++ b/vendor/github.com/jedib0t/go-pretty/v6/table/render_tsv.go @@ -11,6 +11,7 @@ func (t *Table) RenderTSV() string { var out strings.Builder if t.numColumns > 0 { + out.Grow(t.estimatedRenderLength()) if t.title != "" { out.WriteString(t.title) } diff --git a/vendor/github.com/jedib0t/go-pretty/v6/table/style.go b/vendor/github.com/jedib0t/go-pretty/v6/table/style.go index 0b2473de0..0e2058a75 100644 --- a/vendor/github.com/jedib0t/go-pretty/v6/table/style.go +++ b/vendor/github.com/jedib0t/go-pretty/v6/table/style.go @@ -6,6 +6,7 @@ type Style struct { Name string // name of the Style Box BoxStyle // characters to use for the boxes Color ColorOptions // colors to use for the rows and columns + CSV CSVOptions // rendering options for CSV mode Format FormatOptions // formatting options for the rows and columns HTML HTMLOptions // rendering options for HTML mode Markdown MarkdownOptions // rendering options for Markdown mode diff --git a/vendor/github.com/jedib0t/go-pretty/v6/table/style_csv.go b/vendor/github.com/jedib0t/go-pretty/v6/table/style_csv.go new file mode 100644 index 000000000..dc24865fe --- /dev/null +++ b/vendor/github.com/jedib0t/go-pretty/v6/table/style_csv.go @@ -0,0 +1,19 @@ +package table + +// CSVOptions defines options to control CSV rendering. +type CSVOptions struct { + // FieldProtection neutralizes fields that spreadsheet applications could + // interpret as formulas (fields beginning with =, +, -, @, tab or CR) by + // prefixing them with a single-quote, preventing CSV formula injection + // when the rendered output is opened in such an application. + // + // Note that the single-quote becomes part of the field content for + // standards-compliant CSV readers; enable this only when the output is + // destined for a spreadsheet application. + FieldProtection bool +} + +var ( + // DefaultCSVOptions defines sensible CSV rendering defaults. + DefaultCSVOptions = CSVOptions{} +) diff --git a/vendor/github.com/jedib0t/go-pretty/v6/table/table.go b/vendor/github.com/jedib0t/go-pretty/v6/table/table.go index 02d409264..f654f3680 100644 --- a/vendor/github.com/jedib0t/go-pretty/v6/table/table.go +++ b/vendor/github.com/jedib0t/go-pretty/v6/table/table.go @@ -812,6 +812,13 @@ func (t *Table) isIndexColumn(colIdx int, hint renderHint) bool { return t.indexColumn == colIdx+1 || hint.isAutoIndexColumn } +// estimatedRenderLength approximates the rendered output size, to pre-size +// the output builder and avoid repeated re-allocations while rendering. +func (t *Table) estimatedRenderLength() int { + numRows := len(t.rows) + len(t.rowsHeader) + len(t.rowsFooter) + 1 + return numRows * (t.maxRowLength + 1) +} + func (t *Table) render(out *strings.Builder) string { outStr := out.String() if t.suppressTrailingSpaces { diff --git a/vendor/github.com/jedib0t/go-pretty/v6/text/align.go b/vendor/github.com/jedib0t/go-pretty/v6/text/align.go index 18cc59e53..0169b9135 100644 --- a/vendor/github.com/jedib0t/go-pretty/v6/text/align.go +++ b/vendor/github.com/jedib0t/go-pretty/v6/text/align.go @@ -1,10 +1,8 @@ package text import ( - "fmt" "strconv" "strings" - "unicode/utf8" ) // Align denotes how text is to be aligned horizontally. @@ -39,24 +37,39 @@ func (a Align) Apply(text string, maxLength int) string { } text = aComputed.trimString(text) - sLen := utf8.RuneCountInString(text) sLenWoE := StringWidthWithoutEscSequences(text) - numEscChars := sLen - sLenWoE + padding := maxLength - sLenWoE - // now, align the text + // now, align the text by padding the missing display-width with spaces switch aComputed { case AlignDefault, AlignLeft: - return fmt.Sprintf("%-"+strconv.Itoa(maxLength+numEscChars)+"s", text) + return padText(text, 0, padding) case AlignCenter: - if sLenWoE < maxLength { - // left pad with half the number of spaces needed before using %text - return fmt.Sprintf("%"+strconv.Itoa(maxLength+numEscChars)+"s", - text+strings.Repeat(" ", (maxLength-sLenWoE)/2)) - } + // extra space on the left when the padding is odd + return padText(text, padding-padding/2, padding/2) case AlignJustify: return justifyText(text, sLenWoE, maxLength) } - return fmt.Sprintf("%"+strconv.Itoa(maxLength+numEscChars)+"s", text) + return padText(text, padding, 0) +} + +// padText returns the text with the given number of spaces on either side; +// non-positive counts add nothing. +func padText(text string, left int, right int) string { + if left <= 0 && right <= 0 { + return text + } + + var out strings.Builder + out.Grow(len(text) + left + right) + for idx := 0; idx < left; idx++ { + out.WriteByte(' ') + } + out.WriteString(text) + for idx := 0; idx < right; idx++ { + out.WriteByte(' ') + } + return out.String() } // HTMLProperty returns the equivalent HTML horizontal-align tag property. diff --git a/vendor/github.com/jedib0t/go-pretty/v6/text/escape_seq_parser.go b/vendor/github.com/jedib0t/go-pretty/v6/text/escape_seq_parser.go index 66edae697..24444981a 100644 --- a/vendor/github.com/jedib0t/go-pretty/v6/text/escape_seq_parser.go +++ b/vendor/github.com/jedib0t/go-pretty/v6/text/escape_seq_parser.go @@ -1,10 +1,12 @@ package text import ( + "bytes" "fmt" "sort" "strconv" "strings" + "unicode/utf8" ) // Constants @@ -64,6 +66,18 @@ const ( // conceal OSI sequences escapeStartConcealOSI = "\x1b]8;" escapeStopConcealOSI = "\x1b\\" + + // escSeqMaxLength bounds the accumulated escape sequence; real terminals + // cap sequences in the low KBs, and anything longer here is most likely + // malformed input that would otherwise grow the buffer without limit + escSeqMaxLength = 2048 +) + +// []byte forms of the conceal OSI markers for prefix/suffix checks on the +// escape sequence buffer without allocating per call +var ( + escapeStartConcealOSIBytes = []byte(escapeStartConcealOSI) + escapeStopConcealOSIBytes = []byte(escapeStopConcealOSI) ) // 256-color codes @@ -117,8 +131,9 @@ type EscSeqParser struct { inEscSeq bool // escSeqKind identifies the type of escape sequence being parsed (CSI or OSI). escSeqKind escSeqKind - // escapeSeq accumulates the current escape sequence being parsed. - escapeSeq string + // escapeSeq accumulates the current escape sequence being parsed; it is + // bounded by escSeqMaxLength and reused across sequences. + escapeSeq []byte } func (s *EscSeqParser) Codes() []int { @@ -136,7 +151,7 @@ func (s *EscSeqParser) Consume(char rune) { if !s.inEscSeq && char == EscapeStartRune { s.inEscSeq = true s.escSeqKind = escSeqKindUnknown - s.escapeSeq = "" + s.escapeSeq = s.escapeSeq[:0] } else if s.inEscSeq && s.escSeqKind == escSeqKindUnknown { switch char { case EscapeStartRuneCSI: @@ -147,20 +162,27 @@ func (s *EscSeqParser) Consume(char rune) { } if s.inEscSeq { - s.escapeSeq += string(char) + s.escapeSeq = utf8.AppendRune(s.escapeSeq, char) + + // abort on absurdly long (most likely malformed/unterminated) + // sequences instead of accumulating them without limit + if len(s.escapeSeq) > escSeqMaxLength { + s.Reset() + return + } // --- FIX for OSC 8 hyperlinks (e.g. \x1b]8;;url\x07label\x1b]8;;\x07) if s.escSeqKind == escSeqKindOSI && - strings.HasPrefix(s.escapeSeq, escapeStartConcealOSI) && + bytes.HasPrefix(s.escapeSeq, escapeStartConcealOSIBytes) && char == escRuneBEL { // BEL - s.ParseSeq(s.escapeSeq, s.escSeqKind) + s.ParseSeq(string(s.escapeSeq), s.escSeqKind) s.Reset() return } if s.isEscapeStopRune(char) { - s.ParseSeq(s.escapeSeq, s.escSeqKind) + s.ParseSeq(string(s.escapeSeq), s.escSeqKind) s.Reset() } } @@ -186,7 +208,7 @@ func (s *EscSeqParser) ParseSeq(seq string, seqKind escSeqKind) { } func (s *EscSeqParser) ParseString(str string) string { - s.escapeSeq, s.inEscSeq, s.escSeqKind = "", false, escSeqKindUnknown + s.escapeSeq, s.inEscSeq, s.escSeqKind = s.escapeSeq[:0], false, escSeqKindUnknown for _, char := range str { s.Consume(char) } @@ -196,7 +218,7 @@ func (s *EscSeqParser) ParseString(str string) string { func (s *EscSeqParser) Reset() { s.inEscSeq = false s.escSeqKind = escSeqKindUnknown - s.escapeSeq = "" + s.escapeSeq = s.escapeSeq[:0] } func (s *EscSeqParser) Sequence() string { @@ -275,8 +297,8 @@ func (s *EscSeqParser) clearColorRange(isForeground bool) { } func (s *EscSeqParser) isEscapeStopRune(char rune) bool { - if strings.HasPrefix(s.escapeSeq, escapeStartConcealOSI) { - if strings.HasSuffix(s.escapeSeq, escapeStopConcealOSI) { + if bytes.HasPrefix(s.escapeSeq, escapeStartConcealOSIBytes) { + if bytes.HasSuffix(s.escapeSeq, escapeStopConcealOSIBytes) { return true } } else if (s.escSeqKind == escSeqKindCSI && char == EscapeStopRuneCSI) || diff --git a/vendor/github.com/jedib0t/go-pretty/v6/text/hyperlink.go b/vendor/github.com/jedib0t/go-pretty/v6/text/hyperlink.go index 00a551ae2..48952c276 100644 --- a/vendor/github.com/jedib0t/go-pretty/v6/text/hyperlink.go +++ b/vendor/github.com/jedib0t/go-pretty/v6/text/hyperlink.go @@ -1,8 +1,12 @@ package text -import "fmt" +import ( + "fmt" + "strings" +) func Hyperlink(url, text string) string { + url = sanitizeHyperlinkURL(url) if url == "" { return text } @@ -12,3 +16,15 @@ func Hyperlink(url, text string) string { // source https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda return fmt.Sprintf("\x1b]8;;%s\x1b\\%s\x1b]8;;\x1b\\", url, text) } + +// sanitizeHyperlinkURL strips ASCII control characters (including ESC and BEL) +// from the URL; left in place, they could terminate the OSC 8 sequence early +// and inject arbitrary escape sequences into the terminal output. +func sanitizeHyperlinkURL(url string) string { + return strings.Map(func(r rune) rune { + if r < 0x20 || r == 0x7f { + return -1 + } + return r + }, url) +} diff --git a/vendor/github.com/jedib0t/go-pretty/v6/text/string.go b/vendor/github.com/jedib0t/go-pretty/v6/text/string.go index fa3e4f1db..7902b0e06 100644 --- a/vendor/github.com/jedib0t/go-pretty/v6/text/string.go +++ b/vendor/github.com/jedib0t/go-pretty/v6/text/string.go @@ -263,6 +263,15 @@ func StringWidth(str string) int { // StringWidthWithoutEscSequences("Ghost 生命"): 10 // StringWidthWithoutEscSequences("\x1b[33mGhost 生命\x1b[0m"): 10 func StringWidthWithoutEscSequences(str string) int { + // fast-path the common case of a string without escape sequences + if !strings.ContainsRune(str, EscapeStartRune) { + count := 0 + for _, c := range str { + count += RuneWidth(c) + } + return count + } + count, esp := 0, EscSeqParser{} for _, c := range str { if esp.InSequence() { @@ -277,13 +286,16 @@ func StringWidthWithoutEscSequences(str string) int { return count } -// Trim trims a string to the given length while ignoring escape sequences. For -// ex.: +// Trim trims a string to the given display width (maxLen columns) while +// ignoring escape sequences. Wide East Asian characters count as two columns, +// so the result never exceeds maxLen columns. For ex.: // // Trim("Ghost", 3) == "Gho" // Trim("Ghost", 6) == "Ghost" // Trim("\x1b[33mGhost\x1b[0m", 3) == "\x1b[33mGho\x1b[0m" // Trim("\x1b[33mGhost\x1b[0m", 6) == "\x1b[33mGhost\x1b[0m" +// Trim("生命生命", 3) == "生" +// Trim("生命生命", 4) == "生命" func Trim(str string, maxLen int) string { if maxLen <= 0 { return "" @@ -292,7 +304,7 @@ func Trim(str string, maxLen int) string { var out strings.Builder out.Grow(maxLen) - outLen, esp := 0, EscSeqParser{} + outLen, full, esp := 0, false, EscSeqParser{} for _, sChr := range str { if esp.InSequence() { esp.Consume(sChr) @@ -304,10 +316,17 @@ func Trim(str string, maxLen int) string { out.WriteRune(sChr) continue } - if outLen < maxLen { - outLen++ - out.WriteRune(sChr) - continue + // Count the display width of the rune (wide East Asian characters + // occupy two columns) instead of counting runes, so the result never + // exceeds maxLen columns. Once a rune no longer fits, stop emitting + // visible runes but keep copying any trailing escape sequences. + if !full { + if w := RuneWidth(sChr); outLen+w <= maxLen { + outLen += w + out.WriteRune(sChr) + continue + } + full = true } } return out.String() diff --git a/vendor/github.com/jedib0t/go-pretty/v6/text/valign.go b/vendor/github.com/jedib0t/go-pretty/v6/text/valign.go index 8e086d92d..9862590e6 100644 --- a/vendor/github.com/jedib0t/go-pretty/v6/text/valign.go +++ b/vendor/github.com/jedib0t/go-pretty/v6/text/valign.go @@ -27,6 +27,10 @@ func (va VAlign) Apply(lines []string, maxLines int) []string { maxLines = len(lines) } + if maxLines < 1 { // no lines and a negative maxLines + return lines + } + var insertIdx int switch va { case VAlignMiddle: diff --git a/vendor/github.com/jedib0t/go-pretty/v6/text/wrap.go b/vendor/github.com/jedib0t/go-pretty/v6/text/wrap.go index fd657f79b..962085cdf 100644 --- a/vendor/github.com/jedib0t/go-pretty/v6/text/wrap.go +++ b/vendor/github.com/jedib0t/go-pretty/v6/text/wrap.go @@ -89,7 +89,7 @@ func WrapText(str string, wrapLen int) string { func appendChar(char rune, wrapLen int, lineLen *int, inEscSeq bool, lastSeenEscSeq string, out *strings.Builder) { // handle reaching the end of the line as dictated by wrapLen or by finding // a newline character - if (*lineLen == wrapLen && !inEscSeq && char != '\n') || (char == '\n') { + if (*lineLen >= wrapLen && !inEscSeq && char != '\n') || (char == '\n') { if lastSeenEscSeq != "" { // terminate escape sequence and the line; and restart the escape // sequence in the next line @@ -122,7 +122,7 @@ func appendWord(word string, lineIdx *int, lastSeenEscSeq string, wrapLen int, o inEscSeq = true lastSeenEscSeq = "" } - if inEscSeq { + if inEscSeq && len(lastSeenEscSeq) < escSeqMaxLength { lastSeenEscSeq += string(char) } diff --git a/vendor/modules.txt b/vendor/modules.txt index af4c5f21c..5192f4428 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -487,7 +487,7 @@ github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1 # github.com/inconshreveable/mousetrap v1.1.0 ## explicit; go 1.18 github.com/inconshreveable/mousetrap -# github.com/jedib0t/go-pretty/v6 v6.7.10 +# github.com/jedib0t/go-pretty/v6 v6.8.2 ## explicit; go 1.18 github.com/jedib0t/go-pretty/v6/table github.com/jedib0t/go-pretty/v6/text