main.js (6043B)
1 /** 2 * @license Apache-2.0 3 * 4 * Copyright (c) 2022 The Stdlib Authors. 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 'use strict'; 20 21 // MODULES // 22 23 var formatInteger = require( './format_integer.js' ); 24 var isString = require( './is_string.js' ); 25 var formatDouble = require( './format_double.js' ); 26 var spacePad = require( './space_pad.js' ); 27 var zeroPad = require( './zero_pad.js' ); 28 29 30 // VARIABLES // 31 32 var fromCharCode = String.fromCharCode; 33 var isnan = isNaN; // NOTE: We use the global `isNaN` function here instead of `@stdlib/math/base/assert/is-nan` to avoid circular dependencies. 34 var isArray = Array.isArray; // NOTE: We use the global `Array.isArray` function here instead of `@stdlib/assert/is-array` to avoid circular dependencies. 35 36 37 // FUNCTIONS // 38 39 /** 40 * Initializes token object with properties of supplied format identifier object or default values if not present. 41 * 42 * @private 43 * @param {Object} token - format identifier object 44 * @returns {Object} token object 45 */ 46 function initialize( token ) { 47 var out = {}; 48 out.specifier = token.specifier; 49 out.precision = ( token.precision === void 0 ) ? 1 : token.precision; 50 out.width = token.width; 51 out.flags = token.flags || ''; 52 out.mapping = token.mapping; 53 return out; 54 } 55 56 57 // MAIN // 58 59 /** 60 * Generates string from a token array by interpolating values. 61 * 62 * @param {Array} tokens - string parts and format identifier objects 63 * @param {Array} ...args - variable values 64 * @throws {TypeError} first argument must be an array 65 * @throws {Error} invalid flags 66 * @returns {string} formatted string 67 * 68 * @example 69 * var tokens = [ 'beep ', { 'specifier': 's' } ]; 70 * var out = formatInterpolate( tokens, 'boop' ); 71 * // returns 'beep boop' 72 */ 73 function formatInterpolate( tokens ) { 74 var hasPeriod; 75 var flags; 76 var token; 77 var flag; 78 var num; 79 var out; 80 var pos; 81 var i; 82 var j; 83 84 if ( !isArray( tokens ) ) { 85 throw new TypeError( 'invalid argument. First argument must be an array. Value: `' + tokens + '`.' ); 86 } 87 out = ''; 88 pos = 1; 89 for ( i = 0; i < tokens.length; i++ ) { 90 token = tokens[ i ]; 91 if ( isString( token ) ) { 92 out += token; 93 } else { 94 hasPeriod = token.precision !== void 0; 95 token = initialize( token ); 96 if ( !token.specifier ) { 97 throw new TypeError( 'invalid argument. Token is missing `specifier` property. Index: `'+ i +'`. Value: `' + token + '`.' ); 98 } 99 if ( token.mapping ) { 100 pos = token.mapping; 101 } 102 flags = token.flags; 103 for ( j = 0; j < flags.length; j++ ) { 104 flag = flags.charAt( j ); 105 switch ( flag ) { 106 case ' ': 107 token.sign = ' '; 108 break; 109 case '+': 110 token.sign = '+'; 111 break; 112 case '-': 113 token.padRight = true; 114 token.padZeros = false; 115 break; 116 case '0': 117 token.padZeros = flags.indexOf( '-' ) < 0; // NOTE: We use built-in `Array.prototype.indexOf` here instead of `@stdlib/assert/contains` in order to avoid circular dependencies. 118 break; 119 case '#': 120 token.alternate = true; 121 break; 122 default: 123 throw new Error( 'invalid flag: ' + flag ); 124 } 125 } 126 if ( token.width === '*' ) { 127 token.width = parseInt( arguments[ pos ], 10 ); 128 pos += 1; 129 if ( isnan( token.width ) ) { 130 throw new TypeError( 'the argument for * width at position ' + pos + ' is not a number. Value: `' + token.width + '`.' ); 131 } 132 if ( token.width < 0 ) { 133 token.padRight = true; 134 token.width = -token.width; 135 } 136 } 137 if ( hasPeriod ) { 138 if ( token.precision === '*' ) { 139 token.precision = parseInt( arguments[ pos ], 10 ); 140 pos += 1; 141 if ( isnan( token.precision ) ) { 142 throw new TypeError( 'the argument for * precision at position ' + pos + ' is not a number. Value: `' + token.precision + '`.' ); 143 } 144 if ( token.precision < 0 ) { 145 token.precision = 1; 146 hasPeriod = false; 147 } 148 } 149 } 150 token.arg = arguments[ pos ]; 151 switch ( token.specifier ) { 152 case 'b': 153 case 'o': 154 case 'x': 155 case 'X': 156 case 'd': 157 case 'i': 158 case 'u': 159 // Case: %b (binary), %o (octal), %x, %X (hexadecimal), %d, %i (decimal), %u (unsigned decimal) 160 if ( hasPeriod ) { 161 token.padZeros = false; 162 } 163 token.arg = formatInteger( token ); 164 break; 165 case 's': 166 // Case: %s (string) 167 token.maxWidth = ( hasPeriod ) ? token.precision : -1; 168 break; 169 case 'c': 170 // Case: %c (character) 171 if ( !isnan( token.arg ) ) { 172 num = parseInt( token.arg, 10 ); 173 if ( num < 0 || num > 127 ) { 174 throw new Error( 'invalid character code. Value: ' + token.arg ); 175 } 176 token.arg = ( isnan( num ) ) ? 177 String( token.arg ) : 178 fromCharCode( num ); 179 } 180 break; 181 case 'e': 182 case 'E': 183 case 'f': 184 case 'F': 185 case 'g': 186 case 'G': 187 // Case: %e, %E (scientific notation), %f, %F (decimal floating point), %g, %G (uses the shorter of %e/E or %f/F) 188 if ( !hasPeriod ) { 189 token.precision = 6; 190 } 191 token.arg = formatDouble( token ); 192 break; 193 default: 194 throw new Error( 'invalid specifier: ' + token.specifier ); 195 } 196 // Fit argument into field width... 197 if ( token.maxWidth >= 0 && token.arg.length > token.maxWidth ) { 198 token.arg = token.arg.substring( 0, token.maxWidth ); 199 } 200 if ( token.padZeros ) { 201 token.arg = zeroPad( token.arg, token.width || token.precision, token.padRight ); // eslint-disable-line max-len 202 } else if ( token.width ) { 203 token.arg = spacePad( token.arg, token.width, token.padRight ); 204 } 205 out += token.arg || ''; 206 pos += 1; 207 } 208 } 209 return out; 210 } 211 212 213 // EXPORTS // 214 215 module.exports = formatInterpolate;