UNPKG

rsformat

Version:

Formatting/printing library for JavaScript that takes after rust's string formatting

219 lines (218 loc) 7.99 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.fmt_raw = exports.format = void 0; const node_util_1 = require("node:util"); /** * Regex to match for possible formatting insertion points. * Similar to the way formatting is parsed in rust, * but with a few key differences: * - Named arguments before format specifiers aren't allowed, only numbers can be used. * - The - sign (unused in rust) is unsupported. * - Pointer format type 'p' is unsupported. * - Hexadecimal debug types 'x?' and 'X?' are unsupported. * - Specifying precision with * is unsupported. * * The formatter currently matches with a regex * instead of a full-blown parser for simplicity * and performance, as built-in regex matching is * likely to be faster than a js-implemented parser. * However, this will not match incorrectly formatted * insertion points. */ const FORMAT_REGEX = (/\{{2}|\}{2}|\{(\d*?)(?::(?:(.?)(\^|>|<))?(\+)?(#)?(0)?(\d*)?(\.\d*)?(\?|o|x|X|b|e|E)?)?\}/g); /** * Format a string similarly to rust's format! macro. * * @param str String used for formatting * @param params Parameters to be inserted into the format string */ function format(str, ...params) { return fmt_raw(str, params); } exports.format = format; /** * Raw formatting behaviour function called by `format` and printing functions. * * @param str String used for formatting * @param params Parameters to be inserted into the format string * @param options Options passed into formatting * @param options.colors Whether to use colors in debug formatting */ function fmt_raw(str, params, options = { colors: false }) { // Counter used for insertion of unnumbered values let param_counter = 0; str = str.replace(FORMAT_REGEX, ($, $param_number, $fill_character, $align_direction, $sign, $pretty, $pad_zeroes, $width, $precision, $type) => { // Return a bracket if the regex matched an escaped bracket if ($ === '{{') { return '{'; } if ($ === '}}') { return '}'; } // Process parameter number; increment param_counter if not included let param = $param_number === '' ? params[param_counter++] : params[+$param_number]; if (param === undefined) { throw new Error(`parameter ${$param_number || param_counter - 1} either NaN or not provided`); } let param_type = typeof param; let true_length = -1; // Process parameter type switch ($type) { case 'o': param = param.toString(8); break; case 'x': param = param.toString(16); break; case 'X': param = param.toString(16).toUpperCase(); break; case 'b': param = param.toString(2); break; case 'e': switch (param_type) { case 'number': param = param.toExponential(); break; case 'bigint': param = param.toLocaleString('en-US', { notation: 'scientific', maximumFractionDigits: 20, }).toLowerCase(); break; default: param = param.toString(); break; } break; case 'E': switch (param_type) { case 'number': param = param.toExponential().toUpperCase(); break; case 'bigint': param = param.toLocaleString('en-US', { notation: 'scientific', maximumFractionDigits: 20 }); break; default: param = param.toString(); break; } break; case '?': // Do not force sign or align to precision when using inspect $sign = undefined; $precision = undefined; true_length = (0, node_util_1.inspect)(param, { depth: Infinity, colors: false, compact: $pretty !== '#' }).length; param = (0, node_util_1.inspect)(param, { depth: Infinity, colors: options.colors, compact: $pretty !== '#' }); break; default: param = param.toString(); break; } ; if (true_length == -1) { true_length = param.length; } // Compute radix-point precision on numbers if (param_type == 'number' && $precision) { let [pre, post] = param.split('.'); if (post === undefined) { post = ''; } let precision = +$precision.substring(1, $precision.length); if (post.length > precision) { post = post.substring(0, precision); } else while (post.length < precision) { post = post + '0'; } param = pre + '.' + post; } let width; if ($width === undefined) { width = 0; } else { width = +$width; if (Number.isNaN(width)) throw new Error(`invalid width specifier '${$width}' (must be an integer)`); } let filled = false; if ((param_type == 'number') || (param_type == 'bigint')) { // Compute parameter sign let maybe_sign = param.substring(0, 1); if (maybe_sign === '-') { param = param.substring(1, param.length); } else if ($sign === '+') { maybe_sign = '+'; } else { maybe_sign = ''; } // If pretty printing is enabled and the formating calls for a prefix, add it if ($pretty === '#') { switch ($type) { case 'o': maybe_sign += "0o"; break; case 'x': case 'X': maybe_sign += "0x"; break; case 'b': maybe_sign += "0b"; break; } } //pad with zeroes if specified if ($pad_zeroes === '0') { filled = true; while (param.length < width - maybe_sign.length) { param = '0' + param; } } param = maybe_sign + param; } if (!filled && width > true_length) { // Compute fill/align $align_direction ||= '>'; $fill_character ||= ' '; let left = ''; let right = ''; let diff = width - true_length; switch ($align_direction) { case '>': left = $fill_character.repeat(diff); break; case '<': right = $fill_character.repeat(diff); break; case '^': left = $fill_character.repeat(diff - diff / 2); // Prioritise right-aligment on uneven length right = $fill_character.repeat(diff / 2 + diff % 2); break; } param = left + param + right; } return param; }); return str; } exports.fmt_raw = fmt_raw;