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