@thi.ng/strings
Advanced tools
Comparing version 1.15.6 to 2.0.0
@@ -14,2 +14,20 @@ /** | ||
/** | ||
* Returns true iff `x` contains ANSI control sequences. | ||
* | ||
* @param x | ||
*/ | ||
export declare const isAnsi: (x: string) => boolean; | ||
/** | ||
* Returns true iff `x` starts w/ an ANSI control sequence. | ||
* | ||
* @param x | ||
*/ | ||
export declare const isAnsiStart: (x: string) => boolean; | ||
/** | ||
* Returns true iff `x` ends w/ an ANSI control sequence. | ||
* | ||
* @param x | ||
*/ | ||
export declare const isAnsiEnd: (x: string) => boolean; | ||
/** | ||
* Returns length of `x` excluding any ANSI control sequences (via | ||
@@ -16,0 +34,0 @@ * {@link stripAnsi}). |
18
ansi.js
@@ -15,2 +15,20 @@ const RE = /\x1b\[[0-9;]+m/g; | ||
/** | ||
* Returns true iff `x` contains ANSI control sequences. | ||
* | ||
* @param x | ||
*/ | ||
export const isAnsi = (x) => /\x1b\[[0-9;]+m/i.test(x); | ||
/** | ||
* Returns true iff `x` starts w/ an ANSI control sequence. | ||
* | ||
* @param x | ||
*/ | ||
export const isAnsiStart = (x) => /^\x1b\[[0-9;]+m/i.test(x); | ||
/** | ||
* Returns true iff `x` ends w/ an ANSI control sequence. | ||
* | ||
* @param x | ||
*/ | ||
export const isAnsiEnd = (x) => /\x1b\[[0-9;]+m$/i.test(x); | ||
/** | ||
* Returns length of `x` excluding any ANSI control sequences (via | ||
@@ -17,0 +35,0 @@ * {@link stripAnsi}). |
58
api.d.ts
@@ -1,2 +0,2 @@ | ||
import type { FnU } from "@thi.ng/api"; | ||
import type { Fn, Fn2, FnU } from "@thi.ng/api"; | ||
/** | ||
@@ -9,2 +9,58 @@ * UTF-8 Byte Order Mark character | ||
export declare type FnS = FnU<string>; | ||
export interface WordWrapOpts { | ||
/** | ||
* Target line width for word wrapping. | ||
* | ||
* @default 80 | ||
*/ | ||
width: number; | ||
/** | ||
* When adding a word to a line, and the line only has less than this | ||
* option's value chars available and iff the word is longer than that, it | ||
* will be placed into a new line (thus minimizing legibility issues). | ||
* | ||
* @defaultValue 4 | ||
*/ | ||
min: number; | ||
/** | ||
* If true, words longer than {@link WordWrapOpts.width} will be split over | ||
* multiple lines. If false (default), lines *might* become longer than the | ||
* configured wrap width. | ||
* | ||
* @defaultValue false | ||
*/ | ||
hard: boolean; | ||
/** | ||
* Word splitting strategy. Use {@link SPLIT_ANSI} when wordwrapping text w/ | ||
* ANSI colors/seqs. The default {@link SPLIT_PLAIN} only supports plain | ||
* text and will yield wrong results. | ||
* | ||
* @defaultValue SPLIT_PLAIN | ||
*/ | ||
splitter: IWordSplit; | ||
/** | ||
* Word delimiter string or regexp (whitespace by default). | ||
*/ | ||
delimWord: RegExp | string; | ||
/** | ||
* Line delimiter string or regexp (newline chars by default). | ||
*/ | ||
delimLine: RegExp | string; | ||
} | ||
export interface IWordSplit { | ||
/** | ||
* Returns the real length of given string (e.g. with ANSI control sequences | ||
* removed). | ||
*/ | ||
length: Fn<string, number>; | ||
/** | ||
* Takes a string (word) and a desired split position. Returns a possibly | ||
* adjusted split position, e.g. taking any control sequences into account | ||
* (which can't be split). | ||
* | ||
* @param word | ||
* @param pos | ||
*/ | ||
split: Fn2<string, number, number>; | ||
} | ||
//# sourceMappingURL=api.d.ts.map |
@@ -6,2 +6,25 @@ # Change Log | ||
# [2.0.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/strings@1.15.6...@thi.ng/strings@2.0.0) (2021-03-24) | ||
### Features | ||
* **strings:** add ANSI predicates ([928694b](https://github.com/thi-ng/umbrella/commit/928694b0a46a7a58b0b4ab56562afceb0b6c8d8d)) | ||
* **strings:** major update wordWrap() & co. ([9c9c9cc](https://github.com/thi-ng/umbrella/commit/9c9c9cc1abe68ec32edbe91ac5c277561cafd3c4)) | ||
* **strings:** update split() args ([ea503e8](https://github.com/thi-ng/umbrella/commit/ea503e8abdf3598ccd0c1abf5d484164ea73890c)) | ||
### BREAKING CHANGES | ||
* **strings:** major update wordWrap(), wordWrapLines() etc. | ||
- update arguments | ||
- add `WordWrapOpts` to configure wordwrap behavior | ||
- add `IWordSplit` interface and `SPLIT_PLAIN`, `SPLIT_ANSI` impls | ||
- implement hardwrap mode | ||
## [1.15.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/strings@1.15.5...@thi.ng/strings@1.15.6) (2021-03-17) | ||
@@ -8,0 +31,0 @@ |
110
lib/index.js
@@ -13,2 +13,5 @@ 'use strict'; | ||
const stripAnsi = (x) => x.replace(RE, ""); | ||
const isAnsi = (x) => /\x1b\[[0-9;]+m/i.test(x); | ||
const isAnsiStart = (x) => /^\x1b\[[0-9;]+m/i.test(x); | ||
const isAnsiEnd = (x) => /\x1b\[[0-9;]+m$/i.test(x); | ||
const lengthAnsi = (x) => stripAnsi(x).length; | ||
@@ -287,4 +290,5 @@ | ||
const include = ~~includeDelim; | ||
const re = typeof delim === "string" ? new RegExp(delim, "g") : delim; | ||
for (; i < n;) { | ||
const m = delim.exec(src); | ||
const m = re.exec(src); | ||
if (!m) { | ||
@@ -456,36 +460,81 @@ yield src.substring(i); | ||
const wordWrap = (str, lineWidth) => wordWrapLines(str, lineWidth).join("\n"); | ||
const wordWrapLines = (str, lineWidth = 80) => { | ||
const res = []; | ||
for (let line of str.split("\n")) { | ||
if (!line.length) { | ||
res.push(""); | ||
continue; | ||
} | ||
wordWrapLine(line, lineWidth, res); | ||
class Line { | ||
constructor(word, n) { | ||
this.n = 0; | ||
this.w = []; | ||
word != null && this.add(word, n); | ||
} | ||
return res; | ||
add(word, n = word.length) { | ||
this.w.push(word); | ||
this.n += n + ~~(this.n > 0); | ||
return this; | ||
} | ||
toString() { | ||
return this.w.join(" "); | ||
} | ||
} | ||
const SPLIT_PLAIN = { | ||
length: (x) => x.length, | ||
split: (_, max) => max, | ||
}; | ||
const wordWrapLine = (line, lineWidth = 80, acc = []) => { | ||
let ln = 0; | ||
let curr = []; | ||
for (let w of line.split(" ")) { | ||
const l = lengthAnsi(w) + (ln > 0 ? 1 : 0); | ||
if (ln + l <= lineWidth) { | ||
curr.push(w, " "); | ||
ln += l; | ||
const SPLIT_ANSI = { | ||
length: lengthAnsi, | ||
split: (x, max) => { | ||
const re = /\x1b\[[0-9;]+m/g; | ||
let i = max; | ||
let match; | ||
while ((match = re.exec(x))) { | ||
if (match.index >= max) | ||
break; | ||
const n = match[0].length; | ||
i += n; | ||
max += n; | ||
} | ||
else { | ||
acc.push(trimLine(curr)); | ||
curr = [w, " "]; | ||
ln = l; | ||
} | ||
return i; | ||
}, | ||
}; | ||
const append = (acc, word, wordLen, width) => { | ||
const curr = acc[acc.length - 1]; | ||
curr && width - curr.n > wordLen | ||
? curr.add(word, wordLen) | ||
: acc.push(new Line(word, wordLen)); | ||
}; | ||
const wrapWord = (word, opts, offset = 0, acc = []) => { | ||
const width = opts.width || 80; | ||
const impl = opts.splitter || SPLIT_PLAIN; | ||
let len = impl.length(word); | ||
let free = width - offset; | ||
if (free < (opts.min || 4) && free < len) { | ||
free = width; | ||
} | ||
ln && acc.push(trimLine(curr)); | ||
while (opts.hard && len > free) { | ||
const split = impl.split(word, free); | ||
const chunk = word.substr(0, split); | ||
append(acc, chunk, free, width); | ||
word = word.substr(split); | ||
free = width; | ||
len = impl.length(word); | ||
} | ||
append(acc, word, len, width); | ||
return acc; | ||
}; | ||
const trimLine = (x) => { | ||
/^\s+$/.test(x[x.length - 1]) && x.pop(); | ||
return x.join(""); | ||
const wordWrapLine = (line, opts, acc = []) => { | ||
if (!line.length) { | ||
acc.push(new Line()); | ||
return acc; | ||
} | ||
for (let word of split(line, opts.delimWord || /\s/g)) { | ||
const curr = acc[acc.length - 1]; | ||
wrapWord(word, opts, curr && curr.n > 0 ? curr.n + 1 : 0, acc); | ||
} | ||
return acc; | ||
}; | ||
const wordWrapLines = (lines, opts) => { | ||
let acc = []; | ||
for (let line of split(lines, opts.delimLine)) { | ||
acc = acc.concat(wordWrapLine(line, opts)); | ||
} | ||
return acc; | ||
}; | ||
const wordWrap = (str, opts) => wordWrapLines(str, opts).join("\n"); | ||
@@ -504,2 +553,4 @@ exports.ALPHA = ALPHA; | ||
exports.PUNCTUATION = PUNCTUATION; | ||
exports.SPLIT_ANSI = SPLIT_ANSI; | ||
exports.SPLIT_PLAIN = SPLIT_PLAIN; | ||
exports.U16 = U16; | ||
@@ -534,2 +585,5 @@ exports.U24 = U24; | ||
exports.interpolateKeys = interpolateKeys; | ||
exports.isAnsi = isAnsi; | ||
exports.isAnsiEnd = isAnsiEnd; | ||
exports.isAnsiStart = isAnsiStart; | ||
exports.join = join; | ||
@@ -536,0 +590,0 @@ exports.kebab = kebab; |
@@ -1,1 +0,1 @@ | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@thi.ng/memoize"),require("@thi.ng/hex"),require("@thi.ng/errors")):"function"==typeof define&&define.amd?define(["exports","@thi.ng/memoize","@thi.ng/hex","@thi.ng/errors"],t):t(((e="undefined"!=typeof globalThis?globalThis:e||self).thi=e.thi||{},e.thi.ng=e.thi.ng||{},e.thi.ng.strings={}),e.thi.ng.memoize,e.thi.ng.hex,e.thi.ng.errors)}(this,(function(e,t,n,r){"use strict";const s=/\x1b\[[0-9;]+m/g,o=e=>e.replace(s,""),i=e=>o(e).length,a=e=>e.toUpperCase(),g=e=>e.toLowerCase(),l=(e,t="-")=>g(e.replace(/([a-z0-9\u00e0-\u00fd])([A-Z\u00c0-\u00dd])/g,((e,n,r)=>n+t+r))),u=e=>l(e,"_"),c=t.memoizeJ(((e,t)=>e.repeat(t))),p=t.memoizeJ(((e,t="")=>n=>n.length>e?n.substr(0,e-t.length)+t:n)),h=p,f=t.memoizeJ(((e,t=" ")=>{const n=c(String(t),e);return t=>{if(null==t)return n;t=t.toString();const r=(e-t.length)/2;return t.length<e?n.substr(0,r)+t+n.substr(0,r+((1&e)==(1&t.length)?0:1)):p(e)(t)}})),m=t.memoizeJ(((e,t,n="")=>{const r=c("0",t);return s=>(s=(s>>>0).toString(e),n+(s.length<t?r.substr(s.length)+s:s))})),d=m(2,8),b=m(2,16),$=m(2,32),x=n.U8,y=n.U16,S=n.U24,z=n.U32,j=n.U64HL,A={0:"\0",b:"\b",t:"\t",r:"\r",v:"\v",f:"\f",n:"\n","'":"'",'"':'"',"\\":"\\"},C={0:"0",8:"b",9:"t",10:"n",11:"v",12:"f",13:"r",33:"'",34:'"',92:"\\"},M=t.memoizeJ(((e,t=" ")=>{const n=c(String(t),e);return(t,r)=>null==t?n:(t=t.toString(),(r=void 0!==r?r:t.length)<e?n.substr(r)+t:t)})),U=M(2,"0"),w=M(3,"0"),L=M(4,"0"),O=t.memoizeJ(((e,t=!1)=>t?t=>N(t)||t.toFixed(e):t=>t.toFixed(e))),J=t.memoizeJ(((e,t=3)=>{const n=e-t-1,r=Math.pow(10,n),s=-Math.pow(10,n-1),o=Math.pow(10,-(t-1)),i=M(e);return n=>{const a=Math.abs(n);return i(N(n)||(0===n?"0":a<o||a>=r?P(n,e):n.toFixed(t-(n<s?1:0))))}})),P=(e,t)=>e.toExponential(Math.max(t-4-(Math.log(Math.abs(e))/Math.LN10>=10?2:1)-(e<0?1:0),0)),N=e=>isNaN(e)?"NaN":e===1/0?"+∞":e===-1/0?"-∞":void 0,v=(e,...t)=>{const n=[];for(let r=0,s=0,o=e.length;r<o;r++){const o=e[r],i=typeof o;n.push("function"===i?o(t[s++]):"object"===i?o[t[s++]]:o)}return n.join("")};function*E(e,t){let n="string"==typeof e?e.charCodeAt(0):e;const r="string"==typeof t?t.charCodeAt(0):t;if(n<=r)for(;n<=r;n++)yield String.fromCharCode(n);else for(;n>=r;n--)yield String.fromCharCode(n)}const F=(...e)=>{const t={};for(let n of e)for(let e of n)t[e]=!0;return Object.freeze(t)},T=Object.freeze({"\t":!0,"\n":!0,"\v":!0,"\f":!0,"\r":!0," ":!0}),B=F(E("0","9")),R=F(E("0","9"),E("A","F"),E("a","f")),I=F(E("a","z")),_=F(E("A","Z")),k=Object.freeze(Object.assign(Object.assign({},_),I)),H=Object.freeze(Object.assign(Object.assign(Object.assign({},k),B),{_:!0})),W=F(E("!","/"),E(":","@"),E("[","`"),E("{","~")),Z=t.memoizeJ((e=>t=>Math.trunc(t).toLocaleString(e))),G=/\{(\d+)\}/g,q=/\{([a-z0-9_.-]+)\}/gi,K=t.memoize1((e=>t=>t.join(e))),D=t.memoizeJ(((e,t=" ")=>{const n=c(String(t),e);return(t,r)=>null==t?n:(t=t.toString(),(r=void 0!==r?r:t.length)<e?t+n.substr(r):t)})),V="àáäâãåèéëêìíïîòóöôùúüûñçßÿœæŕśńṕẃǵǹḿǘẍźḧ·/_,:;",X=new RegExp(V.split("").join("|"),"g");const Q=(e,t)=>Math.floor((e+t)/t)*t,Y=(e,t=4)=>{let n="",r=e.split(/\t/g),s=r.length-1;for(let e=0;e<s;e++){n+=r[e],n+=c(" ",Q(n.length,t)-n.length)}return n+=r[s],n},ee=(e,t=4)=>{const n=/\s{2,}/g;let r,s=0,o="";for(;r=n.exec(e);){const n=r[0].length;o+=e.substring(s,r.index),s=r.index;const i=r.index+n;for(;s<i;){const e=Q(s,t);e<=i?(o+="\t",s=e):(o+=c(" ",i-s),s=i)}s=i}return o+e.substr(s)},te=t.memoize1(((e=" \t\n\r")=>{e=`(${e.split("").map((e=>`\\${e}`)).join("|")})`;const t=new RegExp(`(^${e}+)|(${e}+$)`,"g");return e=>e.replace(t,"")})),ne=t.memoizeJ(((e,t="")=>n=>n.length>e?t+n.substr(n.length-e+t.length):n)),re=t.memoizeJ(((e,t,n=2)=>{const r=e.map((e=>[e[0],null!=e[2]?e[2]:n,e[1]])).sort(((e,t)=>e[0]-t[0]));return e=>{if(0===e)return`0${t}`;const n=Math.abs(e);for(let t=r.length;--t>=0;){const s=r[t];if(n>=s[0]||0===t)return(e/s[0]).toFixed(s[1])+s[2]}return""}})),se=1024,oe=re([[1," bits",0],[se," Kb"],[se**2," Mb"],[se**3," Gb"]]," bits",2),ie=re([[1," bytes",0],[se," KB"],[se**2," MB"],[se**3," GB"],[se**4," TB"],[se**5," PB"]]," bytes",2),ae=re([[1e-12," ps"],[1e-9," ns"],[1e-6," µs"],[.001," ms"],[1," secs"],[60," mins"],[3600," hours"],[86400," days"]]," secs",3),ge=re([[1e-12," pm"],[1e-9," nm"],[1e-6," µm"],[.001," mm"],[.01," cm"],[1," m"],[1e3," km"]]," m",2),le=re([[1e-12," pg"],[1e-9," ng"],[1e-6," µg"],[.001," mg"],[1," g"],[1e3," kg"],[1e6," t"],[1e9," kt"],[1e12," Mt"]]," g",2),ue=n.uuid,ce=t.memoizeJ(((e,t=3,n=",",r="[",s="]")=>{const o="number"==typeof t?O(t):t;switch(e){case 1:return e=>`${r}${o(e[0])}${s}`;case 2:return e=>`${r}${o(e[0])}${n}${o(e[1])}${s}`;case 3:return e=>`${r}${o(e[0])}${n}${o(e[1])}${n}${o(e[2])}${s}`;case 4:return e=>`${r}${o(e[0])}${n}${o(e[1])}${n}${o(e[2])}${n}${o(e[3])}${s}`;default:return e=>{const t=[];for(let n=0;n<e.length;n++)t.push(o(e[n]));return`${r}${t.join(n)}${s}`}}})),pe=t.memoizeJ((e=>t=>e+t+e)),he=(e,t=80)=>{const n=[];for(let r of e.split("\n"))r.length?fe(r,t,n):n.push("");return n},fe=(e,t=80,n=[])=>{let r=0,s=[];for(let o of e.split(" ")){const e=i(o)+(r>0?1:0);r+e<=t?(s.push(o," "),r+=e):(n.push(me(s)),s=[o," "],r=e)}return r&&n.push(me(s)),n},me=e=>(/^\s+$/.test(e[e.length-1])&&e.pop(),e.join(""));e.ALPHA=k,e.ALPHA_NUM=H,e.B16=b,e.B32=$,e.B8=d,e.BOM="\ufeff",e.DIGITS=B,e.ESCAPES=A,e.ESCAPES_REV=C,e.HEX=R,e.LOWER=I,e.PUNCTUATION=W,e.U16=y,e.U24=S,e.U32=z,e.U64=j,e.U8=x,e.UPPER=_,e.WS=T,e.Z2=U,e.Z3=w,e.Z4=L,e.bits=oe,e.bytes=ie,e.camel=(e,t="-")=>g(e).replace(new RegExp(`\\${t}+(\\w)`,"g"),((e,t)=>a(t))),e.capitalize=e=>e[0].toUpperCase()+e.substr(1),e.center=f,e.charRange=E,e.computeCursorPos=(e,t,n="\n",r=[1,1])=>{if(!e.length)return[1,1];t=Math.min(Math.max(0,t),e.length);const s=e.split(n),o=s.length;for(let e=0;e<o;e++){const n=s[e];if(t<=n.length)return[e+r[0],t+r[1]];t-=n.length+1}return[o+r[0]-1,r[1]]},e.defFormat=e=>(...t)=>v(e,...t),e.escape=e=>e.replace(/[\0\b\t\n\v\f\r'"\\]/g,(e=>`\\${C[e.charCodeAt(0)]}`)).replace(/[\ud800-\udfff]{2}/g,(e=>`\\U${z(e.codePointAt(0))}`)).replace(/[^\u0020-\u007e]/g,(e=>`\\u${y(e.charCodeAt(0))}`)),e.float=O,e.floatFixedWidth=J,e.format=v,e.grams=le,e.hstr=e=>null!=e?`${(e=e.toString()).length}H${e}`:"",e.ignore=e=>"",e.int=e=>String(Math.trunc(e)),e.intLocale=Z,e.interpolate=(e,...t)=>t.length>0?e.replace(G,((e,n)=>String(t[parseInt(n,10)]))):e,e.interpolateKeys=(e,t)=>e.replace(q,((e,n)=>null!=t[n]?String(t[n]):r.illegalArgs(`missing key: ${n}`))),e.join=K,e.kebab=l,e.lengthAnsi=i,e.lower=g,e.maybeParseFloat=(e,t=0)=>{const n=parseFloat(e);return isNaN(n)?t:n},e.maybeParseInt=(e,t=0,n=10)=>{const r=parseInt(e,n);return isNaN(r)?t:r},e.meters=ge,e.padLeft=M,e.padRight=D,e.percent=(e=0)=>t=>(100*t).toFixed(e)+"%",e.radix=m,e.repeat=c,e.seconds=ae,e.slugify=e=>e.toLowerCase().replace(/\s+/g,"-").replace(X,(e=>"aaaaaaeeeeiiiioooouuuuncsyoarsnpwgnmuxzh------"[V.indexOf(e)])).replace(/&+/g,"-and-").replace(/[^\w\-]+/g,"").replace(/\-{2,}/g,"-").replace(/(^-+)|(-+$)/g,""),e.slugifyGH=e=>e.toLowerCase().replace(/[!"#$%&'()*+,./:;<=>?@\[\\\]^`{|}~\u0000-\u001f\u2000-\u206f\u2700-\u27bf\u2e00-\u2e7f]/g,"").replace(/\s/g,"-"),e.snake=u,e.spacesToTabs=(e,t=4)=>e.split(/\r?\n/g).map((e=>ee(e,t))).join("\n"),e.spacesToTabsLine=ee,e.splice=(e,t,n,s=n)=>(n<0&&(n+=e.length),s<0&&(s+=e.length),n>s&&r.illegalArgs("'from' index must be <= 'to'"),s=Math.max(s,0),n<=0?t+e.substr(s):n>=e.length?e+t:e.substr(0,n)+t+e.substr(s)),e.split=function*(e,t=/\r?\n/g,n=!1){let r=0;const s=e.length,o=~~n;for(;r<s;){const n=t.exec(e);if(!n)return void(yield e.substring(r));const s=n[0].length;yield e.substring(r,n.index+o*s),r=n.index+s}},e.str=e=>String(e),e.stringify=(e=!1,t)=>n=>e||"string"!=typeof n&&"number"!=typeof n?JSON.stringify(n,null,t):String(n),e.stripAnsi=o,e.tabsToSpaces=(e,t=4)=>e.split(/\r?\n/g).map((e=>Y(e,t))).join("\n"),e.tabsToSpacesLine=Y,e.trim=te,e.truncate=p,e.truncateLeft=ne,e.truncateRight=h,e.unescape=e=>e.replace(/\\u([0-9a-fA-F]{4})/g,((e,t)=>String.fromCharCode(parseInt(t,16)))).replace(/\\U([0-9a-fA-F]{8})/g,((e,t)=>String.fromCodePoint(parseInt(t,16)))).replace(/\\([0btnvfr'"\\])/g,((e,t)=>A[t])),e.units=re,e.upper=a,e.upperSnake=e=>u(e).toUpperCase(),e.uuid=ue,e.vector=ce,e.wordWrap=(e,t)=>he(e,t).join("\n"),e.wordWrapLine=fe,e.wordWrapLines=he,e.wrap=pe,Object.defineProperty(e,"__esModule",{value:!0})})); | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@thi.ng/memoize"),require("@thi.ng/hex"),require("@thi.ng/errors")):"function"==typeof define&&define.amd?define(["exports","@thi.ng/memoize","@thi.ng/hex","@thi.ng/errors"],t):t(((e="undefined"!=typeof globalThis?globalThis:e||self).thi=e.thi||{},e.thi.ng=e.thi.ng||{},e.thi.ng.strings={}),e.thi.ng.memoize,e.thi.ng.hex,e.thi.ng.errors)}(this,(function(e,t,n,r){"use strict";const s=/\x1b\[[0-9;]+m/g,i=e=>e.replace(s,""),o=e=>i(e).length,a=e=>e.toUpperCase(),l=e=>e.toLowerCase(),g=(e,t="-")=>l(e.replace(/([a-z0-9\u00e0-\u00fd])([A-Z\u00c0-\u00dd])/g,((e,n,r)=>n+t+r))),u=e=>g(e,"_"),c=t.memoizeJ(((e,t)=>e.repeat(t))),h=t.memoizeJ(((e,t="")=>n=>n.length>e?n.substr(0,e-t.length)+t:n)),p=h,f=t.memoizeJ(((e,t=" ")=>{const n=c(String(t),e);return t=>{if(null==t)return n;t=t.toString();const r=(e-t.length)/2;return t.length<e?n.substr(0,r)+t+n.substr(0,r+((1&e)==(1&t.length)?0:1)):h(e)(t)}})),m=t.memoizeJ(((e,t,n="")=>{const r=c("0",t);return s=>(s=(s>>>0).toString(e),n+(s.length<t?r.substr(s.length)+s:s))})),d=m(2,8),b=m(2,16),$=m(2,32),x=n.U8,S=n.U16,y=n.U24,z=n.U32,A=n.U64HL,w={0:"\0",b:"\b",t:"\t",r:"\r",v:"\v",f:"\f",n:"\n","'":"'",'"':'"',"\\":"\\"},j={0:"0",8:"b",9:"t",10:"n",11:"v",12:"f",13:"r",33:"'",34:'"',92:"\\"},C=t.memoizeJ(((e,t=" ")=>{const n=c(String(t),e);return(t,r)=>null==t?n:(t=t.toString(),(r=void 0!==r?r:t.length)<e?n.substr(r)+t:t)})),M=C(2,"0"),L=C(3,"0"),U=C(4,"0"),P=t.memoizeJ(((e,t=!1)=>t?t=>E(t)||t.toFixed(e):t=>t.toFixed(e))),N=t.memoizeJ(((e,t=3)=>{const n=e-t-1,r=Math.pow(10,n),s=-Math.pow(10,n-1),i=Math.pow(10,-(t-1)),o=C(e);return n=>{const a=Math.abs(n);return o(E(n)||(0===n?"0":a<i||a>=r?O(n,e):n.toFixed(t-(n<s?1:0))))}})),O=(e,t)=>e.toExponential(Math.max(t-4-(Math.log(Math.abs(e))/Math.LN10>=10?2:1)-(e<0?1:0),0)),E=e=>isNaN(e)?"NaN":e===1/0?"+∞":e===-1/0?"-∞":void 0,J=(e,...t)=>{const n=[];for(let r=0,s=0,i=e.length;r<i;r++){const i=e[r],o=typeof i;n.push("function"===o?i(t[s++]):"object"===o?i[t[s++]]:i)}return n.join("")};function*T(e,t){let n="string"==typeof e?e.charCodeAt(0):e;const r="string"==typeof t?t.charCodeAt(0):t;if(n<=r)for(;n<=r;n++)yield String.fromCharCode(n);else for(;n>=r;n--)yield String.fromCharCode(n)}const v=(...e)=>{const t={};for(let n of e)for(let e of n)t[e]=!0;return Object.freeze(t)},F=Object.freeze({"\t":!0,"\n":!0,"\v":!0,"\f":!0,"\r":!0," ":!0}),I=v(T("0","9")),R=v(T("0","9"),T("A","F"),T("a","f")),_=v(T("a","z")),B=v(T("A","Z")),k=Object.freeze(Object.assign(Object.assign({},B),_)),W=Object.freeze(Object.assign(Object.assign(Object.assign({},k),I),{_:!0})),H=v(T("!","/"),T(":","@"),T("[","`"),T("{","~")),Z=t.memoizeJ((e=>t=>Math.trunc(t).toLocaleString(e))),G=/\{(\d+)\}/g,q=/\{([a-z0-9_.-]+)\}/gi,K=t.memoize1((e=>t=>t.join(e))),D=t.memoizeJ(((e,t=" ")=>{const n=c(String(t),e);return(t,r)=>null==t?n:(t=t.toString(),(r=void 0!==r?r:t.length)<e?t+n.substr(r):t)})),V="àáäâãåèéëêìíïîòóöôùúüûñçßÿœæŕśńṕẃǵǹḿǘẍźḧ·/_,:;",X=new RegExp(V.split("").join("|"),"g");function*Q(e,t=/\r?\n/g,n=!1){let r=0;const s=e.length,i=~~n,o="string"==typeof t?new RegExp(t,"g"):t;for(;r<s;){const t=o.exec(e);if(!t)return void(yield e.substring(r));const n=t[0].length;yield e.substring(r,t.index+i*n),r=t.index+n}}const Y=(e,t)=>Math.floor((e+t)/t)*t,ee=(e,t=4)=>{let n="",r=e.split(/\t/g),s=r.length-1;for(let e=0;e<s;e++){n+=r[e],n+=c(" ",Y(n.length,t)-n.length)}return n+=r[s],n},te=(e,t=4)=>{const n=/\s{2,}/g;let r,s=0,i="";for(;r=n.exec(e);){const n=r[0].length;i+=e.substring(s,r.index),s=r.index;const o=r.index+n;for(;s<o;){const e=Y(s,t);e<=o?(i+="\t",s=e):(i+=c(" ",o-s),s=o)}s=o}return i+e.substr(s)},ne=t.memoize1(((e=" \t\n\r")=>{e=`(${e.split("").map((e=>`\\${e}`)).join("|")})`;const t=new RegExp(`(^${e}+)|(${e}+$)`,"g");return e=>e.replace(t,"")})),re=t.memoizeJ(((e,t="")=>n=>n.length>e?t+n.substr(n.length-e+t.length):n)),se=t.memoizeJ(((e,t,n=2)=>{const r=e.map((e=>[e[0],null!=e[2]?e[2]:n,e[1]])).sort(((e,t)=>e[0]-t[0]));return e=>{if(0===e)return`0${t}`;const n=Math.abs(e);for(let t=r.length;--t>=0;){const s=r[t];if(n>=s[0]||0===t)return(e/s[0]).toFixed(s[1])+s[2]}return""}})),ie=1024,oe=se([[1," bits",0],[ie," Kb"],[ie**2," Mb"],[ie**3," Gb"]]," bits",2),ae=se([[1," bytes",0],[ie," KB"],[ie**2," MB"],[ie**3," GB"],[ie**4," TB"],[ie**5," PB"]]," bytes",2),le=se([[1e-12," ps"],[1e-9," ns"],[1e-6," µs"],[.001," ms"],[1," secs"],[60," mins"],[3600," hours"],[86400," days"]]," secs",3),ge=se([[1e-12," pm"],[1e-9," nm"],[1e-6," µm"],[.001," mm"],[.01," cm"],[1," m"],[1e3," km"]]," m",2),ue=se([[1e-12," pg"],[1e-9," ng"],[1e-6," µg"],[.001," mg"],[1," g"],[1e3," kg"],[1e6," t"],[1e9," kt"],[1e12," Mt"]]," g",2),ce=n.uuid,he=t.memoizeJ(((e,t=3,n=",",r="[",s="]")=>{const i="number"==typeof t?P(t):t;switch(e){case 1:return e=>`${r}${i(e[0])}${s}`;case 2:return e=>`${r}${i(e[0])}${n}${i(e[1])}${s}`;case 3:return e=>`${r}${i(e[0])}${n}${i(e[1])}${n}${i(e[2])}${s}`;case 4:return e=>`${r}${i(e[0])}${n}${i(e[1])}${n}${i(e[2])}${n}${i(e[3])}${s}`;default:return e=>{const t=[];for(let n=0;n<e.length;n++)t.push(i(e[n]));return`${r}${t.join(n)}${s}`}}})),pe=t.memoizeJ((e=>t=>e+t+e));class fe{constructor(e,t){this.n=0,this.w=[],null!=e&&this.add(e,t)}add(e,t=e.length){return this.w.push(e),this.n+=t+~~(this.n>0),this}toString(){return this.w.join(" ")}}const me={length:e=>e.length,split:(e,t)=>t},de={length:o,split:(e,t)=>{const n=/\x1b\[[0-9;]+m/g;let r,s=t;for(;(r=n.exec(e))&&!(r.index>=t);){const e=r[0].length;s+=e,t+=e}return s}},be=(e,t,n,r)=>{const s=e[e.length-1];s&&r-s.n>n?s.add(t,n):e.push(new fe(t,n))},$e=(e,t,n=0,r=[])=>{const s=t.width||80,i=t.splitter||me;let o=i.length(e),a=s-n;for(a<(t.min||4)&&a<o&&(a=s);t.hard&&o>a;){const t=i.split(e,a),n=e.substr(0,t);be(r,n,a,s),e=e.substr(t),a=s,o=i.length(e)}return be(r,e,o,s),r},xe=(e,t,n=[])=>{if(!e.length)return n.push(new fe),n;for(let r of Q(e,t.delimWord||/\s/g)){const e=n[n.length-1];$e(r,t,e&&e.n>0?e.n+1:0,n)}return n},Se=(e,t)=>{let n=[];for(let r of Q(e,t.delimLine))n=n.concat(xe(r,t));return n};e.ALPHA=k,e.ALPHA_NUM=W,e.B16=b,e.B32=$,e.B8=d,e.BOM="\ufeff",e.DIGITS=I,e.ESCAPES=w,e.ESCAPES_REV=j,e.HEX=R,e.LOWER=_,e.PUNCTUATION=H,e.SPLIT_ANSI=de,e.SPLIT_PLAIN=me,e.U16=S,e.U24=y,e.U32=z,e.U64=A,e.U8=x,e.UPPER=B,e.WS=F,e.Z2=M,e.Z3=L,e.Z4=U,e.bits=oe,e.bytes=ae,e.camel=(e,t="-")=>l(e).replace(new RegExp(`\\${t}+(\\w)`,"g"),((e,t)=>a(t))),e.capitalize=e=>e[0].toUpperCase()+e.substr(1),e.center=f,e.charRange=T,e.computeCursorPos=(e,t,n="\n",r=[1,1])=>{if(!e.length)return[1,1];t=Math.min(Math.max(0,t),e.length);const s=e.split(n),i=s.length;for(let e=0;e<i;e++){const n=s[e];if(t<=n.length)return[e+r[0],t+r[1]];t-=n.length+1}return[i+r[0]-1,r[1]]},e.defFormat=e=>(...t)=>J(e,...t),e.escape=e=>e.replace(/[\0\b\t\n\v\f\r'"\\]/g,(e=>`\\${j[e.charCodeAt(0)]}`)).replace(/[\ud800-\udfff]{2}/g,(e=>`\\U${z(e.codePointAt(0))}`)).replace(/[^\u0020-\u007e]/g,(e=>`\\u${S(e.charCodeAt(0))}`)),e.float=P,e.floatFixedWidth=N,e.format=J,e.grams=ue,e.hstr=e=>null!=e?`${(e=e.toString()).length}H${e}`:"",e.ignore=e=>"",e.int=e=>String(Math.trunc(e)),e.intLocale=Z,e.interpolate=(e,...t)=>t.length>0?e.replace(G,((e,n)=>String(t[parseInt(n,10)]))):e,e.interpolateKeys=(e,t)=>e.replace(q,((e,n)=>null!=t[n]?String(t[n]):r.illegalArgs(`missing key: ${n}`))),e.isAnsi=e=>/\x1b\[[0-9;]+m/i.test(e),e.isAnsiEnd=e=>/\x1b\[[0-9;]+m$/i.test(e),e.isAnsiStart=e=>/^\x1b\[[0-9;]+m/i.test(e),e.join=K,e.kebab=g,e.lengthAnsi=o,e.lower=l,e.maybeParseFloat=(e,t=0)=>{const n=parseFloat(e);return isNaN(n)?t:n},e.maybeParseInt=(e,t=0,n=10)=>{const r=parseInt(e,n);return isNaN(r)?t:r},e.meters=ge,e.padLeft=C,e.padRight=D,e.percent=(e=0)=>t=>(100*t).toFixed(e)+"%",e.radix=m,e.repeat=c,e.seconds=le,e.slugify=e=>e.toLowerCase().replace(/\s+/g,"-").replace(X,(e=>"aaaaaaeeeeiiiioooouuuuncsyoarsnpwgnmuxzh------"[V.indexOf(e)])).replace(/&+/g,"-and-").replace(/[^\w\-]+/g,"").replace(/\-{2,}/g,"-").replace(/(^-+)|(-+$)/g,""),e.slugifyGH=e=>e.toLowerCase().replace(/[!"#$%&'()*+,./:;<=>?@\[\\\]^`{|}~\u0000-\u001f\u2000-\u206f\u2700-\u27bf\u2e00-\u2e7f]/g,"").replace(/\s/g,"-"),e.snake=u,e.spacesToTabs=(e,t=4)=>e.split(/\r?\n/g).map((e=>te(e,t))).join("\n"),e.spacesToTabsLine=te,e.splice=(e,t,n,s=n)=>(n<0&&(n+=e.length),s<0&&(s+=e.length),n>s&&r.illegalArgs("'from' index must be <= 'to'"),s=Math.max(s,0),n<=0?t+e.substr(s):n>=e.length?e+t:e.substr(0,n)+t+e.substr(s)),e.split=Q,e.str=e=>String(e),e.stringify=(e=!1,t)=>n=>e||"string"!=typeof n&&"number"!=typeof n?JSON.stringify(n,null,t):String(n),e.stripAnsi=i,e.tabsToSpaces=(e,t=4)=>e.split(/\r?\n/g).map((e=>ee(e,t))).join("\n"),e.tabsToSpacesLine=ee,e.trim=ne,e.truncate=h,e.truncateLeft=re,e.truncateRight=p,e.unescape=e=>e.replace(/\\u([0-9a-fA-F]{4})/g,((e,t)=>String.fromCharCode(parseInt(t,16)))).replace(/\\U([0-9a-fA-F]{8})/g,((e,t)=>String.fromCodePoint(parseInt(t,16)))).replace(/\\([0btnvfr'"\\])/g,((e,t)=>w[t])),e.units=se,e.upper=a,e.upperSnake=e=>u(e).toUpperCase(),e.uuid=ce,e.vector=he,e.wordWrap=(e,t)=>Se(e,t).join("\n"),e.wordWrapLine=xe,e.wordWrapLines=Se,e.wrap=pe,Object.defineProperty(e,"__esModule",{value:!0})})); |
{ | ||
"name": "@thi.ng/strings", | ||
"version": "1.15.6", | ||
"version": "2.0.0", | ||
"description": "Various string formatting & utility functions", | ||
@@ -93,3 +93,3 @@ "module": "./index.js", | ||
}, | ||
"gitHead": "15f3ea0e15805bc67ca61bf87ce5e8849a7d8e86" | ||
"gitHead": "88f03faab663d8cb36f83712249ef80cb16739c5" | ||
} |
154
README.md
@@ -13,2 +13,13 @@ <!-- This file is generated - DO NOT EDIT! --> | ||
- [About](#about) | ||
- [General](#general) | ||
- [Numeric formatters](#numeric-formatters) | ||
- [Casing](#casing) | ||
- [Slugify](#slugify) | ||
- [ANSI](#ansi) | ||
- [Word wrapping](#word-wrapping) | ||
- [Padding / wrapping](#padding--wrapping) | ||
- [Indentation](#indentation) | ||
- [Char range presets / lookup tables](#char-range-presets--lookup-tables) | ||
- [Units](#units) | ||
- [Miscellaneous](#miscellaneous) | ||
- [Status](#status) | ||
@@ -20,8 +31,2 @@ - [Installation](#installation) | ||
- [Basic usage examples](#basic-usage-examples) | ||
- [General](#general) | ||
- [Case](#case) | ||
- [Numeric & radix-based](#numeric--radix-based) | ||
- [Padding / truncation](#padding--truncation) | ||
- [Units](#units) | ||
- [String creation & editing](#string-creation--editing) | ||
- [Authors](#authors) | ||
@@ -32,7 +37,82 @@ - [License](#license) | ||
Various higher-order, configurable string formatting & utility | ||
functions, some memoized. Please sources / docstrings for now. | ||
Various (80+) string formatting, word wrapping & utility functions, some | ||
higher-order, some memoized. | ||
Partially based on Clojure version of [thi.ng/strf](http://thi.ng/strf). | ||
### General | ||
- `defFormat` / `format` | ||
- `interpolate` / `interpolateKeys` | ||
- `escape` / `unescape` | ||
- `join` / `splice` / `split` | ||
- `repeat` | ||
- `stringify` | ||
### Numeric formatters | ||
- `radix` | ||
- `int` / `intLocale` | ||
- `float` / `floatFixedWidth` | ||
- `maybeParseFloat` / `maybeParseInt` | ||
- `percent` | ||
- `uuid` | ||
- `vector` | ||
- `B8` / `B16` / `B32` - fixed size binary formatters | ||
- `U8` / `U16` / `U24` / `U32` / `U64` - fixed size hex formatters | ||
- `Z2` / `Z3` / `Z4` - fixed sized zero padded number formatters | ||
### Casing | ||
- `lower` / `upper` / `capitalize` | ||
- `camel` / `kebab` / `snake` / `upperSnake` | ||
### Slugify | ||
- `slugify` / `slugifyGH` | ||
### ANSI | ||
- `isAnsi` / `isAnsiEnd` / `isAnsiStart` | ||
- `stripAnsi` | ||
- `lengthAnsi` | ||
### Word wrapping | ||
- `wordWrap` / `wordWrapLine` / `wordWrapLines` | ||
- `SPLIT_PLAIN` / `SPLIT_ANSI` | ||
### Padding / wrapping | ||
- `center` | ||
- `padLeft` / `padRight` | ||
- `truncate` / `truncateLeft` / `truncateRight` | ||
- `trim` | ||
- `wrap` | ||
### Indentation | ||
- `spacesToTabs` / `spacesToTabsLine` | ||
- `tabsToSpaces` / `tabsToSpacesLine` | ||
### Char range presets / lookup tables | ||
- `charRange` | ||
- `ALPHA` / `ALPHA_NUM` / `DIGITS` / `LOWER` / `UPPER` / `HEX` | ||
- `BOM` / `ESCAPES` / `ESCAPES_REV` | ||
- `WS` / `PUNCTUATION` | ||
### Units | ||
- `units` | ||
- `bits` / `bytes` | ||
- `grams` | ||
- `meters` | ||
- `seconds` | ||
### Miscellaneous | ||
- `hstr` - [Hollerith strings](https://en.wikipedia.org/wiki/Hollerith_constant) | ||
- `computeCursorPos` | ||
### Status | ||
@@ -58,3 +138,3 @@ | ||
Package sizes (gzipped, pre-treeshake): ESM: 3.65 KB / CJS: 3.94 KB / UMD: 3.74 KB | ||
Package sizes (gzipped, pre-treeshake): ESM: 3.97 KB / CJS: 4.26 KB / UMD: 4.04 KB | ||
@@ -108,58 +188,2 @@ ## Dependencies | ||
### General | ||
- `defFormat` | ||
- `format` | ||
- `hstr` | ||
- `ignore` | ||
- `interpolate` | ||
- `str` | ||
### Case | ||
- `camel` | ||
- `capitalize` | ||
- `kebab` | ||
- `lower` | ||
- `snake` | ||
- `slugify` | ||
- `upper` | ||
### Numeric & radix-based | ||
- `float` | ||
- `floatFixedWidth` | ||
- `maybeParseFloat` | ||
- `maybeParseInt` | ||
- `percent` | ||
- `radix` | ||
- `uuid` | ||
- `B8` / `B16` / `B32` - binary / bitstring presets | ||
- `U8` / `U16` / `U24` / `U32` / `U64` - hex format presets (unsigned values) | ||
### Padding / truncation | ||
- `center` | ||
- `padLeft` | ||
- `padRight` | ||
- `truncate` | ||
- `truncateLeft` | ||
- `wrap` | ||
- `Z2` / `Z3` / `Z4` - zero-pad presets | ||
### Units | ||
- `units` - define new unit w/ magnitudes & suffixes | ||
- `bits` | ||
- `bytes` | ||
- `grams` | ||
- `meters` | ||
- `seconds` | ||
### String creation & editing | ||
- `charRange` | ||
- `repeat` | ||
- `splice` | ||
## Authors | ||
@@ -166,0 +190,0 @@ |
@@ -15,3 +15,3 @@ /** | ||
*/ | ||
export declare function split(src: string, delim?: RegExp, includeDelim?: boolean): Generator<string, void, unknown>; | ||
export declare function split(src: string, delim?: RegExp | string, includeDelim?: boolean): Generator<string, void, unknown>; | ||
//# sourceMappingURL=split.d.ts.map |
@@ -19,4 +19,5 @@ /** | ||
const include = ~~includeDelim; | ||
const re = typeof delim === "string" ? new RegExp(delim, "g") : delim; | ||
for (; i < n;) { | ||
const m = delim.exec(src); | ||
const m = re.exec(src); | ||
if (!m) { | ||
@@ -23,0 +24,0 @@ yield src.substring(i); |
@@ -1,4 +0,58 @@ | ||
export declare const wordWrap: (str: string, lineWidth?: number | undefined) => string; | ||
export declare const wordWrapLines: (str: string, lineWidth?: number) => string[]; | ||
export declare const wordWrapLine: (line: string, lineWidth?: number, acc?: string[]) => string[]; | ||
import type { IWordSplit, WordWrapOpts } from "./api"; | ||
/** | ||
* Internal representation of a single line (for word wrapping purposes). A thin | ||
* wrapper of individual word and the _logical_ line length (rather than the | ||
* actualy string width). | ||
* | ||
* @internal | ||
*/ | ||
declare class Line { | ||
n: number; | ||
w: string[]; | ||
constructor(word?: string, n?: number); | ||
add(word: string, n?: number): this; | ||
toString(): string; | ||
} | ||
/** | ||
* (Default) wordwrap word splitting strategy for plain text. | ||
*/ | ||
export declare const SPLIT_PLAIN: IWordSplit; | ||
/** | ||
* Wordwrap word splitting strategy for text containing ANSI control sequences. | ||
*/ | ||
export declare const SPLIT_ANSI: IWordSplit; | ||
/** | ||
* Wordwraps a single-`line` string using provided options. Returns array of | ||
* {@link Line} objects, which can simply be `.join("\n")`ed to convert back | ||
* into text. | ||
* | ||
* @see {@link wordWrap} for main user facing alternative. | ||
* | ||
* @param line | ||
* @param opts | ||
* @param acc | ||
* | ||
* @internal | ||
*/ | ||
export declare const wordWrapLine: (line: string, opts: Partial<WordWrapOpts>, acc?: Line[]) => Line[]; | ||
/** | ||
* Wordwraps a multi-`line` string using provided options. Returns array of | ||
* {@link Line} objects, which can simply be `.join("\n")`ed to convert back | ||
* into text. | ||
* | ||
* @see {@link wordWrap} for main user facing alternative. | ||
* | ||
* @param lines | ||
* @param opts | ||
*/ | ||
export declare const wordWrapLines: (lines: string, opts: Partial<WordWrapOpts>) => Line[]; | ||
/** | ||
* Same as {@link wordWrapLines}, but returns wordwrapped result as string. See | ||
* {@link WordWrapOpts} for options. | ||
* | ||
* @param str | ||
* @param opts | ||
*/ | ||
export declare const wordWrap: (str: string, opts: Partial<WordWrapOpts>) => string; | ||
export {}; | ||
//# sourceMappingURL=word-wrap.d.ts.map |
168
word-wrap.js
import { lengthAnsi } from "./ansi"; | ||
export const wordWrap = (str, lineWidth) => wordWrapLines(str, lineWidth).join("\n"); | ||
export const wordWrapLines = (str, lineWidth = 80) => { | ||
const res = []; | ||
for (let line of str.split("\n")) { | ||
if (!line.length) { | ||
res.push(""); | ||
continue; | ||
} | ||
wordWrapLine(line, lineWidth, res); | ||
import { split } from "./split"; | ||
/** | ||
* Internal representation of a single line (for word wrapping purposes). A thin | ||
* wrapper of individual word and the _logical_ line length (rather than the | ||
* actualy string width). | ||
* | ||
* @internal | ||
*/ | ||
class Line { | ||
constructor(word, n) { | ||
this.n = 0; | ||
this.w = []; | ||
word != null && this.add(word, n); | ||
} | ||
return res; | ||
add(word, n = word.length) { | ||
this.w.push(word); | ||
this.n += n + ~~(this.n > 0); | ||
return this; | ||
} | ||
toString() { | ||
return this.w.join(" "); | ||
} | ||
} | ||
/** | ||
* (Default) wordwrap word splitting strategy for plain text. | ||
*/ | ||
export const SPLIT_PLAIN = { | ||
length: (x) => x.length, | ||
split: (_, max) => max, | ||
}; | ||
export const wordWrapLine = (line, lineWidth = 80, acc = []) => { | ||
let ln = 0; | ||
let curr = []; | ||
for (let w of line.split(" ")) { | ||
const l = lengthAnsi(w) + (ln > 0 ? 1 : 0); | ||
if (ln + l <= lineWidth) { | ||
curr.push(w, " "); | ||
ln += l; | ||
/** | ||
* Wordwrap word splitting strategy for text containing ANSI control sequences. | ||
*/ | ||
export const SPLIT_ANSI = { | ||
length: lengthAnsi, | ||
split: (x, max) => { | ||
const re = /\x1b\[[0-9;]+m/g; | ||
let i = max; | ||
let match; | ||
while ((match = re.exec(x))) { | ||
if (match.index >= max) | ||
break; | ||
const n = match[0].length; | ||
i += n; | ||
max += n; | ||
} | ||
else { | ||
acc.push(trimLine(curr)); | ||
curr = [w, " "]; | ||
ln = l; | ||
} | ||
return i; | ||
}, | ||
}; | ||
/** | ||
* Attempts to append given word to current line or else creates a new line. | ||
* | ||
* @internal | ||
*/ | ||
const append = (acc, word, wordLen, width) => { | ||
const curr = acc[acc.length - 1]; | ||
curr && width - curr.n > wordLen | ||
? curr.add(word, wordLen) | ||
: acc.push(new Line(word, wordLen)); | ||
}; | ||
/** | ||
* Depending on wrap mode (hard/soft), splits too long words into multiple lines | ||
* and appends them to `acc`. | ||
* | ||
* @remarks | ||
* Splitting uses the provided {@link IWordSplit} impl (or, if missing, | ||
* {@link SPLIT_PLAIN}). If the current start line only has less than | ||
* {@link WordWrapOpts.min} chars available and the word is longer than that, it | ||
* will be placed into a new line (thus minimizing legibility issues). | ||
* | ||
* @param word | ||
* @param opts | ||
* @param offset | ||
* @param acc | ||
* | ||
* @internal | ||
*/ | ||
const wrapWord = (word, opts, offset = 0, acc = []) => { | ||
const width = opts.width || 80; | ||
const impl = opts.splitter || SPLIT_PLAIN; | ||
let len = impl.length(word); | ||
let free = width - offset; | ||
// don't start word in current line if only | ||
// a few chars left... | ||
if (free < (opts.min || 4) && free < len) { | ||
free = width; | ||
} | ||
ln && acc.push(trimLine(curr)); | ||
// (maybe) hardwrap long word | ||
while (opts.hard && len > free) { | ||
const split = impl.split(word, free); | ||
const chunk = word.substr(0, split); | ||
append(acc, chunk, free, width); | ||
word = word.substr(split); | ||
free = width; | ||
len = impl.length(word); | ||
} | ||
append(acc, word, len, width); | ||
return acc; | ||
}; | ||
const trimLine = (x) => { | ||
/^\s+$/.test(x[x.length - 1]) && x.pop(); | ||
return x.join(""); | ||
/** | ||
* Wordwraps a single-`line` string using provided options. Returns array of | ||
* {@link Line} objects, which can simply be `.join("\n")`ed to convert back | ||
* into text. | ||
* | ||
* @see {@link wordWrap} for main user facing alternative. | ||
* | ||
* @param line | ||
* @param opts | ||
* @param acc | ||
* | ||
* @internal | ||
*/ | ||
export const wordWrapLine = (line, opts, acc = []) => { | ||
if (!line.length) { | ||
acc.push(new Line()); | ||
return acc; | ||
} | ||
for (let word of split(line, opts.delimWord || /\s/g)) { | ||
const curr = acc[acc.length - 1]; | ||
wrapWord(word, opts, curr && curr.n > 0 ? curr.n + 1 : 0, acc); | ||
} | ||
return acc; | ||
}; | ||
/** | ||
* Wordwraps a multi-`line` string using provided options. Returns array of | ||
* {@link Line} objects, which can simply be `.join("\n")`ed to convert back | ||
* into text. | ||
* | ||
* @see {@link wordWrap} for main user facing alternative. | ||
* | ||
* @param lines | ||
* @param opts | ||
*/ | ||
export const wordWrapLines = (lines, opts) => { | ||
let acc = []; | ||
for (let line of split(lines, opts.delimLine)) { | ||
acc = acc.concat(wordWrapLine(line, opts)); | ||
} | ||
return acc; | ||
}; | ||
/** | ||
* Same as {@link wordWrapLines}, but returns wordwrapped result as string. See | ||
* {@link WordWrapOpts} for options. | ||
* | ||
* @param str | ||
* @param opts | ||
*/ | ||
export const wordWrap = (str, opts) => wordWrapLines(str, opts).join("\n"); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
163456
2476
202