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

as-table.js (1 issue)

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