UNPKG

8.6 kBJavaScriptView Raw
1
2module.exports = exports = search
3
4var npm = require("./npm.js")
5 , registry = npm.registry
6 , columnify = require('columnify')
7 , fmt = require('printf')
8 , cols = process.stdout.columns || 80
9 , repo = require('github-url-from-git')
10 , chalk = require('chalk')
11 , mapToRegistry = require("./utils/map-to-registry.js")
12
13search.usage = "npm search [some search terms ...]"
14
15search.completion = function (opts, cb) {
16 var compl = {}
17 , partial = opts.partialWord
18 , ipartial = partial.toLowerCase()
19 , plen = partial.length
20
21 // get the batch of data that matches so far.
22 // this is an example of using npm.commands.search programmatically
23 // to fetch data that has been filtered by a set of arguments.
24 search(opts.conf.argv.remain.slice(2), true, function (er, data) {
25 if (er) return cb(er)
26 Object.keys(data).forEach(function (name) {
27 data[name].words.split(" ").forEach(function (w) {
28 if (w.toLowerCase().indexOf(ipartial) === 0) {
29 compl[partial + w.substr(plen)] = true
30 }
31 })
32 })
33 cb(null, Object.keys(compl))
34 })
35}
36
37function search (args, silent, staleness, cb) {
38 if (typeof cb !== "function") cb = staleness, staleness = 600
39 if (typeof cb !== "function") cb = silent, silent = false
40
41 var searchopts = npm.config.get("searchopts")
42 , searchexclude = npm.config.get("searchexclude")
43 if (typeof searchopts !== "string") searchopts = ""
44 searchopts = searchopts.split(/\s+/)
45 if (typeof searchexclude === "string") {
46 searchexclude = searchexclude.split(/\s+/)
47 } else searchexclude = []
48 var opts = searchopts.concat(args).map(function (s) {
49 return s.toLowerCase()
50 }).filter(function (s) { return s })
51 searchexclude = searchexclude.map(function (s) {
52 return s.toLowerCase()
53 })
54 getFilteredData( staleness, opts, searchexclude, function (er, data) {
55 // now data is the list of data that we want to show.
56 // prettify and print it, and then provide the raw
57 // data to the cb.
58 if (er || silent) return cb(er, data)
59 output(data, args)
60 cb(null, data)
61 })
62}
63
64function getFilteredData (staleness, args, notArgs, cb) {
65 var opts = {
66 timeout : staleness,
67 follow : true,
68 staleOk : true
69 }
70 mapToRegistry("-/all", npm.config, function (er, uri) {
71 if (er) return cb(er)
72
73 registry.get(uri, opts, function (er, data) {
74 if (er) return cb(er)
75 return cb(null, filter(data, args, notArgs))
76 })
77 })
78}
79
80function filter (data, args, notArgs) {
81 // data={<name>:{package data}}
82 return Object.keys(data).map(function (d) {
83 return data[d]
84 }).filter(function (d) {
85 return typeof d === "object"
86 }).map(stripData).map(getWords).filter(function (data) {
87 return filterWords(data, args, notArgs)
88 }).reduce(function (l, r) {
89 l[r.name] = r
90 return l
91 }, {})
92}
93
94function stripData (data) {
95 return { name: data.name
96 , repository: data.repository
97 , description: npm.config.get("description") ? data.description : ""
98 , maintainers: (data.maintainers || []).map(function (m) {
99 return "=" + m.name
100 })
101 , url: !Object.keys(data.versions || {}).length ? data.url : null
102 , keywords: data.keywords || []
103 , version: Object.keys(data.versions || {})[0] || []
104 , time: data.time
105 && data.time.modified
106 && (new Date(data.time.modified).toISOString()
107 .split("T").join(" ")
108 .replace(/:[0-9]{2}\.[0-9]{3}Z$/, ""))
109 .slice(0, -5) // remove time
110 || "prehistoric"
111 }
112}
113
114function getWords (data) {
115 data.words = [ data.name ]
116 .concat(data.description)
117 .concat(data.maintainers)
118 .concat(data.url && ("<" + data.url + ">"))
119 .concat(data.keywords)
120 .map(function (f) { return f && f.trim && f.trim() })
121 .filter(function (f) { return f })
122 .join(" ")
123 .toLowerCase()
124 return data
125}
126
127function filterWords (data, args, notArgs) {
128 var words = data.words
129 for (var i = 0, l = args.length; i < l; i ++) {
130 if (!match(words, args[i])) return false
131 }
132 for (i = 0, l = notArgs.length; i < l; i ++) {
133 if (match(words, notArgs[i])) return false
134 }
135 return true
136}
137
138function match (words, arg) {
139 if (arg.charAt(0) === "/") {
140 arg = arg.replace(/\/$/, "")
141 arg = new RegExp(arg.substr(1, arg.length - 1))
142 return words.match(arg)
143 }
144 return words.indexOf(arg) !== -1
145}
146
147function output (data, args) {
148 var searchsort = (npm.config.get("searchsort") || "NAME").toLowerCase()
149 , sortField = searchsort.replace(/^\-+/, "")
150 , searchRev = searchsort.charAt(0) === "-"
151 , truncate = !npm.config.get("long")
152
153 if (Object.keys(data).length === 0) {
154 return "No match found for "+(args.map(JSON.stringify).join(" "))
155 }
156
157 var lines = Object.keys(data).map(function (d) {
158 // strip keyname
159 return data[d]
160 }).map(function(dat) {
161 dat.author = dat.maintainers
162 delete dat.maintainers
163 dat.date = dat.time
164 delete dat.time
165 return dat
166 }).map(function(dat) {
167 // split keywords on whitespace or ,
168 if (typeof dat.keywords === "string") {
169 dat.keywords = dat.keywords.split(/[,\s]+/)
170 }
171 if (Array.isArray(dat.keywords)) {
172 dat.keywords = dat.keywords.join(' ')
173 }
174
175 // split author on whitespace or ,
176 if (typeof dat.author === "string") {
177 dat.author = dat.author.split(/[,\s]+/)
178 }
179 if (Array.isArray(dat.author)) {
180 dat.author = dat.author.join(' ')
181 }
182 return dat
183 })
184
185 lines.sort(function(a, b) {
186 var aa = a[sortField].toLowerCase()
187 , bb = b[sortField].toLowerCase()
188 return aa === bb ? 0
189 : aa < bb ? -1 : 1
190 })
191
192 if (searchRev) lines.reverse()
193
194 var columns = npm.config.get("description")
195 ? ["name", "description", "author", "date", "version", "keywords"]
196 : ["name", "author", "date", "version", "keywords"]
197
198 console.log();
199 lines.forEach(function(pkg){
200 args.forEach(function(arg){
201 var desc = pkg.description || '';
202 pkg.description = desc.replace(arg, function(arg){
203 return chalk.underline(arg);
204 });
205 });
206
207 console.log(' \033[36m%s\033[0m', pkg.name);
208 if (pkg.repository) console.log(' repo: \033[90m%s\033[0m', repo(pkg.repository.url));
209 console.log(' desc: \033[90m%s\033[0m', description(pkg.description));
210 console.log(' by: \033[90m%s\033[0m', pkg.author.replace(/=/g, ''));
211 console.log();
212 });
213}
214
215/**
216 * Wrap description `str`.
217 */
218
219function description(str) {
220 if (!str) return '';
221 var space;
222 var width = cols - 20;
223 for (var i = 0; i < str.length; ++i) {
224 if (i && i % width == 0) {
225 space = str.indexOf(' ', i);
226 str = str.slice(0, space) + '\n ' + str.slice(space);
227 }
228 }
229 return str;
230}
231
232var colors = [31, 33, 32, 34, 35 ]
233 , cl = colors.length
234
235function addColorMarker (str, arg, i) {
236 var m = i % cl + 1
237 , markStart = String.fromCharCode(m)
238 , markEnd = String.fromCharCode(0)
239
240 if (arg.charAt(0) === "/") {
241 //arg = arg.replace(/\/$/, "")
242 return str.replace( new RegExp(arg.substr(1, arg.length - 2), "gi")
243 , function (bit) { return markStart + bit + markEnd } )
244
245 }
246
247 // just a normal string, do the split/map thing
248 var pieces = str.toLowerCase().split(arg.toLowerCase())
249 , p = 0
250
251 return pieces.map(function (piece) {
252 piece = str.substr(p, piece.length)
253 var mark = markStart
254 + str.substr(p+piece.length, arg.length)
255 + markEnd
256 p += piece.length + arg.length
257 return piece + mark
258 }).join("")
259}
260
261function colorize (line) {
262 for (var i = 0; i < cl; i ++) {
263 var m = i + 1
264 var color = npm.color ? "\033["+colors[i]+"m" : ""
265 line = line.split(String.fromCharCode(m)).join(color)
266 }
267 var uncolor = npm.color ? "\033[0m" : ""
268 return line.split("\u0000").join(uncolor)
269}
270
271function getMaxWidth() {
272 var cols
273 try {
274 var tty = require("tty")
275 , stdout = process.stdout
276 cols = !tty.isatty(stdout.fd) ? Infinity : process.stdout.getWindowSize()[0]
277 cols = (cols === 0) ? Infinity : cols
278 } catch (ex) { cols = Infinity }
279 return cols
280}
281
282function trimToMaxWidth(str) {
283 var maxWidth = getMaxWidth()
284 return str.split('\n').map(function(line) {
285 return line.slice(0, maxWidth)
286 }).join('\n')
287}
288
289function highlightSearchTerms(str, terms) {
290 terms.forEach(function (arg, i) {
291 str = addColorMarker(str, arg, i)
292 })
293
294 return colorize(str).trim()
295}