diff options
Diffstat (limited to 'deps/npm/lib/search.js')
-rw-r--r-- | deps/npm/lib/search.js | 321 |
1 files changed, 69 insertions, 252 deletions
diff --git a/deps/npm/lib/search.js b/deps/npm/lib/search.js index cd6d5ed8ea..c9f3628717 100644 --- a/deps/npm/lib/search.js +++ b/deps/npm/lib/search.js @@ -1,11 +1,15 @@ +'use strict' module.exports = exports = search var npm = require('./npm.js') -var columnify = require('columnify') -var updateIndex = require('./cache/update-index.js') +var allPackageMetadata = require('./search/all-package-metadata.js') +var packageFilter = require('./search/package-filter.js') +var formatPackageStream = require('./search/format-package-stream.js') var usage = require('./utils/usage') var output = require('./utils/output.js') +var log = require('npmlog') +var ms = require('mississippi') search.usage = usage( 'search', @@ -13,276 +17,89 @@ search.usage = usage( ) 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)) - }) + cb(null, []) } -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') +function search (args, cb) { + var staleness = npm.config.get('searchstaleness') - 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) { + var include = prepareIncludes(args, npm.config.get('searchopts')) + if (include.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 -} + var exclude = prepareExcludes(npm.config.get('searchexclude')) -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') + // Used later to figure out whether we had any packages go out + var anyOutput = false - if (Object.keys(data).length === 0) { - return 'No match found for ' + (args.map(JSON.stringify).join(' ')) - } + // Get a stream with *all* the packages. This takes care of dealing + // with the local cache as well, but that's an internal detail. + var allEntriesStream = allPackageMetadata(staleness) - 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 + // Grab a stream that filters those packages according to given params. + var searchSection = (npm.config.get('unicode') ? '🤔 ' : '') + 'search' + var filterStream = streamFilter(function (pkg) { + log.gauge.pulse('search') + log.gauge.show({section: searchSection, logline: 'scanning ' + pkg.name}) + // Simply 'true' if the package matches search parameters. + var match = packageFilter(pkg, include, exclude, { + description: npm.config.get('description') + }) + if (match) { anyOutput = true } + return match }) - lines.sort(function (a, b) { - var aa = a[sortField].toLowerCase() - var bb = b[sortField].toLowerCase() - return aa === bb ? 0 - : aa < bb ? -1 : 1 + // Grab a configured output stream that will spit out packages in the + // desired format. + var outputStream = formatPackageStream({ + args: args, // --searchinclude options are not highlighted + long: npm.config.get('long'), + description: npm.config.get('description'), + json: npm.config.get('json'), + parseable: npm.config.get('parseable'), + color: npm.color + }) + outputStream.on('data', function (chunk) { + output(chunk.toString('utf8')) }) - 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 } - } + log.silly('search', 'searching packages') + ms.pipe(allEntriesStream, filterStream, outputStream, function (er) { + if (er) return cb(er) + if (!anyOutput && !npm.config.get('json') && !npm.config.get('parseable')) { + output('No matches found for ' + (args.map(JSON.stringify).join(' '))) } - ) - output = trimToMaxWidth(output) - output = highlightSearchTerms(output, args) - - return output + log.silly('search', 'index search completed') + log.clearProgress() + cb(null, {}) + }) } -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 prepareIncludes (args, searchopts) { + if (typeof searchopts !== 'string') searchopts = '' + return searchopts.split(/\s+/).concat(args).map(function (s) { + return s.toLowerCase() + }).filter(function (s) { return s }) } -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) +function prepareExcludes (searchexclude) { + var exclude + if (typeof searchexclude === 'string') { + exclude = searchexclude.split(/\s+/) + } else { + exclude = [] } - 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') + return exclude.map(function (s) { + return s.toLowerCase() + }) } -function highlightSearchTerms (str, terms) { - terms.forEach(function (arg, i) { - str = addColorMarker(str, arg, i) +function streamFilter (filter) { + return ms.through.obj(function (chunk, enc, cb) { + if (filter(chunk)) { + this.push(chunk) + } + cb() }) - - return colorize(str).trim() } |