@thaunknown/web-irc
Version:
A TypeScript port of irc-framework's WebIRC client, without the bloat of unnceessary packages.
163 lines • 6.1 kB
JavaScript
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