table.js 5.4 KB

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