1const { Minipass } = require('minipass') 2const columnify = require('columnify') 3const ansiTrim = require('../utils/ansi-trim.js') 4 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 19module.exports = (opts) => { 20 return opts.json ? new JSONOutputStream() : new TextOutputStream(opts) 21} 22 23class JSONOutputStream extends Minipass { 24 #didFirst = false 25 26 write (obj) { 27 if (!this.#didFirst) { 28 super.write('[\n') 29 this.#didFirst = true 30 } else { 31 super.write('\n,\n') 32 } 33 34 return super.write(JSON.stringify(obj)) 35 } 36 37 end () { 38 super.write(this.#didFirst ? ']\n' : '\n[]\n') 39 super.end() 40 } 41} 42 43class TextOutputStream extends Minipass { 44 constructor (opts) { 45 super() 46 this._opts = opts 47 this._line = 0 48 } 49 50 write (pkg) { 51 return super.write(prettify(pkg, ++this._line, this._opts)) 52 } 53} 54 55function prettify (data, num, opts) { 56 var truncate = !opts.long 57 58 var pkg = normalizePackage(data, opts) 59 60 var columns = ['name', 'description', 'author', 'date', 'version', 'keywords'] 61 62 if (opts.parseable) { 63 return columns.map(function (col) { 64 return pkg[col] && ('' + pkg[col]).replace(/\t/g, ' ') 65 }).join('\t') 66 } 67 68 // stdout in tap is never a tty 69 /* istanbul ignore next */ 70 const maxWidth = process.stdout.isTTY ? process.stdout.getWindowSize()[0] : Infinity 71 let output = columnify( 72 [pkg], 73 { 74 include: columns, 75 showHeaders: num <= 1, 76 columnSplitter: ' | ', 77 truncate: truncate, 78 config: { 79 name: { minWidth: 25, maxWidth: 25, truncate: false, truncateMarker: '' }, 80 description: { minWidth: 20, maxWidth: 20 }, 81 author: { minWidth: 15, maxWidth: 15 }, 82 date: { maxWidth: 11 }, 83 version: { minWidth: 8, maxWidth: 8 }, 84 keywords: { maxWidth: Infinity }, 85 }, 86 } 87 ).split('\n').map(line => line.slice(0, maxWidth)).join('\n') 88 89 if (opts.color) { 90 output = highlightSearchTerms(output, opts.args) 91 } 92 93 return output 94} 95 96var colors = [31, 33, 32, 36, 34, 35] 97var cl = colors.length 98 99function addColorMarker (str, arg, i) { 100 var m = i % cl + 1 101 var markStart = String.fromCharCode(m) 102 var markEnd = String.fromCharCode(0) 103 104 if (arg.charAt(0) === '/') { 105 return str.replace( 106 new RegExp(arg.slice(1, -1), 'gi'), 107 bit => markStart + bit + markEnd 108 ) 109 } 110 111 // just a normal string, do the split/map thing 112 var pieces = str.toLowerCase().split(arg.toLowerCase()) 113 var p = 0 114 115 return pieces.map(function (piece) { 116 piece = str.slice(p, p + piece.length) 117 var mark = markStart + 118 str.slice(p + piece.length, p + piece.length + arg.length) + 119 markEnd 120 p += piece.length + arg.length 121 return piece + mark 122 }).join('') 123} 124 125function colorize (line) { 126 for (var i = 0; i < cl; i++) { 127 var m = i + 1 128 var color = '\u001B[' + colors[i] + 'm' 129 line = line.split(String.fromCharCode(m)).join(color) 130 } 131 var uncolor = '\u001B[0m' 132 return line.split('\u0000').join(uncolor) 133} 134 135function highlightSearchTerms (str, terms) { 136 terms.forEach(function (arg, i) { 137 str = addColorMarker(str, arg, i) 138 }) 139 140 return colorize(str).trim() 141} 142 143function normalizePackage (data, opts) { 144 return { 145 name: ansiTrim(data.name), 146 description: ansiTrim(data.description), 147 author: data.maintainers.map((m) => `=${ansiTrim(m.username)}`).join(' '), 148 keywords: Array.isArray(data.keywords) 149 ? data.keywords.map(ansiTrim).join(' ') 150 : typeof data.keywords === 'string' 151 ? ansiTrim(data.keywords.replace(/[,\s]+/, ' ')) 152 : '', 153 version: data.version, 154 date: (data.date && 155 (data.date.toISOString() // remove time 156 .split('T').join(' ') 157 .replace(/:[0-9]{2}\.[0-9]{3}Z$/, '')) 158 .slice(0, -5)) || 159 'prehistoric', 160 } 161} 162