0
0
mirror of https://github.com/crazy-max/diun.git synced 2024-12-22 19:38:28 +00:00
crazy-max_diun/vendor/github.com/jedib0t/go-pretty/v6/table/table.go
dependabot[bot] 6f7b5b313d
chore(deps): bump github.com/jedib0t/go-pretty/v6 from 6.5.9 to 6.6.5
Bumps [github.com/jedib0t/go-pretty/v6](https://github.com/jedib0t/go-pretty) from 6.5.9 to 6.6.5.
- [Release notes](https://github.com/jedib0t/go-pretty/releases)
- [Commits](https://github.com/jedib0t/go-pretty/compare/v6.5.9...v6.6.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-15 14:33:08 +00:00

894 lines
27 KiB
Go

package table
import (
"fmt"
"io"
"strings"
"time"
"unicode"
"github.com/jedib0t/go-pretty/v6/text"
)
// Table helps print a 2-dimensional array in a human-readable pretty-table.
type Table struct {
// allowedRowLength is the max allowed length for a row (or line of output)
allowedRowLength int
// enable automatic indexing of the rows and columns like a spreadsheet?
autoIndex bool
// autoIndexVIndexMaxLength denotes the length in chars for the last row
autoIndexVIndexMaxLength int
// caption stores the text to be rendered just below the table; and doesn't
// get used when rendered as a CSV
caption string
// columnIsNonNumeric stores if a column contains non-numbers in all rows
columnIsNonNumeric []bool
// columnConfigs stores the custom-configuration for 1 or more columns
columnConfigs []ColumnConfig
// columnConfigMap stores the custom-configuration by column
// number and is generated before rendering
columnConfigMap map[int]ColumnConfig
// firstRowOfPage tells if the renderer is on the first row of a page?
firstRowOfPage bool
// htmlCSSClass stores the HTML CSS Class to use on the <table> node
htmlCSSClass string
// indexColumn stores the number of the column considered as the "index"
indexColumn int
// maxColumnLengths stores the length of the longest line in each column
maxColumnLengths []int
// maxRowLength stores the length of the longest row
maxRowLength int
// numColumns stores the (max.) number of columns seen
numColumns int
// numLinesRendered keeps track of the number of lines rendered and helps in
// paginating long tables
numLinesRendered int
// outputMirror stores an io.Writer where the "Render" functions would write
outputMirror io.Writer
// pager controls how the output is separated into pages
pager pager
// rows stores the rows that make up the body (in string form)
rows []rowStr
// rowsColors stores the text.Colors over-rides for each row as defined by
// rowPainter or rowPainterWithAttributes
rowsColors []text.Colors
// rowsConfigs stores RowConfig for each row
rowsConfigMap map[int]RowConfig
// rowsRaw stores the rows that make up the body
rowsRaw []Row
// rowsFooter stores the rows that make up the footer (in string form)
rowsFooter []rowStr
// rowsFooterConfigs stores RowConfig for each footer row
rowsFooterConfigMap map[int]RowConfig
// rowsFooterRaw stores the rows that make up the footer
rowsFooterRaw []Row
// rowsHeader stores the rows that make up the header (in string form)
rowsHeader []rowStr
// rowsHeaderConfigs stores RowConfig for each header row
rowsHeaderConfigMap map[int]RowConfig
// rowsHeaderRaw stores the rows that make up the header
rowsHeaderRaw []Row
// rowPainter is a custom function that given a Row, returns the colors to
// use on the entire row
rowPainter RowPainter
// rowPainterWithAttributes is same as rowPainter, but with attributes
rowPainterWithAttributes RowPainterWithAttributes
// rowSeparator is a dummy row that contains the separator columns (dashes
// that make up the separator between header/body/footer
rowSeparator rowStr
// separators is used to keep track of all rowIndices after which a
// separator has to be rendered
separators map[int]bool
// sortBy stores a map of Column
sortBy []SortBy
// sortedRowIndices is the output of sorting
sortedRowIndices []int
// style contains all the strings used to draw the table, and more
style *Style
// suppressEmptyColumns hides columns which have no content on all regular
// rows
suppressEmptyColumns bool
// suppressTrailingSpaces removes all trailing spaces from the end of the last column
suppressTrailingSpaces bool
// title contains the text to appear above the table
title string
}
// AppendFooter appends the row to the List of footers to render.
//
// Only the first item in the "config" will be tagged against this row.
func (t *Table) AppendFooter(row Row, config ...RowConfig) {
t.rowsFooterRaw = append(t.rowsFooterRaw, row)
if len(config) > 0 {
if t.rowsFooterConfigMap == nil {
t.rowsFooterConfigMap = make(map[int]RowConfig)
}
t.rowsFooterConfigMap[len(t.rowsFooterRaw)-1] = config[0]
}
}
// AppendHeader appends the row to the List of headers to render.
//
// Only the first item in the "config" will be tagged against this row.
func (t *Table) AppendHeader(row Row, config ...RowConfig) {
t.rowsHeaderRaw = append(t.rowsHeaderRaw, row)
if len(config) > 0 {
if t.rowsHeaderConfigMap == nil {
t.rowsHeaderConfigMap = make(map[int]RowConfig)
}
t.rowsHeaderConfigMap[len(t.rowsHeaderRaw)-1] = config[0]
}
}
// AppendRow appends the row to the List of rows to render.
//
// Only the first item in the "config" will be tagged against this row.
func (t *Table) AppendRow(row Row, config ...RowConfig) {
t.rowsRaw = append(t.rowsRaw, row)
if len(config) > 0 {
if t.rowsConfigMap == nil {
t.rowsConfigMap = make(map[int]RowConfig)
}
t.rowsConfigMap[len(t.rowsRaw)-1] = config[0]
}
}
// AppendRows appends the rows to the List of rows to render.
//
// Only the first item in the "config" will be tagged against all the rows.
func (t *Table) AppendRows(rows []Row, config ...RowConfig) {
for _, row := range rows {
t.AppendRow(row, config...)
}
}
// AppendSeparator helps render a separator row after the current last row. You
// could call this function over and over, but it will be a no-op unless you
// call AppendRow or AppendRows in between. Likewise, if the last thing you
// append is a separator, it will not be rendered in addition to the usual table
// separator.
//
// ******************************************************************************
// Please note the following caveats:
// 1. SetPageSize(): this may end up creating consecutive separator rows near
// the end of a page or at the beginning of a page
// 2. SortBy(): since SortBy could inherently alter the ordering of rows, the
// separators may not appear after the row it was originally intended to
// follow
//
// ******************************************************************************
func (t *Table) AppendSeparator() {
if t.separators == nil {
t.separators = make(map[int]bool)
}
if len(t.rowsRaw) > 0 {
t.separators[len(t.rowsRaw)-1] = true
}
}
// ImportGrid helps import 1d or 2d arrays as rows.
func (t *Table) ImportGrid(grid interface{}) bool {
rows := objAsSlice(grid)
if rows == nil {
return false
}
addedRows := false
for _, row := range rows {
rowAsSlice := objAsSlice(row)
if rowAsSlice != nil {
t.AppendRow(rowAsSlice)
} else if row != nil {
t.AppendRow(Row{row})
}
addedRows = true
}
return addedRows
}
// Length returns the number of rows to be rendered.
func (t *Table) Length() int {
return len(t.rowsRaw)
}
// Pager returns an object that splits the table output into pages and
// lets you move back and forth through them.
func (t *Table) Pager(opts ...PagerOption) Pager {
for _, opt := range opts {
opt(t)
}
// use a temporary page separator for splitting up the pages
tempPageSep := fmt.Sprintf("%p // page separator // %d", t.rows, time.Now().UnixNano())
// backup
origOutputMirror, origPageSep := t.outputMirror, t.Style().Box.PageSeparator
// restore on exit
defer func() {
t.outputMirror = origOutputMirror
t.Style().Box.PageSeparator = origPageSep
}()
// override
t.outputMirror = nil
t.Style().Box.PageSeparator = tempPageSep
// render
t.pager.pages = strings.Split(t.Render(), tempPageSep)
return &t.pager
}
// ResetFooters resets and clears all the Footer rows appended earlier.
func (t *Table) ResetFooters() {
t.rowsFooterRaw = nil
}
// ResetHeaders resets and clears all the Header rows appended earlier.
func (t *Table) ResetHeaders() {
t.rowsHeaderRaw = nil
}
// ResetRows resets and clears all the rows appended earlier.
func (t *Table) ResetRows() {
t.rowsRaw = nil
t.separators = nil
}
// SetAllowedRowLength sets the maximum allowed length or a row (or line of
// output) when rendered as a table. Rows that are longer than this limit will
// be "snipped" to the length. Length has to be a positive value to take effect.
//
// Deprecated: in favor if Style().Size.WidthMax
func (t *Table) SetAllowedRowLength(length int) {
t.allowedRowLength = length
}
// SetAutoIndex adds a generated header with columns such as "A", "B", "C", etc.
// and a leading column with the row number similar to what you'd see on any
// spreadsheet application. NOTE: Appending a Header will void this
// functionality.
func (t *Table) SetAutoIndex(autoIndex bool) {
t.autoIndex = autoIndex
}
// SetCaption sets the text to be rendered just below the table. This will not
// show up when the Table is rendered as a CSV.
func (t *Table) SetCaption(format string, a ...interface{}) {
t.caption = fmt.Sprintf(format, a...)
}
// SetColumnConfigs sets the configs for each Column.
func (t *Table) SetColumnConfigs(configs []ColumnConfig) {
t.columnConfigs = configs
}
// SetHTMLCSSClass sets the HTML CSS Class to use on the <table> node
// when rendering the Table in HTML format.
//
// Deprecated: in favor of Style().HTML.CSSClass
func (t *Table) SetHTMLCSSClass(cssClass string) {
t.htmlCSSClass = cssClass
}
// SetIndexColumn sets the given Column # as the column that has the row
// "Number". Valid values range from 1 to N. Note that this is not 0-indexed.
func (t *Table) SetIndexColumn(colNum int) {
t.indexColumn = colNum
}
// SetOutputMirror sets an io.Writer for all the Render functions to "Write" to
// in addition to returning a string.
func (t *Table) SetOutputMirror(mirror io.Writer) {
t.outputMirror = mirror
t.pager.SetOutputMirror(mirror)
}
// SetPageSize sets the maximum number of lines to render before rendering the
// header rows again. This can be useful when dealing with tables containing a
// long list of rows that can span pages. Please note that the pagination logic
// will not consider Header/Footer lines for paging.
func (t *Table) SetPageSize(numLines int) {
t.pager.size = numLines
}
// SetRowPainter sets up the function which determines the colors to use on a
// row. Before rendering, this function is invoked on all rows and the color
// of each row is determined. This color takes precedence over other ways to
// set color (ColumnConfig.Color*, SetColor*()).
func (t *Table) SetRowPainter(painter interface{}) {
// TODO: fix interface on major version bump to accept only
// one type of RowPainter: RowPainterWithAttributes renamed to RowPainter
// reset both so only one is set at any given time
t.rowPainter = nil
t.rowPainterWithAttributes = nil
// if called as SetRowPainter(RowPainter(func...))
switch painter.(type) {
case RowPainter:
t.rowPainter = painter.(RowPainter)
return
case RowPainterWithAttributes:
t.rowPainterWithAttributes = painter.(RowPainterWithAttributes)
return
}
// if called as SetRowPainter(func...)
switch fmt.Sprintf("%T", painter) {
case "func(table.Row) text.Colors":
t.rowPainter = painter.(func(row Row) text.Colors)
return
case "func(table.Row, table.RowAttributes) text.Colors":
t.rowPainterWithAttributes = painter.(func(row Row, attr RowAttributes) text.Colors)
return
}
}
// SetStyle overrides the DefaultStyle with the provided one.
func (t *Table) SetStyle(style Style) {
t.style = &style
}
// SetTitle sets the title text to be rendered above the table.
func (t *Table) SetTitle(format string, a ...interface{}) {
t.title = fmt.Sprintf(format, a...)
}
// SortBy sets the rules for sorting the Rows in the order specified. i.e., the
// first SortBy instruction takes precedence over the second and so on. Any
// duplicate instructions on the same column will be discarded while sorting.
func (t *Table) SortBy(sortBy []SortBy) {
t.sortBy = sortBy
}
// Style returns the current style.
func (t *Table) Style() *Style {
if t.style == nil {
tempStyle := StyleDefault
t.style = &tempStyle
}
// override WidthMax with allowedRowLength until allowedRowLength is
// removed from code
if t.allowedRowLength > 0 {
t.style.Size.WidthMax = t.allowedRowLength
}
return t.style
}
// SuppressEmptyColumns hides columns when the column is empty in ALL the
// regular rows.
func (t *Table) SuppressEmptyColumns() {
t.suppressEmptyColumns = true
}
// SuppressTrailingSpaces removes all trailing spaces from the output.
func (t *Table) SuppressTrailingSpaces() {
t.suppressTrailingSpaces = true
}
func (t *Table) getAlign(colIdx int, hint renderHint) text.Align {
align := text.AlignDefault
if cfg, ok := t.columnConfigMap[colIdx]; ok {
if hint.isHeaderRow {
align = cfg.AlignHeader
} else if hint.isFooterRow {
align = cfg.AlignFooter
} else {
align = cfg.Align
}
}
if align == text.AlignDefault {
if !t.columnIsNonNumeric[colIdx] {
align = text.AlignRight
} else if hint.isAutoIndexRow {
align = text.AlignCenter
} else if hint.isHeaderRow {
align = t.style.Format.HeaderAlign
} else if hint.isFooterRow {
align = t.style.Format.FooterAlign
} else {
align = t.style.Format.RowAlign
}
}
return align
}
func (t *Table) getAutoIndexColumnIDs() rowStr {
row := make(rowStr, t.numColumns)
for colIdx := range row {
row[colIdx] = AutoIndexColumnID(colIdx)
}
return row
}
func (t *Table) getBorderColors(hint renderHint) text.Colors {
if t.style.Options.DoNotColorBordersAndSeparators {
return nil
} else if t.style.Color.Border != nil {
return t.style.Color.Border
} else if hint.isTitleRow {
return t.style.Title.Colors
} else if hint.isHeaderRow {
return t.style.Color.Header
} else if hint.isFooterRow {
return t.style.Color.Footer
} else if t.autoIndex {
return t.style.Color.IndexColumn
} else if hint.rowNumber%2 == 0 && t.style.Color.RowAlternate != nil {
return t.style.Color.RowAlternate
}
return t.style.Color.Row
}
func (t *Table) getBorderLeft(hint renderHint) string {
border := t.style.Box.Left
if hint.isBorderTop {
if t.title != "" {
border = t.style.Box.LeftSeparator
} else {
border = t.style.Box.TopLeft
}
} else if hint.isBorderBottom {
border = t.style.Box.BottomLeft
} else if hint.isSeparatorRow {
if t.autoIndex && hint.isHeaderOrFooterSeparator() {
border = t.style.Box.Left
} else if !t.autoIndex && t.shouldMergeCellsVertically(0, hint) {
border = t.style.Box.Left
} else {
border = t.style.Box.LeftSeparator
}
}
return border
}
func (t *Table) getBorderRight(hint renderHint) string {
border := t.style.Box.Right
if hint.isBorderTop {
if t.title != "" {
border = t.style.Box.RightSeparator
} else {
border = t.style.Box.TopRight
}
} else if hint.isBorderBottom {
border = t.style.Box.BottomRight
} else if hint.isSeparatorRow {
if t.shouldMergeCellsVertically(t.numColumns-1, hint) {
border = t.style.Box.Right
} else {
border = t.style.Box.RightSeparator
}
}
return border
}
func (t *Table) getColumnColors(colIdx int, hint renderHint) text.Colors {
if hint.isBorderOrSeparator() {
if colors := t.getColumnColorsForBorderOrSeparator(hint); colors != nil {
return colors
}
}
if t.hasRowPainter() && hint.isRegularNonSeparatorRow() && !t.isIndexColumn(colIdx, hint) {
if colors := t.rowsColors[hint.rowNumber-1]; colors != nil {
return colors
}
}
if cfg, ok := t.columnConfigMap[colIdx]; ok {
if hint.isSeparatorRow {
return nil
} else if hint.isHeaderRow {
return cfg.ColorsHeader
} else if hint.isFooterRow {
return cfg.ColorsFooter
}
return cfg.Colors
}
return nil
}
func (t *Table) getColumnColorsForBorderOrSeparator(hint renderHint) text.Colors {
if t.style.Options.DoNotColorBordersAndSeparators {
return text.Colors{} // not nil to force caller to paint with no colors
}
if (hint.isBorderBottom || hint.isBorderTop) && t.style.Color.Border != nil {
return t.style.Color.Border
}
if hint.isSeparatorRow && t.style.Color.Separator != nil {
return t.style.Color.Separator
}
return nil
}
func (t *Table) getColumnSeparator(row rowStr, colIdx int, hint renderHint) string {
separator := t.style.Box.MiddleVertical
if hint.isSeparatorRow {
if hint.isBorderTop {
if t.shouldMergeCellsHorizontallyBelow(row, colIdx, hint) {
separator = t.style.Box.MiddleHorizontal
} else {
separator = t.style.Box.TopSeparator
}
} else if hint.isBorderBottom {
if t.shouldMergeCellsHorizontallyAbove(row, colIdx, hint) {
separator = t.style.Box.MiddleHorizontal
} else {
separator = t.style.Box.BottomSeparator
}
} else {
sm1 := t.shouldMergeCellsHorizontallyAbove(row, colIdx, hint)
sm2 := t.shouldMergeCellsHorizontallyBelow(row, colIdx, hint)
separator = t.getColumnSeparatorNonBorder(sm1, sm2, colIdx, hint)
}
}
return separator
}
func (t *Table) getColumnSeparatorNonBorder(mergeCellsAbove bool, mergeCellsBelow bool, colIdx int, hint renderHint) string {
mergeNextCol := t.shouldMergeCellsVertically(colIdx, hint)
if hint.isAutoIndexColumn {
return t.getColumnSeparatorNonBorderAutoIndex(mergeNextCol, hint)
}
mergeCurrCol := t.shouldMergeCellsVertically(colIdx-1, hint)
return t.getColumnSeparatorNonBorderNonAutoIndex(mergeCellsAbove, mergeCellsBelow, mergeCurrCol, mergeNextCol)
}
func (t *Table) getColumnSeparatorNonBorderAutoIndex(mergeNextCol bool, hint renderHint) string {
if hint.isHeaderOrFooterSeparator() {
if mergeNextCol {
return t.style.Box.MiddleVertical
}
return t.style.Box.LeftSeparator
} else if mergeNextCol {
return t.style.Box.RightSeparator
}
return t.style.Box.MiddleSeparator
}
func (t *Table) getColumnSeparatorNonBorderNonAutoIndex(mergeCellsAbove bool, mergeCellsBelow bool, mergeCurrCol bool, mergeNextCol bool) string {
if mergeCellsAbove && mergeCellsBelow && mergeCurrCol && mergeNextCol {
return t.style.Box.EmptySeparator
} else if mergeCellsAbove && mergeCellsBelow {
return t.style.Box.MiddleHorizontal
} else if mergeCellsAbove {
return t.style.Box.TopSeparator
} else if mergeCellsBelow {
return t.style.Box.BottomSeparator
} else if mergeCurrCol && mergeNextCol {
return t.style.Box.MiddleVertical
} else if mergeCurrCol {
return t.style.Box.LeftSeparator
} else if mergeNextCol {
return t.style.Box.RightSeparator
}
return t.style.Box.MiddleSeparator
}
func (t *Table) getColumnTransformer(colIdx int, hint renderHint) text.Transformer {
var transformer text.Transformer
if cfg, ok := t.columnConfigMap[colIdx]; ok {
if hint.isHeaderRow {
transformer = cfg.TransformerHeader
} else if hint.isFooterRow {
transformer = cfg.TransformerFooter
} else {
transformer = cfg.Transformer
}
}
return transformer
}
func (t *Table) getColumnWidthMax(colIdx int) int {
if cfg, ok := t.columnConfigMap[colIdx]; ok {
return cfg.WidthMax
}
return 0
}
func (t *Table) getColumnWidthMin(colIdx int) int {
if cfg, ok := t.columnConfigMap[colIdx]; ok {
return cfg.WidthMin
}
return 0
}
func (t *Table) getFormat(hint renderHint) text.Format {
if hint.isSeparatorRow {
return text.FormatDefault
} else if hint.isHeaderRow {
return t.style.Format.Header
} else if hint.isFooterRow {
return t.style.Format.Footer
}
return t.style.Format.Row
}
func (t *Table) getMaxColumnLengthForMerging(colIdx int) int {
maxColumnLength := t.maxColumnLengths[colIdx]
maxColumnLength += text.StringWidthWithoutEscSequences(t.style.Box.PaddingRight + t.style.Box.PaddingLeft)
if t.style.Options.SeparateColumns {
maxColumnLength += text.StringWidthWithoutEscSequences(t.style.Box.EmptySeparator)
}
return maxColumnLength
}
// getMergedColumnIndices returns a map of colIdx values to all the other colIdx
// values (that are being merged) and their lengths.
func (t *Table) getMergedColumnIndices(row rowStr, hint renderHint) mergedColumnIndices {
if !t.getRowConfig(hint).AutoMerge {
return nil
}
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] {
break
}
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
}
func (t *Table) getRow(rowIdx int, hint renderHint) rowStr {
switch {
case hint.isHeaderRow:
if rowIdx >= 0 && rowIdx < len(t.rowsHeader) {
return t.rowsHeader[rowIdx]
}
case hint.isFooterRow:
if rowIdx >= 0 && rowIdx < len(t.rowsFooter) {
return t.rowsFooter[rowIdx]
}
default:
if rowIdx >= 0 && rowIdx < len(t.rows) {
return t.rows[rowIdx]
}
}
return rowStr{}
}
func (t *Table) getRowConfig(hint renderHint) RowConfig {
rowIdx := hint.rowNumber - 1
if rowIdx < 0 {
rowIdx = 0
}
switch {
case hint.isHeaderRow:
return t.rowsHeaderConfigMap[rowIdx]
case hint.isFooterRow:
return t.rowsFooterConfigMap[rowIdx]
default:
return t.rowsConfigMap[rowIdx]
}
}
func (t *Table) getSeparatorColors(hint renderHint) text.Colors {
if t.style.Options.DoNotColorBordersAndSeparators {
return nil
} else if (hint.isBorderBottom || hint.isBorderTop) && t.style.Color.Border != nil {
return t.style.Color.Border
} else if t.style.Color.Separator != nil {
return t.style.Color.Separator
} else if hint.isHeaderRow {
return t.style.Color.Header
} else if hint.isFooterRow {
return t.style.Color.Footer
} else if hint.isAutoIndexColumn {
return t.style.Color.IndexColumn
} else if hint.rowNumber > 0 && hint.rowNumber%2 == 0 {
return t.style.Color.RowAlternate
}
return t.style.Color.Row
}
func (t *Table) getVAlign(colIdx int, hint renderHint) text.VAlign {
vAlign := text.VAlignDefault
if cfg, ok := t.columnConfigMap[colIdx]; ok {
if hint.isHeaderRow {
vAlign = cfg.VAlignHeader
} else if hint.isFooterRow {
vAlign = cfg.VAlignFooter
} else {
vAlign = cfg.VAlign
}
}
if vAlign == text.VAlignDefault {
if hint.isHeaderRow {
vAlign = t.style.Format.HeaderVAlign
} else if hint.isFooterRow {
vAlign = t.style.Format.FooterVAlign
} else {
vAlign = t.style.Format.RowVAlign
}
}
return vAlign
}
func (t *Table) hasHiddenColumns() bool {
for _, cc := range t.columnConfigMap {
if cc.Hidden {
return true
}
}
return false
}
func (t *Table) hasRowPainter() bool {
return t.rowPainter != nil || t.rowPainterWithAttributes != nil
}
func (t *Table) hideColumns() map[int]int {
colIdxMap := make(map[int]int)
numColumns := 0
hideColumnsInRows := func(rows []rowStr) []rowStr {
var rsp []rowStr
for _, row := range rows {
var rowNew rowStr
for colIdx, col := range row {
cc := t.columnConfigMap[colIdx]
if !cc.Hidden {
rowNew = append(rowNew, col)
colIdxMap[colIdx] = len(rowNew) - 1
}
}
if len(rowNew) > numColumns {
numColumns = len(rowNew)
}
rsp = append(rsp, rowNew)
}
return rsp
}
// hide columns as directed
t.rows = hideColumnsInRows(t.rows)
t.rowsFooter = hideColumnsInRows(t.rowsFooter)
t.rowsHeader = hideColumnsInRows(t.rowsHeader)
// reset numColumns to the new number of columns
t.numColumns = numColumns
return colIdxMap
}
func (t *Table) isIndexColumn(colIdx int, hint renderHint) bool {
return t.indexColumn == colIdx+1 || hint.isAutoIndexColumn
}
func (t *Table) render(out *strings.Builder) string {
outStr := out.String()
if t.suppressTrailingSpaces {
var trimmed []string
for _, line := range strings.Split(outStr, "\n") {
trimmed = append(trimmed, strings.TrimRightFunc(line, unicode.IsSpace))
}
outStr = strings.Join(trimmed, "\n")
}
if t.outputMirror != nil && len(outStr) > 0 {
_, _ = t.outputMirror.Write([]byte(outStr))
_, _ = t.outputMirror.Write([]byte("\n"))
}
return outStr
}
func (t *Table) shouldMergeCellsHorizontallyAbove(row rowStr, colIdx int, hint renderHint) bool {
if hint.isAutoIndexColumn || hint.isAutoIndexRow {
return false
}
rowConfig := t.getRowConfig(hint)
if hint.isSeparatorRow {
if hint.isHeaderRow && hint.rowNumber == 1 {
rowConfig = t.getRowConfig(hint)
row = t.getRow(hint.rowNumber-1, hint)
} else if hint.isFooterRow && hint.isFirstRow {
rowConfig = t.getRowConfig(renderHint{isLastRow: true, rowNumber: len(t.rows)})
row = t.getRow(len(t.rows)-1, renderHint{})
} else if hint.isFooterRow && hint.isBorderBottom {
row = t.getRow(len(t.rowsFooter)-1, renderHint{isFooterRow: true})
} else {
row = t.getRow(hint.rowNumber-1, hint)
}
}
if rowConfig.AutoMerge {
return row.areEqual(colIdx-1, colIdx)
}
return false
}
func (t *Table) shouldMergeCellsHorizontallyBelow(row rowStr, colIdx int, hint renderHint) bool {
if hint.isAutoIndexColumn || hint.isAutoIndexRow {
return false
}
var rowConfig RowConfig
if hint.isSeparatorRow {
if hint.isRegularRow() {
rowConfig = t.getRowConfig(renderHint{rowNumber: hint.rowNumber + 1})
row = t.getRow(hint.rowNumber, renderHint{})
} else if hint.isHeaderRow && hint.rowNumber == 0 {
rowConfig = t.getRowConfig(renderHint{isHeaderRow: true, rowNumber: 1})
row = t.getRow(0, hint)
} else if hint.isHeaderRow && hint.isLastRow {
rowConfig = t.getRowConfig(renderHint{rowNumber: 1})
row = t.getRow(0, renderHint{})
} else if hint.isHeaderRow {
rowConfig = t.getRowConfig(renderHint{isHeaderRow: true, rowNumber: hint.rowNumber + 1})
row = t.getRow(hint.rowNumber, hint)
} else if hint.isFooterRow && hint.rowNumber >= 0 {
rowConfig = t.getRowConfig(renderHint{isFooterRow: true, rowNumber: 1})
row = t.getRow(hint.rowNumber, renderHint{isFooterRow: true})
}
}
if rowConfig.AutoMerge {
return row.areEqual(colIdx-1, colIdx)
}
return false
}
func (t *Table) shouldMergeCellsVertically(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)
rowNext := t.getRow(hint.rowNumber, hint)
if colIdx < len(rowPrev) && colIdx < len(rowNext) {
return rowPrev[colIdx] == rowNext[colIdx]
}
} else {
rowPrev := t.getRow(hint.rowNumber-2, hint)
rowCurr := t.getRow(hint.rowNumber-1, hint)
if colIdx < len(rowPrev) && colIdx < len(rowCurr) {
return rowPrev[colIdx] == rowCurr[colIdx]
}
}
}
return false
}
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] {
return false
}
pageSize := numRows
if t.pager.size > 0 {
pageSize = t.pager.size
}
if rowIdx%pageSize == pageSize-1 { // last row of page
return false
}
if rowIdx == numRows-1 { // last row of table
return false
}
return true
}
func (t *Table) wrapRow(row rowStr) (int, rowStr) {
colMaxLines := 0
rowWrapped := make(rowStr, len(row))
for colIdx, colStr := range row {
widthEnforcer := t.columnConfigMap[colIdx].getWidthMaxEnforcer()
maxWidth := t.getColumnWidthMax(colIdx)
if maxWidth == 0 {
maxWidth = t.maxColumnLengths[colIdx]
}
rowWrapped[colIdx] = widthEnforcer(colStr, maxWidth)
colNumLines := strings.Count(rowWrapped[colIdx], "\n") + 1
if colNumLines > colMaxLines {
colMaxLines = colNumLines
}
}
return colMaxLines, rowWrapped
}