tabulate.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520
  1. package gotabulate
  2. import "fmt"
  3. import "bytes"
  4. import "github.com/mattn/go-runewidth"
  5. import "unicode/utf8"
  6. import "math"
  7. // Basic Structure of TableFormat
  8. type TableFormat struct {
  9. LineTop Line
  10. LineBelowHeader Line
  11. LineBetweenRows Line
  12. LineBottom Line
  13. HeaderRow Row
  14. DataRow Row
  15. TitleRow Row
  16. Padding int
  17. HeaderHide bool
  18. FitScreen bool
  19. }
  20. // Represents a Line
  21. type Line struct {
  22. begin string
  23. hline string
  24. sep string
  25. end string
  26. }
  27. // Represents a Row
  28. type Row struct {
  29. begin string
  30. sep string
  31. end string
  32. }
  33. // Table Formats that are available to the user
  34. // The user can define his own format, just by addind an entry to this map
  35. // and calling it with Render function e.g t.Render("customFormat")
  36. var TableFormats = map[string]TableFormat{
  37. "simple": TableFormat{
  38. LineTop: Line{"", "-", " ", ""},
  39. LineBelowHeader: Line{"", "-", " ", ""},
  40. LineBottom: Line{"", "-", " ", ""},
  41. HeaderRow: Row{"", " ", ""},
  42. DataRow: Row{"", " ", ""},
  43. TitleRow: Row{"", " ", ""},
  44. Padding: 1,
  45. },
  46. "plain": TableFormat{
  47. HeaderRow: Row{"", " ", ""},
  48. DataRow: Row{"", " ", ""},
  49. TitleRow: Row{"", " ", ""},
  50. Padding: 1,
  51. },
  52. "grid": TableFormat{
  53. LineTop: Line{"+", "-", "+", "+"},
  54. LineBelowHeader: Line{"+", "=", "+", "+"},
  55. LineBetweenRows: Line{"+", "-", "+", "+"},
  56. LineBottom: Line{"+", "-", "+", "+"},
  57. HeaderRow: Row{"|", "|", "|"},
  58. DataRow: Row{"|", "|", "|"},
  59. TitleRow: Row{"|", " ", "|"},
  60. Padding: 1,
  61. },
  62. }
  63. // Minimum padding that will be applied
  64. var MIN_PADDING = 5
  65. // Main Tabulate structure
  66. type Tabulate struct {
  67. Data []*TabulateRow
  68. Title string
  69. TitleAlign string
  70. Headers []string
  71. FloatFormat byte
  72. TableFormat TableFormat
  73. Align string
  74. EmptyVar string
  75. HideLines []string
  76. MaxSize int
  77. WrapStrings bool
  78. WrapDelimiter rune
  79. SplitConcat string
  80. }
  81. // Represents normalized tabulate Row
  82. type TabulateRow struct {
  83. Elements []string
  84. Continuos bool
  85. }
  86. type writeBuffer struct {
  87. Buffer bytes.Buffer
  88. }
  89. func createBuffer() *writeBuffer {
  90. return &writeBuffer{}
  91. }
  92. func (b *writeBuffer) Write(str string, count int) *writeBuffer {
  93. for i := 0; i < count; i++ {
  94. b.Buffer.WriteString(str)
  95. }
  96. return b
  97. }
  98. func (b *writeBuffer) String() string {
  99. return b.Buffer.String()
  100. }
  101. // Add padding to each cell
  102. func (t *Tabulate) padRow(arr []string, padding int) []string {
  103. if len(arr) < 1 {
  104. return arr
  105. }
  106. padded := make([]string, len(arr))
  107. for index, el := range arr {
  108. b := createBuffer()
  109. b.Write(" ", padding)
  110. b.Write(el, 1)
  111. b.Write(" ", padding)
  112. padded[index] = b.String()
  113. }
  114. return padded
  115. }
  116. // Align right (Add padding left)
  117. func (t *Tabulate) padLeft(width int, str string) string {
  118. b := createBuffer()
  119. b.Write(" ", (width - runewidth.StringWidth(str)))
  120. b.Write(str, 1)
  121. return b.String()
  122. }
  123. // Align Left (Add padding right)
  124. func (t *Tabulate) padRight(width int, str string) string {
  125. b := createBuffer()
  126. b.Write(str, 1)
  127. b.Write(" ", (width - runewidth.StringWidth(str)))
  128. return b.String()
  129. }
  130. // Center the element in the cell
  131. func (t *Tabulate) padCenter(width int, str string) string {
  132. b := createBuffer()
  133. padding := int(math.Ceil(float64((width - runewidth.StringWidth(str))) / 2.0))
  134. b.Write(" ", padding)
  135. b.Write(str, 1)
  136. b.Write(" ", (width - runewidth.StringWidth(b.String())))
  137. return b.String()
  138. }
  139. // Build Line based on padded_widths from t.GetWidths()
  140. func (t *Tabulate) buildLine(padded_widths []int, padding []int, l Line) string {
  141. cells := make([]string, len(padded_widths))
  142. for i, _ := range cells {
  143. b := createBuffer()
  144. b.Write(l.hline, padding[i]+MIN_PADDING)
  145. cells[i] = b.String()
  146. }
  147. var buffer bytes.Buffer
  148. buffer.WriteString(l.begin)
  149. // Print contents
  150. for i := 0; i < len(cells); i++ {
  151. buffer.WriteString(cells[i])
  152. if i != len(cells)-1 {
  153. buffer.WriteString(l.sep)
  154. }
  155. }
  156. buffer.WriteString(l.end)
  157. return buffer.String()
  158. }
  159. // Build Row based on padded_widths from t.GetWidths()
  160. func (t *Tabulate) buildRow(elements []string, padded_widths []int, paddings []int, d Row) string {
  161. var buffer bytes.Buffer
  162. buffer.WriteString(d.begin)
  163. padFunc := t.getAlignFunc()
  164. // Print contents
  165. for i := 0; i < len(padded_widths); i++ {
  166. output := ""
  167. if len(elements) <= i || (len(elements) > i && elements[i] == " nil ") {
  168. output = padFunc(padded_widths[i], t.EmptyVar)
  169. } else if len(elements) > i {
  170. output = padFunc(padded_widths[i], elements[i])
  171. }
  172. buffer.WriteString(output)
  173. if i != len(padded_widths)-1 {
  174. buffer.WriteString(d.sep)
  175. }
  176. }
  177. buffer.WriteString(d.end)
  178. return buffer.String()
  179. }
  180. //SetWrapDelimiter assigns the character ina string that the rednderer
  181. //will attempt to split strings on when a cell must be wrapped
  182. func (t *Tabulate) SetWrapDelimiter(r rune) {
  183. t.WrapDelimiter = r
  184. }
  185. //SetSplitConcat assigns the character that will be used when a WrapDelimiter is
  186. //set but the renderer cannot abide by the desired split. This may happen when
  187. //the WrapDelimiter is a space ' ' but a single word is longer than the width of a cell
  188. func (t *Tabulate) SetSplitConcat(r string) {
  189. t.SplitConcat = r
  190. }
  191. // Render the data table
  192. func (t *Tabulate) Render(format ...interface{}) string {
  193. var lines []string
  194. // If headers are set use them, otherwise pop the first row
  195. if len(t.Headers) < 1 && len(t.Data) > 1 {
  196. t.Headers, t.Data = t.Data[0].Elements, t.Data[1:]
  197. }
  198. // Use the format that was passed as parameter, otherwise
  199. // use the format defined in the struct
  200. if len(format) > 0 {
  201. t.TableFormat = TableFormats[format[0].(string)]
  202. }
  203. // If Wrap Strings is set to True,then break up the string to multiple cells
  204. if t.WrapStrings {
  205. t.Data = t.wrapCellData()
  206. }
  207. // Check if Data is present
  208. if len(t.Data) < 1 {
  209. return ""
  210. }
  211. if len(t.Headers) < len(t.Data[0].Elements) {
  212. diff := len(t.Data[0].Elements) - len(t.Headers)
  213. padded_header := make([]string, diff)
  214. for _, e := range t.Headers {
  215. padded_header = append(padded_header, e)
  216. }
  217. t.Headers = padded_header
  218. }
  219. // Get Column widths for all columns
  220. cols := t.getWidths(t.Headers, t.Data)
  221. padded_widths := make([]int, len(cols))
  222. for i, _ := range padded_widths {
  223. padded_widths[i] = cols[i] + MIN_PADDING*t.TableFormat.Padding
  224. }
  225. // Calculate total width of the table
  226. totalWidth := len(t.TableFormat.DataRow.sep) * (len(cols) - 1) // Include all but the final separator
  227. for _, w := range padded_widths {
  228. totalWidth += w
  229. }
  230. // Start appending lines
  231. if len(t.Title) > 0 {
  232. if !inSlice("aboveTitle", t.HideLines) {
  233. lines = append(lines, t.buildLine(padded_widths, cols, t.TableFormat.LineTop))
  234. }
  235. savedAlign := t.Align
  236. if len(t.TitleAlign) > 0 {
  237. t.SetAlign(t.TitleAlign) // Temporary replace alignment with the title alignment
  238. }
  239. lines = append(lines, t.buildRow([]string{t.Title}, []int{totalWidth}, nil, t.TableFormat.TitleRow))
  240. t.SetAlign(savedAlign)
  241. }
  242. // Append top line if not hidden
  243. if !inSlice("top", t.HideLines) {
  244. lines = append(lines, t.buildLine(padded_widths, cols, t.TableFormat.LineTop))
  245. }
  246. // Add Header
  247. lines = append(lines, t.buildRow(t.padRow(t.Headers, t.TableFormat.Padding), padded_widths, cols, t.TableFormat.HeaderRow))
  248. // Add Line Below Header if not hidden
  249. if !inSlice("belowheader", t.HideLines) {
  250. lines = append(lines, t.buildLine(padded_widths, cols, t.TableFormat.LineBelowHeader))
  251. }
  252. // Add Data Rows
  253. for index, element := range t.Data {
  254. lines = append(lines, t.buildRow(t.padRow(element.Elements, t.TableFormat.Padding), padded_widths, cols, t.TableFormat.DataRow))
  255. if index < len(t.Data)-1 {
  256. if element.Continuos != true && !inSlice("betweenLine", t.HideLines) {
  257. lines = append(lines, t.buildLine(padded_widths, cols, t.TableFormat.LineBetweenRows))
  258. }
  259. }
  260. }
  261. if !inSlice("bottomLine", t.HideLines) {
  262. lines = append(lines, t.buildLine(padded_widths, cols, t.TableFormat.LineBottom))
  263. }
  264. // Join lines
  265. var buffer bytes.Buffer
  266. for _, line := range lines {
  267. buffer.WriteString(line + "\n")
  268. }
  269. return buffer.String()
  270. }
  271. // Calculate the max column width for each element
  272. func (t *Tabulate) getWidths(headers []string, data []*TabulateRow) []int {
  273. widths := make([]int, len(headers))
  274. current_max := len(t.EmptyVar)
  275. for i := 0; i < len(headers); i++ {
  276. current_max = runewidth.StringWidth(headers[i])
  277. for _, item := range data {
  278. if len(item.Elements) > i && len(widths) > i {
  279. element := item.Elements[i]
  280. strLength := runewidth.StringWidth(element)
  281. if strLength > current_max {
  282. widths[i] = strLength
  283. current_max = strLength
  284. } else {
  285. widths[i] = current_max
  286. }
  287. }
  288. }
  289. }
  290. return widths
  291. }
  292. // SetTitle sets the title of the table can also accept a second string to define an alignment for the title
  293. func (t *Tabulate) SetTitle(title ...string) *Tabulate {
  294. t.Title = title[0]
  295. if len(title) > 1 {
  296. t.TitleAlign = title[1]
  297. }
  298. return t
  299. }
  300. // Set Headers of the table
  301. // If Headers count is less than the data row count, the headers will be padded to the right
  302. func (t *Tabulate) SetHeaders(headers []string) *Tabulate {
  303. t.Headers = headers
  304. return t
  305. }
  306. // Set Float Formatting
  307. // will be used in strconv.FormatFloat(element, format, -1, 64)
  308. func (t *Tabulate) SetFloatFormat(format byte) *Tabulate {
  309. t.FloatFormat = format
  310. return t
  311. }
  312. // Set Align Type, Available options: left, right, center
  313. func (t *Tabulate) SetAlign(align string) {
  314. t.Align = align
  315. }
  316. // Select the padding function based on the align type
  317. func (t *Tabulate) getAlignFunc() func(int, string) string {
  318. if len(t.Align) < 1 || t.Align == "right" {
  319. return t.padLeft
  320. } else if t.Align == "left" {
  321. return t.padRight
  322. } else {
  323. return t.padCenter
  324. }
  325. }
  326. // Set how an empty cell will be represented
  327. func (t *Tabulate) SetEmptyString(empty string) {
  328. t.EmptyVar = empty + " "
  329. }
  330. // Set which lines to hide.
  331. // Can be:
  332. // top - Top line of the table,
  333. // belowheader - Line below the header,
  334. // bottom - Bottom line of the table
  335. func (t *Tabulate) SetHideLines(hide []string) {
  336. t.HideLines = hide
  337. }
  338. func (t *Tabulate) SetWrapStrings(wrap bool) {
  339. t.WrapStrings = wrap
  340. }
  341. // Sets the maximum size of cell
  342. // If WrapStrings is set to true, then the string inside
  343. // the cell will be split up into multiple cell
  344. func (t *Tabulate) SetMaxCellSize(max int) {
  345. t.MaxSize = max
  346. }
  347. func (t *Tabulate) splitElement(e string) (bool, string) {
  348. //check if we are not attempting to smartly wrap
  349. if t.WrapDelimiter == 0 {
  350. if t.SplitConcat == "" {
  351. return false, runewidth.Truncate(e, t.MaxSize, "")
  352. } else {
  353. return false, runewidth.Truncate(e, t.MaxSize, t.SplitConcat)
  354. }
  355. }
  356. //we are attempting to wrap
  357. //grab the current width
  358. var i int
  359. for i = t.MaxSize; i > 1; i-- {
  360. //loop through our proposed truncation size looking for one that ends on
  361. //our requested delimiter
  362. x := runewidth.Truncate(e, i, "")
  363. //check if the NEXT string is a
  364. //delimiter, if it IS, then we truncate and tell the caller to shrink
  365. r, _ := utf8.DecodeRuneInString(e[i:])
  366. if r == 0 || r == 1 {
  367. //decode failed, take the truncation as is
  368. return false, x
  369. }
  370. if r == t.WrapDelimiter {
  371. return true, x //inform the caller that they can remove the next rune
  372. }
  373. }
  374. //didn't find a good length, truncate at will
  375. if t.SplitConcat != "" {
  376. return false, runewidth.Truncate(e, t.MaxSize, t.SplitConcat)
  377. }
  378. return false, runewidth.Truncate(e, t.MaxSize, "")
  379. }
  380. // If string size is larger than t.MaxSize, then split it to multiple cells (downwards)
  381. func (t *Tabulate) wrapCellData() []*TabulateRow {
  382. var arr []*TabulateRow
  383. var cleanSplit bool
  384. var addr int
  385. if len(t.Data) == 0 {
  386. return arr
  387. }
  388. next := t.Data[0]
  389. for index := 0; index <= len(t.Data); index++ {
  390. elements := next.Elements
  391. new_elements := make([]string, len(elements))
  392. for i, e := range elements {
  393. if runewidth.StringWidth(e) > t.MaxSize {
  394. elements[i] = runewidth.Truncate(e, t.MaxSize, "")
  395. cleanSplit, elements[i] = t.splitElement(e)
  396. if cleanSplit {
  397. //remove the next rune
  398. r, w := utf8.DecodeRuneInString(e[len(elements[i]):])
  399. if r != 0 && r != 1 {
  400. addr = w
  401. }
  402. } else {
  403. addr = 0
  404. }
  405. new_elements[i] = e[len(elements[i])+addr:]
  406. next.Continuos = true
  407. }
  408. }
  409. if next.Continuos {
  410. arr = append(arr, next)
  411. next = &TabulateRow{Elements: new_elements}
  412. index--
  413. } else if index+1 < len(t.Data) {
  414. arr = append(arr, next)
  415. next = t.Data[index+1]
  416. } else if index >= len(t.Data) {
  417. arr = append(arr, next)
  418. }
  419. }
  420. return arr
  421. }
  422. // Create a new Tabulate Object
  423. // Accepts 2D String Array, 2D Int Array, 2D Int64 Array,
  424. // 2D Bool Array, 2D Float64 Array, 2D interface{} Array,
  425. // Map map[strig]string, Map map[string]interface{},
  426. func Create(data interface{}) *Tabulate {
  427. t := &Tabulate{FloatFormat: 'f', MaxSize: 30}
  428. switch v := data.(type) {
  429. case [][]string:
  430. t.Data = createFromString(data.([][]string))
  431. case [][]int32:
  432. t.Data = createFromInt32(data.([][]int32))
  433. case [][]int64:
  434. t.Data = createFromInt64(data.([][]int64))
  435. case [][]int:
  436. t.Data = createFromInt(data.([][]int))
  437. case [][]bool:
  438. t.Data = createFromBool(data.([][]bool))
  439. case [][]float64:
  440. t.Data = createFromFloat64(data.([][]float64), t.FloatFormat)
  441. case [][]interface{}:
  442. t.Data = createFromMixed(data.([][]interface{}), t.FloatFormat)
  443. case []string:
  444. t.Data = createFromString([][]string{data.([]string)})
  445. case []interface{}:
  446. t.Data = createFromMixed([][]interface{}{data.([]interface{})}, t.FloatFormat)
  447. case map[string][]interface{}:
  448. t.Headers, t.Data = createFromMapMixed(data.(map[string][]interface{}), t.FloatFormat)
  449. case map[string][]string:
  450. t.Headers, t.Data = createFromMapString(data.(map[string][]string))
  451. default:
  452. fmt.Println(v)
  453. }
  454. return t
  455. }