UNPKG

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