tabulate.go 12 KB

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