table.go 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  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. "fmt"
  10. "io"
  11. "regexp"
  12. "strings"
  13. )
  14. const (
  15. MAX_ROW_WIDTH = 30
  16. )
  17. const (
  18. CENTRE = "+"
  19. ROW = "-"
  20. COLUMN = "|"
  21. SPACE = " "
  22. )
  23. const (
  24. ALIGN_DEFAULT = iota
  25. ALIGN_CENTRE
  26. ALIGN_RIGHT
  27. ALIGN_LEFT
  28. )
  29. var (
  30. decimal = regexp.MustCompile(`^\d*\.?\d*$`)
  31. percent = regexp.MustCompile(`^\d*\.?\d*$%$`)
  32. )
  33. type Table struct {
  34. out io.Writer
  35. rows [][]string
  36. lines [][][]string
  37. cs map[int]int
  38. rs map[int]int
  39. headers []string
  40. footers []string
  41. autoFmt bool
  42. autoWrap bool
  43. mW int
  44. pCenter string
  45. pRow string
  46. pColumn string
  47. tColumn int
  48. tRow int
  49. align int
  50. rowLine bool
  51. hdrLine bool
  52. border bool
  53. colSize int
  54. }
  55. // Start New Table
  56. // Take io.Writer Directly
  57. func NewWriter(writer io.Writer) *Table {
  58. t := &Table{
  59. out: writer,
  60. rows: [][]string{},
  61. lines: [][][]string{},
  62. cs: make(map[int]int),
  63. rs: make(map[int]int),
  64. headers: []string{},
  65. footers: []string{},
  66. autoFmt: true,
  67. autoWrap: true,
  68. mW: MAX_ROW_WIDTH,
  69. pCenter: CENTRE,
  70. pRow: ROW,
  71. pColumn: COLUMN,
  72. tColumn: -1,
  73. tRow: -1,
  74. align: ALIGN_DEFAULT,
  75. rowLine: false,
  76. hdrLine: true,
  77. border: true,
  78. colSize: -1}
  79. return t
  80. }
  81. // Render table output
  82. func (t Table) Render() {
  83. if t.border {
  84. t.printLine(true)
  85. }
  86. t.printHeading()
  87. t.printRows()
  88. if !t.rowLine && t.border {
  89. t.printLine(true)
  90. }
  91. t.printFooter()
  92. }
  93. // Set table header
  94. func (t *Table) SetHeader(keys []string) {
  95. t.colSize = len(keys)
  96. for i, v := range keys {
  97. t.parseDimension(v, i, -1)
  98. t.headers = append(t.headers, v)
  99. }
  100. }
  101. // Set table Footer
  102. func (t *Table) SetFooter(keys []string) {
  103. //t.colSize = len(keys)
  104. for i, v := range keys {
  105. t.parseDimension(v, i, -1)
  106. t.footers = append(t.footers, v)
  107. }
  108. }
  109. // Turn header autoformatting on/off. Default is on (true).
  110. func (t *Table) SetAutoFormatHeaders(auto bool) {
  111. t.autoFmt = auto
  112. }
  113. // Turn automatic multiline text adjustment on/off. Default is on (true).
  114. func (t *Table) SetAutoWrapText(auto bool) {
  115. t.autoWrap = auto
  116. }
  117. // Set the Default column width
  118. func (t *Table) SetColWidth(width int) {
  119. t.mW = width
  120. }
  121. // Set the Column Separator
  122. func (t *Table) SetColumnSeparator(sep string) {
  123. t.pColumn = sep
  124. }
  125. // Set the Row Separator
  126. func (t *Table) SetRowSeparator(sep string) {
  127. t.pRow = sep
  128. }
  129. // Set the center Separator
  130. func (t *Table) SetCenterSeparator(sep string) {
  131. t.pCenter = sep
  132. }
  133. // Set Table Alignment
  134. func (t *Table) SetAlignment(align int) {
  135. t.align = align
  136. }
  137. // Set Header Line
  138. // This would enable / disable a line after the header
  139. func (t *Table) SetHeaderLine(line bool) {
  140. t.hdrLine = line
  141. }
  142. // Set Row Line
  143. // This would enable / disable a line on each row of the table
  144. func (t *Table) SetRowLine(line bool) {
  145. t.rowLine = line
  146. }
  147. // Set Table Border
  148. // This would enable / disable line around the table
  149. func (t *Table) SetBorder(border bool) {
  150. t.border = border
  151. }
  152. // Append row to table
  153. func (t *Table) Append(row []string) {
  154. rowSize := len(t.headers)
  155. if rowSize > t.colSize {
  156. t.colSize = rowSize
  157. }
  158. n := len(t.lines)
  159. line := [][]string{}
  160. for i, v := range row {
  161. // Detect string width
  162. // Detect String height
  163. // Break strings into words
  164. out := t.parseDimension(v, i, n)
  165. // Append broken words
  166. line = append(line, out)
  167. }
  168. t.lines = append(t.lines, line)
  169. }
  170. // Allow Support for Bulk Append
  171. // Eliminates repeated for loops
  172. func (t *Table) AppendBulk(rows [][]string) {
  173. for _, row := range rows {
  174. t.Append(row)
  175. }
  176. }
  177. // Print line based on row width
  178. func (t Table) printLine(nl bool) {
  179. fmt.Fprint(t.out, t.pCenter)
  180. for i := 0; i < len(t.cs); i++ {
  181. v := t.cs[i]
  182. fmt.Fprintf(t.out, "%s%s%s%s",
  183. t.pRow,
  184. strings.Repeat(string(t.pRow), v),
  185. t.pRow,
  186. t.pCenter)
  187. }
  188. if nl {
  189. fmt.Fprintln(t.out)
  190. }
  191. }
  192. // Print heading information
  193. func (t Table) printHeading() {
  194. // Check if headers is available
  195. if len(t.headers) < 1 {
  196. return
  197. }
  198. // Check if border is set
  199. // Replace with space if not set
  200. fmt.Fprint(t.out, ConditionString(t.border, t.pColumn, SPACE))
  201. // Identify last column
  202. end := len(t.cs) - 1
  203. // Print Heading column
  204. for i := 0; i <= end; i++ {
  205. v := t.cs[i]
  206. h := t.headers[i]
  207. if t.autoFmt {
  208. h = Title(h)
  209. }
  210. pad := ConditionString((i == end && !t.border), SPACE, t.pColumn)
  211. fmt.Fprintf(t.out, " %s %s",
  212. Pad(h, SPACE, v),
  213. pad)
  214. }
  215. // Next line
  216. fmt.Fprintln(t.out)
  217. if t.hdrLine {
  218. t.printLine(true)
  219. }
  220. }
  221. // Print heading information
  222. func (t Table) printFooter() {
  223. // Check if headers is available
  224. if len(t.footers) < 1 {
  225. return
  226. }
  227. // Only print line if border is not set
  228. if !t.border {
  229. t.printLine(true)
  230. }
  231. // Check if border is set
  232. // Replace with space if not set
  233. fmt.Fprint(t.out, ConditionString(t.border, t.pColumn, SPACE))
  234. // Identify last column
  235. end := len(t.cs) - 1
  236. // Print Heading column
  237. for i := 0; i <= end; i++ {
  238. v := t.cs[i]
  239. f := t.footers[i]
  240. if t.autoFmt {
  241. f = Title(f)
  242. }
  243. pad := ConditionString((i == end && !t.border), SPACE, t.pColumn)
  244. if len(t.footers[i]) == 0 {
  245. pad = SPACE
  246. }
  247. fmt.Fprintf(t.out, " %s %s",
  248. Pad(f, SPACE, v),
  249. pad)
  250. }
  251. // Next line
  252. fmt.Fprintln(t.out)
  253. //t.printLine(true)
  254. hasPrinted := false
  255. for i := 0; i <= end; i++ {
  256. v := t.cs[i]
  257. pad := t.pRow
  258. center := t.pCenter
  259. length := len(t.footers[i])
  260. if length > 0 {
  261. hasPrinted = true
  262. }
  263. // Set center to be space if length is 0
  264. if length == 0 && !t.border {
  265. center = SPACE
  266. }
  267. // Print first junction
  268. if i == 0 {
  269. fmt.Fprint(t.out, center)
  270. }
  271. // Pad With space of length is 0
  272. if length == 0 {
  273. pad = SPACE
  274. }
  275. // Ignore left space of it has printed before
  276. if hasPrinted || t.border {
  277. pad = t.pRow
  278. center = t.pCenter
  279. }
  280. // Change Center start position
  281. if center == SPACE {
  282. if i < end && len(t.footers[i+1]) != 0 {
  283. center = t.pCenter
  284. }
  285. }
  286. // Print the footer
  287. fmt.Fprintf(t.out, "%s%s%s%s",
  288. pad,
  289. strings.Repeat(string(pad), v),
  290. pad,
  291. center)
  292. }
  293. fmt.Fprintln(t.out)
  294. }
  295. func (t Table) printRows() {
  296. for i, lines := range t.lines {
  297. t.printRow(lines, i)
  298. }
  299. }
  300. // Print Row Information
  301. // Adjust column alignment based on type
  302. func (t Table) printRow(columns [][]string, colKey int) {
  303. // Get Maximum Height
  304. max := t.rs[colKey]
  305. total := len(columns)
  306. // TODO Fix uneven col size
  307. // if total < t.colSize {
  308. // for n := t.colSize - total; n < t.colSize ; n++ {
  309. // columns = append(columns, []string{SPACE})
  310. // t.cs[n] = t.mW
  311. // }
  312. //}
  313. // Pad Each Height
  314. // pads := []int{}
  315. pads := []int{}
  316. for i, line := range columns {
  317. length := len(line)
  318. pad := max - length
  319. pads = append(pads, pad)
  320. for n := 0; n < pad; n++ {
  321. columns[i] = append(columns[i], " ")
  322. }
  323. }
  324. //fmt.Println(max, "\n")
  325. for x := 0; x < max; x++ {
  326. for y := 0; y < total; y++ {
  327. // Check if border is set
  328. fmt.Fprint(t.out, ConditionString((!t.border && y == 0), SPACE, t.pColumn))
  329. fmt.Fprintf(t.out, SPACE)
  330. str := columns[y][x]
  331. // This would print alignment
  332. // Default alignment would use multiple configuration
  333. switch t.align {
  334. case ALIGN_CENTRE: //
  335. fmt.Fprintf(t.out, "%s", Pad(str, SPACE, t.cs[y]))
  336. case ALIGN_RIGHT:
  337. fmt.Fprintf(t.out, "%s", PadLeft(str, SPACE, t.cs[y]))
  338. case ALIGN_LEFT:
  339. fmt.Fprintf(t.out, "%s", PadRight(str, SPACE, t.cs[y]))
  340. default:
  341. if decimal.MatchString(strings.TrimSpace(str)) || percent.MatchString(strings.TrimSpace(str)) {
  342. fmt.Fprintf(t.out, "%s", PadLeft(str, SPACE, t.cs[y]))
  343. } else {
  344. fmt.Fprintf(t.out, "%s", PadRight(str, SPACE, t.cs[y]))
  345. // TODO Custom alignment per column
  346. //if max == 1 || pads[y] > 0 {
  347. // fmt.Fprintf(t.out, "%s", Pad(str, SPACE, t.cs[y]))
  348. //} else {
  349. // fmt.Fprintf(t.out, "%s", PadRight(str, SPACE, t.cs[y]))
  350. //}
  351. }
  352. }
  353. fmt.Fprintf(t.out, SPACE)
  354. }
  355. // Check if border is set
  356. // Replace with space if not set
  357. fmt.Fprint(t.out, ConditionString(t.border, t.pColumn, SPACE))
  358. fmt.Fprintln(t.out)
  359. }
  360. if t.rowLine {
  361. t.printLine(true)
  362. }
  363. }
  364. func (t *Table) parseDimension(str string, colKey, rowKey int) []string {
  365. var (
  366. raw []string
  367. max int
  368. )
  369. w := DisplayWidth(str)
  370. // Calculate Width
  371. // Check if with is grater than maximum width
  372. if w > t.mW {
  373. w = t.mW
  374. }
  375. // Check if width exists
  376. v, ok := t.cs[colKey]
  377. if !ok || v < w || v == 0 {
  378. t.cs[colKey] = w
  379. }
  380. if rowKey == -1 {
  381. return raw
  382. }
  383. // Calculate Height
  384. if t.autoWrap {
  385. raw, _ = WrapString(str, t.cs[colKey])
  386. } else {
  387. raw = getLines(str)
  388. }
  389. for _, line := range raw {
  390. if w := DisplayWidth(line); w > max {
  391. max = w
  392. }
  393. }
  394. // Make sure the with is the same length as maximum word
  395. // Important for cases where the width is smaller than maxu word
  396. if max > t.cs[colKey] {
  397. t.cs[colKey] = max
  398. }
  399. h := len(raw)
  400. v, ok = t.rs[rowKey]
  401. if !ok || v < h || v == 0 {
  402. t.rs[rowKey] = h
  403. }
  404. //fmt.Printf("Raw %+v %d\n", raw, len(raw))
  405. return raw
  406. }