• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict'
2
3var ms = require('mississippi')
4var jsonstream = require('JSONStream')
5var columnify = require('columnify')
6
7// This module consumes package data in the following format:
8//
9// {
10//   name: String,
11//   description: String,
12//   maintainers: [{ username: String, email: String }],
13//   keywords: String | [String],
14//   version: String,
15//   date: Date // can be null,
16// }
17//
18// The returned stream will format this package data
19// into a byte stream of formatted, displayable output.
20
21module.exports = formatPackageStream
22function formatPackageStream (opts) {
23  opts = opts || {}
24  if (opts.json) {
25    return jsonOutputStream()
26  } else {
27    return textOutputStream(opts)
28  }
29}
30
31function jsonOutputStream () {
32  return ms.pipeline.obj(
33    ms.through.obj(),
34    jsonstream.stringify('[', ',', ']'),
35    ms.through()
36  )
37}
38
39function textOutputStream (opts) {
40  var line = 0
41  return ms.through.obj(function (pkg, enc, cb) {
42    cb(null, prettify(pkg, ++line, opts))
43  })
44}
45
46function prettify (data, num, opts) {
47  opts = opts || {}
48  var truncate = !opts.long
49
50  var pkg = normalizePackage(data, opts)
51
52  var columns = opts.description
53    ? ['name', 'description', 'author', 'date', 'version', 'keywords']
54    : ['name', 'author', 'date', 'version', 'keywords']
55
56  if (opts.parseable) {
57    return columns.map(function (col) {
58      return pkg[col] && ('' + pkg[col]).replace(/\t/g, ' ')
59    }).join('\t')
60  }
61
62  var output = columnify(
63    [pkg],
64    {
65      include: columns,
66      showHeaders: num <= 1,
67      columnSplitter: ' | ',
68      truncate: truncate,
69      config: {
70        name: { minWidth: 25, maxWidth: 25, truncate: false, truncateMarker: '' },
71        description: { minWidth: 20, maxWidth: 20 },
72        author: { minWidth: 15, maxWidth: 15 },
73        date: { maxWidth: 11 },
74        version: { minWidth: 8, maxWidth: 8 },
75        keywords: { maxWidth: Infinity }
76      }
77    }
78  )
79  output = trimToMaxWidth(output)
80  if (opts.color) {
81    output = highlightSearchTerms(output, opts.args)
82  }
83  return output
84}
85
86var colors = [31, 33, 32, 36, 34, 35]
87var cl = colors.length
88
89function addColorMarker (str, arg, i) {
90  var m = i % cl + 1
91  var markStart = String.fromCharCode(m)
92  var markEnd = String.fromCharCode(0)
93
94  if (arg.charAt(0) === '/') {
95    return str.replace(
96      new RegExp(arg.substr(1, arg.length - 2), 'gi'),
97      function (bit) { return markStart + bit + markEnd }
98    )
99  }
100
101  // just a normal string, do the split/map thing
102  var pieces = str.toLowerCase().split(arg.toLowerCase())
103  var p = 0
104
105  return pieces.map(function (piece) {
106    piece = str.substr(p, piece.length)
107    var mark = markStart +
108               str.substr(p + piece.length, arg.length) +
109               markEnd
110    p += piece.length + arg.length
111    return piece + mark
112  }).join('')
113}
114
115function colorize (line) {
116  for (var i = 0; i < cl; i++) {
117    var m = i + 1
118    var color = '\u001B[' + colors[i] + 'm'
119    line = line.split(String.fromCharCode(m)).join(color)
120  }
121  var uncolor = '\u001B[0m'
122  return line.split('\u0000').join(uncolor)
123}
124
125function getMaxWidth () {
126  var cols
127  try {
128    var tty = require('tty')
129    var stdout = process.stdout
130    cols = !tty.isatty(stdout.fd) ? Infinity : process.stdout.getWindowSize()[0]
131    cols = (cols === 0) ? Infinity : cols
132  } catch (ex) { cols = Infinity }
133  return cols
134}
135
136function trimToMaxWidth (str) {
137  var maxWidth = getMaxWidth()
138  return str.split('\n').map(function (line) {
139    return line.slice(0, maxWidth)
140  }).join('\n')
141}
142
143function highlightSearchTerms (str, terms) {
144  terms.forEach(function (arg, i) {
145    str = addColorMarker(str, arg, i)
146  })
147
148  return colorize(str).trim()
149}
150
151function normalizePackage (data, opts) {
152  opts = opts || {}
153  return {
154    name: data.name,
155    description: opts.description ? data.description : '',
156    author: (data.maintainers || []).map(function (m) {
157      return '=' + m.username
158    }).join(' '),
159    keywords: Array.isArray(data.keywords)
160      ? data.keywords.join(' ')
161      : typeof data.keywords === 'string'
162        ? data.keywords.replace(/[,\s]+/, ' ')
163        : '',
164    version: data.version,
165    date: (data.date &&
166          (data.date.toISOString() // remove time
167            .split('T').join(' ')
168            .replace(/:[0-9]{2}\.[0-9]{3}Z$/, ''))
169            .slice(0, -5)) ||
170          'prehistoric'
171  }
172}
173