format-package-stream.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. 'use strict'
  2. var ms = require('mississippi')
  3. var jsonstream = require('JSONStream')
  4. var columnify = require('columnify')
  5. // This module consumes package data in the following format:
  6. //
  7. // {
  8. // name: String,
  9. // description: String,
  10. // maintainers: [{ username: String, email: String }],
  11. // keywords: String | [String],
  12. // version: String,
  13. // date: Date // can be null,
  14. // }
  15. //
  16. // The returned stream will format this package data
  17. // into a byte stream of formatted, displayable output.
  18. module.exports = formatPackageStream
  19. function formatPackageStream (opts) {
  20. opts = opts || {}
  21. if (opts.json) {
  22. return jsonOutputStream()
  23. } else {
  24. return textOutputStream(opts)
  25. }
  26. }
  27. function jsonOutputStream () {
  28. return ms.pipeline.obj(
  29. ms.through.obj(),
  30. jsonstream.stringify('[', ',', ']'),
  31. ms.through()
  32. )
  33. }
  34. function textOutputStream (opts) {
  35. var line = 0
  36. return ms.through.obj(function (pkg, enc, cb) {
  37. cb(null, prettify(pkg, ++line, opts))
  38. })
  39. }
  40. function prettify (data, num, opts) {
  41. opts = opts || {}
  42. var truncate = !opts.long
  43. var pkg = normalizePackage(data, opts)
  44. var columns = opts.description
  45. ? ['name', 'description', 'author', 'date', 'version', 'keywords']
  46. : ['name', 'author', 'date', 'version', 'keywords']
  47. if (opts.parseable) {
  48. return columns.map(function (col) {
  49. return pkg[col] && ('' + pkg[col]).replace(/\t/g, ' ')
  50. }).join('\t')
  51. }
  52. var output = columnify(
  53. [pkg],
  54. {
  55. include: columns,
  56. showHeaders: num <= 1,
  57. columnSplitter: ' | ',
  58. truncate: truncate,
  59. config: {
  60. name: { minWidth: 25, maxWidth: 25, truncate: false, truncateMarker: '' },
  61. description: { minWidth: 20, maxWidth: 20 },
  62. author: { minWidth: 15, maxWidth: 15 },
  63. date: { maxWidth: 11 },
  64. version: { minWidth: 8, maxWidth: 8 },
  65. keywords: { maxWidth: Infinity }
  66. }
  67. }
  68. )
  69. output = trimToMaxWidth(output)
  70. if (opts.color) {
  71. output = highlightSearchTerms(output, opts.args)
  72. }
  73. return output
  74. }
  75. var colors = [31, 33, 32, 36, 34, 35]
  76. var cl = colors.length
  77. function addColorMarker (str, arg, i) {
  78. var m = i % cl + 1
  79. var markStart = String.fromCharCode(m)
  80. var markEnd = String.fromCharCode(0)
  81. if (arg.charAt(0) === '/') {
  82. return str.replace(
  83. new RegExp(arg.substr(1, arg.length - 2), 'gi'),
  84. function (bit) { return markStart + bit + markEnd }
  85. )
  86. }
  87. // just a normal string, do the split/map thing
  88. var pieces = str.toLowerCase().split(arg.toLowerCase())
  89. var p = 0
  90. return pieces.map(function (piece) {
  91. piece = str.substr(p, piece.length)
  92. var mark = markStart +
  93. str.substr(p + piece.length, arg.length) +
  94. markEnd
  95. p += piece.length + arg.length
  96. return piece + mark
  97. }).join('')
  98. }
  99. function colorize (line) {
  100. for (var i = 0; i < cl; i++) {
  101. var m = i + 1
  102. var color = '\u001B[' + colors[i] + 'm'
  103. line = line.split(String.fromCharCode(m)).join(color)
  104. }
  105. var uncolor = '\u001B[0m'
  106. return line.split('\u0000').join(uncolor)
  107. }
  108. function getMaxWidth () {
  109. var cols
  110. try {
  111. var tty = require('tty')
  112. var stdout = process.stdout
  113. cols = !tty.isatty(stdout.fd) ? Infinity : process.stdout.getWindowSize()[0]
  114. cols = (cols === 0) ? Infinity : cols
  115. } catch (ex) { cols = Infinity }
  116. return cols
  117. }
  118. function trimToMaxWidth (str) {
  119. var maxWidth = getMaxWidth()
  120. return str.split('\n').map(function (line) {
  121. return line.slice(0, maxWidth)
  122. }).join('\n')
  123. }
  124. function highlightSearchTerms (str, terms) {
  125. terms.forEach(function (arg, i) {
  126. str = addColorMarker(str, arg, i)
  127. })
  128. return colorize(str).trim()
  129. }
  130. function normalizePackage (data, opts) {
  131. opts = opts || {}
  132. return {
  133. name: data.name,
  134. description: opts.description ? data.description : '',
  135. author: (data.maintainers || []).map(function (m) {
  136. return '=' + m.username
  137. }).join(' '),
  138. keywords: Array.isArray(data.keywords)
  139. ? data.keywords.join(' ')
  140. : typeof data.keywords === 'string'
  141. ? data.keywords.replace(/[,\s]+/, ' ')
  142. : '',
  143. version: data.version,
  144. date: (data.date &&
  145. (data.date.toISOString() // remove time
  146. .split('T').join(' ')
  147. .replace(/:[0-9]{2}\.[0-9]{3}Z$/, ''))
  148. .slice(0, -5)) ||
  149. 'prehistoric'
  150. }
  151. }