UNPKG

@thaunknown/web-irc

Version:

A TypeScript port of irc-framework's WebIRC client, without the bloat of unnceessary packages.

163 lines 6.1 kB
import GraphemeSplitter from 'grapheme-splitter'; import { text2arr } from 'uint8-util'; const graphemeSplitter = new GraphemeSplitter(); /* abstract */ class SubstringTooLargeForLineError extends Error { /* substring: string */ /* opts: Options */ constructor(substring /* : string */, opts /* : Options */) { super(); // Maintains proper stack trace for where our error was thrown (only available on V8) // @ts-ignore if (Error.captureStackTrace) { // @ts-ignore Error.captureStackTrace(this, this.constructor); } // Custom debugging information this.substring = substring; this.opts = opts; } get name() { return this.constructor.name; } } class WordTooLargeForLineError extends SubstringTooLargeForLineError { get message() { return `${size(this.substring)} byte word can't fit in a ${this.opts.bytes} byte block: ${this.substring}`; } } class GraphemeTooLargeForLineError extends SubstringTooLargeForLineError { get message() { return `${size(this.substring)} byte grapheme can't fit in a ${this.opts.bytes} byte block: ${this.substring}`; } } class CodepointTooLargeForLineError extends SubstringTooLargeForLineError { get message() { return `${size(this.substring)} byte codepoint can't fit in a ${this.opts.bytes} byte block: ${this.substring}`; } } function size(str /* : string */) { const byteArray = text2arr(str); const bytes = byteArray.byteLength; return bytes; } /* export interface Options { bytes: number, allowBreakingWords?: boolean, allowBreakingGraphemes?: boolean, } */ function* lineBreak(str /* : string */, opts /* : Options */) { let line = ''; let previousWhitespace = ''; for (const [word, trailingWhitespace] of wordBreak(str)) { // word fits in current line if (size(line) + size(previousWhitespace) + size(word) <= opts.bytes) { line += previousWhitespace + word; previousWhitespace = trailingWhitespace; continue; } // can fit word in a line by itself if (size(word) <= opts.bytes) { if (line) { yield line; // yield previously built up line } // previously buffered whitespace is discarded as it was replaced by a line break // store new whitespace for later previousWhitespace = trailingWhitespace; line = word; // next line starts with word continue; } // can't fit word into a line by itself if (!opts.allowBreakingWords) { throw new WordTooLargeForLineError(word, opts); } // try to fit part of word into current line const wordPreviousWhitespace = trailingWhitespace; for (const grapheme of graphemeSplitter.iterateGraphemes(word)) { // can fit next grapheme if (size(line) + size(previousWhitespace) + size(grapheme) <= opts.bytes) { line += previousWhitespace + grapheme; previousWhitespace = ''; continue; } // can fit next grapheme into a line by itself if (size(grapheme) <= opts.bytes) { if (line) { yield line; } previousWhitespace = ''; line = grapheme; continue; } // grapheme can't fit in a single line if (!opts.allowBreakingGraphemes) { throw new GraphemeTooLargeForLineError(grapheme, opts); } // break grapheme into codepoints instead for (const codepoint of grapheme) { // can fit codepoint into current line if (size(line) + size(previousWhitespace) + size(codepoint) <= opts.bytes) { line += previousWhitespace + codepoint; previousWhitespace = ''; continue; } // can fit codepoint into its own line if (size(codepoint) <= opts.bytes) { if (line) { yield line; } previousWhitespace = ''; line = codepoint; continue; } // can't fit codepoint into its own line throw new CodepointTooLargeForLineError(codepoint, opts); } // end of codepoint loop } // end of grapheme loop previousWhitespace = wordPreviousWhitespace; } // end of [word, trailingWhitespace] loop // unyielded leftovers when we're done iterating over the input string if (previousWhitespace) { if (size(line) + size(previousWhitespace) <= opts.bytes) { line += previousWhitespace; // retain trailing whitespace on input line if possible } } if (line) { yield line; } } // yields [word, trailingWhitespace] tuples function* wordBreak(str /* : string */) { let word = ''; let trailingWhitespace = ''; for (const grapheme of graphemeSplitter.iterateGraphemes(str)) { // grapheme is whitespace if (/^\s+$/.test(grapheme)) { // collect whitespace trailingWhitespace += grapheme; continue; } // grapheme is non-whitespace // start of new word if (trailingWhitespace) { yield [word, trailingWhitespace]; word = grapheme; trailingWhitespace = ''; continue; } // continuation of word word += grapheme; } // possible leftovers at end of input string if (word) { yield [word, trailingWhitespace]; } // trailingWhitespace can't be non-empty unless word is non-empty } export default { WordTooLargeForLineError, GraphemeTooLargeForLineError, CodepointTooLargeForLineError, lineBreak, wordBreak }; //# sourceMappingURL=linebreak.js.map