table.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. const stripAnsi = require('strip-ansi')
  2. const log = require('../util/log')
  3. function keyValueArray (columns, keys, obj) {
  4. return keys.map(el => columns[el](obj))
  5. }
  6. function findMaxLengths (rows) {
  7. var widths = []
  8. for (let i = 0; i < rows.length; i++) {
  9. for (let j = 0; j < rows[i].length; j++) {
  10. let item = stripAnsi(rows[i][j]) + ''
  11. if (!widths[j] || widths[j] < item.length) {
  12. widths[j] = item.length
  13. }
  14. }
  15. }
  16. return widths
  17. }
  18. function createTable (rows, fitWidth) {
  19. function padEnd (string, maxLength, fillString) {
  20. // Don't pad to infinity.
  21. if (maxLength === Infinity) {
  22. return string
  23. }
  24. // Coerce to string.
  25. string = string + ''
  26. // Pad space chars.
  27. while (stripAnsi(string).length < maxLength) {
  28. string = string + fillString
  29. }
  30. // If the string is too long, substring it.
  31. if (string.length > maxLength) {
  32. return string.substr(0, maxLength)
  33. }
  34. return string
  35. }
  36. // Find target width.
  37. if (fitWidth > 0) {
  38. var targetWidth = Math.floor((fitWidth / rows[0].length) - 3)
  39. } else {
  40. targetWidth = Infinity
  41. }
  42. let maxWidths = findMaxLengths(rows)
  43. let widths = []
  44. // Budget for how much more space we can add if needed.
  45. var budget = 0
  46. // Calcualte initial column sizes.
  47. for (let i = 0; i < maxWidths.length; i++) {
  48. if (maxWidths[i] < targetWidth) {
  49. budget += (targetWidth - maxWidths[i])
  50. widths[i] = maxWidths[i]
  51. } else {
  52. widths[i] = targetWidth
  53. }
  54. }
  55. if (targetWidth < Infinity) {
  56. // Add budget until all items can be shown, or we run out of extra space.
  57. while (budget > 0) {
  58. var okCount = 0
  59. for (let i = 0; i < widths.length; i++) {
  60. let diff = maxWidths[i] - widths[i]
  61. // If the diff is >0, that means that there may be wrapping.
  62. if (diff > 0 && budget > 0) {
  63. // Add extra spaces.
  64. widths[i] += 1
  65. budget -= 1
  66. } else {
  67. okCount += 1
  68. }
  69. }
  70. // If they all are Ok, end.
  71. if (okCount === widths.length) {
  72. break
  73. }
  74. }
  75. }
  76. // Store the output rows
  77. var outputRows = []
  78. // Cursors for each item in the current row.
  79. var cursors = []
  80. // Additonal row overflow flag
  81. let flag = false
  82. for (let i = 0; i < rows.length; i++) {
  83. var line = []
  84. for (let j = 0; j < rows[i].length; j++) {
  85. cursors[j] = cursors[j] || 0
  86. // Extract item
  87. let rawItem = rows[i][j] + ''
  88. // Slice for this row.
  89. let inputItem = rawItem.substr(cursors[j], widths[j])
  90. if (inputItem === '-') {
  91. line.push(padEnd(inputItem, widths[j], '-'))
  92. } else {
  93. // Pad the item and add to the line.
  94. let item = padEnd(inputItem, widths[j], ' ')
  95. line.push(item)
  96. // If the item is too long for one row, flag it to be printed below.
  97. if (cursors[j] + inputItem.length < rawItem.length && inputItem !== '') {
  98. flag = true
  99. }
  100. // Advance cursor.
  101. cursors[j] += widths[j]
  102. }
  103. }
  104. // Add line to output rows.
  105. outputRows.push(line)
  106. // If the flag is true, we need to repeat the last line.
  107. if (flag) {
  108. i -= 1
  109. flag = false
  110. } else {
  111. // Reset cursors.
  112. cursors = []
  113. }
  114. }
  115. return outputRows
  116. }
  117. module.exports.format = function (data, options) {
  118. // Keys for each column
  119. var keys = []
  120. // Separators for each column
  121. var separators = []
  122. // Add to collection of keys
  123. for (var key in options.columns) {
  124. keys.push(key)
  125. separators.push('-')
  126. }
  127. // Create the rows
  128. var items = [
  129. keys,
  130. separators,
  131. ...data.map(data => keyValueArray(options.columns, keys, data))
  132. ]
  133. if (options.program && options.program.output !== undefined) {
  134. var targetWidth = 120
  135. } else {
  136. targetWidth = process.stdout.columns
  137. }
  138. // Normalize column widths.
  139. items = createTable(items, targetWidth).map(el => {
  140. return el.join(' | ').replace(/\n/g, '')
  141. }).join('\n')
  142. if (options.program) {
  143. // Disable color output
  144. if (!options.program.color) { items = stripAnsi(items) }
  145. // If reporting output is defined, ignore console log here.
  146. if (options.program.output === undefined) {
  147. log.raw(items)
  148. }
  149. } else {
  150. log.raw(items)
  151. }
  152. return items
  153. }
  154. const fs = require('fs-extra')
  155. const path = require('path')
  156. module.exports.finalReport = async function (reports, program) {
  157. if (program.output === undefined) {
  158. return
  159. }
  160. // Ensure the output directory exists.
  161. fs.ensureDirSync(program.output)
  162. // Write each report to the disk
  163. for (var report of reports) {
  164. var outPath = path.join(program.output, report.name + '.txt')
  165. log.action('saving', outPath)
  166. fs.writeFileSync(outPath, report.contents, 'utf8')
  167. }
  168. }