chore(deps): bump github.com/jedib0t/go-pretty/v6 from 6.6.5 to 6.6.7

Bumps [github.com/jedib0t/go-pretty/v6](https://github.com/jedib0t/go-pretty) from 6.6.5 to 6.6.7.
- [Release notes](https://github.com/jedib0t/go-pretty/releases)
- [Commits](https://github.com/jedib0t/go-pretty/compare/v6.6.5...v6.6.7)

---
updated-dependencies:
- dependency-name: github.com/jedib0t/go-pretty/v6
  dependency-version: 6.6.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
dependabot[bot]
2025-06-14 23:03:18 +00:00
committed by GitHub
parent 47cd4cbff6
commit 73c93a0343
13 changed files with 204 additions and 80 deletions

View File

@@ -1,6 +1,7 @@
[![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go)](https://pkg.go.dev/github.com/felixge/fgprof)
![GitHub Workflow Status](https://img.shields.io/github/workflow/status/felixge/fgprof/Go)
![GitHub](https://img.shields.io/github/license/felixge/fgprof)
[![go-recipes](https://raw.githubusercontent.com/nikolaydubina/go-recipes/main/badge.svg?raw=true)](https://github.com/nikolaydubina/go-recipes)
# :rocket: fgprof - The Full Go Profiler

View File

@@ -6,6 +6,7 @@ package fgprof
import (
"fmt"
"io"
"math"
"runtime"
"sort"
"strings"
@@ -37,16 +38,19 @@ func Start(w io.Writer, format Format) func() error {
const hz = 99
ticker := time.NewTicker(time.Second / hz)
stopCh := make(chan struct{})
prof := &profiler{}
profile := newWallclockProfile()
var sampleCount int64
go func() {
defer ticker.Stop()
for {
select {
case <-ticker.C:
sampleCount++
stacks := prof.GoroutineProfile()
profile.Add(stacks)
case <-stopCh:
@@ -59,7 +63,14 @@ func Start(w io.Writer, format Format) func() error {
stopCh <- struct{}{}
endTime := time.Now()
profile.Ignore(prof.SelfFrames()...)
return profile.Export(w, format, hz, startTime, endTime)
// Compute actual sample rate in case, due to performance issues, we
// were not actually able to sample at the given hz. Converting
// everything to float avoids integers being rounded in the wrong
// direction and improves the correctness of times in profiles.
duration := endTime.Sub(startTime)
actualHz := float64(sampleCount) / (float64(duration) / 1e9)
return profile.Export(w, format, int(math.Round(actualHz)), startTime, endTime)
}
}
@@ -70,6 +81,11 @@ type profiler struct {
selfFrame *runtime.Frame
}
// nullTerminationWorkaround deals with a regression in go1.23, see:
// - https://github.com/felixge/fgprof/issues/33
// - https://go-review.googlesource.com/c/go/+/609815
var nullTerminationWorkaround = runtime.Version() == "go1.23.0"
// GoroutineProfile returns the stacks of all goroutines currently managed by
// the scheduler. This includes both goroutines that are currently running
// (On-CPU), as well as waiting (Off-CPU).
@@ -96,6 +112,11 @@ func (p *profiler) GoroutineProfile() []runtime.StackRecord {
// p.stacks dynamically as well, but let's not over-engineer this until we
// understand those cases better.
for {
if nullTerminationWorkaround {
for i := range p.stacks {
p.stacks[i].Stack0 = [32]uintptr{}
}
}
n, ok := runtime.GoroutineProfile(p.stacks)
if !ok {
p.stacks = make([]runtime.StackRecord, int(float64(n)*1.1))

View File

@@ -25,8 +25,8 @@ type ColumnConfig struct {
// AutoMerge merges cells with similar values and prevents separators from
// being drawn. Caveats:
// * VAlign is applied on the individual cell and not on the merged cell
// * Does not work in CSV/HTML/Markdown render modes
// * Does not work well with horizontal auto-merge (RowConfig.AutoMerge)
// * Does not work in CSV/Markdown render modes
//
// Works best when:
// * Style().Options.SeparateRows == true
@@ -87,8 +87,8 @@ type RowConfig struct {
// being drawn. Caveats:
// * Align is overridden to text.AlignCenter on the merged cell (unless set
// by AutoMergeAlign value below)
// * Does not work in CSV/HTML/Markdown render modes
// * Does not work well with vertical auto-merge (ColumnConfig.AutoMerge)
// * Does not work in CSV/Markdown render modes
AutoMerge bool
// Alignment to use on a merge (defaults to text.AlignCenter)

View File

@@ -67,7 +67,7 @@ func (t *Table) renderColumn(out *strings.Builder, row rowStr, colIdx int, maxCo
}
// extract the text, convert-case if not-empty and align horizontally
mergeVertically := t.shouldMergeCellsVertically(colIdx, hint)
mergeVertically := t.shouldMergeCellsVerticallyAbove(colIdx, hint)
var colStr string
if mergeVertically {
// leave colStr empty; align will expand the column as necessary

View File

@@ -159,6 +159,10 @@ func (t *Table) htmlRenderRow(out *strings.Builder, row rowStr, hint renderHint)
if colIdx == 0 && t.autoIndex {
t.htmlRenderColumnAutoIndex(out, hint)
}
// auto-merged columns should be skipped
if t.shouldMergeCellsVerticallyAbove(colIdx, hint) {
continue
}
align := t.getAlign(colIdx, hint)
rowConfig := t.getRowConfig(hint)
@@ -184,6 +188,9 @@ func (t *Table) htmlRenderRow(out *strings.Builder, row rowStr, hint renderHint)
if extraColumnsRendered > 0 {
out.WriteString(" colspan=")
out.WriteString(fmt.Sprint(extraColumnsRendered + 1))
} else if rowSpan := t.shouldMergeCellsVerticallyBelow(colIdx, hint); rowSpan > 1 {
out.WriteString(" rowspan=")
out.WriteString(fmt.Sprint(rowSpan))
}
out.WriteString(">")
if len(colStr) == 0 {
@@ -222,6 +229,7 @@ func (t *Table) htmlRenderRows(out *strings.Builder, rows []rowStr, hint renderH
t.htmlRenderRow(out, row, hint)
shouldRenderTagClose = true
}
t.firstRowOfPage = false
}
if shouldRenderTagClose {
out.WriteString(" </")

View File

@@ -2,6 +2,7 @@ package table
import (
"fmt"
"sort"
"strings"
"unicode"
@@ -57,35 +58,93 @@ func (t *Table) extractMaxColumnLengths(rows []rowStr, hint renderHint) {
}
func (t *Table) extractMaxColumnLengthsFromRow(row rowStr, mci mergedColumnIndices) {
for colIdx, colStr := range row {
for colIdx := 0; colIdx < len(row); colIdx++ {
colStr := row[colIdx]
longestLineLen := text.LongestLineLen(colStr)
maxColWidth := t.getColumnWidthMax(colIdx)
if maxColWidth > 0 && maxColWidth < longestLineLen {
longestLineLen = maxColWidth
}
mergedColumnsLength := mci.mergedLength(colIdx, t.maxColumnLengths)
if longestLineLen > mergedColumnsLength {
if mergedColumnsLength > 0 {
t.extractMaxColumnLengthsFromRowForMergedColumns(colIdx, longestLineLen, mci)
} else {
t.maxColumnLengths[colIdx] = longestLineLen
if mergeEndIndex, ok := mci[colIdx]; ok {
startIndexMap := t.maxMergedColumnLengths[mergeEndIndex]
if startIndexMap == nil {
startIndexMap = make(map[int]int)
t.maxMergedColumnLengths[mergeEndIndex] = startIndexMap
}
} else if maxColWidth == 0 && longestLineLen > t.maxColumnLengths[colIdx] {
if longestLineLen > startIndexMap[colIdx] {
startIndexMap[colIdx] = longestLineLen
}
colIdx = mergeEndIndex
} else if longestLineLen > t.maxColumnLengths[colIdx] {
t.maxColumnLengths[colIdx] = longestLineLen
}
}
}
func (t *Table) extractMaxColumnLengthsFromRowForMergedColumns(colIdx int, mergedColumnLength int, mci mergedColumnIndices) {
numMergedColumns := mci.len(colIdx)
mergedColumnLength -= (numMergedColumns - 1) * text.StringWidthWithoutEscSequences(t.style.Box.MiddleSeparator)
maxLengthSplitAcrossColumns := mergedColumnLength / numMergedColumns
if maxLengthSplitAcrossColumns > t.maxColumnLengths[colIdx] {
t.maxColumnLengths[colIdx] = maxLengthSplitAcrossColumns
}
for otherColIdx := range mci[colIdx] {
if maxLengthSplitAcrossColumns > t.maxColumnLengths[otherColIdx] {
t.maxColumnLengths[otherColIdx] = maxLengthSplitAcrossColumns
// reBalanceMaxMergedColumnLengths tries to re-balance the merged column lengths
// across all columns. It does this from the lowest end index to the highest,
// and within that set from the highest start index to the lowest. It
// distributes the length across the columns not already exceeding the average.
func (t *Table) reBalanceMaxMergedColumnLengths() {
endIndexKeys, startIndexKeysMap := getSortedKeys(t.maxMergedColumnLengths)
middleSepLen := text.StringWidthWithoutEscSequences(t.style.Box.MiddleSeparator)
for _, endIndexKey := range endIndexKeys {
startIndexKeys := startIndexKeysMap[endIndexKey]
for idx := len(startIndexKeys) - 1; idx >= 0; idx-- {
startIndexKey := startIndexKeys[idx]
columnBalanceMap := map[int]struct{}{}
for index := startIndexKey; index <= endIndexKey; index++ {
columnBalanceMap[index] = struct{}{}
}
mergedColumnLength := t.maxMergedColumnLengths[endIndexKey][startIndexKey] -
((len(columnBalanceMap) - 1) * middleSepLen)
// keep reducing the set of columns until the remainder are the ones less than
// the average of the remaining length (total merged length - all lengths > average)
for {
if mergedColumnLength <= 0 { // already exceeded the merged length
columnBalanceMap = map[int]struct{}{}
break
}
numMergedColumns := len(columnBalanceMap)
maxLengthSplitAcrossColumns := mergedColumnLength / numMergedColumns
mapReduced := false
for mergedColumn := range columnBalanceMap {
maxColumnLength := t.maxColumnLengths[mergedColumn]
if maxColumnLength >= maxLengthSplitAcrossColumns {
mapReduced = true
mergedColumnLength -= maxColumnLength
delete(columnBalanceMap, mergedColumn)
}
}
if !mapReduced {
break
}
}
// act on any remaining columns that need balancing
if len(columnBalanceMap) > 0 {
// remove the max column sizes from the remaining amount to balance, then
// share out the remainder amongst the columns.
numRebalancedColumns := len(columnBalanceMap)
balanceColumns := make([]int, 0, numRebalancedColumns)
for balanceColumn := range columnBalanceMap {
mergedColumnLength -= t.maxColumnLengths[balanceColumn]
balanceColumns = append(balanceColumns, balanceColumn)
}
// pad out the columns one by one
sort.Ints(balanceColumns)
columnLengthRemaining := mergedColumnLength
columnsRemaining := numRebalancedColumns
for index := 0; index < numRebalancedColumns; index++ {
balancedSpace := columnLengthRemaining / columnsRemaining
balanceColumn := balanceColumns[index]
t.maxColumnLengths[balanceColumn] += balancedSpace
columnLengthRemaining -= balancedSpace
columnsRemaining--
}
}
}
}
}
@@ -136,6 +195,7 @@ func (t *Table) initForRenderColumnConfigs() {
func (t *Table) initForRenderColumnLengths() {
t.maxColumnLengths = make([]int, t.numColumns)
t.maxMergedColumnLengths = make(map[int]map[int]int)
t.extractMaxColumnLengths(t.rowsHeader, renderHint{isHeaderRow: true})
t.extractMaxColumnLengths(t.rows, renderHint{})
t.extractMaxColumnLengths(t.rowsFooter, renderHint{isFooterRow: true})
@@ -147,6 +207,7 @@ func (t *Table) initForRenderColumnLengths() {
t.maxColumnLengths[colIdx] = minWidth
}
}
t.reBalanceMaxMergedColumnLengths()
}
func (t *Table) initForRenderHideColumns() {

View File

@@ -50,7 +50,8 @@ func (t *Table) tsvRenderRow(out *strings.Builder, row rowStr, hint renderHint)
}
if strings.ContainsAny(col, "\t\n\"") || strings.Contains(col, " ") {
out.WriteString(fmt.Sprintf("\"%s\"", t.tsvFixDoubleQuotes(col)))
col = strings.ReplaceAll(col, "\"", "\"\"") // fix double-quotes
out.WriteString(fmt.Sprintf("\"%s\"", col))
} else {
out.WriteString(col)
}
@@ -61,10 +62,6 @@ func (t *Table) tsvRenderRow(out *strings.Builder, row rowStr, hint renderHint)
}
}
func (t *Table) tsvFixDoubleQuotes(str string) string {
return strings.Replace(str, "\"", "\"\"", -1)
}
func (t *Table) tsvRenderRows(out *strings.Builder, rows []rowStr, hint renderHint) {
for idx, row := range rows {
hint.rowNumber = idx + 1

View File

@@ -36,6 +36,9 @@ type Table struct {
indexColumn int
// maxColumnLengths stores the length of the longest line in each column
maxColumnLengths []int
// maxMergedColumnLengths stores the longest lengths for merged columns
// endIndex -> startIndex -> maxMergedLength
maxMergedColumnLengths map[int]map[int]int
// maxRowLength stores the length of the longest row
maxRowLength int
// numColumns stores the (max.) number of columns seen
@@ -431,7 +434,7 @@ func (t *Table) getBorderLeft(hint renderHint) string {
} else if hint.isSeparatorRow {
if t.autoIndex && hint.isHeaderOrFooterSeparator() {
border = t.style.Box.Left
} else if !t.autoIndex && t.shouldMergeCellsVertically(0, hint) {
} else if !t.autoIndex && t.shouldMergeCellsVerticallyAbove(0, hint) {
border = t.style.Box.Left
} else {
border = t.style.Box.LeftSeparator
@@ -451,7 +454,7 @@ func (t *Table) getBorderRight(hint renderHint) string {
} else if hint.isBorderBottom {
border = t.style.Box.BottomRight
} else if hint.isSeparatorRow {
if t.shouldMergeCellsVertically(t.numColumns-1, hint) {
if t.shouldMergeCellsVerticallyAbove(t.numColumns-1, hint) {
border = t.style.Box.Right
} else {
border = t.style.Box.RightSeparator
@@ -522,12 +525,12 @@ func (t *Table) getColumnSeparator(row rowStr, colIdx int, hint renderHint) stri
}
func (t *Table) getColumnSeparatorNonBorder(mergeCellsAbove bool, mergeCellsBelow bool, colIdx int, hint renderHint) string {
mergeNextCol := t.shouldMergeCellsVertically(colIdx, hint)
mergeNextCol := t.shouldMergeCellsVerticallyAbove(colIdx, hint)
if hint.isAutoIndexColumn {
return t.getColumnSeparatorNonBorderAutoIndex(mergeNextCol, hint)
}
mergeCurrCol := t.shouldMergeCellsVertically(colIdx-1, hint)
mergeCurrCol := t.shouldMergeCellsVerticallyAbove(colIdx-1, hint)
return t.getColumnSeparatorNonBorderNonAutoIndex(mergeCellsAbove, mergeCellsBelow, mergeCurrCol, mergeNextCol)
}
@@ -619,19 +622,19 @@ func (t *Table) getMergedColumnIndices(row rowStr, hint renderHint) mergedColumn
mci := make(mergedColumnIndices)
for colIdx := 0; colIdx < t.numColumns-1; colIdx++ {
// look backward
for otherColIdx := colIdx - 1; colIdx >= 0 && otherColIdx >= 0; otherColIdx-- {
if row[colIdx] != row[otherColIdx] {
for otherColIdx := colIdx + 1; otherColIdx < len(row); otherColIdx++ {
colsEqual := row[colIdx] == row[otherColIdx]
if !colsEqual {
lastEqual := otherColIdx - 1
if colIdx != lastEqual {
mci[colIdx] = lastEqual
colIdx = lastEqual
}
break
} else if colsEqual && otherColIdx == len(row)-1 {
mci[colIdx] = otherColIdx
colIdx = otherColIdx
}
mci.safeAppend(colIdx, otherColIdx)
}
// look forward
for otherColIdx := colIdx + 1; colIdx < len(row) && otherColIdx < len(row); otherColIdx++ {
if row[colIdx] != row[otherColIdx] {
break
}
mci.safeAppend(colIdx, otherColIdx)
}
}
return mci
@@ -836,7 +839,7 @@ func (t *Table) shouldMergeCellsHorizontallyBelow(row rowStr, colIdx int, hint r
return false
}
func (t *Table) shouldMergeCellsVertically(colIdx int, hint renderHint) bool {
func (t *Table) shouldMergeCellsVerticallyAbove(colIdx int, hint renderHint) bool {
if !t.firstRowOfPage && t.columnConfigMap[colIdx].AutoMerge && colIdx < t.numColumns {
if hint.isSeparatorRow {
rowPrev := t.getRow(hint.rowNumber-1, hint)
@@ -855,6 +858,23 @@ func (t *Table) shouldMergeCellsVertically(colIdx int, hint renderHint) bool {
return false
}
func (t *Table) shouldMergeCellsVerticallyBelow(colIdx int, hint renderHint) int {
numRowsToMerge := 0
if t.columnConfigMap[colIdx].AutoMerge && colIdx < t.numColumns {
numRowsToMerge = 1
rowCurr := t.getRow(hint.rowNumber-1, hint)
for rowIdx := hint.rowNumber; rowIdx < len(t.rows); rowIdx++ {
rowNext := t.getRow(rowIdx, hint)
if colIdx < len(rowCurr) && colIdx < len(rowNext) && rowNext[colIdx] == rowCurr[colIdx] {
numRowsToMerge++
} else {
break
}
}
}
return numRowsToMerge
}
func (t *Table) shouldSeparateRows(rowIdx int, numRows int) bool {
// not asked to separate rows and no manually added separator
if !t.style.Options.SeparateRows && !t.separators[rowIdx] {

View File

@@ -2,6 +2,7 @@ package table
import (
"reflect"
"sort"
)
// AutoIndexColumnID returns a unique Column ID/Name for the given Column Number.
@@ -40,33 +41,7 @@ func isNumber(x interface{}) bool {
return false
}
type mergedColumnIndices map[int]map[int]bool
func (m mergedColumnIndices) mergedLength(colIdx int, maxColumnLengths []int) int {
mergedLength := maxColumnLengths[colIdx]
for otherColIdx := range m[colIdx] {
mergedLength += maxColumnLengths[otherColIdx]
}
return mergedLength
}
func (m mergedColumnIndices) len(colIdx int) int {
return len(m[colIdx]) + 1
}
func (m mergedColumnIndices) safeAppend(colIdx, otherColIdx int) {
// map
if m[colIdx] == nil {
m[colIdx] = make(map[int]bool)
}
m[colIdx][otherColIdx] = true
// reverse map
if m[otherColIdx] == nil {
m[otherColIdx] = make(map[int]bool)
}
m[otherColIdx][colIdx] = true
}
type mergedColumnIndices map[int]int
func objAsSlice(in interface{}) []interface{} {
var out []interface{}
@@ -110,3 +85,19 @@ func objIsSlice(in interface{}) bool {
k := reflect.TypeOf(in).Kind()
return k == reflect.Slice || k == reflect.Array
}
func getSortedKeys(input map[int]map[int]int) ([]int, map[int][]int) {
keys := make([]int, 0, len(input))
subkeysMap := make(map[int][]int)
for key, subMap := range input {
keys = append(keys, key)
subkeys := make([]int, 0, len(subMap))
for subkey := range subMap {
subkeys = append(subkeys, subkey)
}
sort.Ints(subkeys)
subkeysMap[key] = subkeys
}
sort.Ints(keys)
return keys, subkeysMap
}

View File

@@ -2,13 +2,14 @@ package text
import (
"fmt"
"os"
"sort"
"strconv"
"strings"
"sync"
)
var colorsEnabled = areANSICodesSupported()
var colorsEnabled = areColorsOnInTheEnv() && areANSICodesSupported()
// DisableColors (forcefully) disables color coding globally.
func DisableColors() {
@@ -20,6 +21,15 @@ func EnableColors() {
colorsEnabled = true
}
// areColorsOnInTheEnv returns true is colors are not disable using
// well known environment variables.
func areColorsOnInTheEnv() bool {
if os.Getenv("FORCE_COLOR") == "1" {
return true
}
return os.Getenv("NO_COLOR") == "" || os.Getenv("NO_COLOR") == "0"
}
// The logic here is inspired from github.com/fatih/color; the following is
// the bare minimum logic required to print Colored to the console.
// The differences: