table.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668
  1. // 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. // This module is a Table Writer API for the Go Programming Language.
  5. // The protocols were written in pure Go and works on windows and unix systems
  6. // Create & Generate text based table
  7. package tablewriter
  8. import (
  9. "bytes"
  10. "fmt"
  11. "io"
  12. "regexp"
  13. "strings"
  14. )
  15. const (
  16. MAX_ROW_WIDTH = 30
  17. )
  18. const (
  19. CENTER = "+"
  20. ROW = "-"
  21. COLUMN = "|"
  22. SPACE = " "
  23. NEWLINE = "\n"
  24. )
  25. const (
  26. ALIGN_DEFAULT = iota
  27. ALIGN_CENTER
  28. ALIGN_RIGHT
  29. ALIGN_LEFT
  30. )
  31. var (
  32. decimal = regexp.MustCompile(`^-*\d*\.?\d*$`)
  33. percent = regexp.MustCompile(`^-*\d*\.?\d*$%$`)
  34. )
  35. type Border struct {
  36. Left bool
  37. Right bool
  38. Top bool
  39. Bottom bool
  40. }
  41. type Table struct {
  42. out io.Writer
  43. rows [][]string
  44. lines [][][]string
  45. cs map[int]int
  46. rs map[int]int
  47. headers []string
  48. footers []string
  49. autoFmt bool
  50. autoWrap bool
  51. mW int
  52. pCenter string
  53. pRow string
  54. pColumn string
  55. tColumn int
  56. tRow int
  57. hAlign int
  58. fAlign int
  59. align int
  60. newLine string
  61. rowLine bool
  62. autoMergeCells bool
  63. hdrLine bool
  64. borders Border
  65. colSize int
  66. }
  67. // Start New Table
  68. // Take io.Writer Directly
  69. func NewWriter(writer io.Writer) *Table {
  70. t := &Table{
  71. out: writer,
  72. rows: [][]string{},
  73. lines: [][][]string{},
  74. cs: make(map[int]int),
  75. rs: make(map[int]int),
  76. headers: []string{},
  77. footers: []string{},
  78. autoFmt: true,
  79. autoWrap: true,
  80. mW: MAX_ROW_WIDTH,
  81. pCenter: CENTER,
  82. pRow: ROW,
  83. pColumn: COLUMN,
  84. tColumn: -1,
  85. tRow: -1,
  86. hAlign: ALIGN_DEFAULT,
  87. fAlign: ALIGN_DEFAULT,
  88. align: ALIGN_DEFAULT,
  89. newLine: NEWLINE,
  90. rowLine: false,
  91. hdrLine: true,
  92. borders: Border{Left: true, Right: true, Bottom: true, Top: true},
  93. colSize: -1}
  94. return t
  95. }
  96. // Render table output
  97. func (t *Table) Render() {
  98. if t.borders.Top {
  99. t.printLine(true)
  100. }
  101. t.printHeading()
  102. if t.autoMergeCells {
  103. t.printRowsMergeCells()
  104. } else {
  105. t.printRows()
  106. }
  107. if !t.rowLine && t.borders.Bottom {
  108. t.printLine(true)
  109. }
  110. t.printFooter()
  111. }
  112. // Set table header
  113. func (t *Table) SetHeader(keys []string) {
  114. t.colSize = len(keys)
  115. for i, v := range keys {
  116. t.parseDimension(v, i, -1)
  117. t.headers = append(t.headers, v)
  118. }
  119. }
  120. // Set table Footer
  121. func (t *Table) SetFooter(keys []string) {
  122. //t.colSize = len(keys)
  123. for i, v := range keys {
  124. t.parseDimension(v, i, -1)
  125. t.footers = append(t.footers, v)
  126. }
  127. }
  128. // Turn header autoformatting on/off. Default is on (true).
  129. func (t *Table) SetAutoFormatHeaders(auto bool) {
  130. t.autoFmt = auto
  131. }
  132. // Turn automatic multiline text adjustment on/off. Default is on (true).
  133. func (t *Table) SetAutoWrapText(auto bool) {
  134. t.autoWrap = auto
  135. }
  136. // Set the Default column width
  137. func (t *Table) SetColWidth(width int) {
  138. t.mW = width
  139. }
  140. // Set the Column Separator
  141. func (t *Table) SetColumnSeparator(sep string) {
  142. t.pColumn = sep
  143. }
  144. // Set the Row Separator
  145. func (t *Table) SetRowSeparator(sep string) {
  146. t.pRow = sep
  147. }
  148. // Set the center Separator
  149. func (t *Table) SetCenterSeparator(sep string) {
  150. t.pCenter = sep
  151. }
  152. // Set Header Alignment
  153. func (t *Table) SetHeaderAlignment(hAlign int) {
  154. t.hAlign = hAlign
  155. }
  156. // Set Footer Alignment
  157. func (t *Table) SetFooterAlignment(fAlign int) {
  158. t.fAlign = fAlign
  159. }
  160. // Set Table Alignment
  161. func (t *Table) SetAlignment(align int) {
  162. t.align = align
  163. }
  164. // Set New Line
  165. func (t *Table) SetNewLine(nl string) {
  166. t.newLine = nl
  167. }
  168. // Set Header Line
  169. // This would enable / disable a line after the header
  170. func (t *Table) SetHeaderLine(line bool) {
  171. t.hdrLine = line
  172. }
  173. // Set Row Line
  174. // This would enable / disable a line on each row of the table
  175. func (t *Table) SetRowLine(line bool) {
  176. t.rowLine = line
  177. }
  178. // Set Auto Merge Cells
  179. // This would enable / disable the merge of cells with identical values
  180. func (t *Table) SetAutoMergeCells(auto bool) {
  181. t.autoMergeCells = auto
  182. }
  183. // Set Table Border
  184. // This would enable / disable line around the table
  185. func (t *Table) SetBorder(border bool) {
  186. t.SetBorders(Border{border, border, border, border})
  187. }
  188. func (t *Table) SetBorders(border Border) {
  189. t.borders = border
  190. }
  191. // Append row to table
  192. func (t *Table) Append(row []string) {
  193. rowSize := len(t.headers)
  194. if rowSize > t.colSize {
  195. t.colSize = rowSize
  196. }
  197. n := len(t.lines)
  198. line := [][]string{}
  199. for i, v := range row {
  200. // Detect string width
  201. // Detect String height
  202. // Break strings into words
  203. out := t.parseDimension(v, i, n)
  204. // Append broken words
  205. line = append(line, out)
  206. }
  207. t.lines = append(t.lines, line)
  208. }
  209. // Allow Support for Bulk Append
  210. // Eliminates repeated for loops
  211. func (t *Table) AppendBulk(rows [][]string) {
  212. for _, row := range rows {
  213. t.Append(row)
  214. }
  215. }
  216. // Clear rows
  217. func (t *Table) ClearRows() {
  218. t.lines = [][][]string{}
  219. }
  220. // Clear footer
  221. func (t *Table) ClearFooter() {
  222. t.footers = []string{}
  223. }
  224. // Print line based on row width
  225. func (t *Table) printLine(nl bool) {
  226. fmt.Fprint(t.out, t.pCenter)
  227. for i := 0; i < len(t.cs); i++ {
  228. v := t.cs[i]
  229. fmt.Fprintf(t.out, "%s%s%s%s",
  230. t.pRow,
  231. strings.Repeat(string(t.pRow), v),
  232. t.pRow,
  233. t.pCenter)
  234. }
  235. if nl {
  236. fmt.Fprint(t.out, t.newLine)
  237. }
  238. }
  239. // Print line based on row width with our without cell separator
  240. func (t *Table) printLineOptionalCellSeparators(nl bool, displayCellSeparator []bool) {
  241. fmt.Fprint(t.out, t.pCenter)
  242. for i := 0; i < len(t.cs); i++ {
  243. v := t.cs[i]
  244. if i > len(displayCellSeparator) || displayCellSeparator[i] {
  245. // Display the cell separator
  246. fmt.Fprintf(t.out, "%s%s%s%s",
  247. t.pRow,
  248. strings.Repeat(string(t.pRow), v),
  249. t.pRow,
  250. t.pCenter)
  251. } else {
  252. // Don't display the cell separator for this cell
  253. fmt.Fprintf(t.out, "%s%s",
  254. strings.Repeat(" ", v+2),
  255. t.pCenter)
  256. }
  257. }
  258. if nl {
  259. fmt.Fprint(t.out, t.newLine)
  260. }
  261. }
  262. // Return the PadRight function if align is left, PadLeft if align is right,
  263. // and Pad by default
  264. func pad(align int) func(string, string, int) string {
  265. padFunc := Pad
  266. switch align {
  267. case ALIGN_LEFT:
  268. padFunc = PadRight
  269. case ALIGN_RIGHT:
  270. padFunc = PadLeft
  271. }
  272. return padFunc
  273. }
  274. // Print heading information
  275. func (t *Table) printHeading() {
  276. // Check if headers is available
  277. if len(t.headers) < 1 {
  278. return
  279. }
  280. // Check if border is set
  281. // Replace with space if not set
  282. fmt.Fprint(t.out, ConditionString(t.borders.Left, t.pColumn, SPACE))
  283. // Identify last column
  284. end := len(t.cs) - 1
  285. // Get pad function
  286. padFunc := pad(t.hAlign)
  287. // Print Heading column
  288. for i := 0; i <= end; i++ {
  289. v := t.cs[i]
  290. h := t.headers[i]
  291. if t.autoFmt {
  292. h = Title(h)
  293. }
  294. pad := ConditionString((i == end && !t.borders.Left), SPACE, t.pColumn)
  295. fmt.Fprintf(t.out, " %s %s",
  296. padFunc(h, SPACE, v),
  297. pad)
  298. }
  299. // Next line
  300. fmt.Fprint(t.out, t.newLine)
  301. if t.hdrLine {
  302. t.printLine(true)
  303. }
  304. }
  305. // Print heading information
  306. func (t *Table) printFooter() {
  307. // Check if headers is available
  308. if len(t.footers) < 1 {
  309. return
  310. }
  311. // Only print line if border is not set
  312. if !t.borders.Bottom {
  313. t.printLine(true)
  314. }
  315. // Check if border is set
  316. // Replace with space if not set
  317. fmt.Fprint(t.out, ConditionString(t.borders.Bottom, t.pColumn, SPACE))
  318. // Identify last column
  319. end := len(t.cs) - 1
  320. // Get pad function
  321. padFunc := pad(t.fAlign)
  322. // Print Heading column
  323. for i := 0; i <= end; i++ {
  324. v := t.cs[i]
  325. f := t.footers[i]
  326. if t.autoFmt {
  327. f = Title(f)
  328. }
  329. pad := ConditionString((i == end && !t.borders.Top), SPACE, t.pColumn)
  330. if len(t.footers[i]) == 0 {
  331. pad = SPACE
  332. }
  333. fmt.Fprintf(t.out, " %s %s",
  334. padFunc(f, SPACE, v),
  335. pad)
  336. }
  337. // Next line
  338. fmt.Fprint(t.out, t.newLine)
  339. //t.printLine(true)
  340. hasPrinted := false
  341. for i := 0; i <= end; i++ {
  342. v := t.cs[i]
  343. pad := t.pRow
  344. center := t.pCenter
  345. length := len(t.footers[i])
  346. if length > 0 {
  347. hasPrinted = true
  348. }
  349. // Set center to be space if length is 0
  350. if length == 0 && !t.borders.Right {
  351. center = SPACE
  352. }
  353. // Print first junction
  354. if i == 0 {
  355. fmt.Fprint(t.out, center)
  356. }
  357. // Pad With space of length is 0
  358. if length == 0 {
  359. pad = SPACE
  360. }
  361. // Ignore left space of it has printed before
  362. if hasPrinted || t.borders.Left {
  363. pad = t.pRow
  364. center = t.pCenter
  365. }
  366. // Change Center start position
  367. if center == SPACE {
  368. if i < end && len(t.footers[i+1]) != 0 {
  369. center = t.pCenter
  370. }
  371. }
  372. // Print the footer
  373. fmt.Fprintf(t.out, "%s%s%s%s",
  374. pad,
  375. strings.Repeat(string(pad), v),
  376. pad,
  377. center)
  378. }
  379. fmt.Fprint(t.out, t.newLine)
  380. }
  381. func (t *Table) printRows() {
  382. for i, lines := range t.lines {
  383. t.printRow(lines, i)
  384. }
  385. }
  386. // Print Row Information
  387. // Adjust column alignment based on type
  388. func (t *Table) printRow(columns [][]string, colKey int) {
  389. // Get Maximum Height
  390. max := t.rs[colKey]
  391. total := len(columns)
  392. // TODO Fix uneven col size
  393. // if total < t.colSize {
  394. // for n := t.colSize - total; n < t.colSize ; n++ {
  395. // columns = append(columns, []string{SPACE})
  396. // t.cs[n] = t.mW
  397. // }
  398. //}
  399. // Pad Each Height
  400. // pads := []int{}
  401. pads := []int{}
  402. for i, line := range columns {
  403. length := len(line)
  404. pad := max - length
  405. pads = append(pads, pad)
  406. for n := 0; n < pad; n++ {
  407. columns[i] = append(columns[i], " ")
  408. }
  409. }
  410. //fmt.Println(max, "\n")
  411. for x := 0; x < max; x++ {
  412. for y := 0; y < total; y++ {
  413. // Check if border is set
  414. fmt.Fprint(t.out, ConditionString((!t.borders.Left && y == 0), SPACE, t.pColumn))
  415. fmt.Fprintf(t.out, SPACE)
  416. str := columns[y][x]
  417. // This would print alignment
  418. // Default alignment would use multiple configuration
  419. switch t.align {
  420. case ALIGN_CENTER: //
  421. fmt.Fprintf(t.out, "%s", Pad(str, SPACE, t.cs[y]))
  422. case ALIGN_RIGHT:
  423. fmt.Fprintf(t.out, "%s", PadLeft(str, SPACE, t.cs[y]))
  424. case ALIGN_LEFT:
  425. fmt.Fprintf(t.out, "%s", PadRight(str, SPACE, t.cs[y]))
  426. default:
  427. if decimal.MatchString(strings.TrimSpace(str)) || percent.MatchString(strings.TrimSpace(str)) {
  428. fmt.Fprintf(t.out, "%s", PadLeft(str, SPACE, t.cs[y]))
  429. } else {
  430. fmt.Fprintf(t.out, "%s", PadRight(str, SPACE, t.cs[y]))
  431. // TODO Custom alignment per column
  432. //if max == 1 || pads[y] > 0 {
  433. // fmt.Fprintf(t.out, "%s", Pad(str, SPACE, t.cs[y]))
  434. //} else {
  435. // fmt.Fprintf(t.out, "%s", PadRight(str, SPACE, t.cs[y]))
  436. //}
  437. }
  438. }
  439. fmt.Fprintf(t.out, SPACE)
  440. }
  441. // Check if border is set
  442. // Replace with space if not set
  443. fmt.Fprint(t.out, ConditionString(t.borders.Left, t.pColumn, SPACE))
  444. fmt.Fprint(t.out, t.newLine)
  445. }
  446. if t.rowLine {
  447. t.printLine(true)
  448. }
  449. }
  450. // Print the rows of the table and merge the cells that are identical
  451. func (t *Table) printRowsMergeCells() {
  452. var previousLine []string
  453. var displayCellBorder []bool
  454. var tmpWriter bytes.Buffer
  455. for i, lines := range t.lines {
  456. // We store the display of the current line in a tmp writer, as we need to know which border needs to be print above
  457. previousLine, displayCellBorder = t.printRowMergeCells(&tmpWriter, lines, i, previousLine)
  458. if i > 0 { //We don't need to print borders above first line
  459. if t.rowLine {
  460. t.printLineOptionalCellSeparators(true, displayCellBorder)
  461. }
  462. }
  463. tmpWriter.WriteTo(t.out)
  464. }
  465. //Print the end of the table
  466. if t.rowLine {
  467. t.printLine(true)
  468. }
  469. }
  470. // Print Row Information to a writer and merge identical cells.
  471. // Adjust column alignment based on type
  472. func (t *Table) printRowMergeCells(writer io.Writer, columns [][]string, colKey int, previousLine []string) ([]string, []bool) {
  473. // Get Maximum Height
  474. max := t.rs[colKey]
  475. total := len(columns)
  476. // Pad Each Height
  477. pads := []int{}
  478. for i, line := range columns {
  479. length := len(line)
  480. pad := max - length
  481. pads = append(pads, pad)
  482. for n := 0; n < pad; n++ {
  483. columns[i] = append(columns[i], " ")
  484. }
  485. }
  486. var displayCellBorder []bool
  487. for x := 0; x < max; x++ {
  488. for y := 0; y < total; y++ {
  489. // Check if border is set
  490. fmt.Fprint(writer, ConditionString((!t.borders.Left && y == 0), SPACE, t.pColumn))
  491. fmt.Fprintf(writer, SPACE)
  492. str := columns[y][x]
  493. if t.autoMergeCells {
  494. //Store the full line to merge mutli-lines cells
  495. fullLine := strings.Join(columns[y], " ")
  496. if len(previousLine) > y && fullLine == previousLine[y] && fullLine != "" {
  497. // If this cell is identical to the one above but not empty, we don't display the border and keep the cell empty.
  498. displayCellBorder = append(displayCellBorder, false)
  499. str = ""
  500. } else {
  501. // First line or different content, keep the content and print the cell border
  502. displayCellBorder = append(displayCellBorder, true)
  503. }
  504. }
  505. // This would print alignment
  506. // Default alignment would use multiple configuration
  507. switch t.align {
  508. case ALIGN_CENTER: //
  509. fmt.Fprintf(writer, "%s", Pad(str, SPACE, t.cs[y]))
  510. case ALIGN_RIGHT:
  511. fmt.Fprintf(writer, "%s", PadLeft(str, SPACE, t.cs[y]))
  512. case ALIGN_LEFT:
  513. fmt.Fprintf(writer, "%s", PadRight(str, SPACE, t.cs[y]))
  514. default:
  515. if decimal.MatchString(strings.TrimSpace(str)) || percent.MatchString(strings.TrimSpace(str)) {
  516. fmt.Fprintf(writer, "%s", PadLeft(str, SPACE, t.cs[y]))
  517. } else {
  518. fmt.Fprintf(writer, "%s", PadRight(str, SPACE, t.cs[y]))
  519. }
  520. }
  521. fmt.Fprintf(writer, SPACE)
  522. }
  523. // Check if border is set
  524. // Replace with space if not set
  525. fmt.Fprint(writer, ConditionString(t.borders.Left, t.pColumn, SPACE))
  526. fmt.Fprint(writer, t.newLine)
  527. }
  528. //The new previous line is the current one
  529. previousLine = make([]string, total)
  530. for y := 0; y < total; y++ {
  531. previousLine[y] = strings.Join(columns[y], " ") //Store the full line for multi-lines cells
  532. }
  533. //Returns the newly added line and wether or not a border should be displayed above.
  534. return previousLine, displayCellBorder
  535. }
  536. func (t *Table) parseDimension(str string, colKey, rowKey int) []string {
  537. var (
  538. raw []string
  539. max int
  540. )
  541. w := DisplayWidth(str)
  542. // Calculate Width
  543. // Check if with is grater than maximum width
  544. if w > t.mW {
  545. w = t.mW
  546. }
  547. // Check if width exists
  548. v, ok := t.cs[colKey]
  549. if !ok || v < w || v == 0 {
  550. t.cs[colKey] = w
  551. }
  552. if rowKey == -1 {
  553. return raw
  554. }
  555. // Calculate Height
  556. if t.autoWrap {
  557. raw, _ = WrapString(str, t.cs[colKey])
  558. } else {
  559. raw = getLines(str)
  560. }
  561. for _, line := range raw {
  562. if w := DisplayWidth(line); w > max {
  563. max = w
  564. }
  565. }
  566. // Make sure the with is the same length as maximum word
  567. // Important for cases where the width is smaller than maxu word
  568. if max > t.cs[colKey] {
  569. t.cs[colKey] = max
  570. }
  571. h := len(raw)
  572. v, ok = t.rs[rowKey]
  573. if !ok || v < h || v == 0 {
  574. t.rs[rowKey] = h
  575. }
  576. //fmt.Printf("Raw %+v %d\n", raw, len(raw))
  577. return raw
  578. }