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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
'use strict'
const os = require('os')
const t = require('typical')
 
/**
 * @module wordwrapjs
 */
 
const re = {
  chunk: /[^\s-]+?-\b|\S+|\s+|\r\n?|\n/g,
  ansiEscapeSequence: /\u001b.*?m/g
}
 
/**
 * @alias module:wordwrapjs
 * @typicalname wordwrap
 */
class WordWrap {
  constructor (text, options) {
    options = options || {}
    if (!t.isDefined(text)) text = ''
 
    this._lines = String(text).split(/\r\n|\n/g)
    this.options = options
    this.options.width = options.width === undefined ? 30 : options.width
  }
 
  lines () {
    const flatten = require('reduce-flatten')
 
    /* trim each line of the supplied text */
    return this._lines.map(trimLine.bind(this))
 
      /* split each line into an array of chunks, else mark it empty */
      .map(line => line.match(re.chunk) || [ '~~empty~~' ])
 
      /* optionally, break each word on the line into pieces */
      .map(lineWords => {
        if (this.options.break) {
          return lineWords.map(breakWord.bind(this))
        } else {
          return lineWords
        }
      })
      .map(lineWords => lineWords.reduce(flatten, []))
 
      /* transforming the line of words to one or more new lines wrapped to size */
      .map(lineWords => {
        return lineWords
          .reduce((lines, word) => {
            let currentLine = lines[lines.length - 1]
            if (replaceAnsi(word).length + replaceAnsi(currentLine).length > this.options.width) {
              lines.push(word)
            } else {
              lines[lines.length - 1] += word
            }
            return lines
          }, [ '' ])
      })
      .reduce(flatten, [])
 
      /* trim the wrapped lines */
      .map(trimLine.bind(this))
 
      /* filter out empty lines */
      .filter(line => line.trim())
 
      /* restore the user's original empty lines */
      .map(line => line.replace('~~empty~~', ''))
  }
 
  wrap () {
    return this.lines().join(os.EOL)
  }
 
  toString () {
    return this.wrap()
  }
 
  /**
   * @param {string} - the input text to wrap
   * @param [options] {object} - optional configuration
   * @param [options.width] {number} - the max column width in characters (defaults to 30).
   * @param [options.break] {boolean} - if true, words exceeding the specified `width` will be forcefully broken
   * @param [options.noTrim] {boolean} - By default, each line output is trimmed. If `noTrim` is set, no line-trimming occurs - all whitespace from the input text is left in.
   * @return {string}
   */
  static wrap (text, options) {
    const block = new this(text, options)
    return block.wrap()
  }
 
  /**
   * Wraps the input text, returning an array of strings (lines).
   * @param {string} - input text
   * @param {object} - Accepts same options as constructor.
   */
  static lines (text, options) {
    const block = new this(text, options)
    return block.lines()
  }
 
  /**
   * Returns true if the input text would be wrapped if passed into `.wrap()`.
   * @param {string} - input text
   * @return {boolean}
   */
  static isWrappable (text) {
    if (t.isDefined(text)) {
      text = String(text)
      var matches = text.match(re.chunk)
      return matches ? matches.length > 1 : false
    }
  }
 
  /**
   * Splits the input text into an array of words and whitespace.
   * @param {string} - input text
   * @returns {string[]}
   */
  static getChunks (text) {
    return text.match(re.chunk) || []
  }
}
 
function trimLine (line) {
  return this.options.noTrim ? line : line.trim()
}
 
function replaceAnsi (string) {
  return string.replace(re.ansiEscapeSequence, '')
}
 
/* break a word into several pieces */
function breakWord (word) {
  if (replaceAnsi(word).length > this.options.width) {
    const letters = word.split('')
    let piece
    const pieces = []
    while ((piece = letters.splice(0, this.options.width)).length) {
      pieces.push(piece.join(''))
    }
    return pieces
  } else {
    return word
  }
}
 
module.exports = WordWrap