blob: 08ae0520844aab1735bf16e9dfed89964fba8ec2 [file] [log] [blame]
Naveen Sampath04696f72022-06-13 15:19:14 +05301// Copyright 2014 Oleku Konko All rights reserved.
2// Use of this source code is governed by a MIT
3// license that can be found in the LICENSE file.
4
5// This module is a Table Writer API for the Go Programming Language.
6// The protocols were written in pure Go and works on windows and unix systems
7
8// Create & Generate text based table
9package tablewriter
10
11import (
12 "bytes"
13 "errors"
14 "fmt"
15 "io"
16 "reflect"
17 "regexp"
18 "strings"
19)
20
21const (
22 MAX_ROW_WIDTH = 30
23)
24
25const (
26 CENTER = "+"
27 ROW = "-"
28 COLUMN = "|"
29 SPACE = " "
30 NEWLINE = "\n"
31)
32
33const (
34 ALIGN_DEFAULT = iota
35 ALIGN_CENTER
36 ALIGN_RIGHT
37 ALIGN_LEFT
38)
39
40var (
41 decimal = regexp.MustCompile(`^-?(?:\d{1,3}(?:,\d{3})*|\d+)(?:\.\d+)?$`)
42 percent = regexp.MustCompile(`^-?\d+\.?\d*$%$`)
43)
44
45type Border struct {
46 Left bool
47 Right bool
48 Top bool
49 Bottom bool
50}
51
52type Table struct {
53 separators []int
54 out io.Writer
55 rows [][]string
56 lines [][][]string
57 cs map[int]int
58 rs map[int]int
59 headers [][]string
60 footers [][]string
61 caption bool
62 captionText string
63 autoFmt bool
64 autoWrap bool
65 reflowText bool
66 mW int
67 pCenter string
68 pRow string
69 pColumn string
70 tColumn int
71 tRow int
72 hAlign int
73 fAlign int
74 align int
75 newLine string
76 rowLine bool
77 autoMergeCells bool
78 columnsToAutoMergeCells map[int]bool
79 noWhiteSpace bool
80 tablePadding string
81 hdrLine bool
82 borders Border
83 colSize int
84 headerParams []string
85 columnsParams []string
86 footerParams []string
87 columnsAlign []int
88}
89
90// Start New Table
91// Take io.Writer Directly
92func NewWriter(writer io.Writer) *Table {
93 t := &Table{
94 separators: []int{},
95 out: writer,
96 rows: [][]string{},
97 lines: [][][]string{},
98 cs: make(map[int]int),
99 rs: make(map[int]int),
100 headers: [][]string{},
101 footers: [][]string{},
102 caption: false,
103 captionText: "Table caption.",
104 autoFmt: true,
105 autoWrap: true,
106 reflowText: true,
107 mW: MAX_ROW_WIDTH,
108 pCenter: CENTER,
109 pRow: ROW,
110 pColumn: COLUMN,
111 tColumn: -1,
112 tRow: -1,
113 hAlign: ALIGN_DEFAULT,
114 fAlign: ALIGN_DEFAULT,
115 align: ALIGN_DEFAULT,
116 newLine: NEWLINE,
117 rowLine: false,
118 hdrLine: true,
119 borders: Border{Left: true, Right: true, Bottom: true, Top: true},
120 colSize: -1,
121 headerParams: []string{},
122 columnsParams: []string{},
123 footerParams: []string{},
124 columnsAlign: []int{}}
125 return t
126}
127
128func (t *Table) AddSeparator() {
129 t.separators = append(t.separators, len(t.lines))
130}
131
132// Render table output
133func (t *Table) Render() {
134 if t.borders.Top {
135 t.printLine(true)
136 }
137 t.printHeading()
138 if t.autoMergeCells {
139 t.printRowsMergeCells()
140 } else {
141 t.printRows()
142 }
143 if !t.rowLine && t.borders.Bottom {
144 t.printLine(true)
145 }
146 t.printFooter()
147
148 if t.caption {
149 t.printCaption()
150 }
151}
152
153const (
154 headerRowIdx = -1
155 footerRowIdx = -2
156)
157
158// Set table header
159func (t *Table) SetHeader(keys []string) {
160 t.colSize = len(keys)
161 for i, v := range keys {
162 lines := t.parseDimension(v, i, headerRowIdx)
163 t.headers = append(t.headers, lines)
164 }
165}
166
167// Set table Footer
168func (t *Table) SetFooter(keys []string) {
169 //t.colSize = len(keys)
170 for i, v := range keys {
171 lines := t.parseDimension(v, i, footerRowIdx)
172 t.footers = append(t.footers, lines)
173 }
174}
175
176// Set table Caption
177func (t *Table) SetCaption(caption bool, captionText ...string) {
178 t.caption = caption
179 if len(captionText) == 1 {
180 t.captionText = captionText[0]
181 }
182}
183
184// Turn header autoformatting on/off. Default is on (true).
185func (t *Table) SetAutoFormatHeaders(auto bool) {
186 t.autoFmt = auto
187}
188
189// Turn automatic multiline text adjustment on/off. Default is on (true).
190func (t *Table) SetAutoWrapText(auto bool) {
191 t.autoWrap = auto
192}
193
194// Turn automatic reflowing of multiline text when rewrapping. Default is on (true).
195func (t *Table) SetReflowDuringAutoWrap(auto bool) {
196 t.reflowText = auto
197}
198
199// Set the Default column width
200func (t *Table) SetColWidth(width int) {
201 t.mW = width
202}
203
204// Set the minimal width for a column
205func (t *Table) SetColMinWidth(column int, width int) {
206 t.cs[column] = width
207}
208
209// Set the Column Separator
210func (t *Table) SetColumnSeparator(sep string) {
211 t.pColumn = sep
212}
213
214// Set the Row Separator
215func (t *Table) SetRowSeparator(sep string) {
216 t.pRow = sep
217}
218
219// Set the center Separator
220func (t *Table) SetCenterSeparator(sep string) {
221 t.pCenter = sep
222}
223
224// Set Header Alignment
225func (t *Table) SetHeaderAlignment(hAlign int) {
226 t.hAlign = hAlign
227}
228
229// Set Footer Alignment
230func (t *Table) SetFooterAlignment(fAlign int) {
231 t.fAlign = fAlign
232}
233
234// Set Table Alignment
235func (t *Table) SetAlignment(align int) {
236 t.align = align
237}
238
239// Set No White Space
240func (t *Table) SetNoWhiteSpace(allow bool) {
241 t.noWhiteSpace = allow
242}
243
244// Set Table Padding
245func (t *Table) SetTablePadding(padding string) {
246 t.tablePadding = padding
247}
248
249func (t *Table) SetColumnAlignment(keys []int) {
250 for _, v := range keys {
251 switch v {
252 case ALIGN_CENTER:
253 break
254 case ALIGN_LEFT:
255 break
256 case ALIGN_RIGHT:
257 break
258 default:
259 v = ALIGN_DEFAULT
260 }
261 t.columnsAlign = append(t.columnsAlign, v)
262 }
263}
264
265// Set New Line
266func (t *Table) SetNewLine(nl string) {
267 t.newLine = nl
268}
269
270// Set Header Line
271// This would enable / disable a line after the header
272func (t *Table) SetHeaderLine(line bool) {
273 t.hdrLine = line
274}
275
276// Set Row Line
277// This would enable / disable a line on each row of the table
278func (t *Table) SetRowLine(line bool) {
279 t.rowLine = line
280}
281
282// Set Auto Merge Cells
283// This would enable / disable the merge of cells with identical values
284func (t *Table) SetAutoMergeCells(auto bool) {
285 t.autoMergeCells = auto
286}
287
288// Set Auto Merge Cells By Column Index
289// This would enable / disable the merge of cells with identical values for specific columns
290// If cols is empty, it is the same as `SetAutoMergeCells(true)`.
291func (t *Table) SetAutoMergeCellsByColumnIndex(cols []int) {
292 t.autoMergeCells = true
293
294 if len(cols) > 0 {
295 m := make(map[int]bool)
296 for _, col := range cols {
297 m[col] = true
298 }
299 t.columnsToAutoMergeCells = m
300 }
301}
302
303// Set Table Border
304// This would enable / disable line around the table
305func (t *Table) SetBorder(border bool) {
306 t.SetBorders(Border{border, border, border, border})
307}
308
309func (t *Table) SetBorders(border Border) {
310 t.borders = border
311}
312
313// SetStructs sets header and rows from slice of struct.
314// If something that is not a slice is passed, error will be returned.
315// The tag specified by "tablewriter" for the struct becomes the header.
316// If not specified or empty, the field name will be used.
317// The field of the first element of the slice is used as the header.
318// If the element implements fmt.Stringer, the result will be used.
319// And the slice contains nil, it will be skipped without rendering.
320func (t *Table) SetStructs(v interface{}) error {
321 if v == nil {
322 return errors.New("nil value")
323 }
324 vt := reflect.TypeOf(v)
325 vv := reflect.ValueOf(v)
326 switch vt.Kind() {
327 case reflect.Slice, reflect.Array:
328 if vv.Len() < 1 {
329 return errors.New("empty value")
330 }
331
332 // check first element to set header
333 first := vv.Index(0)
334 e := first.Type()
335 switch e.Kind() {
336 case reflect.Struct:
337 // OK
338 case reflect.Ptr:
339 if first.IsNil() {
340 return errors.New("the first element is nil")
341 }
342 e = first.Elem().Type()
343 if e.Kind() != reflect.Struct {
344 return fmt.Errorf("invalid kind %s", e.Kind())
345 }
346 default:
347 return fmt.Errorf("invalid kind %s", e.Kind())
348 }
349 n := e.NumField()
350 headers := make([]string, n)
351 for i := 0; i < n; i++ {
352 f := e.Field(i)
353 header := f.Tag.Get("tablewriter")
354 if header == "" {
355 header = f.Name
356 }
357 headers[i] = header
358 }
359 t.SetHeader(headers)
360
361 for i := 0; i < vv.Len(); i++ {
362 item := reflect.Indirect(vv.Index(i))
363 itemType := reflect.TypeOf(item)
364 switch itemType.Kind() {
365 case reflect.Struct:
366 // OK
367 default:
368 return fmt.Errorf("invalid item type %v", itemType.Kind())
369 }
370 if !item.IsValid() {
371 // skip rendering
372 continue
373 }
374 nf := item.NumField()
375 if n != nf {
376 return errors.New("invalid num of field")
377 }
378 rows := make([]string, nf)
379 for j := 0; j < nf; j++ {
380 f := reflect.Indirect(item.Field(j))
381 if f.Kind() == reflect.Ptr {
382 f = f.Elem()
383 }
384 if f.IsValid() {
385 if s, ok := f.Interface().(fmt.Stringer); ok {
386 rows[j] = s.String()
387 continue
388 }
389 rows[j] = fmt.Sprint(f)
390 } else {
391 rows[j] = "nil"
392 }
393 }
394 t.Append(rows)
395 }
396 default:
397 return fmt.Errorf("invalid type %T", v)
398 }
399 return nil
400}
401
402// Append row to table
403func (t *Table) Append(row []string) {
404 rowSize := len(t.headers)
405 if rowSize > t.colSize {
406 t.colSize = rowSize
407 }
408
409 n := len(t.lines)
410 line := [][]string{}
411 for i, v := range row {
412
413 // Detect string width
414 // Detect String height
415 // Break strings into words
416 out := t.parseDimension(v, i, n)
417
418 // Append broken words
419 line = append(line, out)
420 }
421 t.lines = append(t.lines, line)
422}
423
424// Append row to table with color attributes
425func (t *Table) Rich(row []string, colors []Colors) {
426 rowSize := len(t.headers)
427 if rowSize > t.colSize {
428 t.colSize = rowSize
429 }
430
431 n := len(t.lines)
432 line := [][]string{}
433 for i, v := range row {
434
435 // Detect string width
436 // Detect String height
437 // Break strings into words
438 out := t.parseDimension(v, i, n)
439
440 if len(colors) > i {
441 color := colors[i]
442 out[0] = format(out[0], color)
443 }
444
445 // Append broken words
446 line = append(line, out)
447 }
448 t.lines = append(t.lines, line)
449}
450
451// Allow Support for Bulk Append
452// Eliminates repeated for loops
453func (t *Table) AppendBulk(rows [][]string) {
454 for _, row := range rows {
455 t.Append(row)
456 }
457}
458
459// NumLines to get the number of lines
460func (t *Table) NumLines() int {
461 return len(t.lines)
462}
463
464// Clear rows
465func (t *Table) ClearRows() {
466 t.lines = [][][]string{}
467}
468
469// Clear footer
470func (t *Table) ClearFooter() {
471 t.footers = [][]string{}
472}
473
474// Center based on position and border.
475func (t *Table) center(i int) string {
476 if i == -1 && !t.borders.Left {
477 return t.pRow
478 }
479
480 if i == len(t.cs)-1 && !t.borders.Right {
481 return t.pRow
482 }
483
484 return t.pCenter
485}
486
487// Print line based on row width
488func (t *Table) printLine(nl bool) {
489 fmt.Fprint(t.out, t.center(-1))
490 for i := 0; i < len(t.cs); i++ {
491 v := t.cs[i]
492 fmt.Fprintf(t.out, "%s%s%s%s",
493 t.pRow,
494 strings.Repeat(string(t.pRow), v),
495 t.pRow,
496 t.center(i))
497 }
498 if nl {
499 fmt.Fprint(t.out, t.newLine)
500 }
501}
502
503// Print line based on row width with our without cell separator
504func (t *Table) printLineOptionalCellSeparators(nl bool, displayCellSeparator []bool) {
505 fmt.Fprint(t.out, t.pCenter)
506 for i := 0; i < len(t.cs); i++ {
507 v := t.cs[i]
508 if i > len(displayCellSeparator) || displayCellSeparator[i] {
509 // Display the cell separator
510 fmt.Fprintf(t.out, "%s%s%s%s",
511 t.pRow,
512 strings.Repeat(string(t.pRow), v),
513 t.pRow,
514 t.pCenter)
515 } else {
516 // Don't display the cell separator for this cell
517 fmt.Fprintf(t.out, "%s%s",
518 strings.Repeat(" ", v+2),
519 t.pCenter)
520 }
521 }
522 if nl {
523 fmt.Fprint(t.out, t.newLine)
524 }
525}
526
527// Return the PadRight function if align is left, PadLeft if align is right,
528// and Pad by default
529func pad(align int) func(string, string, int) string {
530 padFunc := Pad
531 switch align {
532 case ALIGN_LEFT:
533 padFunc = PadRight
534 case ALIGN_RIGHT:
535 padFunc = PadLeft
536 }
537 return padFunc
538}
539
540// Print heading information
541func (t *Table) printHeading() {
542 // Check if headers is available
543 if len(t.headers) < 1 {
544 return
545 }
546
547 // Identify last column
548 end := len(t.cs) - 1
549
550 // Get pad function
551 padFunc := pad(t.hAlign)
552
553 // Checking for ANSI escape sequences for header
554 is_esc_seq := false
555 if len(t.headerParams) > 0 {
556 is_esc_seq = true
557 }
558
559 // Maximum height.
560 max := t.rs[headerRowIdx]
561
562 // Print Heading
563 for x := 0; x < max; x++ {
564 // Check if border is set
565 // Replace with space if not set
566 if !t.noWhiteSpace {
567 fmt.Fprint(t.out, ConditionString(t.borders.Left, t.pColumn, SPACE))
568 }
569
570 for y := 0; y <= end; y++ {
571 v := t.cs[y]
572 h := ""
573
574 if y < len(t.headers) && x < len(t.headers[y]) {
575 h = t.headers[y][x]
576 }
577 if t.autoFmt {
578 h = Title(h)
579 }
580 pad := ConditionString((y == end && !t.borders.Left), SPACE, t.pColumn)
581 if t.noWhiteSpace {
582 pad = ConditionString((y == end && !t.borders.Left), SPACE, t.tablePadding)
583 }
584 if is_esc_seq {
585 if !t.noWhiteSpace {
586 fmt.Fprintf(t.out, " %s %s",
587 format(padFunc(h, SPACE, v),
588 t.headerParams[y]), pad)
589 } else {
590 fmt.Fprintf(t.out, "%s %s",
591 format(padFunc(h, SPACE, v),
592 t.headerParams[y]), pad)
593 }
594 } else {
595 if !t.noWhiteSpace {
596 fmt.Fprintf(t.out, " %s %s",
597 padFunc(h, SPACE, v),
598 pad)
599 } else {
600 // the spaces between breaks the kube formatting
601 fmt.Fprintf(t.out, "%s%s",
602 padFunc(h, SPACE, v),
603 pad)
604 }
605 }
606 }
607 // Next line
608 fmt.Fprint(t.out, t.newLine)
609 }
610 if t.hdrLine {
611 t.printLine(true)
612 }
613}
614
615// Print heading information
616func (t *Table) printFooter() {
617 // Check if headers is available
618 if len(t.footers) < 1 {
619 return
620 }
621
622 // Only print line if border is not set
623 if !t.borders.Bottom {
624 t.printLine(true)
625 }
626
627 // Identify last column
628 end := len(t.cs) - 1
629
630 // Get pad function
631 padFunc := pad(t.fAlign)
632
633 // Checking for ANSI escape sequences for header
634 is_esc_seq := false
635 if len(t.footerParams) > 0 {
636 is_esc_seq = true
637 }
638
639 // Maximum height.
640 max := t.rs[footerRowIdx]
641
642 // Print Footer
643 erasePad := make([]bool, len(t.footers))
644 for x := 0; x < max; x++ {
645 // Check if border is set
646 // Replace with space if not set
647 fmt.Fprint(t.out, ConditionString(t.borders.Bottom, t.pColumn, SPACE))
648
649 for y := 0; y <= end; y++ {
650 v := t.cs[y]
651 f := ""
652 if y < len(t.footers) && x < len(t.footers[y]) {
653 f = t.footers[y][x]
654 }
655 if t.autoFmt {
656 f = Title(f)
657 }
658 pad := ConditionString((y == end && !t.borders.Top), SPACE, t.pColumn)
659
660 if erasePad[y] || (x == 0 && len(f) == 0) {
661 pad = SPACE
662 erasePad[y] = true
663 }
664
665 if is_esc_seq {
666 fmt.Fprintf(t.out, " %s %s",
667 format(padFunc(f, SPACE, v),
668 t.footerParams[y]), pad)
669 } else {
670 fmt.Fprintf(t.out, " %s %s",
671 padFunc(f, SPACE, v),
672 pad)
673 }
674
675 //fmt.Fprintf(t.out, " %s %s",
676 // padFunc(f, SPACE, v),
677 // pad)
678 }
679 // Next line
680 fmt.Fprint(t.out, t.newLine)
681 //t.printLine(true)
682 }
683
684 hasPrinted := false
685
686 for i := 0; i <= end; i++ {
687 v := t.cs[i]
688 pad := t.pRow
689 center := t.pCenter
690 length := len(t.footers[i][0])
691
692 if length > 0 {
693 hasPrinted = true
694 }
695
696 // Set center to be space if length is 0
697 if length == 0 && !t.borders.Right {
698 center = SPACE
699 }
700
701 // Print first junction
702 if i == 0 {
703 if length > 0 && !t.borders.Left {
704 center = t.pRow
705 }
706 fmt.Fprint(t.out, center)
707 }
708
709 // Pad With space of length is 0
710 if length == 0 {
711 pad = SPACE
712 }
713 // Ignore left space as it has printed before
714 if hasPrinted || t.borders.Left {
715 pad = t.pRow
716 center = t.pCenter
717 }
718
719 // Change Center end position
720 if center != SPACE {
721 if i == end && !t.borders.Right {
722 center = t.pRow
723 }
724 }
725
726 // Change Center start position
727 if center == SPACE {
728 if i < end && len(t.footers[i+1][0]) != 0 {
729 if !t.borders.Left {
730 center = t.pRow
731 } else {
732 center = t.pCenter
733 }
734 }
735 }
736
737 // Print the footer
738 fmt.Fprintf(t.out, "%s%s%s%s",
739 pad,
740 strings.Repeat(string(pad), v),
741 pad,
742 center)
743
744 }
745
746 fmt.Fprint(t.out, t.newLine)
747}
748
749// Print caption text
750func (t Table) printCaption() {
751 width := t.getTableWidth()
752 paragraph, _ := WrapString(t.captionText, width)
753 for linecount := 0; linecount < len(paragraph); linecount++ {
754 fmt.Fprintln(t.out, paragraph[linecount])
755 }
756}
757
758// Calculate the total number of characters in a row
759func (t Table) getTableWidth() int {
760 var chars int
761 for _, v := range t.cs {
762 chars += v
763 }
764
765 // Add chars, spaces, seperators to calculate the total width of the table.
766 // ncols := t.colSize
767 // spaces := ncols * 2
768 // seps := ncols + 1
769
770 return (chars + (3 * t.colSize) + 2)
771}
772
773func (t Table) printRows() {
774 sepIdx := 0
775 for i, lines := range t.lines {
776 if sepIdx < len(t.separators) && i == t.separators[sepIdx] {
777 t.printLine(true)
778 if sepIdx < len(t.separators) {
779 sepIdx++
780 }
781 }
782 t.printRow(lines, i)
783 }
784}
785
786func (t *Table) fillAlignment(num int) {
787 if len(t.columnsAlign) < num {
788 t.columnsAlign = make([]int, num)
789 for i := range t.columnsAlign {
790 t.columnsAlign[i] = t.align
791 }
792 }
793}
794
795// Print Row Information
796// Adjust column alignment based on type
797
798func (t *Table) printRow(columns [][]string, rowIdx int) {
799 // Get Maximum Height
800 max := t.rs[rowIdx]
801 total := len(columns)
802
803 // TODO Fix uneven col size
804 // if total < t.colSize {
805 // for n := t.colSize - total; n < t.colSize ; n++ {
806 // columns = append(columns, []string{SPACE})
807 // t.cs[n] = t.mW
808 // }
809 //}
810
811 // Pad Each Height
812 pads := []int{}
813
814 // Checking for ANSI escape sequences for columns
815 is_esc_seq := false
816 if len(t.columnsParams) > 0 {
817 is_esc_seq = true
818 }
819 t.fillAlignment(total)
820
821 for i, line := range columns {
822 length := len(line)
823 pad := max - length
824 pads = append(pads, pad)
825 for n := 0; n < pad; n++ {
826 columns[i] = append(columns[i], " ")
827 }
828 }
829 //fmt.Println(max, "\n")
830 for x := 0; x < max; x++ {
831 for y := 0; y < total; y++ {
832
833 // Check if border is set
834 if !t.noWhiteSpace {
835 fmt.Fprint(t.out, ConditionString((!t.borders.Left && y == 0), SPACE, t.pColumn))
836 fmt.Fprintf(t.out, SPACE)
837 }
838
839 str := columns[y][x]
840
841 // Embedding escape sequence with column value
842 if is_esc_seq {
843 str = format(str, t.columnsParams[y])
844 }
845
846 // This would print alignment
847 // Default alignment would use multiple configuration
848 switch t.columnsAlign[y] {
849 case ALIGN_CENTER: //
850 fmt.Fprintf(t.out, "%s", Pad(str, SPACE, t.cs[y]))
851 case ALIGN_RIGHT:
852 fmt.Fprintf(t.out, "%s", PadLeft(str, SPACE, t.cs[y]))
853 case ALIGN_LEFT:
854 fmt.Fprintf(t.out, "%s", PadRight(str, SPACE, t.cs[y]))
855 default:
856 if decimal.MatchString(strings.TrimSpace(str)) || percent.MatchString(strings.TrimSpace(str)) {
857 fmt.Fprintf(t.out, "%s", PadLeft(str, SPACE, t.cs[y]))
858 } else {
859 fmt.Fprintf(t.out, "%s", PadRight(str, SPACE, t.cs[y]))
860
861 // TODO Custom alignment per column
862 //if max == 1 || pads[y] > 0 {
863 // fmt.Fprintf(t.out, "%s", Pad(str, SPACE, t.cs[y]))
864 //} else {
865 // fmt.Fprintf(t.out, "%s", PadRight(str, SPACE, t.cs[y]))
866 //}
867
868 }
869 }
870 if !t.noWhiteSpace {
871 fmt.Fprintf(t.out, SPACE)
872 } else {
873 fmt.Fprintf(t.out, t.tablePadding)
874 }
875 }
876 // Check if border is set
877 // Replace with space if not set
878 if !t.noWhiteSpace {
879 fmt.Fprint(t.out, ConditionString(t.borders.Left, t.pColumn, SPACE))
880 }
881 fmt.Fprint(t.out, t.newLine)
882 }
883
884 if t.rowLine {
885 t.printLine(true)
886 }
887}
888
889// Print the rows of the table and merge the cells that are identical
890func (t *Table) printRowsMergeCells() {
891 var previousLine []string
892 var displayCellBorder []bool
893 var tmpWriter bytes.Buffer
894
895 sepIdx := 0
896 for i, lines := range t.lines {
897 // We store the display of the current line in a tmp writer, as we need to know which border needs to be print above
898 previousLine, displayCellBorder = t.printRowMergeCells(&tmpWriter, lines, i, previousLine)
899 if i > 0 { // We don't need to print borders above first line
900 if sepIdx < len(t.separators) && i == t.separators[sepIdx] {
901 t.printLine(true)
902 if sepIdx < len(t.separators) {
903 sepIdx++
904 }
905 }
906 if t.rowLine {
907 t.printLineOptionalCellSeparators(true, displayCellBorder)
908 }
909 }
910 tmpWriter.WriteTo(t.out)
911 }
912 //Print the end of the table
913 if t.rowLine {
914 t.printLine(true)
915 }
916}
917
918// Print Row Information to a writer and merge identical cells.
919// Adjust column alignment based on type
920
921func (t *Table) printRowMergeCells(writer io.Writer, columns [][]string, rowIdx int, previousLine []string) ([]string, []bool) {
922 // Get Maximum Height
923 max := t.rs[rowIdx]
924 total := len(columns)
925
926 // Pad Each Height
927 pads := []int{}
928
929 // Checking for ANSI escape sequences for columns
930 is_esc_seq := false
931 if len(t.columnsParams) > 0 {
932 is_esc_seq = true
933 }
934 for i, line := range columns {
935 length := len(line)
936 pad := max - length
937 pads = append(pads, pad)
938 for n := 0; n < pad; n++ {
939 columns[i] = append(columns[i], " ")
940 }
941 }
942
943 var displayCellBorder []bool
944 t.fillAlignment(total)
945 for x := 0; x < max; x++ {
946 for y := 0; y < total; y++ {
947
948 // Check if border is set
949 fmt.Fprint(writer, ConditionString((!t.borders.Left && y == 0), SPACE, t.pColumn))
950
951 fmt.Fprintf(writer, SPACE)
952
953 str := columns[y][x]
954
955 // Embedding escape sequence with column value
956 if is_esc_seq {
957 str = format(str, t.columnsParams[y])
958 }
959
960 if t.autoMergeCells {
961 // Skip separator lines
962 for _, i := range t.separators {
963 if i == rowIdx {
964 continue
965 }
966 }
967
968 var mergeCell bool
969 if t.columnsToAutoMergeCells != nil {
970 // Check to see if the column index is in columnsToAutoMergeCells.
971 if t.columnsToAutoMergeCells[y] {
972 mergeCell = true
973 }
974 } else {
975 // columnsToAutoMergeCells was not set.
976 mergeCell = true
977 }
978 //Store the full line to merge mutli-lines cells
979 fullLine := strings.TrimRight(strings.Join(columns[y], " "), " ")
980 if len(previousLine) > y && fullLine == previousLine[y] && fullLine != "" && mergeCell {
981 // If this cell is identical to the one above but not empty, we don't display the border and keep the cell empty.
982 displayCellBorder = append(displayCellBorder, false)
983 str = ""
984 } else {
985 // First line or different content, keep the content and print the cell border
986 displayCellBorder = append(displayCellBorder, true)
987 }
988 }
989
990 // This would print alignment
991 // Default alignment would use multiple configuration
992 switch t.columnsAlign[y] {
993 case ALIGN_CENTER: //
994 fmt.Fprintf(writer, "%s", Pad(str, SPACE, t.cs[y]))
995 case ALIGN_RIGHT:
996 fmt.Fprintf(writer, "%s", PadLeft(str, SPACE, t.cs[y]))
997 case ALIGN_LEFT:
998 fmt.Fprintf(writer, "%s", PadRight(str, SPACE, t.cs[y]))
999 default:
1000 if decimal.MatchString(strings.TrimSpace(str)) || percent.MatchString(strings.TrimSpace(str)) {
1001 fmt.Fprintf(writer, "%s", PadLeft(str, SPACE, t.cs[y]))
1002 } else {
1003 fmt.Fprintf(writer, "%s", PadRight(str, SPACE, t.cs[y]))
1004 }
1005 }
1006 fmt.Fprintf(writer, SPACE)
1007 }
1008 // Check if border is set
1009 // Replace with space if not set
1010 fmt.Fprint(writer, ConditionString(t.borders.Left, t.pColumn, SPACE))
1011 fmt.Fprint(writer, t.newLine)
1012 }
1013
1014 //The new previous line is the current one
1015 previousLine = make([]string, total)
1016 for y := 0; y < total; y++ {
1017 previousLine[y] = strings.TrimRight(strings.Join(columns[y], " "), " ") //Store the full line for multi-lines cells
1018 }
1019 //Returns the newly added line and wether or not a border should be displayed above.
1020 return previousLine, displayCellBorder
1021}
1022
1023func (t *Table) parseDimension(str string, colKey, rowKey int) []string {
1024 var (
1025 raw []string
1026 maxWidth int
1027 )
1028
1029 raw = getLines(str)
1030 maxWidth = 0
1031 for _, line := range raw {
1032 if w := DisplayWidth(line); w > maxWidth {
1033 maxWidth = w
1034 }
1035 }
1036
1037 // If wrapping, ensure that all paragraphs in the cell fit in the
1038 // specified width.
1039 if t.autoWrap {
1040 // If there's a maximum allowed width for wrapping, use that.
1041 if maxWidth > t.mW {
1042 maxWidth = t.mW
1043 }
1044
1045 // In the process of doing so, we need to recompute maxWidth. This
1046 // is because perhaps a word in the cell is longer than the
1047 // allowed maximum width in t.mW.
1048 newMaxWidth := maxWidth
1049 newRaw := make([]string, 0, len(raw))
1050
1051 if t.reflowText {
1052 // Make a single paragraph of everything.
1053 raw = []string{strings.Join(raw, " ")}
1054 }
1055 for i, para := range raw {
1056 paraLines, _ := WrapString(para, maxWidth)
1057 for _, line := range paraLines {
1058 if w := DisplayWidth(line); w > newMaxWidth {
1059 newMaxWidth = w
1060 }
1061 }
1062 if i > 0 {
1063 newRaw = append(newRaw, " ")
1064 }
1065 newRaw = append(newRaw, paraLines...)
1066 }
1067 raw = newRaw
1068 maxWidth = newMaxWidth
1069 }
1070
1071 // Store the new known maximum width.
1072 v, ok := t.cs[colKey]
1073 if !ok || v < maxWidth || v == 0 {
1074 t.cs[colKey] = maxWidth
1075 }
1076
1077 // Remember the number of lines for the row printer.
1078 h := len(raw)
1079 v, ok = t.rs[rowKey]
1080
1081 if !ok || v < h || v == 0 {
1082 t.rs[rowKey] = h
1083 }
1084 //fmt.Printf("Raw %+v %d\n", raw, len(raw))
1085 return raw
1086}