150 lines
3.9 KiB
JavaScript
150 lines
3.9 KiB
JavaScript
|
'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
|