table.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663
  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. // Print line based on row width
  217. func (t Table) printLine(nl bool) {
  218. fmt.Fprint(t.out, t.pCenter)
  219. for i := 0; i < len(t.cs); i++ {
  220. v := t.cs[i]
  221. fmt.Fprintf(t.out, "%s%s%s%s",
  222. t.pRow,
  223. strings.Repeat(string(t.pRow), v),
  224. t.pRow,
  225. t.pCenter)
  226. }
  227. if nl {
  228. fmt.Fprint(t.out, t.newLine)
  229. }
  230. }
  231. // Print line based on row width with our without cell separator
  232. func (t Table) printLineOptionalCellSeparators(nl bool, displayCellSeparator []bool) {
  233. fmt.Fprint(t.out, t.pCenter)
  234. for i := 0; i < len(t.cs); i++ {
  235. v := t.cs[i]
  236. if i > len(displayCellSeparator) || displayCellSeparator[i] {
  237. // Display the cell separator
  238. fmt.Fprintf(t.out, "%s%s%s%s",
  239. t.pRow,
  240. strings.Repeat(string(t.pRow), v),
  241. t.pRow,
  242. t.pCenter)
  243. } else {
  244. // Don't display the cell separator for this cell
  245. fmt.Fprintf(t.out, "%s%s",
  246. strings.Repeat(" ", v+2),
  247. t.pCenter)
  248. }
  249. }
  250. if nl {
  251. fmt.Fprint(t.out, t.newLine)
  252. }
  253. }
  254. // Return the PadRight function if align is left, PadLeft if align is right,
  255. // and Pad by default
  256. func pad(align int) func(string, string, int) string {
  257. padFunc := Pad
  258. switch align {
  259. case ALIGN_LEFT:
  260. padFunc = PadRight
  261. case ALIGN_RIGHT:
  262. padFunc = PadLeft
  263. }
  264. return padFunc
  265. }
  266. // Print heading information
  267. func (t Table) printHeading() {
  268. // Check if headers is available
  269. if len(t.headers) < 1 {
  270. return
  271. }
  272. // Check if border is set
  273. // Replace with space if not set
  274. fmt.Fprint(t.out, ConditionString(t.borders.Left, t.pColumn, SPACE))
  275. // Identify last column
  276. end := len(t.cs) - 1
  277. // Get pad function
  278. padFunc := pad(t.hAlign)
  279. // Print Heading column
  280. for i := 0; i <= end; i++ {
  281. v := t.cs[i]
  282. h := t.headers[i]
  283. if t.autoFmt {
  284. h = Title(h)
  285. }
  286. pad := ConditionString((i == end && !t.borders.Left), SPACE, t.pColumn)
  287. fmt.Fprintf(t.out, " %s %s",
  288. padFunc(h, SPACE, v),
  289. pad)
  290. }
  291. // Next line
  292. fmt.Fprint(t.out, t.newLine)
  293. if t.hdrLine {
  294. t.printLine(true)
  295. }
  296. }
  297. // Print heading information
  298. func (t Table) printFooter() {
  299. // Check if headers is available
  300. if len(t.footers) < 1 {
  301. return
  302. }
  303. // Only print line if border is not set
  304. if !t.borders.Bottom {
  305. t.printLine(true)
  306. }
  307. // Check if border is set
  308. // Replace with space if not set
  309. fmt.Fprint(t.out, ConditionString(t.borders.Bottom, t.pColumn, SPACE))
  310. // Identify last column
  311. end := len(t.cs) - 1
  312. // Get pad function
  313. padFunc := pad(t.fAlign)
  314. // Print Heading column
  315. for i := 0; i <= end; i++ {
  316. v := t.cs[i]
  317. f := t.footers[i]
  318. if t.autoFmt {
  319. f = Title(f)
  320. }
  321. pad := ConditionString((i == end && !t.borders.Top), SPACE, t.pColumn)
  322. if len(t.footers[i]) == 0 {
  323. pad = SPACE
  324. }
  325. fmt.Fprintf(t.out, " %s %s",
  326. padFunc(f, SPACE, v),
  327. pad)
  328. }
  329. // Next line
  330. fmt.Fprint(t.out, t.newLine)
  331. //t.printLine(true)
  332. hasPrinted := false
  333. for i := 0; i <= end; i++ {
  334. v := t.cs[i]
  335. pad := t.pRow
  336. center := t.pCenter
  337. length := len(t.footers[i])
  338. if length > 0 {
  339. hasPrinted = true
  340. }
  341. // Set center to be space if length is 0
  342. if length == 0 && !t.borders.Right {
  343. center = SPACE
  344. }
  345. // Print first junction
  346. if i == 0 {
  347. fmt.Fprint(t.out, center)
  348. }
  349. // Pad With space of length is 0
  350. if length == 0 {
  351. pad = SPACE
  352. }
  353. // Ignore left space of it has printed before
  354. if hasPrinted || t.borders.Left {
  355. pad = t.pRow
  356. center = t.pCenter
  357. }
  358. // Change Center start position
  359. if center == SPACE {
  360. if i < end && len(t.footers[i+1]) != 0 {
  361. center = t.pCenter
  362. }
  363. }
  364. // Print the footer
  365. fmt.Fprintf(t.out, "%s%s%s%s",
  366. pad,
  367. strings.Repeat(string(pad), v),
  368. pad,
  369. center)
  370. }
  371. fmt.Fprint(t.out, t.newLine)
  372. }
  373. func (t Table) printRows() {
  374. for i, lines := range t.lines {
  375. t.printRow(lines, i)
  376. }
  377. }
  378. // Print Row Information
  379. // Adjust column alignment based on type
  380. func (t Table) printRow(columns [][]string, colKey int) {
  381. // Get Maximum Height
  382. max := t.rs[colKey]
  383. total := len(columns)
  384. // TODO Fix uneven col size
  385. // if total < t.colSize {
  386. // for n := t.colSize - total; n < t.colSize ; n++ {
  387. // columns = append(columns, []string{SPACE})
  388. // t.cs[n] = t.mW
  389. // }
  390. //}
  391. // Pad Each Height
  392. // pads := []int{}
  393. pads := []int{}
  394. for i, line := range columns {
  395. length := len(line)
  396. pad := max - length
  397. pads = append(pads, pad)
  398. for n := 0; n < pad; n++ {
  399. columns[i] = append(columns[i], " ")
  400. }
  401. }
  402. //fmt.Println(max, "\n")
  403. for x := 0; x < max; x++ {
  404. for y := 0; y < total; y++ {
  405. // Check if border is set
  406. fmt.Fprint(t.out, ConditionString((!t.borders.Left && y == 0), SPACE, t.pColumn))
  407. fmt.Fprintf(t.out, SPACE)
  408. str := columns[y][x]
  409. // This would print alignment
  410. // Default alignment would use multiple configuration
  411. switch t.align {
  412. case ALIGN_CENTER: //
  413. fmt.Fprintf(t.out, "%s", Pad(str, SPACE, t.cs[y]))
  414. case ALIGN_RIGHT:
  415. fmt.Fprintf(t.out, "%s", PadLeft(str, SPACE, t.cs[y]))
  416. case ALIGN_LEFT:
  417. fmt.Fprintf(t.out, "%s", PadRight(str, SPACE, t.cs[y]))
  418. default:
  419. if decimal.MatchString(strings.TrimSpace(str)) || percent.MatchString(strings.TrimSpace(str)) {
  420. fmt.Fprintf(t.out, "%s", PadLeft(str, SPACE, t.cs[y]))
  421. } else {
  422. fmt.Fprintf(t.out, "%s", PadRight(str, SPACE, t.cs[y]))
  423. // TODO Custom alignment per column
  424. //if max == 1 || pads[y] > 0 {
  425. // fmt.Fprintf(t.out, "%s", Pad(str, SPACE, t.cs[y]))
  426. //} else {
  427. // fmt.Fprintf(t.out, "%s", PadRight(str, SPACE, t.cs[y]))
  428. //}
  429. }
  430. }
  431. fmt.Fprintf(t.out, SPACE)
  432. }
  433. // Check if border is set
  434. // Replace with space if not set
  435. fmt.Fprint(t.out, ConditionString(t.borders.Left, t.pColumn, SPACE))
  436. fmt.Fprint(t.out, t.newLine)
  437. }
  438. if t.rowLine {
  439. t.printLine(true)
  440. }
  441. }
  442. // Print the rows of the table and merge the cells that are identical
  443. func (t Table) printRowsMergeCells() {
  444. var previousLine []string
  445. var displayCellBorder []bool
  446. var tmpWriter bytes.Buffer
  447. for i, lines := range t.lines {
  448. // We store the display of the current line in a tmp writer, as we need to know which border needs to be print above
  449. previousLine, displayCellBorder = t.printRowMergeCells(&tmpWriter, lines, i, previousLine)
  450. if i > 0 { //We don't need to print borders above first line
  451. if t.rowLine {
  452. t.printLineOptionalCellSeparators(true, displayCellBorder)
  453. }
  454. }
  455. tmpWriter.WriteTo(t.out)
  456. }
  457. //Print the end of the table
  458. if t.rowLine {
  459. t.printLine(true)
  460. }
  461. }
  462. // Print Row Information to a writer and merge identical cells.
  463. // Adjust column alignment based on type
  464. func (t Table) printRowMergeCells(writer io.Writer, columns [][]string, colKey int, previousLine []string) ([]string, []bool) {
  465. // Get Maximum Height
  466. max := t.rs[colKey]
  467. total := len(columns)
  468. // Pad Each Height
  469. pads := []int{}
  470. for i, line := range columns {
  471. length := len(line)
  472. pad := max - length
  473. pads = append(pads, pad)
  474. for n := 0; n < pad; n++ {
  475. columns[i] = append(columns[i], " ")
  476. }
  477. }
  478. var displayCellBorder []bool
  479. for x := 0; x < max; x++ {
  480. for y := 0; y < total; y++ {
  481. // Check if border is set
  482. fmt.Fprint(writer, ConditionString((!t.borders.Left && y == 0), SPACE, t.pColumn))
  483. fmt.Fprintf(writer, SPACE)
  484. str := columns[y][x]
  485. if t.autoMergeCells {
  486. //Store the full line to merge mutli-lines cells
  487. fullLine := strings.Join(columns[y], " ")
  488. if len(previousLine) > y && fullLine == previousLine[y] && fullLine != "" {
  489. // If this cell is identical to the one above but not empty, we don't display the border and keep the cell empty.
  490. displayCellBorder = append(displayCellBorder, false)
  491. str = ""
  492. } else {
  493. // First line or different content, keep the content and print the cell border
  494. displayCellBorder = append(displayCellBorder, true)
  495. }
  496. }
  497. // This would print alignment
  498. // Default alignment would use multiple configuration
  499. switch t.align {
  500. case ALIGN_CENTER: //
  501. fmt.Fprintf(writer, "%s", Pad(str, SPACE, t.cs[y]))
  502. case ALIGN_RIGHT:
  503. fmt.Fprintf(writer, "%s", PadLeft(str, SPACE, t.cs[y]))
  504. case ALIGN_LEFT:
  505. fmt.Fprintf(writer, "%s", PadRight(str, SPACE, t.cs[y]))
  506. default:
  507. if decimal.MatchString(strings.TrimSpace(str)) || percent.MatchString(strings.TrimSpace(str)) {
  508. fmt.Fprintf(writer, "%s", PadLeft(str, SPACE, t.cs[y]))
  509. } else {
  510. fmt.Fprintf(writer, "%s", PadRight(str, SPACE, t.cs[y]))
  511. }
  512. }
  513. fmt.Fprintf(writer, SPACE)
  514. }
  515. // Check if border is set
  516. // Replace with space if not set
  517. fmt.Fprint(writer, ConditionString(t.borders.Left, t.pColumn, SPACE))
  518. fmt.Fprint(writer, t.newLine)
  519. }
  520. //The new previous line is the current one
  521. previousLine = make([]string, total)
  522. for y := 0; y < total; y++ {
  523. previousLine[y] = strings.Join(columns[y], " ") //Store the full line for multi-lines cells
  524. }
  525. //Returns the newly added line and wether or not a border should be displayed above.
  526. return previousLine, displayCellBorder
  527. }
  528. func (t *Table) parseDimension(str string, colKey, rowKey int) []string {
  529. var (
  530. raw []string
  531. max int
  532. )
  533. w := DisplayWidth(str)
  534. // Calculate Width
  535. // Check if with is grater than maximum width
  536. if w > t.mW {
  537. w = t.mW
  538. }
  539. // Check if width exists
  540. v, ok := t.cs[colKey]
  541. if !ok || v < w || v == 0 {
  542. t.cs[colKey] = w
  543. }
  544. if rowKey == -1 {
  545. return raw
  546. }
  547. // Calculate Height
  548. if t.autoWrap {
  549. raw, _ = WrapString(str, t.cs[colKey])
  550. } else {
  551. raw = getLines(str)
  552. }
  553. for _, line := range raw {
  554. if w := DisplayWidth(line); w > max {
  555. max = w
  556. }
  557. }
  558. // Make sure the with is the same length as maximum word
  559. // Important for cases where the width is smaller than maxu word
  560. if max > t.cs[colKey] {
  561. t.cs[colKey] = max
  562. }
  563. h := len(raw)
  564. v, ok = t.rs[rowKey]
  565. if !ok || v < h || v == 0 {
  566. t.rs[rowKey] = h
  567. }
  568. //fmt.Printf("Raw %+v %d\n", raw, len(raw))
  569. return raw
  570. }