File size: 3,300 Bytes
5641073
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
"use strict";

const O = Object
    , { first, strlen } = require ('printable-characters') // handles ANSI codes and invisible characters
    , limit = (s, n) => (first (s, n - 1) + '…')

const asColumns = (rows, cfg_) => {
    
    const

        zip = (arrs, f) => arrs.reduce ((a, b) => b.map ((b, i) => [...a[i] || [], b]), []).map (args => f (...args)),

    /*  Convert cell data to string (converting multiline text to singleline) */

        cells           = rows.map (r => r.map (c => c.replace (/\n/g, '\\n'))),

    /*  Compute column widths (per row) and max widths (per column)     */

        cellWidths      = cells.map (r => r.map (strlen)),
        maxWidths       = zip (cellWidths, Math.max),

    /*  Default config     */

        cfg             = O.assign ({
                            delimiter: '  ',
                            minColumnWidths: maxWidths.map (x => 0),
                            maxTotalWidth: 0 }, cfg_),

        delimiterLength = strlen (cfg.delimiter),

    /*  Project desired column widths, taking maxTotalWidth and minColumnWidths in account.     */

        totalWidth      = maxWidths.reduce ((a, b) => a + b, 0),
        relativeWidths  = maxWidths.map (w => w / totalWidth),
        maxTotalWidth   = cfg.maxTotalWidth - (delimiterLength * (maxWidths.length - 1)),
        excessWidth     = Math.max (0, totalWidth - maxTotalWidth),
        computedWidths  = zip ([cfg.minColumnWidths, maxWidths, relativeWidths],
                            (min, max, relative) => Math.max (min, Math.floor (max - excessWidth * relative))),

    /*  This is how many symbols we should pad or cut (per column).  */

        restCellWidths  = cellWidths.map (widths => zip ([computedWidths, widths], (a, b) => a - b))

    /*  Perform final composition.   */

        return zip ([cells, restCellWidths], (a, b) =>
                zip ([a, b], (str, w) => (w >= 0)
                                            ? (cfg.right ? (' '.repeat (w) + str) : (str + ' '.repeat (w)))
                                            : (limit (str, strlen (str) + w))).join (cfg.delimiter))
}

const asTable = cfg => O.assign (arr => {

/*  Print arrays  */

    if (arr[0] && Array.isArray (arr[0])) {
        return asColumns (arr.map (r => r.map (
                                                (c, i) => (c === undefined) ? '' : cfg.print (c, i)
                                              )
                                  ),
                          cfg).join ('\n')
    }

/*  Print objects   */

    const colNames = [...new Set ([].concat (...arr.map (O.keys)))],
          columns  = [colNames.map (cfg.title),
                      ...arr.map (o => colNames.map (
                                           key => (o[key] === undefined) ? '' : cfg.print (o[key], key)
                                       )
                                 )
                     ],
          lines    = asColumns (columns, cfg)

    return (cfg.dash ? [lines[0], cfg.dash.repeat (strlen (lines[0])), ...lines.slice (1)] : lines).join ('\n')

}, cfg, {

    configure: newConfig => asTable (O.assign ({}, cfg, newConfig)),
})

module.exports = asTable ({

    maxTotalWidth: Number.MAX_SAFE_INTEGER,
    print: String,
    title: String,
    dash: '-',
    right: false
})