UNPKG

7.83 kBJavaScriptView Raw
1
2/**
3 * Module dependencies.
4 */
5
6var colors = require('colors/safe')
7 , utils = require('./utils')
8 , repeat = utils.repeat
9 , truncate = utils.truncate
10 , pad = utils.pad;
11
12/**
13 * Table constructor
14 *
15 * @param {Object} options
16 * @api public
17 */
18
19function Table (options){
20 this.options = utils.options({
21 chars: {
22 'top': '─'
23 , 'top-mid': '┬'
24 , 'top-left': '┌'
25 , 'top-right': '┐'
26 , 'bottom': '─'
27 , 'bottom-mid': '┴'
28 , 'bottom-left': '└'
29 , 'bottom-right': '┘'
30 , 'left': '│'
31 , 'left-mid': '├'
32 , 'mid': '─'
33 , 'mid-mid': '┼'
34 , 'right': '│'
35 , 'right-mid': '┤'
36 , 'middle': '│'
37 }
38 , truncate: '…'
39 , colWidths: []
40 , colAligns: []
41 , style: {
42 'padding-left': 1
43 , 'padding-right': 1
44 , head: ['red']
45 , border: ['grey']
46 , compact : false
47 }
48 , head: []
49 }, options);
50
51 if (options && options.rows) {
52 for (var i = 0; i < options.rows.length; i++) {
53 this.push(options.rows[i]);
54 }
55 }
56}
57
58/**
59 * Inherit from Array.
60 */
61
62Table.prototype.__proto__ = Array.prototype;
63
64/**
65 * Width getter
66 *
67 * @return {Number} width
68 * @api public
69 */
70
71Table.prototype.__defineGetter__('width', function (){
72 var str = this.toString().split("\n");
73 if (str.length) return str[0].length;
74 return 0;
75});
76
77/**
78 * Render to a string.
79 *
80 * @return {String} table representation
81 * @api public
82 */
83
84Table.prototype.render
85Table.prototype.toString = function (){
86 var ret = ''
87 , options = this.options
88 , style = options.style
89 , head = options.head
90 , chars = options.chars
91 , truncater = options.truncate
92 , colWidths = options.colWidths || new Array(this.head.length)
93 , totalWidth = 0;
94
95 if (!head.length && !this.length) return '';
96
97 if (!colWidths.length){
98 var all_rows = this.slice(0);
99 if (head.length) { all_rows = all_rows.concat([head]) };
100
101 all_rows.forEach(function(cells){
102 // horizontal (arrays)
103 if (typeof cells === 'object' && cells.length) {
104 extractColumnWidths(cells);
105
106 // vertical (objects)
107 } else {
108 var header_cell = Object.keys(cells)[0]
109 , value_cell = cells[header_cell];
110
111 colWidths[0] = Math.max(colWidths[0] || 0, get_width(header_cell) || 0);
112
113 // cross (objects w/ array values)
114 if (typeof value_cell === 'object' && value_cell.length) {
115 extractColumnWidths(value_cell, 1);
116 } else {
117 colWidths[1] = Math.max(colWidths[1] || 0, get_width(value_cell) || 0);
118 }
119 }
120 });
121 };
122
123 totalWidth = (colWidths.length == 1 ? colWidths[0] : colWidths.reduce(
124 function (a, b){
125 return a + b
126 })) + colWidths.length + 1;
127
128 function extractColumnWidths(arr, offset) {
129 var offset = offset || 0;
130 arr.forEach(function(cell, i){
131 colWidths[i + offset] = Math.max(colWidths[i + offset] || 0, get_width(cell) || 0);
132 });
133 };
134
135 function get_width(obj) {
136 return typeof obj == 'object' && obj.width != undefined
137 ? obj.width
138 : ((typeof obj == 'object' ? utils.strlen(obj.text) : utils.strlen(obj)) + (style['padding-left'] || 0) + (style['padding-right'] || 0))
139 }
140
141 // draws a line
142 function line (line, left, right, intersection){
143 var width = 0
144 , line =
145 left
146 + repeat(line, totalWidth - 2)
147 + right;
148
149 colWidths.forEach(function (w, i){
150 if (i == colWidths.length - 1) return;
151 width += w + 1;
152 line = line.substr(0, width) + intersection + line.substr(width + 1);
153 });
154
155 return applyStyles(options.style.border, line);
156 };
157
158 // draws the top line
159 function lineTop (){
160 var l = line(chars.top
161 , chars['top-left'] || chars.top
162 , chars['top-right'] || chars.top
163 , chars['top-mid']);
164 if (l)
165 ret += l + "\n";
166 };
167
168 function generateRow (items, style) {
169 var cells = []
170 , max_height = 0;
171
172 // prepare vertical and cross table data
173 if (!Array.isArray(items) && typeof items === "object") {
174 var key = Object.keys(items)[0]
175 , value = items[key]
176 , first_cell_head = true;
177
178 if (Array.isArray(value)) {
179 items = value;
180 items.unshift(key);
181 } else {
182 items = [key, value];
183 }
184 }
185
186 // transform array of item strings into structure of cells
187 items.forEach(function (item, i) {
188 var contents = item.toString().split("\n").reduce(function (memo, l) {
189 memo.push(string(l, i));
190 return memo;
191 }, [])
192
193 var height = contents.length;
194 if (height > max_height) { max_height = height };
195
196 cells.push({ contents: contents , height: height });
197 });
198
199 // transform vertical cells into horizontal lines
200 var lines = new Array(max_height);
201 cells.forEach(function (cell, i) {
202 cell.contents.forEach(function (line, j) {
203 if (!lines[j]) { lines[j] = [] };
204 if (style || (first_cell_head && i === 0 && options.style.head)) {
205 line = applyStyles(options.style.head, line)
206 }
207
208 lines[j].push(line);
209 });
210
211 // populate empty lines in cell
212 for (var j = cell.height, l = max_height; j < l; j++) {
213 if (!lines[j]) { lines[j] = [] };
214 lines[j].push(string('', i));
215 }
216 });
217 var ret = "";
218 lines.forEach(function (line, index) {
219 if (ret.length > 0) {
220 ret += "\n" + applyStyles(options.style.border, chars.left);
221 }
222
223 ret += line.join(applyStyles(options.style.border, chars.middle)) + applyStyles(options.style.border, chars.right);
224 });
225
226 return applyStyles(options.style.border, chars.left) + ret;
227 };
228
229 function applyStyles(styles, subject) {
230 if (!subject)
231 return '';
232 styles.forEach(function(style) {
233 subject = colors[style](subject);
234 });
235 return subject;
236 };
237
238 // renders a string, by padding it or truncating it
239 function string (str, index){
240 var str = String(typeof str == 'object' && str.text ? str.text : str)
241 , length = utils.strlen(str)
242 , width = colWidths[index]
243 - (style['padding-left'] || 0)
244 - (style['padding-right'] || 0)
245 , align = options.colAligns[index] || 'left';
246
247 return repeat(' ', style['padding-left'] || 0)
248 + (length == width ? str :
249 (length < width
250 ? pad(str, ( width + (str.length - length) ), ' ', align == 'left' ? 'right' :
251 (align == 'middle' ? 'both' : 'left'))
252 : (truncater ? truncate(str, width, truncater) : str))
253 )
254 + repeat(' ', style['padding-right'] || 0);
255 };
256
257 if (head.length){
258 lineTop();
259
260 ret += generateRow(head, style.head) + "\n"
261 }
262
263 if (this.length)
264 this.forEach(function (cells, i){
265 if (!head.length && i == 0)
266 lineTop();
267 else {
268 if (!style.compact || i<(!!head.length) ?1:0 || cells.length == 0){
269 var l = line(chars.mid
270 , chars['left-mid']
271 , chars['right-mid']
272 , chars['mid-mid']);
273 if (l)
274 ret += l + "\n"
275 }
276 }
277
278 if (cells.hasOwnProperty("length") && !cells.length) {
279 return
280 } else {
281 ret += generateRow(cells) + "\n";
282 };
283 });
284
285 var l = line(chars.bottom
286 , chars['bottom-left'] || chars.bottom
287 , chars['bottom-right'] || chars.bottom
288 , chars['bottom-mid']);
289 if (l)
290 ret += l;
291 else
292 // trim the last '\n' if we didn't add the bottom decoration
293 ret = ret.slice(0, -1);
294
295 return ret;
296};
297
298/**
299 * Module exports.
300 */
301
302module.exports = Table;
303
304module.exports.version = '0.0.1';