You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

288 lines
7.8 KiB

module.exports = exports = search
var npm = require('./npm.js')
var columnify = require('columnify')
var updateIndex = require('./cache/update-index.js')
var usage = require('./utils/usage')
var output = require('./utils/output.js')
search.usage = usage(
'search',
'npm search [--long] [search terms ...]'
)
search.completion = function (opts, cb) {
var compl = {}
var partial = opts.partialWord
var ipartial = partial.toLowerCase()
var plen = partial.length
// get the batch of data that matches so far.
// this is an example of using npm.commands.search programmatically
// to fetch data that has been filtered by a set of arguments.
search(opts.conf.argv.remain.slice(2), true, function (er, data) {
if (er) return cb(er)
Object.keys(data).forEach(function (name) {
data[name].words.split(' ').forEach(function (w) {
if (w.toLowerCase().indexOf(ipartial) === 0) {
compl[partial + w.substr(plen)] = true
}
})
})
cb(null, Object.keys(compl))
})
}
function search (args, silent, staleness, cb) {
if (typeof cb !== 'function') {
cb = staleness
staleness = 600
}
if (typeof cb !== 'function') {
cb = silent
silent = false
}
var searchopts = npm.config.get('searchopts')
var searchexclude = npm.config.get('searchexclude')
if (typeof searchopts !== 'string') searchopts = ''
searchopts = searchopts.split(/\s+/)
var opts = searchopts.concat(args).map(function (s) {
return s.toLowerCase()
}).filter(function (s) { return s })
if (opts.length === 0) {
return cb(new Error('search must be called with arguments'))
}
if (typeof searchexclude === 'string') {
searchexclude = searchexclude.split(/\s+/)
} else {
searchexclude = []
}
searchexclude = searchexclude.map(function (s) {
return s.toLowerCase()
})
getFilteredData(staleness, opts, searchexclude, function (er, data) {
// now data is the list of data that we want to show.
// prettify and print it, and then provide the raw
// data to the cb.
if (er || silent) return cb(er, data)
output(prettify(data, args))
cb(null, data)
})
}
function getFilteredData (staleness, args, notArgs, cb) {
updateIndex(staleness, function (er, data) {
if (er) return cb(er)
return cb(null, filter(data, args, notArgs))
})
}
function filter (data, args, notArgs) {
// data={<name>:{package data}}
return Object.keys(data).map(function (d) {
return data[d]
}).filter(function (d) {
return typeof d === 'object'
}).map(stripData).map(getWords).filter(function (data) {
return filterWords(data, args, notArgs)
}).reduce(function (l, r) {
l[r.name] = r
return l
}, {})
}
function stripData (data) {
return {
name: data.name,
description: npm.config.get('description') ? data.description : '',
maintainers: (data.maintainers || []).map(function (m) {
return '=' + m.name
}),
url: !Object.keys(data.versions || {}).length ? data.url : null,
keywords: data.keywords || [],
version: Object.keys(data.versions || {})[0] || [],
time: data.time &&
data.time.modified &&
(new Date(data.time.modified).toISOString() // remove time
.split('T').join(' ')
.replace(/:[0-9]{2}\.[0-9]{3}Z$/, ''))
.slice(0, -5) ||
'prehistoric'
}
}
function getWords (data) {
data.words = [ data.name ]
.concat(data.description)
.concat(data.maintainers)
.concat(data.url && ('<' + data.url + '>'))
.concat(data.keywords)
.map(function (f) { return f && f.trim && f.trim() })
.filter(function (f) { return f })
.join(' ')
.toLowerCase()
return data
}
function filterWords (data, args, notArgs) {
var words = data.words
for (var i = 0, l = args.length; i < l; i++) {
if (!match(words, args[i])) return false
}
for (i = 0, l = notArgs.length; i < l; i++) {
if (match(words, notArgs[i])) return false
}
return true
}
function match (words, arg) {
if (arg.charAt(0) === '/') {
arg = arg.replace(/\/$/, '')
arg = new RegExp(arg.substr(1, arg.length - 1))
return words.match(arg)
}
return words.indexOf(arg) !== -1
}
function prettify (data, args) {
var searchsort = (npm.config.get('searchsort') || 'NAME').toLowerCase()
var sortField = searchsort.replace(/^\-+/, '')
var searchRev = searchsort.charAt(0) === '-'
var truncate = !npm.config.get('long')
if (Object.keys(data).length === 0) {
return 'No match found for ' + (args.map(JSON.stringify).join(' '))
}
var lines = Object.keys(data).map(function (d) {
// strip keyname
return data[d]
}).map(function (dat) {
dat.author = dat.maintainers
delete dat.maintainers
dat.date = dat.time
delete dat.time
return dat
}).map(function (dat) {
// split keywords on whitespace or ,
if (typeof dat.keywords === 'string') {
dat.keywords = dat.keywords.split(/[,\s]+/)
}
if (Array.isArray(dat.keywords)) {
dat.keywords = dat.keywords.join(' ')
}
// split author on whitespace or ,
if (typeof dat.author === 'string') {
dat.author = dat.author.split(/[,\s]+/)
}
if (Array.isArray(dat.author)) {
dat.author = dat.author.join(' ')
}
return dat
})
lines.sort(function (a, b) {
var aa = a[sortField].toLowerCase()
var bb = b[sortField].toLowerCase()
return aa === bb ? 0
: aa < bb ? -1 : 1
})
if (searchRev) lines.reverse()
var columns = npm.config.get('description')
? ['name', 'description', 'author', 'date', 'version', 'keywords']
: ['name', 'author', 'date', 'version', 'keywords']
var output = columnify(
lines,
{
include: columns,
truncate: truncate,
config: {
name: { maxWidth: 40, truncate: false, truncateMarker: '' },
description: { maxWidth: 60 },
author: { maxWidth: 20 },
date: { maxWidth: 11 },
version: { maxWidth: 11 },
keywords: { maxWidth: Infinity }
}
}
)
output = trimToMaxWidth(output)
output = highlightSearchTerms(output, 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'),
function (bit) { return 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 = npm.color ? '\u001B[' + colors[i] + 'm' : ''
line = line.split(String.fromCharCode(m)).join(color)
}
var uncolor = npm.color ? '\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()
}