Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

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.
  • Loading branch information...
commit d5e340cc03eb96b359128ed07f0db43773b2e2c2 1 parent f0756e5
Tim Oxley authored March 25, 2012
14  doc/cli/npm-search.md
Source Rendered
@@ -3,7 +3,7 @@ npm-search(1) -- Search for packages
3 3
 
4 4
 ## SYNOPSIS
5 5
 
6  
-    npm search [search terms ...]
  6
+    npm search [--long] [search terms ...]
7 7
     npm s [search terms ...]
8 8
     npm se [search terms ...]
9 9
 
@@ -15,6 +15,18 @@ If a term starts with `/`, then it's interpreted as a regular expression.
15 15
 A trailing `/` will be ignored in this case.  (Note that many regular
16 16
 expression characters must be escaped or quoted in most shells.)
17 17
 
  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
+
18 30
 ## SEE ALSO
19 31
 
20 32
 * npm-registry(7)
167  lib/search.js
@@ -3,6 +3,7 @@ module.exports = exports = search
3 3
 
4 4
 var npm = require("./npm.js")
5 5
   , registry = npm.registry
  6
+  , columnify = require('columnify')
6 7
 
7 8
 search.usage = "npm search [some search terms ...]"
8 9
 
@@ -91,7 +92,8 @@ function stripData (data) {
91 92
                  && (new Date(data.time.modified).toISOString()
92 93
                      .split("T").join(" ")
93 94
                      .replace(/:[0-9]{2}\.[0-9]{3}Z$/, ""))
94  
-                 || "(prehistoric)"
  95
+                     .slice(0, -5) // remove time
  96
+                 || "prehistoric"
95 97
          }
96 98
 }
97 99
 
@@ -129,102 +131,77 @@ function match (words, arg) {
129 131
 }
130 132
 
131 133
 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(/^\-+/, "")
157 136
     , 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
+  }
159 142
 
160  
-  lines = Object.keys(data).map(function (d) {
  143
+  var lines = Object.keys(data).map(function (d) {
  144
+    // strip keyname
161 145
     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]+/)
168 156
     }
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) {
190 172
     var aa = a[sortField].toLowerCase()
191 173
       , bb = b[sortField].toLowerCase()
192 174
     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
212 176
   })
213 177
 
214  
-  if (lines.length === 0) {
215  
-    return "No match found for "+(args.map(JSON.stringify).join(" "))
216  
-  }
  178
+  if (searchRev) lines.reverse()
217 179
 
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)
223 198
 
  199
+  return output
224 200
 }
225 201
 
226 202
 var colors = [31, 33, 32, 36, 34, 35 ]
227 203
   , cl = colors.length
  204
+
228 205
 function addColorMarker (str, arg, i) {
229 206
   var m = i % cl + 1
230 207
     , markStart = String.fromCharCode(m)
@@ -260,3 +237,29 @@ function colorize (line) {
260 237
   var uncolor = npm.color ? "\033[0m" : ""
261 238
   return line.split("\u0000").join(uncolor)
262 239
 }
  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
+}
189  node_modules/columnify/Readme.md
Source Rendered
... ...
@@ -0,0 +1,189 @@
  1
+# columnify
  2
+
  3
+[![Build Status](https://travis-ci.org/timoxley/columnify.png?branch=master)](https://travis-ci.org/timoxley/columnify)
  4
+
  5
+Create text-based columns suitable for console output. 
  6
+Supports minimum and maximum column widths via truncation and text wrapping.
  7
+
  8
+Designed to [handle sensible wrapping in npm search results](https://github.com/isaacs/npm/pull/2328).
  9
+
  10
+`npm search` before & after integrating columnify:
  11
+
  12
+![npm-tidy-search](https://f.cloud.github.com/assets/43438/1848959/ae02ad04-76a1-11e3-8255-4781debffc26.gif)
  13
+
  14
+## Installation & Update
  15
+
  16
+```
  17
+$ npm install --save columnify@latest
  18
+```
  19
+
  20
+## Usage
  21
+
  22
+```js
  23
+var columnify = require('columnify')
  24
+var columns = columnify(data, options)
  25
+console.log(columns)
  26
+```
  27
+
  28
+## Examples
  29
+
  30
+### Simple Columns
  31
+
  32
+Text is aligned under column headings. Columns are automatically resized
  33
+to fit the content of the largest cell.  Each cell will be padded with
  34
+spaces to fill the available space and ensure column contents are
  35
+left-aligned.
  36
+
  37
+```js
  38
+var columnify = require('columnify')
  39
+
  40
+var columns = columnify([{
  41
+  name: 'mod1',
  42
+  version: '0.0.1'
  43
+}, {
  44
+  name: 'module2',
  45
+  version: '0.2.0'
  46
+}])
  47
+
  48
+console.log(columns)
  49
+```
  50
+```
  51
+NAME    VERSION
  52
+mod1    0.0.1  
  53
+module2 0.2.0  
  54
+```
  55
+
  56
+### Wrapping Column Cells
  57
+
  58
+You can define the maximum width before wrapping for individual cells in
  59
+columns. Minimum width is also supported. Wrapping will happen at word
  60
+boundaries. Empty cells or those which do not fill the max/min width
  61
+will be padded with spaces.
  62
+
  63
+```js
  64
+var columnify = require('columnify')
  65
+
  66
+var columns = columnify([{
  67
+  name: 'mod1',
  68
+  description: 'some description which happens to be far larger than the max',
  69
+  version: '0.0.1',
  70
+}, {
  71
+  name: 'module-two',
  72
+  description: 'another description larger than the max',
  73
+  version: '0.2.0',
  74
+})
  75
+
  76
+console.log(columns)
  77
+```
  78
+```
  79
+NAME       DESCRIPTION                    VERSION
  80
+mod1       some description which happens 0.0.1
  81
+           to be far larger than the max
  82
+module-two another description larger     0.2.0
  83
+           than the max
  84
+```
  85
+
  86
+### Truncated Columns
  87
+
  88
+You can disable wrapping and instead truncate content at the maximum
  89
+column width. Truncation respects word boundaries.  A truncation marker,
  90
+`…` will appear next to the last word in any truncated line.
  91
+
  92
+```js
  93
+var columns = columnify(data, {
  94
+  truncate: true,
  95
+  config: {
  96
+    description: {
  97
+      maxWidth: 20
  98
+    }
  99
+  }
  100
+})
  101
+
  102
+console.log(columns)
  103
+```
  104
+
  105
+```
  106
+NAME       DESCRIPTION          VERSION
  107
+mod1       some description…    0.0.1  
  108
+module-two another description… 0.2.0  
  109
+```
  110
+
  111
+
  112
+### Custom Truncation Marker
  113
+
  114
+You can change the truncation marker to something other than the default
  115
+`…`.
  116
+
  117
+```js
  118
+var columns = columnify(data, {
  119
+  truncate: true,
  120
+  truncateMarker: '>',
  121
+  widths: {
  122
+    description: {
  123
+      maxWidth: 20
  124
+    }
  125
+  }
  126
+})
  127
+
  128
+console.log(columns)
  129
+```
  130
+
  131
+```
  132
+NAME       DESCRIPTION          VERSION
  133
+mod1       some description>    0.0.1  
  134
+module-two another description> 0.2.0  
  135
+```
  136
+
  137
+### Custom Column Splitter
  138
+
  139
+If your columns need some bling, you can split columns with custom
  140
+characters.
  141
+
  142
+```js
  143
+
  144
+var columns = columnify(data, {
  145
+  columnSplitter: ' | '
  146
+})
  147
+
  148
+console.log(columns)
  149
+```
  150
+```
  151
+NAME       | DESCRIPTION                                                  | VERSION
  152
+mod1       | some description which happens to be far larger than the max | 0.0.1
  153
+module-two | another description larger than the max                      | 0.2.0
  154
+```
  155
+
  156
+### Filtering & Ordering Columns
  157
+
  158
+By default, all properties are converted into columns, whether or not
  159
+they exist on every object or not.
  160
+
  161
+To explicitly specify which columns to include, and in which order,
  162
+supply an "include" array:
  163
+
  164
+```js
  165
+var data = [{
  166
+  name: 'module1',
  167
+  description: 'some description',
  168
+  version: '0.0.1',
  169
+}, {
  170
+  name: 'module2',
  171
+  description: 'another description',
  172
+  version: '0.2.0',
  173
+}]
  174
+
  175
+var columns = columnify(data, {
  176
+  include: ['name', 'version'] // note description not included
  177
+})
  178
+
  179
+console.log(columns)
  180
+```
  181
+
  182
+```
  183
+NAME    VERSION
  184
+module1 0.0.1
  185
+module2 0.2.0
  186
+```
  187
+## License
  188
+
  189
+MIT
210  node_modules/columnify/index.js
... ...
@@ -0,0 +1,210 @@
  1
+"use strict"
  2
+
  3
+var utils = require('./utils')
  4
+var padRight = utils.padRight
  5
+var splitIntoLines = utils.splitIntoLines
  6
+var splitLongWords = utils.splitLongWords
  7
+
  8
+var DEFAULTS = {
  9
+  maxWidth: Infinity,
  10
+  minWidth: 0,
  11
+  columnSplitter: ' ',
  12
+  truncate: false,
  13
+  truncateMarker: '…',
  14
+  headingTransform: function(key) {
  15
+    return key.toUpperCase()
  16
+  },
  17
+  dataTransform: function(cell, column, index) {
  18
+    return cell
  19
+  }
  20
+}
  21
+
  22
+module.exports = function(items, options) {
  23
+
  24
+  options = options || {}
  25
+
  26
+  var columnConfigs = options.config || {}
  27
+  delete options.config // remove config so doesn't appear on every column.
  28
+
  29
+  // Option defaults inheritance:
  30
+  // options.config[columnName] => options => DEFAULTS
  31
+  options = mixin(options, DEFAULTS)
  32
+  options.config = options.config || Object.create(null)
  33
+
  34
+  options.spacing = options.spacing || '\n' // probably useless
  35
+
  36
+  var columnNames = options.include || [] // optional user-supplied columns to include
  37
+
  38
+  // if not suppled column names, automatically determine columns from data keys
  39
+  if (!columnNames.length) {
  40
+    items.forEach(function(item) {
  41
+      for (var columnName in item) {
  42
+        if (columnNames.indexOf(columnName) === -1) columnNames.push(columnName)
  43
+      }
  44
+    })
  45
+  }
  46
+
  47
+  // initialize column defaults (each column inherits from options.config)
  48
+  var columns = columnNames.reduce(function(columns, columnName) {
  49
+    var column = Object.create(options)
  50
+    columns[columnName] = mixin(column, columnConfigs[columnName])
  51
+    return columns
  52
+  }, Object.create(null))
  53
+
  54
+  // sanitize column settings
  55
+  columnNames.forEach(function(columnName) {
  56
+    var column = columns[columnName]
  57
+    column.maxWidth = Math.ceil(column.maxWidth)
  58
+    column.minWidth = Math.ceil(column.minWidth)
  59
+    column.truncate = !!column.truncate
  60
+  })
  61
+
  62
+  // sanitize data
  63
+  items = items.map(function(item) {
  64
+    var result = Object.create(null)
  65
+    columnNames.forEach(function(columnName) {
  66
+      // null/undefined -> ''
  67
+      result[columnName] = item[columnName] != null ? item[columnName] : ''
  68
+      // toString everything
  69
+      result[columnName] = '' + result[columnName]
  70
+      // remove funky chars
  71
+      result[columnName] = result[columnName].replace(/\s+/g, " ")
  72
+    })
  73
+    return result
  74
+  })
  75
+
  76
+  // transform data cells
  77
+  columnNames.forEach(function(columnName) {
  78
+    var column = columns[columnName]
  79
+    items = items.map(function(item, index) {
  80
+      item[columnName] = column.dataTransform(item[columnName], column, index)
  81
+      return item
  82
+    })
  83
+  })
  84
+
  85
+  // add headers
  86
+  var headers = {}
  87
+  columnNames.forEach(function(columnName) {
  88
+    var column = columns[columnName]
  89
+    headers[columnName] = column.headingTransform(columnName)
  90
+  })
  91
+  items.unshift(headers)
  92
+
  93
+  // get actual max-width between min & max
  94
+  // based on length of data in columns
  95
+  columnNames.forEach(function(columnName) {
  96
+    var column = columns[columnName]
  97
+    column.width = items.map(function(item) {
  98
+      return item[columnName]
  99
+    }).reduce(function(min, cur) {
  100
+      return Math.max(min, Math.min(column.maxWidth, Math.max(column.minWidth, cur.length)))
  101
+    }, 0)
  102
+  })
  103
+
  104
+  // split long words so they can break onto multiple lines
  105
+  columnNames.forEach(function(columnName) {
  106
+    var column = columns[columnName]
  107
+    items = items.map(function(item) {
  108
+      item[columnName] = splitLongWords(item[columnName], column.width, column.truncateMarker)
  109
+      return item
  110
+    })
  111
+  })
  112
+
  113
+  // wrap long lines. each item is now an array of lines.
  114
+  columnNames.forEach(function(columnName) {
  115
+    var column = columns[columnName]
  116
+    items = items.map(function(item, index) {
  117
+      var cell = item[columnName]
  118
+      item[columnName] = splitIntoLines(cell, column.width)
  119
+
  120
+      // if truncating required, only include first line + add truncation char
  121
+      if (column.truncate && item[columnName].length > 1) {
  122
+          item[columnName] = splitIntoLines(cell, column.width - column.truncateMarker.length)
  123
+          var firstLine = item[columnName][0]
  124
+          if (!endsWith(firstLine, column.truncateMarker)) item[columnName][0] += column.truncateMarker
  125
+          item[columnName] = item[columnName].slice(0, 1)
  126
+      }
  127
+      return item
  128
+    })
  129
+  })
  130
+
  131
+  // recalculate column widths from truncated output/lines
  132
+  columnNames.forEach(function(columnName) {
  133
+    var column = columns[columnName]
  134
+    column.width = items.map(function(item) {
  135
+      return item[columnName].reduce(function(min, cur) {
  136
+        return Math.max(min, Math.min(column.maxWidth, Math.max(column.minWidth, cur.length)))
  137
+      }, 0)
  138
+    }).reduce(function(min, cur) {
  139
+      return Math.max(min, Math.min(column.maxWidth, Math.max(column.minWidth, cur)))
  140
+    }, 0)
  141
+  })
  142
+
  143
+  var rows = createRows(items, columns, columnNames) // merge lines into rows
  144
+
  145
+  // conceive output
  146
+  return rows.reduce(function(output, row) {
  147
+    return output.concat(row.reduce(function(rowOut, line) {
  148
+      return rowOut.concat(line.join(options.columnSplitter))
  149
+    }, []))
  150
+  }, []).join(options.spacing)
  151
+}
  152
+
  153
+/**
  154
+ * Convert wrapped lines into rows with padded values.
  155
+ *
  156
+ * @param Array items data to process
  157
+ * @param Array columns column width settings for wrapping
  158
+ * @param Array columnNames column ordering
  159
+ * @return Array items wrapped in arrays, corresponding to lines
  160
+ */
  161
+
  162
+function createRows(items, columns, columnNames) {
  163
+  return items.map(function(item) {
  164
+    var row = []
  165
+    var numLines = 0
  166
+    columnNames.forEach(function(columnName) {
  167
+      numLines = Math.max(numLines, item[columnName].length)
  168
+    })
  169
+    // combine matching lines of each rows
  170
+    for (var i = 0; i < numLines; i++) {
  171
+      row[i] = row[i] || []
  172
+      columnNames.forEach(function(columnName) {
  173
+        var column = columns[columnName]
  174
+        var val = item[columnName][i] || '' // || '' ensures empty columns get padded
  175
+        row[i].push(padRight(val, column.width))
  176
+      })
  177
+    }
  178
+    return row
  179
+  })
  180
+}
  181
+
  182
+/**
  183
+ * Generic source->target mixin.
  184
+ * Copy properties from `source` into `target` if target doesn't have them.
  185
+ * Destructive. Modifies `target`.
  186
+ *
  187
+ * @param target Object target for mixin properties.
  188
+ * @param source Object source of mixin properties.
  189
+ * @return Object `target` after mixin applied.
  190
+ */
  191
+
  192
+function mixin(target, source) {
  193
+  source = source || {}
  194
+  for (var key in source) {
  195
+    if (target.hasOwnProperty(key)) continue
  196
+    target[key] = source[key]
  197
+  }
  198
+  return target
  199
+}
  200
+
  201
+/**
  202
+ * Adapted from String.prototype.endsWith polyfill.
  203
+ */
  204
+
  205
+function endsWith(target, searchString, position) {
  206
+  position = position || target.length;
  207
+  position = position - searchString.length;
  208
+  var lastIndex = target.lastIndexOf(searchString);
  209
+  return lastIndex !== -1 && lastIndex === position;
  210
+}
42  node_modules/columnify/package.json
... ...
@@ -0,0 +1,42 @@
  1
+{
  2
+  "name": "columnify",
  3
+  "version": "0.1.2",
  4
+  "description": "Render data in text columns, supports in-column text-wrap.",
  5
+  "main": "index.js",
  6
+  "scripts": {
  7
+    "test": "tap test"
  8
+  },
  9
+  "author": {
  10
+    "name": "Tim Oxley"
  11
+  },
  12
+  "license": "MIT",
  13
+  "devDependencies": {
  14
+    "tape": "~2.3.0",
  15
+    "tap": "~0.4.6"
  16
+  },
  17
+  "repository": {
  18
+    "type": "git",
  19
+    "url": "git://github.com/timoxley/columnify.git"
  20
+  },
  21
+  "keywords": [
  22
+    "column",
  23
+    "text",
  24
+    "ansi",
  25
+    "console",
  26
+    "terminal",
  27
+    "wrap",
  28
+    "table"
  29
+  ],
  30
+  "bugs": {
  31
+    "url": "https://github.com/timoxley/columnify/issues"
  32
+  },
  33
+  "homepage": "https://github.com/timoxley/columnify",
  34
+  "readme": "# columnify\n\n[![Build Status](https://travis-ci.org/timoxley/columnify.png?branch=master)](https://travis-ci.org/timoxley/columnify)\n\nCreate text-based columns suitable for console output. \nSupports minimum and maximum column widths via truncation and text wrapping.\n\nDesigned to [handle sensible wrapping in npm search results](https://github.com/isaacs/npm/pull/2328).\n\n`npm search` before & after integrating columnify:\n\n![npm-tidy-search](https://f.cloud.github.com/assets/43438/1848959/ae02ad04-76a1-11e3-8255-4781debffc26.gif)\n\n## Installation & Update\n\n```\n$ npm install --save columnify@latest\n```\n\n## Usage\n\n```js\nvar columnify = require('columnify')\nvar columns = columnify(data, options)\nconsole.log(columns)\n```\n\n## Examples\n\n### Simple Columns\n\nText is aligned under column headings. Columns are automatically resized\nto fit the content of the largest cell.  Each cell will be padded with\nspaces to fill the available space and ensure column contents are\nleft-aligned.\n\n```js\nvar columnify = require('columnify')\n\nvar columns = columnify([{\n  name: 'mod1',\n  version: '0.0.1'\n}, {\n  name: 'module2',\n  version: '0.2.0'\n}])\n\nconsole.log(columns)\n```\n```\nNAME    VERSION\nmod1    0.0.1  \nmodule2 0.2.0  \n```\n\n### Wrapping Column Cells\n\nYou can define the maximum width before wrapping for individual cells in\ncolumns. Minimum width is also supported. Wrapping will happen at word\nboundaries. Empty cells or those which do not fill the max/min width\nwill be padded with spaces.\n\n```js\nvar columnify = require('columnify')\n\nvar columns = columnify([{\n  name: 'mod1',\n  description: 'some description which happens to be far larger than the max',\n  version: '0.0.1',\n}, {\n  name: 'module-two',\n  description: 'another description larger than the max',\n  version: '0.2.0',\n})\n\nconsole.log(columns)\n```\n```\nNAME       DESCRIPTION                    VERSION\nmod1       some description which happens 0.0.1\n           to be far larger than the max\nmodule-two another description larger     0.2.0\n           than the max\n```\n\n### Truncated Columns\n\nYou can disable wrapping and instead truncate content at the maximum\ncolumn width. Truncation respects word boundaries.  A truncation marker,\n`…` will appear next to the last word in any truncated line.\n\n```js\nvar columns = columnify(data, {\n  truncate: true,\n  config: {\n    description: {\n      maxWidth: 20\n    }\n  }\n})\n\nconsole.log(columns)\n```\n\n```\nNAME       DESCRIPTION          VERSION\nmod1       some description…    0.0.1  \nmodule-two another description… 0.2.0  \n```\n\n\n### Custom Truncation Marker\n\nYou can change the truncation marker to something other than the default\n`…`.\n\n```js\nvar columns = columnify(data, {\n  truncate: true,\n  truncateMarker: '>',\n  widths: {\n    description: {\n      maxWidth: 20\n    }\n  }\n})\n\nconsole.log(columns)\n```\n\n```\nNAME       DESCRIPTION          VERSION\nmod1       some description>    0.0.1  \nmodule-two another description> 0.2.0  \n```\n\n### Custom Column Splitter\n\nIf your columns need some bling, you can split columns with custom\ncharacters.\n\n```js\n\nvar columns = columnify(data, {\n  columnSplitter: ' | '\n})\n\nconsole.log(columns)\n```\n```\nNAME       | DESCRIPTION                                                  | VERSION\nmod1       | some description which happens to be far larger than the max | 0.0.1\nmodule-two | another description larger than the max                      | 0.2.0\n```\n\n### Filtering & Ordering Columns\n\nBy default, all properties are converted into columns, whether or not\nthey exist on every object or not.\n\nTo explicitly specify which columns to include, and in which order,\nsupply an \"include\" array:\n\n```js\nvar data = [{\n  name: 'module1',\n  description: 'some description',\n  version: '0.0.1',\n}, {\n  name: 'module2',\n  description: 'another description',\n  version: '0.2.0',\n}]\n\nvar columns = columnify(data, {\n  include: ['name', 'version'] // note description not included\n})\n\nconsole.log(columns)\n```\n\n```\nNAME    VERSION\nmodule1 0.0.1\nmodule2 0.2.0\n```\n## License\n\nMIT\n",
  35
+  "readmeFilename": "Readme.md",
  36
+  "_id": "columnify@0.1.2",
  37
+  "dist": {
  38
+    "shasum": "ab1a1f1e37b26ba4b87c6920fb717fe51c827042"
  39
+  },
  40
+  "_from": "columnify@0.1.2",
  41
+  "_resolved": "https://registry.npmjs.org/columnify/-/columnify-0.1.2.tgz"
  42
+}
76  node_modules/columnify/utils.js
... ...
@@ -0,0 +1,76 @@
  1
+/**
  2
+ * Pad `str` up to total length `max` with `chr`.
  3
+ * If `str` is longer than `max`, padRight will return `str` unaltered.
  4
+ *
  5
+ * @param String str string to pad
  6
+ * @param Number max total length of output string
  7
+ * @param String chr optional. Character to pad with. default: ' '
  8
+ * @return String padded str
  9
+ */
  10
+
  11
+function padRight(str, max, chr) {
  12
+  str = str != null ? str : ''
  13
+  str = String(str)
  14
+  var length = 1 + max - str.length
  15
+  if (length <= 0) return str
  16
+  return str + Array.apply(null, {length: length})
  17
+  .join(chr || ' ')
  18
+}
  19
+
  20
+/**
  21
+ * Split a String `str` into lines of maxiumum length `max`.
  22
+ * Splits on word boundaries.
  23
+ *
  24
+ * @param String str string to split
  25
+ * @param Number max length of each line
  26
+ * @return Array Array containing lines.
  27
+ */
  28
+
  29
+function splitIntoLines(str, max) {
  30
+  return str.trim().split(' ').reduce(function(lines, word) {
  31
+    var line = lines[lines.length - 1]
  32
+    if (line && line.join(' ').length + word.length < max) {
  33
+      lines[lines.length - 1].push(word) // add to line
  34
+    }
  35
+    else lines.push([word]) // new line
  36
+    return lines
  37
+  }, []).map(function(l) {
  38
+    return l.join(' ')
  39
+  })
  40
+}
  41
+
  42
+/**
  43
+ * Add spaces and `truncationChar` between words of
  44
+ * `str` which are longer than `max`.
  45
+ *
  46
+ * @param String str string to split
  47
+ * @param Number max length of each line
  48
+ * @param Number truncationChar character to append to split words
  49
+ * @return String
  50
+ */
  51
+
  52
+function splitLongWords(str, max, truncationChar, result) {
  53
+  str = str.trim()
  54
+  result = result || []
  55
+  if (!str) return result.join(' ') || ''
  56
+  var words = str.split(' ')
  57
+  var word = words.shift() || str
  58
+
  59
+  if (word.length > max) {
  60
+    var remainder = word.slice(max - truncationChar.length) // get remainder
  61
+    words.unshift(remainder) // save remainder for next loop
  62
+
  63
+    word = word.slice(0, max - truncationChar.length) // grab truncated word
  64
+    word += truncationChar // add trailing … or whatever
  65
+  }
  66
+  result.push(word)
  67
+  return splitLongWords(words.join(' '), max, truncationChar, result)
  68
+}
  69
+
  70
+/**
  71
+ * Exports
  72
+ */
  73
+
  74
+module.exports.padRight = padRight
  75
+module.exports.splitIntoLines = splitIntoLines
  76
+module.exports.splitLongWords = splitLongWords
6  package.json
@@ -79,7 +79,8 @@
79 79
     "text-table": "~0.2.0",
80 80
     "ansicolors": "~0.3.2",
81 81
     "ansistyles": "~0.1.3",
82  
-    "path-is-inside": "~1.0.0"
  82
+    "path-is-inside": "~1.0.0",
  83
+    "columnify": "0.1.2"
83 84
   },
84 85
   "bundleDependencies": [
85 86
     "semver",
@@ -129,7 +130,8 @@
129 130
     "text-table",
130 131
     "ansicolors",
131 132
     "ansistyles",
132  
-    "path-is-inside"
  133
+    "path-is-inside",
  134
+    "columnify"
133 135
   ],
134 136
   "devDependencies": {
135 137
     "ronn": "~0.3.6",

0 notes on commit d5e340c

Please sign in to comment.
Something went wrong with that request. Please try again.