Skip to content

Commit 20439b2

Browse files
timoxleydomenic
authored andcommitted
Tidy search output via columnify.
* Enable line wrapping|truncation via --long option. * Remove updated time (but leave date) from npm search results. * Increase space for name from 20 to 30 characters. * Remove unnecessary brackets around 'prehistoric'. * Make date column fit the word 'prehistoric' better. * Tighten space between status and name.
1 parent f0756e5 commit 20439b2

File tree

7 files changed

+619
-85
lines changed

7 files changed

+619
-85
lines changed

doc/cli/npm-search.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ npm-search(1) -- Search for packages
33

44
## SYNOPSIS
55

6-
npm search [search terms ...]
6+
npm search [--long] [search terms ...]
77
npm s [search terms ...]
88
npm se [search terms ...]
99

@@ -15,6 +15,18 @@ If a term starts with `/`, then it's interpreted as a regular expression.
1515
A trailing `/` will be ignored in this case. (Note that many regular
1616
expression characters must be escaped or quoted in most shells.)
1717

18+
## CONFIGURATION
19+
20+
### long
21+
22+
* Default: false
23+
* Type: Boolean
24+
25+
Display full package descriptions and other long text across multiple
26+
lines. When disabled (default) search results are truncated to fit
27+
neatly on a single line. Modules with extremely long names will
28+
fall on multiple lines.
29+
1830
## SEE ALSO
1931

2032
* npm-registry(7)

lib/search.js

Lines changed: 85 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module.exports = exports = search
33

44
var npm = require("./npm.js")
55
, registry = npm.registry
6+
, columnify = require('columnify')
67

78
search.usage = "npm search [some search terms ...]"
89

@@ -91,7 +92,8 @@ function stripData (data) {
9192
&& (new Date(data.time.modified).toISOString()
9293
.split("T").join(" ")
9394
.replace(/:[0-9]{2}\.[0-9]{3}Z$/, ""))
94-
|| "(prehistoric)"
95+
.slice(0, -5) // remove time
96+
|| "prehistoric"
9597
}
9698
}
9799

@@ -129,102 +131,77 @@ function match (words, arg) {
129131
}
130132

131133
function prettify (data, args) {
132-
try {
133-
var tty = require("tty")
134-
, stdout = process.stdout
135-
, cols = !tty.isatty(stdout.fd) ? Infinity
136-
: process.stdout.getWindowSize()[0]
137-
cols = (cols == 0) ? Infinity : cols
138-
} catch (ex) { cols = Infinity }
139-
140-
// name, desc, author, keywords
141-
var longest = []
142-
, spaces
143-
, maxLen = npm.config.get("description")
144-
? [20, 60, 20, 20, 10, Infinity]
145-
: [20, 20, 20, 10, Infinity]
146-
, headings = npm.config.get("description")
147-
? ["NAME", "DESCRIPTION", "AUTHOR", "DATE", "VERSION", "KEYWORDS"]
148-
: ["NAME", "AUTHOR", "DATE", "VERSION", "KEYWORDS"]
149-
, lines
150-
, searchsort = (npm.config.get("searchsort") || "NAME").toLowerCase()
151-
, sortFields = { name: 0
152-
, description: 1
153-
, author: 2
154-
, date: 3
155-
, version: 4
156-
, keywords: 5 }
134+
var searchsort = (npm.config.get("searchsort") || "NAME").toLowerCase()
135+
, sortField = searchsort.replace(/^\-+/, "")
157136
, searchRev = searchsort.charAt(0) === "-"
158-
, sortField = sortFields[searchsort.replace(/^\-+/, "")]
137+
, truncate = !npm.config.get("long")
138+
139+
if (Object.keys(data).length === 0) {
140+
return "No match found for "+(args.map(JSON.stringify).join(" "))
141+
}
159142

160-
lines = Object.keys(data).map(function (d) {
143+
var lines = Object.keys(data).map(function (d) {
144+
// strip keyname
161145
return data[d]
162-
}).map(function (data) {
163-
// turn a pkg data into a string
164-
// [name,who,desc,targets,keywords] tuple
165-
// also set longest to the longest name
166-
if (typeof data.keywords === "string") {
167-
data.keywords = data.keywords.split(/[,\s]+/)
146+
}).map(function(dat) {
147+
dat.author = dat.maintainers
148+
delete dat.maintainers
149+
dat.date = dat.time
150+
delete dat.time
151+
return dat
152+
}).map(function(dat) {
153+
// split keywords on whitespace or ,
154+
if (typeof dat.keywords === "string") {
155+
dat.keywords = dat.keywords.split(/[,\s]+/)
168156
}
169-
if (!Array.isArray(data.keywords)) data.keywords = []
170-
var l = [ data.name
171-
, data.description || ""
172-
, data.maintainers.join(" ")
173-
, data.time
174-
, data.version || ""
175-
, (data.keywords || []).join(" ")
176-
]
177-
l.forEach(function (s, i) {
178-
var len = s.length
179-
longest[i] = Math.min(maxLen[i] || Infinity
180-
,Math.max(longest[i] || 0, len))
181-
if (len > longest[i]) {
182-
l._undent = l._undent || []
183-
l._undent[i] = len - longest[i]
184-
}
185-
l[i] = ('' + l[i]).replace(/\s+/g, " ")
186-
})
187-
return l
188-
}).sort(function (a, b) {
189-
// a and b are "line" objects of [name, desc, maint, time, kw]
157+
if (Array.isArray(dat.keywords)) {
158+
dat.keywords = dat.keywords.join(' ')
159+
}
160+
161+
// split author on whitespace or ,
162+
if (typeof dat.author === "string") {
163+
dat.author = dat.author.split(/[,\s]+/)
164+
}
165+
if (Array.isArray(dat.author)) {
166+
dat.author = dat.author.join(' ')
167+
}
168+
return dat
169+
})
170+
171+
lines.sort(function(a, b) {
190172
var aa = a[sortField].toLowerCase()
191173
, bb = b[sortField].toLowerCase()
192174
return aa === bb ? 0
193-
: aa < bb ? (searchRev ? 1 : -1)
194-
: (searchRev ? -1 : 1)
195-
}).map(function (line) {
196-
return line.map(function (s, i) {
197-
spaces = spaces || longest.map(function (n) {
198-
return new Array(n + 2).join(" ")
199-
})
200-
var len = s.length
201-
if (line._undent && line._undent[i - 1]) {
202-
len += line._undent[i - 1] - 1
203-
}
204-
return s + spaces[i].substr(len)
205-
}).join(" ").substr(0, cols).trim()
206-
}).map(function (line) {
207-
// colorize!
208-
args.forEach(function (arg, i) {
209-
line = addColorMarker(line, arg, i)
210-
})
211-
return colorize(line).trim()
175+
: aa < bb ? -1 : 1
212176
})
213177

214-
if (lines.length === 0) {
215-
return "No match found for "+(args.map(JSON.stringify).join(" "))
216-
}
178+
if (searchRev) lines.reverse()
217179

218-
// build the heading padded to the longest in each field
219-
return headings.map(function (h, i) {
220-
var space = Math.max(2, 3 + (longest[i] || 0) - h.length)
221-
return h + (new Array(space).join(" "))
222-
}).join("").substr(0, cols).trim() + "\n" + lines.join("\n")
180+
var columns = npm.config.get("description")
181+
? ["name", "description", "author", "date", "version", "keywords"]
182+
: ["name", "author", "date", "version", "keywords"]
183+
184+
var output = columnify(lines, {
185+
include: columns
186+
, truncate: truncate
187+
, config: {
188+
name: { maxWidth: 40, truncate: false, truncateMarker: '' }
189+
, description: { maxWidth: 60 }
190+
, author: { maxWidth: 20 }
191+
, date: { maxWidth: 11 }
192+
, version: { maxWidth: 11 }
193+
, keywords: { maxWidth: Infinity }
194+
}
195+
})
196+
output = trimToMaxWidth(output)
197+
output = highlightSearchTerms(output, args)
223198

199+
return output
224200
}
225201

226202
var colors = [31, 33, 32, 36, 34, 35 ]
227203
, cl = colors.length
204+
228205
function addColorMarker (str, arg, i) {
229206
var m = i % cl + 1
230207
, markStart = String.fromCharCode(m)
@@ -260,3 +237,29 @@ function colorize (line) {
260237
var uncolor = npm.color ? "\033[0m" : ""
261238
return line.split("\u0000").join(uncolor)
262239
}
240+
241+
function getMaxWidth() {
242+
try {
243+
var tty = require("tty")
244+
, stdout = process.stdout
245+
, cols = !tty.isatty(stdout.fd) ? Infinity
246+
: process.stdout.getWindowSize()[0]
247+
cols = (cols == 0) ? Infinity : cols
248+
} catch (ex) { cols = Infinity }
249+
return cols
250+
}
251+
252+
function trimToMaxWidth(str) {
253+
var maxWidth = getMaxWidth()
254+
return str.split('\n').map(function(line) {
255+
return line.slice(0, maxWidth)
256+
}).join('\n')
257+
}
258+
259+
function highlightSearchTerms(str, terms) {
260+
terms.forEach(function (arg, i) {
261+
str = addColorMarker(str, arg, i)
262+
})
263+
264+
return colorize(str).trim()
265+
}

0 commit comments

Comments
 (0)