1 package table
2
3 import (
4 "fmt"
5 "io"
6 "strings"
7
8 "github.com/jedib0t/go-pretty/v6/text"
9 )
10
11
12 type Row []interface{}
13
14 func (r Row) findColumnNumber(colName string) int {
15 for colIdx, col := range r {
16 if fmt.Sprint(col) == colName {
17 return colIdx + 1
18 }
19 }
20 return 0
21 }
22
23
24
25 type RowPainter func(row Row) text.Colors
26
27
28 type rowStr []string
29
30
31 func (row rowStr) areEqual(colIdx1 int, colIdx2 int) bool {
32 return colIdx1 >= 0 && colIdx2 < len(row) && row[colIdx1] == row[colIdx2]
33 }
34
35
36 type Table struct {
37
38 allowedRowLength int
39
40 autoIndex bool
41
42 autoIndexVIndexMaxLength int
43
44
45 caption string
46
47 columnIsNonNumeric []bool
48
49 columnConfigs []ColumnConfig
50
51
52 columnConfigMap map[int]ColumnConfig
53
54 htmlCSSClass string
55
56 indexColumn int
57
58 maxColumnLengths []int
59
60 maxRowLength int
61
62 numColumns int
63
64
65 numLinesRendered int
66
67 outputMirror io.Writer
68
69
70
71 pageSize int
72
73 rows []rowStr
74
75
76 rowsColors []text.Colors
77
78 rowsConfigMap map[int]RowConfig
79
80 rowsRaw []Row
81
82 rowsFooter []rowStr
83
84 rowsFooterConfigMap map[int]RowConfig
85
86 rowsFooterRaw []Row
87
88 rowsHeader []rowStr
89
90 rowsHeaderConfigMap map[int]RowConfig
91
92 rowsHeaderRaw []Row
93
94
95 rowPainter RowPainter
96
97
98 rowSeparator rowStr
99
100
101 separators map[int]bool
102
103 sortBy []SortBy
104
105 style *Style
106
107
108 suppressEmptyColumns bool
109
110 title string
111 }
112
113
114
115
116 func (t *Table) AppendFooter(row Row, config ...RowConfig) {
117 t.rowsFooterRaw = append(t.rowsFooterRaw, row)
118 if len(config) > 0 {
119 if t.rowsFooterConfigMap == nil {
120 t.rowsFooterConfigMap = make(map[int]RowConfig)
121 }
122 t.rowsFooterConfigMap[len(t.rowsFooterRaw)-1] = config[0]
123 }
124 }
125
126
127
128
129 func (t *Table) AppendHeader(row Row, config ...RowConfig) {
130 t.rowsHeaderRaw = append(t.rowsHeaderRaw, row)
131 if len(config) > 0 {
132 if t.rowsHeaderConfigMap == nil {
133 t.rowsHeaderConfigMap = make(map[int]RowConfig)
134 }
135 t.rowsHeaderConfigMap[len(t.rowsHeaderRaw)-1] = config[0]
136 }
137 }
138
139
140
141
142 func (t *Table) AppendRow(row Row, config ...RowConfig) {
143 t.rowsRaw = append(t.rowsRaw, row)
144 if len(config) > 0 {
145 if t.rowsConfigMap == nil {
146 t.rowsConfigMap = make(map[int]RowConfig)
147 }
148 t.rowsConfigMap[len(t.rowsRaw)-1] = config[0]
149 }
150 }
151
152
153
154
155 func (t *Table) AppendRows(rows []Row, config ...RowConfig) {
156 for _, row := range rows {
157 t.AppendRow(row, config...)
158 }
159 }
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175 func (t *Table) AppendSeparator() {
176 if t.separators == nil {
177 t.separators = make(map[int]bool)
178 }
179 if len(t.rowsRaw) > 0 {
180 t.separators[len(t.rowsRaw)-1] = true
181 }
182 }
183
184
185 func (t *Table) Length() int {
186 return len(t.rowsRaw)
187 }
188
189
190 func (t *Table) ResetFooters() {
191 t.rowsFooterRaw = nil
192 }
193
194
195 func (t *Table) ResetHeaders() {
196 t.rowsHeaderRaw = nil
197 }
198
199
200 func (t *Table) ResetRows() {
201 t.rowsRaw = nil
202 t.separators = nil
203 }
204
205
206
207
208 func (t *Table) SetAllowedRowLength(length int) {
209 t.allowedRowLength = length
210 }
211
212
213
214
215
216 func (t *Table) SetAutoIndex(autoIndex bool) {
217 t.autoIndex = autoIndex
218 }
219
220
221
222 func (t *Table) SetCaption(format string, a ...interface{}) {
223 t.caption = fmt.Sprintf(format, a...)
224 }
225
226
227 func (t *Table) SetColumnConfigs(configs []ColumnConfig) {
228 t.columnConfigs = configs
229 }
230
231
232
233
234
235 func (t *Table) SetHTMLCSSClass(cssClass string) {
236 t.htmlCSSClass = cssClass
237 }
238
239
240
241 func (t *Table) SetIndexColumn(colNum int) {
242 t.indexColumn = colNum
243 }
244
245
246
247 func (t *Table) SetOutputMirror(mirror io.Writer) {
248 t.outputMirror = mirror
249 }
250
251
252
253
254
255 func (t *Table) SetPageSize(numLines int) {
256 t.pageSize = numLines
257 }
258
259
260
261
262
263 func (t *Table) SetRowPainter(painter RowPainter) {
264 t.rowPainter = painter
265 }
266
267
268 func (t *Table) SetStyle(style Style) {
269 t.style = &style
270 }
271
272
273 func (t *Table) SetTitle(format string, a ...interface{}) {
274 t.title = fmt.Sprintf(format, a...)
275 }
276
277
278
279
280 func (t *Table) SortBy(sortBy []SortBy) {
281 t.sortBy = sortBy
282 }
283
284
285 func (t *Table) Style() *Style {
286 if t.style == nil {
287 tempStyle := StyleDefault
288 t.style = &tempStyle
289 }
290 return t.style
291 }
292
293
294
295 func (t *Table) SuppressEmptyColumns() {
296 t.suppressEmptyColumns = true
297 }
298
299 func (t *Table) analyzeAndStringify(row Row, hint renderHint) rowStr {
300
301 if len(row) > t.numColumns {
302
303 if t.numColumns == 0 {
304 t.columnIsNonNumeric = make([]bool, len(row))
305 } else {
306 t.columnIsNonNumeric = append(t.columnIsNonNumeric, make([]bool, len(row)-t.numColumns)...)
307 }
308
309 t.numColumns = len(row)
310 }
311
312
313 rowOut := make(rowStr, len(row))
314 for colIdx, col := range row {
315
316 if !hint.isHeaderRow && !hint.isFooterRow && !t.columnIsNonNumeric[colIdx] && !isNumber(col) {
317 t.columnIsNonNumeric[colIdx] = true
318 }
319
320 rowOut[colIdx] = t.analyzeAndStringifyColumn(colIdx, col, hint)
321 }
322 return rowOut
323 }
324
325 func (t *Table) analyzeAndStringifyColumn(colIdx int, col interface{}, hint renderHint) string {
326
327 var colStr string
328 if transformer := t.getColumnTransformer(colIdx, hint); transformer != nil {
329 colStr = transformer(col)
330 } else if colStrVal, ok := col.(string); ok {
331 colStr = colStrVal
332 } else {
333 colStr = fmt.Sprint(col)
334 }
335 if strings.Contains(colStr, "\t") {
336 colStr = strings.Replace(colStr, "\t", " ", -1)
337 }
338 if strings.Contains(colStr, "\r") {
339 colStr = strings.Replace(colStr, "\r", "", -1)
340 }
341 return fmt.Sprintf("%s%s", t.style.Format.Direction.Modifier(), colStr)
342 }
343
344 func (t *Table) getAlign(colIdx int, hint renderHint) text.Align {
345 align := text.AlignDefault
346 if cfg, ok := t.columnConfigMap[colIdx]; ok {
347 if hint.isHeaderRow {
348 align = cfg.AlignHeader
349 } else if hint.isFooterRow {
350 align = cfg.AlignFooter
351 } else {
352 align = cfg.Align
353 }
354 }
355 if align == text.AlignDefault {
356 if !t.columnIsNonNumeric[colIdx] {
357 align = text.AlignRight
358 } else if hint.isAutoIndexRow {
359 align = text.AlignCenter
360 }
361 }
362 return align
363 }
364
365 func (t *Table) getAutoIndexColumnIDs() rowStr {
366 row := make(rowStr, t.numColumns)
367 for colIdx := range row {
368 row[colIdx] = AutoIndexColumnID(colIdx)
369 }
370 return row
371 }
372
373 func (t *Table) getBorderColors(hint renderHint) text.Colors {
374 if hint.isFooterRow {
375 return t.style.Color.Footer
376 } else if t.autoIndex {
377 return t.style.Color.IndexColumn
378 }
379 return t.style.Color.Header
380 }
381
382 func (t *Table) getBorderLeft(hint renderHint) string {
383 border := t.style.Box.Left
384 if hint.isBorderTop {
385 if t.title != "" {
386 border = t.style.Box.LeftSeparator
387 } else {
388 border = t.style.Box.TopLeft
389 }
390 } else if hint.isBorderBottom {
391 border = t.style.Box.BottomLeft
392 } else if hint.isSeparatorRow {
393 if t.autoIndex && hint.isHeaderOrFooterSeparator() {
394 border = t.style.Box.Left
395 } else if !t.autoIndex && t.shouldMergeCellsVertically(0, hint) {
396 border = t.style.Box.Left
397 } else {
398 border = t.style.Box.LeftSeparator
399 }
400 }
401 return border
402 }
403
404 func (t *Table) getBorderRight(hint renderHint) string {
405 border := t.style.Box.Right
406 if hint.isBorderTop {
407 if t.title != "" {
408 border = t.style.Box.RightSeparator
409 } else {
410 border = t.style.Box.TopRight
411 }
412 } else if hint.isBorderBottom {
413 border = t.style.Box.BottomRight
414 } else if hint.isSeparatorRow {
415 if t.shouldMergeCellsVertically(t.numColumns-1, hint) {
416 border = t.style.Box.Right
417 } else {
418 border = t.style.Box.RightSeparator
419 }
420 }
421 return border
422 }
423
424 func (t *Table) getColumnColors(colIdx int, hint renderHint) text.Colors {
425 if t.rowPainter != nil && hint.isRegularNonSeparatorRow() && !t.isIndexColumn(colIdx, hint) {
426 colors := t.rowsColors[hint.rowNumber-1]
427 if colors != nil {
428 return colors
429 }
430 }
431 if cfg, ok := t.columnConfigMap[colIdx]; ok {
432 if hint.isSeparatorRow {
433 return nil
434 } else if hint.isHeaderRow {
435 return cfg.ColorsHeader
436 } else if hint.isFooterRow {
437 return cfg.ColorsFooter
438 }
439 return cfg.Colors
440 }
441 return nil
442 }
443
444 func (t *Table) getColumnSeparator(row rowStr, colIdx int, hint renderHint) string {
445 separator := t.style.Box.MiddleVertical
446 if hint.isSeparatorRow {
447 if hint.isBorderTop {
448 if t.shouldMergeCellsHorizontallyBelow(row, colIdx, hint) {
449 separator = t.style.Box.MiddleHorizontal
450 } else {
451 separator = t.style.Box.TopSeparator
452 }
453 } else if hint.isBorderBottom {
454 if t.shouldMergeCellsHorizontallyAbove(row, colIdx, hint) {
455 separator = t.style.Box.MiddleHorizontal
456 } else {
457 separator = t.style.Box.BottomSeparator
458 }
459 } else {
460 separator = t.getColumnSeparatorNonBorder(
461 t.shouldMergeCellsHorizontallyAbove(row, colIdx, hint),
462 t.shouldMergeCellsHorizontallyBelow(row, colIdx, hint),
463 colIdx,
464 hint,
465 )
466 }
467 }
468 return separator
469 }
470
471 func (t *Table) getColumnSeparatorNonBorder(mergeCellsAbove bool, mergeCellsBelow bool, colIdx int, hint renderHint) string {
472 mergeNextCol := t.shouldMergeCellsVertically(colIdx, hint)
473 if hint.isAutoIndexColumn {
474 return t.getColumnSeparatorNonBorderAutoIndex(mergeNextCol, hint)
475 }
476
477 mergeCurrCol := t.shouldMergeCellsVertically(colIdx-1, hint)
478 return t.getColumnSeparatorNonBorderNonAutoIndex(mergeCellsAbove, mergeCellsBelow, mergeCurrCol, mergeNextCol)
479 }
480
481 func (t *Table) getColumnSeparatorNonBorderAutoIndex(mergeNextCol bool, hint renderHint) string {
482 if hint.isHeaderOrFooterSeparator() {
483 if mergeNextCol {
484 return t.style.Box.MiddleVertical
485 }
486 return t.style.Box.LeftSeparator
487 } else if mergeNextCol {
488 return t.style.Box.RightSeparator
489 }
490 return t.style.Box.MiddleSeparator
491 }
492
493 func (t *Table) getColumnSeparatorNonBorderNonAutoIndex(mergeCellsAbove bool, mergeCellsBelow bool, mergeCurrCol bool, mergeNextCol bool) string {
494 if mergeCellsAbove && mergeCellsBelow && mergeCurrCol && mergeNextCol {
495 return t.style.Box.EmptySeparator
496 } else if mergeCellsAbove && mergeCellsBelow {
497 return t.style.Box.MiddleHorizontal
498 } else if mergeCellsAbove {
499 return t.style.Box.TopSeparator
500 } else if mergeCellsBelow {
501 return t.style.Box.BottomSeparator
502 } else if mergeCurrCol && mergeNextCol {
503 return t.style.Box.MiddleVertical
504 } else if mergeCurrCol {
505 return t.style.Box.LeftSeparator
506 } else if mergeNextCol {
507 return t.style.Box.RightSeparator
508 }
509 return t.style.Box.MiddleSeparator
510 }
511
512 func (t *Table) getColumnTransformer(colIdx int, hint renderHint) text.Transformer {
513 var transformer text.Transformer
514 if cfg, ok := t.columnConfigMap[colIdx]; ok {
515 if hint.isHeaderRow {
516 transformer = cfg.TransformerHeader
517 } else if hint.isFooterRow {
518 transformer = cfg.TransformerFooter
519 } else {
520 transformer = cfg.Transformer
521 }
522 }
523 return transformer
524 }
525
526 func (t *Table) getColumnWidthMax(colIdx int) int {
527 if cfg, ok := t.columnConfigMap[colIdx]; ok {
528 return cfg.WidthMax
529 }
530 return 0
531 }
532
533 func (t *Table) getColumnWidthMin(colIdx int) int {
534 if cfg, ok := t.columnConfigMap[colIdx]; ok {
535 return cfg.WidthMin
536 }
537 return 0
538 }
539
540 func (t *Table) getFormat(hint renderHint) text.Format {
541 if hint.isSeparatorRow {
542 return text.FormatDefault
543 } else if hint.isHeaderRow {
544 return t.style.Format.Header
545 } else if hint.isFooterRow {
546 return t.style.Format.Footer
547 }
548 return t.style.Format.Row
549 }
550
551 func (t *Table) getMaxColumnLengthForMerging(colIdx int) int {
552 maxColumnLength := t.maxColumnLengths[colIdx]
553 maxColumnLength += text.RuneWidthWithoutEscSequences(t.style.Box.PaddingRight + t.style.Box.PaddingLeft)
554 if t.style.Options.SeparateColumns {
555 maxColumnLength += text.RuneWidthWithoutEscSequences(t.style.Box.EmptySeparator)
556 }
557 return maxColumnLength
558 }
559
560 func (t *Table) getRow(rowIdx int, hint renderHint) rowStr {
561 switch {
562 case hint.isHeaderRow:
563 if rowIdx >= 0 && rowIdx < len(t.rowsHeader) {
564 return t.rowsHeader[rowIdx]
565 }
566 case hint.isFooterRow:
567 if rowIdx >= 0 && rowIdx < len(t.rowsFooter) {
568 return t.rowsFooter[rowIdx]
569 }
570 default:
571 if rowIdx >= 0 && rowIdx < len(t.rows) {
572 return t.rows[rowIdx]
573 }
574 }
575 return rowStr{}
576 }
577
578 func (t *Table) getRowConfig(hint renderHint) RowConfig {
579 rowIdx := hint.rowNumber - 1
580 if rowIdx < 0 {
581 rowIdx = 0
582 }
583
584 switch {
585 case hint.isHeaderRow:
586 return t.rowsHeaderConfigMap[rowIdx]
587 case hint.isFooterRow:
588 return t.rowsFooterConfigMap[rowIdx]
589 default:
590 return t.rowsConfigMap[rowIdx]
591 }
592 }
593
594 func (t *Table) getSeparatorColors(hint renderHint) text.Colors {
595 if hint.isHeaderRow {
596 return t.style.Color.Header
597 } else if hint.isFooterRow {
598 return t.style.Color.Footer
599 } else if hint.isAutoIndexColumn {
600 return t.style.Color.IndexColumn
601 } else if hint.rowNumber > 0 && hint.rowNumber%2 == 0 {
602 return t.style.Color.RowAlternate
603 }
604 return t.style.Color.Row
605 }
606
607 func (t *Table) getVAlign(colIdx int, hint renderHint) text.VAlign {
608 vAlign := text.VAlignDefault
609 if cfg, ok := t.columnConfigMap[colIdx]; ok {
610 if hint.isHeaderRow {
611 vAlign = cfg.VAlignHeader
612 } else if hint.isFooterRow {
613 vAlign = cfg.VAlignFooter
614 } else {
615 vAlign = cfg.VAlign
616 }
617 }
618 return vAlign
619 }
620
621 func (t *Table) hasHiddenColumns() bool {
622 for _, cc := range t.columnConfigMap {
623 if cc.Hidden {
624 return true
625 }
626 }
627 return false
628 }
629
630 func (t *Table) initForRender() {
631
632 t.Style()
633
634
635 t.initForRenderColumnConfigs()
636
637
638 t.initForRenderRows()
639
640
641 t.initForRenderColumnLengths()
642
643
644 t.initForRenderRowSeparator()
645
646
647 t.numLinesRendered = 0
648 }
649
650 func (t *Table) initForRenderColumnConfigs() {
651 t.columnConfigMap = map[int]ColumnConfig{}
652 for _, colCfg := range t.columnConfigs {
653
654
655 if colCfg.Number == 0 {
656 for _, row := range t.rowsHeaderRaw {
657 colCfg.Number = row.findColumnNumber(colCfg.Name)
658 if colCfg.Number > 0 {
659 break
660 }
661 }
662 }
663 if colCfg.Number > 0 {
664 t.columnConfigMap[colCfg.Number-1] = colCfg
665 }
666 }
667 }
668
669 func (t *Table) initForRenderColumnLengths() {
670 t.maxColumnLengths = make([]int, t.numColumns)
671 t.parseRowForMaxColumnLengths(t.rowsHeader)
672 t.parseRowForMaxColumnLengths(t.rows)
673 t.parseRowForMaxColumnLengths(t.rowsFooter)
674
675
676 for colIdx := range t.maxColumnLengths {
677 maxWidth := t.getColumnWidthMax(colIdx)
678 if maxWidth > 0 && t.maxColumnLengths[colIdx] > maxWidth {
679 t.maxColumnLengths[colIdx] = maxWidth
680 }
681 minWidth := t.getColumnWidthMin(colIdx)
682 if minWidth > 0 && t.maxColumnLengths[colIdx] < minWidth {
683 t.maxColumnLengths[colIdx] = minWidth
684 }
685 }
686 }
687
688 func (t *Table) hideColumns() map[int]int {
689 colIdxMap := make(map[int]int)
690 numColumns := 0
691 hideColumnsInRows := func(rows []rowStr) []rowStr {
692 var rsp []rowStr
693 for _, row := range rows {
694 var rowNew rowStr
695 for colIdx, col := range row {
696 cc := t.columnConfigMap[colIdx]
697 if !cc.Hidden {
698 rowNew = append(rowNew, col)
699 colIdxMap[colIdx] = len(rowNew) - 1
700 }
701 }
702 if len(rowNew) > numColumns {
703 numColumns = len(rowNew)
704 }
705 rsp = append(rsp, rowNew)
706 }
707 return rsp
708 }
709
710
711 t.rows = hideColumnsInRows(t.rows)
712 t.rowsFooter = hideColumnsInRows(t.rowsFooter)
713 t.rowsHeader = hideColumnsInRows(t.rowsHeader)
714
715
716 t.numColumns = numColumns
717
718 return colIdxMap
719 }
720
721 func (t *Table) initForRenderHideColumns() {
722 if !t.hasHiddenColumns() {
723 return
724 }
725 colIdxMap := t.hideColumns()
726
727
728 columnIsNonNumeric := make([]bool, t.numColumns)
729 for oldColIdx, nonNumeric := range t.columnIsNonNumeric {
730 if newColIdx, ok := colIdxMap[oldColIdx]; ok {
731 columnIsNonNumeric[newColIdx] = nonNumeric
732 }
733 }
734 t.columnIsNonNumeric = columnIsNonNumeric
735
736
737 columnConfigMap := make(map[int]ColumnConfig)
738 for oldColIdx, cc := range t.columnConfigMap {
739 if newColIdx, ok := colIdxMap[oldColIdx]; ok {
740 columnConfigMap[newColIdx] = cc
741 }
742 }
743 t.columnConfigMap = columnConfigMap
744 }
745
746 func (t *Table) initForRenderRows() {
747 t.reset()
748
749
750 t.autoIndexVIndexMaxLength = len(fmt.Sprint(len(t.rowsRaw)))
751
752
753 if t.rowPainter != nil {
754 t.rowsColors = make([]text.Colors, len(t.rowsRaw))
755 }
756 t.rows = t.initForRenderRowsStringify(t.rowsRaw, renderHint{})
757 t.rowsFooter = t.initForRenderRowsStringify(t.rowsFooterRaw, renderHint{isFooterRow: true})
758 t.rowsHeader = t.initForRenderRowsStringify(t.rowsHeaderRaw, renderHint{isHeaderRow: true})
759
760
761 t.initForRenderSortRows()
762
763
764 t.initForRenderSuppressColumns()
765
766
767 t.initForRenderHideColumns()
768 }
769
770 func (t *Table) initForRenderRowsStringify(rows []Row, hint renderHint) []rowStr {
771 rowsStr := make([]rowStr, len(rows))
772 for idx, row := range rows {
773 if t.rowPainter != nil && hint.isRegularRow() {
774 t.rowsColors[idx] = t.rowPainter(row)
775 }
776 rowsStr[idx] = t.analyzeAndStringify(row, hint)
777 }
778 return rowsStr
779 }
780
781 func (t *Table) initForRenderRowSeparator() {
782 t.maxRowLength = 0
783 if t.autoIndex {
784 t.maxRowLength += text.RuneWidthWithoutEscSequences(t.style.Box.PaddingLeft)
785 t.maxRowLength += len(fmt.Sprint(len(t.rows)))
786 t.maxRowLength += text.RuneWidthWithoutEscSequences(t.style.Box.PaddingRight)
787 if t.style.Options.SeparateColumns {
788 t.maxRowLength += text.RuneWidthWithoutEscSequences(t.style.Box.MiddleSeparator)
789 }
790 }
791 if t.style.Options.SeparateColumns {
792 t.maxRowLength += text.RuneWidthWithoutEscSequences(t.style.Box.MiddleSeparator) * (t.numColumns - 1)
793 }
794 t.rowSeparator = make(rowStr, t.numColumns)
795 for colIdx, maxColumnLength := range t.maxColumnLengths {
796 maxColumnLength += text.RuneWidthWithoutEscSequences(t.style.Box.PaddingLeft + t.style.Box.PaddingRight)
797 t.maxRowLength += maxColumnLength
798 t.rowSeparator[colIdx] = text.RepeatAndTrim(t.style.Box.MiddleHorizontal, maxColumnLength)
799 }
800 if t.style.Options.DrawBorder {
801 t.maxRowLength += text.RuneWidthWithoutEscSequences(t.style.Box.Left + t.style.Box.Right)
802 }
803 }
804
805 func (t *Table) initForRenderSortRows() {
806 if len(t.sortBy) == 0 {
807 return
808 }
809
810
811 sortedRowIndices := t.getSortedRowIndices()
812 sortedRows := make([]rowStr, len(t.rows))
813 for idx := range t.rows {
814 sortedRows[idx] = t.rows[sortedRowIndices[idx]]
815 }
816 t.rows = sortedRows
817
818
819 if len(t.rowsColors) > 0 {
820 sortedRowsColors := make([]text.Colors, len(t.rows))
821 for idx := range t.rows {
822 sortedRowsColors[idx] = t.rowsColors[sortedRowIndices[idx]]
823 }
824 t.rowsColors = sortedRowsColors
825 }
826 }
827
828 func (t *Table) initForRenderSuppressColumns() {
829 shouldSuppressColumn := func(colIdx int) bool {
830 for _, row := range t.rows {
831 if colIdx < len(row) && row[colIdx] != "" {
832 return false
833 }
834 }
835 return true
836 }
837
838 if t.suppressEmptyColumns {
839 for colIdx := 0; colIdx < t.numColumns; colIdx++ {
840 if shouldSuppressColumn(colIdx) {
841 cc := t.columnConfigMap[colIdx]
842 cc.Hidden = true
843 t.columnConfigMap[colIdx] = cc
844 }
845 }
846 }
847 }
848
849 func (t *Table) isIndexColumn(colIdx int, hint renderHint) bool {
850 return t.indexColumn == colIdx+1 || hint.isAutoIndexColumn
851 }
852
853 func (t *Table) parseRowForMaxColumnLengths(rows []rowStr) {
854 for _, row := range rows {
855 for colIdx, colStr := range row {
856 longestLineLen := text.LongestLineLen(colStr)
857 if longestLineLen > t.maxColumnLengths[colIdx] {
858 t.maxColumnLengths[colIdx] = longestLineLen
859 }
860 }
861 }
862 }
863
864 func (t *Table) render(out *strings.Builder) string {
865 outStr := out.String()
866 if t.outputMirror != nil && len(outStr) > 0 {
867 _, _ = t.outputMirror.Write([]byte(outStr))
868 _, _ = t.outputMirror.Write([]byte("\n"))
869 }
870 return outStr
871 }
872
873 func (t *Table) reset() {
874 t.autoIndexVIndexMaxLength = 0
875 t.columnIsNonNumeric = nil
876 t.maxColumnLengths = nil
877 t.maxRowLength = 0
878 t.numColumns = 0
879 t.rowsColors = nil
880 t.rowSeparator = nil
881 t.rows = nil
882 t.rowsFooter = nil
883 t.rowsHeader = nil
884 }
885
886 func (t *Table) shouldMergeCellsHorizontallyAbove(row rowStr, colIdx int, hint renderHint) bool {
887 if hint.isAutoIndexColumn || hint.isAutoIndexRow {
888 return false
889 }
890
891 rowConfig := t.getRowConfig(hint)
892 if hint.isSeparatorRow {
893 if hint.isHeaderRow && hint.rowNumber == 1 {
894 rowConfig = t.getRowConfig(hint)
895 row = t.getRow(hint.rowNumber-1, hint)
896 } else if hint.isFooterRow && hint.isFirstRow {
897 rowConfig = t.getRowConfig(renderHint{isLastRow: true, rowNumber: len(t.rows)})
898 row = t.getRow(len(t.rows)-1, renderHint{})
899 } else if hint.isFooterRow && hint.isBorderBottom {
900 row = t.getRow(len(t.rowsFooter)-1, renderHint{isFooterRow: true})
901 } else {
902 row = t.getRow(hint.rowNumber-1, hint)
903 }
904 }
905
906 if rowConfig.AutoMerge {
907 return row.areEqual(colIdx-1, colIdx)
908 }
909 return false
910 }
911
912 func (t *Table) shouldMergeCellsHorizontallyBelow(row rowStr, colIdx int, hint renderHint) bool {
913 if hint.isAutoIndexColumn || hint.isAutoIndexRow {
914 return false
915 }
916
917 var rowConfig RowConfig
918 if hint.isSeparatorRow {
919 if hint.isHeaderRow && hint.rowNumber == 0 {
920 rowConfig = t.getRowConfig(renderHint{isHeaderRow: true, rowNumber: 1})
921 row = t.getRow(0, hint)
922 } else if hint.isHeaderRow && hint.isLastRow {
923 rowConfig = t.getRowConfig(renderHint{rowNumber: 1})
924 row = t.getRow(0, renderHint{})
925 } else if hint.isHeaderRow {
926 rowConfig = t.getRowConfig(renderHint{isHeaderRow: true, rowNumber: hint.rowNumber + 1})
927 row = t.getRow(hint.rowNumber, hint)
928 } else if hint.isFooterRow && hint.rowNumber >= 0 {
929 rowConfig = t.getRowConfig(renderHint{isFooterRow: true, rowNumber: 1})
930 row = t.getRow(hint.rowNumber, renderHint{isFooterRow: true})
931 } else if hint.isRegularRow() {
932 rowConfig = t.getRowConfig(renderHint{rowNumber: hint.rowNumber + 1})
933 row = t.getRow(hint.rowNumber, renderHint{})
934 }
935 }
936
937 if rowConfig.AutoMerge {
938 return row.areEqual(colIdx-1, colIdx)
939 }
940 return false
941 }
942
943 func (t *Table) shouldMergeCellsVertically(colIdx int, hint renderHint) bool {
944 if t.columnConfigMap[colIdx].AutoMerge && colIdx < t.numColumns {
945 if hint.isSeparatorRow {
946 rowPrev := t.getRow(hint.rowNumber-1, hint)
947 rowNext := t.getRow(hint.rowNumber, hint)
948 if colIdx < len(rowPrev) && colIdx < len(rowNext) {
949 return rowPrev[colIdx] == rowNext[colIdx] || "" == rowNext[colIdx]
950 }
951 } else {
952 rowPrev := t.getRow(hint.rowNumber-2, hint)
953 rowCurr := t.getRow(hint.rowNumber-1, hint)
954 if colIdx < len(rowPrev) && colIdx < len(rowCurr) {
955 return rowPrev[colIdx] == rowCurr[colIdx] || "" == rowCurr[colIdx]
956 }
957 }
958 }
959 return false
960 }
961
962 func (t *Table) wrapRow(row rowStr) (int, rowStr) {
963 colMaxLines := 0
964 rowWrapped := make(rowStr, len(row))
965 for colIdx, colStr := range row {
966 widthEnforcer := t.columnConfigMap[colIdx].getWidthMaxEnforcer()
967 rowWrapped[colIdx] = widthEnforcer(colStr, t.maxColumnLengths[colIdx])
968 colNumLines := strings.Count(rowWrapped[colIdx], "\n") + 1
969 if colNumLines > colMaxLines {
970 colMaxLines = colNumLines
971 }
972 }
973 return colMaxLines, rowWrapped
974 }
975
976
977 type renderHint struct {
978 isAutoIndexColumn bool
979 isAutoIndexRow bool
980 isBorderBottom bool
981 isBorderTop bool
982 isFirstRow bool
983 isFooterRow bool
984 isHeaderRow bool
985 isLastLineOfRow bool
986 isLastRow bool
987 isSeparatorRow bool
988 rowLineNumber int
989 rowNumber int
990 }
991
992 func (h *renderHint) isRegularRow() bool {
993 return !h.isHeaderRow && !h.isFooterRow
994 }
995
996 func (h *renderHint) isRegularNonSeparatorRow() bool {
997 return !h.isHeaderRow && !h.isFooterRow && !h.isSeparatorRow
998 }
999
1000 func (h *renderHint) isHeaderOrFooterSeparator() bool {
1001 return h.isSeparatorRow && !h.isBorderBottom && !h.isBorderTop &&
1002 ((h.isHeaderRow && !h.isLastRow) || (h.isFooterRow && (!h.isFirstRow || h.rowNumber > 0)))
1003 }
1004
1005 func (h *renderHint) isLastLineOfLastRow() bool {
1006 return h.isLastLineOfRow && h.isLastRow
1007 }
1008
View as plain text