bot/node_modules/npm/lib/search/format-package-stream.js

193 lines
4.6 KiB
JavaScript

// XXX these output classes should not live in here forever. it'd be good to
// split them out, perhaps to libnpmsearch
const Minipass = require('minipass')
const columnify = require('columnify')
// This module consumes package data in the following format:
//
// {
// name: String,
// description: String,
// maintainers: [{ username: String, email: String }],
// keywords: String | [String],
// version: String,
// date: Date // can be null,
// }
//
// The returned stream will format this package data
// into a byte stream of formatted, displayable output.
module.exports = (opts = {}) =>
opts.json ? new JSONOutputStream() : new TextOutputStream(opts)
class JSONOutputStream extends Minipass {
constructor () {
super()
this._didFirst = false
}
write (obj) {
if (!this._didFirst) {
super.write('[\n')
this._didFirst = true
} else {
super.write('\n,\n')
}
try {
return super.write(JSON.stringify(obj))
} catch (er) {
return this.emit('error', er)
}
}
end () {
super.write(this._didFirst ? ']\n' : '\n[]\n')
super.end()
}
}
class TextOutputStream extends Minipass {
constructor (opts) {
super()
this._opts = opts
this._line = 0
}
write (pkg) {
return super.write(prettify(pkg, ++this._line, this._opts))
}
}
function prettify (data, num, opts) {
opts = opts || {}
var truncate = !opts.long
var pkg = normalizePackage(data, opts)
var columns = opts.description
? ['name', 'description', 'author', 'date', 'version', 'keywords']
: ['name', 'author', 'date', 'version', 'keywords']
if (opts.parseable) {
return columns.map(function (col) {
return pkg[col] && ('' + pkg[col]).replace(/\t/g, ' ')
}).join('\t')
}
var output = columnify(
[pkg],
{
include: columns,
showHeaders: num <= 1,
columnSplitter: ' | ',
truncate: truncate,
config: {
name: { minWidth: 25, maxWidth: 25, truncate: false, truncateMarker: '' },
description: { minWidth: 20, maxWidth: 20 },
author: { minWidth: 15, maxWidth: 15 },
date: { maxWidth: 11 },
version: { minWidth: 8, maxWidth: 8 },
keywords: { maxWidth: Infinity },
},
}
)
output = trimToMaxWidth(output)
if (opts.color) {
output = highlightSearchTerms(output, opts.args)
}
return output
}
var colors = [31, 33, 32, 36, 34, 35]
var cl = colors.length
function addColorMarker (str, arg, i) {
var m = i % cl + 1
var markStart = String.fromCharCode(m)
var markEnd = String.fromCharCode(0)
if (arg.charAt(0) === '/') {
return str.replace(
new RegExp(arg.substr(1, arg.length - 2), 'gi'),
bit => markStart + bit + markEnd
)
}
// just a normal string, do the split/map thing
var pieces = str.toLowerCase().split(arg.toLowerCase())
var p = 0
return pieces.map(function (piece) {
piece = str.substr(p, piece.length)
var mark = markStart +
str.substr(p + piece.length, arg.length) +
markEnd
p += piece.length + arg.length
return piece + mark
}).join('')
}
function colorize (line) {
for (var i = 0; i < cl; i++) {
var m = i + 1
var color = '\u001B[' + colors[i] + 'm'
line = line.split(String.fromCharCode(m)).join(color)
}
var uncolor = '\u001B[0m'
return line.split('\u0000').join(uncolor)
}
function getMaxWidth () {
var cols
try {
var tty = require('tty')
var stdout = process.stdout
cols = !tty.isatty(stdout.fd) ? Infinity : process.stdout.getWindowSize()[0]
cols = (cols === 0) ? Infinity : cols
} catch (ex) {
cols = Infinity
}
return cols
}
function trimToMaxWidth (str) {
var maxWidth = getMaxWidth()
return str.split('\n').map(function (line) {
return line.slice(0, maxWidth)
}).join('\n')
}
function highlightSearchTerms (str, terms) {
terms.forEach(function (arg, i) {
str = addColorMarker(str, arg, i)
})
return colorize(str).trim()
}
function normalizePackage (data, opts) {
opts = opts || {}
return {
name: data.name,
description: opts.description ? data.description : '',
author: (data.maintainers || []).map(function (m) {
return '=' + m.username
}).join(' '),
keywords: Array.isArray(data.keywords)
? data.keywords.join(' ')
: typeof data.keywords === 'string'
? data.keywords.replace(/[,\s]+/, ' ')
: '',
version: data.version,
date: (data.date &&
(data.date.toISOString() // remove time
.split('T').join(' ')
.replace(/:[0-9]{2}\.[0-9]{3}Z$/, ''))
.slice(0, -5)) ||
'prehistoric',
}
}