UNPKG

@blameitonyourisp/blurrid

Version:

Generate and render blurred placeholders for lazy loaded images.

291 lines (268 loc) 11.4 kB
// Copyright (c) 2023 James Reid. All rights reserved. // // This source code file is licensed under the terms of the MIT license, a copy // of which may be found in the LICENSE.md file in the root of this repository. // // For a template copy of the license see one of the following 3rd party sites: // - <https://opensource.org/licenses/MIT> // - <https://choosealicense.com/licenses/mit> // - <https://spdx.org/licenses/MIT> /** * @ignore * @file Functions for decorating cli strings. * @author James Reid */ // @ts-check // @@no-imports // @@body /** * Custom error which renders an error in a uniform way, taking an options * object with reserved name and message fields, and then any other fields * relevant to the error, and rendering them with indentation. */ class DecoratedError extends Error { /** * Build new error based on supplied options object which may set the custom * name of the error, the top level message of the error, and then any * number of other custom fields. * * @param {Object.<string,string|number|boolean>} options - Options object * containing any string keys, and string, number or boolean values. */ constructor(options) { super() let message = "" // Parse all properties in options argument; if key is name, use value // to set the custom name of the error, if key is message, append value // top of error message, otherwise render a grey, tabbed key-value // pair to the end of the message string. for (const key in options) { switch (key) { case "name": this.name = /** @type {string} */ (options[key]) break case "message": message = `${options[key]}\n${message}` break default: { const detail = `${key}: ${options[key]}` message = `${message}${decorateFg(detail, "gray", 1)}\n` } } } // Set error message which will be displayed. this.message = message || "" } } /** * Decorate a string with control characters for changing cli string colors and * text decoration. Adds supplied list of modifiers for the string, then uses * reset character to set text back to default. * * @summary Return original string decorated with control characters. * @param {string} string - String to be decorated. * @param {object} options - Options object. * @param {string[]=} options.modifiers - String array of modifiers/control * characters to apply input string. * @param {number=} options.tabs - Number of tab indents to include on string. * @param {number=} options.tabSize - Number of spaces for each tab. * @returns {string} Original string decorated with control characters. */ const decorate = (string, { modifiers = [], tabs = 0, tabSize = 4 } = {}) => { // Make compound modifier string form array of supplied control characters. let compoundModifier = "" for (const modifier of modifiers) { compoundModifier = `${compoundModifier}${modifier}` } // Wrap input string with control characters and reset character. const reset = cliModifiers.decorations.reset const indent = " ".repeat(tabs * tabSize) return `${compoundModifier}${indent}${string}${reset}` } /** * Wrapper around decorate function to decorate a string with a foreground * colour control character, and optional number of tab indents. * * @summary Decorate a string with a foreground colour control character. * @param {string} string - String to be decorated. * @param {string} color - Desired foreground colour of string. * @param {number} [tabs=0] - Number of tab indents to include on string. * @returns {string} Original string decorated with color control character. */ const decorateFg = (string, color, tabs = 0) => { const colorModifier = cliModifiers.fgColors[color] // Return decorated string depending on if color resolves to valid control // character from object of cli modifiers. return colorModifier ? decorate(string, { modifiers: [colorModifier], tabs }) : decorate(string, { tabs }) } /** * Wrapper around decorate function to decorate a string with a background * colour control character, and optional number of tab indents. * * @summary Decorate a string with a background colour control character. * @param {string} string - String to be decorated. * @param {string} color - Desired background colour of string. * @param {number} [tabs=0] - Number of tab indents to include on string. * @returns {string} Original string decorated with color control character. */ const decorateBg = (string, color, tabs = 0) => { const colorModifier = cliModifiers.bgColors[color] // Return decorated string depending on if color resolves to valid control // character from object of cli modifiers. return colorModifier ? decorate(string, { modifiers: [colorModifier], tabs }) : decorate(string, { tabs }) } /** * Wrapper around String.prototype.padEnd method, which pads end of string * whilst ignoring length of control characters which will not be rendered by * the console (i.e. pad string such that the displayed string will be the * correct length when logged in the console). * * @summary Pad end of string, ignoring length of control characters. * @param {string} string - String to be padded. * @param {number} maxLength - Maximum length of string. * @param {string} [fillString] - Optional fill string passed to * String.prototype.padEnd method * @returns {string} Original string padded at end, ignoring length of control * characters. */ const padEndDecorated = (string, maxLength, fillString) => { // Calculate length of decorators in a string using control regex. const decoratorLength = string.match(/\x1b\[\d*m/g)?.join("").length || 0 return string.padEnd(maxLength + decoratorLength, fillString) } /** * Wrapper around String.prototype.padStart method, which pads start of string * whilst ignoring length of control characters which will not be rendered by * the console (i.e. pad string such that the displayed string will be the * correct length when logged in the console). * * @summary Pad start of string, ignoring length of control characters. * @param {string} string - String to be padded. * @param {number} maxLength - Maximum length of string. * @param {string} [fillString] - Optional fill string passed to * String.prototype.padStart method * @returns {string} Original string padded at end, ignoring length of control * characters. */ const padStartDecorated = (string, maxLength, fillString) => { // Calculate length of decorators in a string using control regex. const decoratorLength = string.match(/\x1b\[\d*m/g)?.join("").length || 0 return string.padStart(maxLength + decoratorLength, fillString) } // Object of cli control strings for decorations etc. const cliModifiers = { // Foreground modifiers. /** @type {Object.<string,string>} */ fgColors: { black: "\x1b[30m", red: "\x1b[31m", green: "\x1b[32m", yellow: "\x1b[33m", blue: "\x1b[34m", magenta: "\x1b[35m", cyan: "\x1b[36m", white: "\x1b[37m", gray: "\x1b[90m" }, // Background modifiers. /** @type {Object.<string,string>} */ bgColors: { black: "\x1b[40m", red: "\x1b[41m", green: "\x1b[42m", yellow: "\x1b[43m", blue: "\x1b[44m", magenta: "\x1b[45m", cyan: "\x1b[46m", white: "\x1b[47m", gray: "\x1b[100m" }, // Decoration modifiers. /** @type {Object.<string,string>} */ decorations: { reset: "\x1b[0m", bright: "\x1b[1m", dim: "\x1b[2m", underline: "\x1b[4m", blink: "\x1b[5m", reverse: "\x1b[7m", hidden: "\x1b[8m" } } /** * Convert kebab-case string to camelCase string, removing all hyphens in input * string, and capitalising the first character following each hyphen. Options * available for generating UpperCamelCase strings, and spaced strings too. * * @summary Convert kebab-case string to camelCase string. * @param {string} kebabCaseString - Input kebab-case string. * @param {boolean} isUpper - Should return string be in UpperCamelCase? * @param {boolean} isSpaced - Should return string replace hyphens with * whitespace characters? * @returns {string} camelCase string version of input. */ const toCamelCase = (kebabCaseString, isUpper = false, isSpaced = false) => { // Reduce split input string with starting value object containing an empty // string and flag set to isUpper for if next character should be capital. return (kebabCaseString.split("").reduce((acc, cur) => { // Ignore hyphens, or replace with spaces as required. if (cur === "-") { return isSpaced ? { string: `${acc.string} `, isCapital: true } : { ...acc, isCapital: true } } // Concatenate string with next character set to uppercase if required. const nextChar = acc.isCapital ? cur.toUpperCase() : cur.toLowerCase() return { string: `${acc.string}${nextChar}`, isCapital: false } }, { string: "", isCapital: isUpper }).string) } /** * Convert camelCaseString to kebab-case-string, adding hyphens between words. * If input string includes spaces (for example the verbatim title of a * markdown documentation file), replace with hyphens - this is good for * converting a title string to a valid kebab-case-filename. Option also * available for generating Upper-Kebab-Case strings. * * @summary Convert camelCaseString to kebab-case-string. * @param {string} camelCaseString - Input camelCaseString. * @param {boolean} isUpper - Should return string be in Upper-Kebab-Case? * @returns {string} kebab-case-string version of input. */ const toKebabCase = (camelCaseString, isUpper = false) => { // Reduce split input string with starting value object containing an empty // string and a new word flag set to true. return (camelCaseString.split("").reduce((acc, cur) => { // Ignore spaces in the input string, but set new word flag to true. if (cur === " ") { return { ...acc, isNewWord: true } } // If new word flag is set, or if an uppercase letter is found, insert // a hyphen before appending the upper/lowercase version of the current // letter depending on the isUpper argument. else if (acc.isNewWord || cur.match(/[A-Z]/)) { const nextChar = isUpper ? cur.toUpperCase() : cur.toLowerCase() return acc.string ? { string: `${acc.string}-${nextChar}`, isNewWord: false } : { string: `${nextChar}`, isNewWord: false } } // Otherwise, append current letter in lowercase. const nextChar = cur.toLowerCase() return { string: `${acc.string}${nextChar}`, isNewWord: false } }, { string: "", isNewWord: true }).string) } // @@exports export { DecoratedError, decorate, decorateFg, decorateBg, padEndDecorated, padStartDecorated, cliModifiers, toCamelCase, toKebabCase }