mirror of https://github.com/lukechilds/node.git
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.
223 lines
6.8 KiB
223 lines
6.8 KiB
13 years ago
|
|
||
|
module.exports = exports = search
|
||
|
|
||
|
var npm = require("./npm.js")
|
||
|
, registry = require("./utils/npm-registry-client/index.js")
|
||
|
, semver = require("semver")
|
||
|
, output
|
||
|
, log = require("./utils/log.js")
|
||
|
|
||
|
search.usage = "npm search [some search terms ...]"
|
||
|
|
||
|
search.completion = function (opts, cb) {
|
||
|
var compl = {}
|
||
|
, partial = opts.partialWord
|
||
|
, ipartial = partial.toLowerCase()
|
||
|
, 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")
|
||
|
, searchexclude = npm.config.get("searchexclude")
|
||
|
if (typeof searchopts !== "string") searchopts = ""
|
||
|
searchopts = searchopts.split(/\s+/)
|
||
|
if (typeof searchexclude === "string") {
|
||
|
searchexclude = searchexclude.split(/\s+/)
|
||
|
} else searchexclude = []
|
||
|
var opts = searchopts.concat(args).map(function (s) {
|
||
|
return s.toLowerCase()
|
||
|
}).filter(function (s) { return s })
|
||
|
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)
|
||
|
function cb (er) { return cb_(er, data) }
|
||
|
output = output || require("./utils/output.js")
|
||
|
output.write(prettify(data, args), cb)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
function getFilteredData (staleness, args, notArgs, cb) {
|
||
|
registry.get( "/-/all", null, staleness, false
|
||
|
, true, 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 || []
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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 (words.indexOf(args[i]) === -1) {
|
||
|
return false
|
||
|
}
|
||
|
}
|
||
|
for (var i = 0, l = notArgs.length; i < l; i ++) {
|
||
|
if (words.indexOf(notArgs[i]) !== -1) return false
|
||
|
}
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
function prettify (data, args) {
|
||
|
try {
|
||
|
var tty = require("tty")
|
||
|
, stdout = process.stdout
|
||
|
, cols = !tty.isatty(stdout.fd) ? Infinity
|
||
|
: stdout._handle ? stdout._handle.getWindowSize()[0]
|
||
|
: tty.getWindowSize()[1]
|
||
|
} catch (ex) { cols = Infinity }
|
||
|
|
||
|
// name, desc, author, keywords
|
||
|
var longest = []
|
||
|
, spaces
|
||
|
, maxLen = [20, 60, 20, Infinity]
|
||
|
, headings = ["NAME", "DESCRIPTION", "AUTHOR", "KEYWORDS"]
|
||
|
, lines
|
||
|
|
||
|
lines = Object.keys(data).map(function (d) {
|
||
|
return data[d]
|
||
|
}).filter(function (data) {
|
||
|
return data.name
|
||
|
}).map(function (data) {
|
||
|
// turn a pkg data into a string
|
||
|
// [name,who,desc,targets,keywords] tuple
|
||
|
// also set longest to the longest name
|
||
|
if (typeof data.keywords === "string") {
|
||
|
data.keywords = data.keywords.split(/[,\s]+/)
|
||
|
}
|
||
|
if (!Array.isArray(data.keywords)) data.keywords = []
|
||
|
var l = [ data.name
|
||
|
, data.description || ""
|
||
|
, data.maintainers.join(" ")
|
||
|
, (data.keywords || []).join(" ")
|
||
|
]
|
||
|
l.forEach(function (s, i) {
|
||
|
var len = s.length
|
||
|
longest[i] = Math.min(maxLen[i] || Infinity
|
||
|
,Math.max(longest[i] || 0, len))
|
||
|
if (len > longest[i]) {
|
||
|
l._undent = l._undent || []
|
||
|
l._undent[i] = len - longest[i]
|
||
|
}
|
||
|
l[i] = l[i].replace(/\s+/g, " ")
|
||
|
})
|
||
|
return l
|
||
|
}).map(function (line) {
|
||
|
return line.map(function (s, i) {
|
||
|
spaces = spaces || longest.map(function (n) {
|
||
|
return new Array(n + 2).join(" ")
|
||
|
})
|
||
|
var len = s.length
|
||
|
if (line._undent && line._undent[i - 1]) {
|
||
|
len += line._undent[i - 1] - 1
|
||
|
}
|
||
|
return s + spaces[i].substr(len)
|
||
|
}).join(" ").substr(0, cols).trim()
|
||
|
}).sort(function (a, b) {
|
||
|
return a === b ? 0 : a > b ? 1 : -1
|
||
|
}).map(function (line) {
|
||
|
// colorize!
|
||
|
args.forEach(function (arg, i) {
|
||
|
line = addColorMarker(line, arg, i)
|
||
|
})
|
||
|
return colorize(line).trim()
|
||
|
})
|
||
|
|
||
|
if (lines.length === 0) {
|
||
|
return "No match found for "+(args.map(JSON.stringify).join(" "))
|
||
|
}
|
||
|
|
||
|
// build the heading padded to the longest in each field
|
||
|
return headings.map(function (h, i) {
|
||
|
var space = Math.max(2, 3 + (longest[i] || 0) - h.length)
|
||
|
return h + (new Array(space).join(" "))
|
||
|
}).join("").substr(0, cols).trim() + "\n" + lines.join("\n")
|
||
|
|
||
|
}
|
||
|
|
||
|
var colors = [31, 33, 32, 36, 34, 35 ]
|
||
|
, cl = colors.length
|
||
|
function addColorMarker (str, arg, i) {
|
||
|
var m = i % cl + 1
|
||
|
, markStart = String.fromCharCode(m)
|
||
|
, markEnd = String.fromCharCode(0)
|
||
|
, pieces = str.toLowerCase().split(arg.toLowerCase())
|
||
|
, p = 0
|
||
|
return pieces.map(function (piece, i) {
|
||
|
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("")
|
||
|
return str.split(arg).join(mark)
|
||
|
}
|
||
|
function colorize (line) {
|
||
|
for (var i = 0; i < cl; i ++) {
|
||
|
var m = i + 1
|
||
|
line = line.split(String.fromCharCode(m)).join("\033["+colors[i]+"m")
|
||
|
}
|
||
|
return line.split("\u0000").join("\033[0m")
|
||
|
}
|