liudong
2023-05-29 340f156319b863525e50e900c58e59b86ecb3d5e
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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
/*
 * grunt
 * http://gruntjs.com/
 *
 * Copyright (c) 2016 "Cowboy" Ben Alman
 * Licensed under the MIT license.
 * https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
 */
 
'use strict';
 
var chalk = require('chalk');
var _ = require('lodash');
 
// Pretty-format a word list.
exports.wordlist = function(arr, options) {
  options = _.defaults(options || {}, {
    separator: ', ',
    color: 'cyan'
  });
  return arr.map(function(item) {
    return options.color ? chalk[options.color](item) : item;
  }).join(options.separator);
};
 
// Return a string, uncolored (suitable for testing .length, etc).
exports.uncolor = function(str) {
  return str.replace(/\x1B\[\d+m/g, '');
};
 
// Word-wrap text to a given width, permitting ANSI color codes.
exports.wraptext = function(width, text) {
  // notes to self:
  // grab 1st character or ansi code from string
  // if ansi code, add to array and save for later, strip from front of string
  // if character, add to array and increment counter, strip from front of string
  // if width + 1 is reached and current character isn't space:
  //  slice off everything after last space in array and prepend it to string
  //  etc
 
  // This result array will be joined on \n.
  var result = [];
  var matches, color, tmp;
  var captured = [];
  var charlen = 0;
 
  while (matches = text.match(/(?:(\x1B\[\d+m)|\n|(.))([\s\S]*)/)) {
    // Updated text to be everything not matched.
    text = matches[3];
 
    // Matched a color code?
    if (matches[1]) {
      // Save last captured color code for later use.
      color = matches[1];
      // Capture color code.
      captured.push(matches[1]);
      continue;
 
    // Matched a non-newline character?
    } else if (matches[2]) {
      // If this is the first character and a previous color code was set, push
      // that onto the captured array first.
      if (charlen === 0 && color) { captured.push(color); }
      // Push the matched character.
      captured.push(matches[2]);
      // Increment the current charlen.
      charlen++;
      // If not yet at the width limit or a space was matched, continue.
      if (charlen <= width || matches[2] === ' ') { continue; }
      // The current charlen exceeds the width and a space wasn't matched.
      // "Roll everything back" until the last space character.
      tmp = captured.lastIndexOf(' ');
      text = captured.slice(tmp === -1 ? tmp : tmp + 1).join('') + text;
      captured = captured.slice(0, tmp);
    }
 
    // The limit has been reached. Push captured string onto result array.
    result.push(captured.join(''));
 
    // Reset captured array and charlen.
    captured = [];
    charlen = 0;
  }
 
  result.push(captured.join(''));
  return result.join('\n');
};
 
// Format output into columns, wrapping words as-necessary.
exports.table = function(widths, texts) {
  var rows = [];
  widths.forEach(function(width, i) {
    var lines = this.wraptext(width, texts[i]).split('\n');
    lines.forEach(function(line, j) {
      var row = rows[j];
      if (!row) { row = rows[j] = []; }
      row[i] = line;
    });
  }, this);
 
  var lines = [];
  rows.forEach(function(row) {
    var txt = '';
    var column;
    for (var i = 0; i < row.length; i++) {
      column = row[i] || '';
      txt += column;
      var diff = widths[i] - this.uncolor(column).length;
      if (diff > 0) { txt += _.repeat(' ', diff); }
    }
    lines.push(txt);
  }, this);
 
  return lines.join('\n');
};