Completed
Push — master ( 48aa77...64eb14 )
by Vitaly
24s
created

as-table.js (1 issue)

1
"use strict";
2
3
const
4
5
O = Object,
0 ignored issues
show
Comprehensibility Best Practice introduced by
You seem to be aliasing the built-in name Object as O. This makes your code very difficult to follow, consider using the built-in name directly.
Loading history...
6
7
{ first, strlen } = require ('printable-characters'), // handles ANSI codes and invisible characters
8
9
limit = (s, n) => (first (s, n - 1) + '…'),
10
11
asColumns = (rows, cfg_) => {
12
    
13
    const
14
15
        zip = (arrs, f) => arrs.reduce ((a, b) => b.map ((b, i) => [...a[i] || [], b]), []).map (args => f (...args)),
16
17
    /*  Convert cell data to string (converting multiline text to singleline) */
18
19
        cells           = rows.map (r => r.map (c => (c === undefined) ? '' : cfg_.print (c).replace (/\n/g, '\\n'))),
20
21
    /*  Compute column widths (per row) and max widths (per column)     */
22
23
        cellWidths      = cells.map (r => r.map (strlen)),
24
        maxWidths       = zip (cellWidths, Math.max),
25
26
    /*  Default config     */
27
28
        cfg             = O.assign ({
29
                            delimiter: '  ',
30
                            minColumnWidths: maxWidths.map (x => 0),
31
                            maxTotalWidth: 0 }, cfg_),
32
33
        delimiterLength = strlen (cfg.delimiter),
34
35
    /*  Project desired column widths, taking maxTotalWidth and minColumnWidths in account.     */
36
37
        totalWidth      = maxWidths.reduce ((a, b) => a + b, 0),
38
        relativeWidths  = maxWidths.map (w => w / totalWidth),
39
        maxTotalWidth   = cfg.maxTotalWidth - (delimiterLength * (maxWidths.length - 1)),
40
        excessWidth     = Math.max (0, totalWidth - maxTotalWidth),
41
        computedWidths  = zip ([cfg.minColumnWidths, maxWidths, relativeWidths],
42
                            (min, max, relative) => Math.max (min, Math.floor (max - excessWidth * relative))),
43
44
    /*  This is how many symbols we should pad or cut (per column).  */
45
46
        restCellWidths  = cellWidths.map (widths => zip ([computedWidths, widths], (a, b) => a - b))
47
48
    /*  Perform final composition.   */
49
50
        return zip ([cells, restCellWidths], (a, b) =>
51
                zip ([a, b], (str, w) => (w >= 0)
52
                                            ? (str + ' '.repeat (w))
53
                                            : (limit (str, strlen (str) + w))).join (cfg.delimiter))
54
},
55
56
asTable = cfg => O.assign (arr => {
57
58
/*  Print arrays  */
59
60
    if (arr[0] && Array.isArray (arr[0]))
61
        return asColumns (arr, cfg).join ('\n')
62
63
/*  Print objects   */
64
65
    const colNames        = [...new Set ([].concat (...arr.map (O.keys)))],
66
          columns         = [colNames.map (cfg.title), ...arr.map (o => colNames.map (key => o[key]))],
67
          lines           = asColumns (columns, cfg)
68
69
    return [lines[0], cfg.dash.repeat (strlen (lines[0])), ...lines.slice (1)].join ('\n')
70
71
}, cfg, {
72
73
    configure: newConfig => asTable (O.assign ({}, cfg, newConfig)),
74
})
75
76
module.exports = asTable ({
77
78
    maxTotalWidth: Number.MAX_SAFE_INTEGER,
79
    print: String,
80
    title: String,
81
    dash: '-'
82
})