216 lines
5.9 KiB
JavaScript
216 lines
5.9 KiB
JavaScript
/**
|
|
* @license Apache-2.0
|
|
*
|
|
* Copyright (c) 2022 The Stdlib Authors.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
// MODULES //
|
|
|
|
var formatInteger = require( './format_integer.js' );
|
|
var isString = require( './is_string.js' );
|
|
var formatDouble = require( './format_double.js' );
|
|
var spacePad = require( './space_pad.js' );
|
|
var zeroPad = require( './zero_pad.js' );
|
|
|
|
|
|
// VARIABLES //
|
|
|
|
var fromCharCode = String.fromCharCode;
|
|
var isnan = isNaN; // NOTE: We use the global `isNaN` function here instead of `@stdlib/math/base/assert/is-nan` to avoid circular dependencies.
|
|
var isArray = Array.isArray; // NOTE: We use the global `Array.isArray` function here instead of `@stdlib/assert/is-array` to avoid circular dependencies.
|
|
|
|
|
|
// FUNCTIONS //
|
|
|
|
/**
|
|
* Initializes token object with properties of supplied format identifier object or default values if not present.
|
|
*
|
|
* @private
|
|
* @param {Object} token - format identifier object
|
|
* @returns {Object} token object
|
|
*/
|
|
function initialize( token ) {
|
|
var out = {};
|
|
out.specifier = token.specifier;
|
|
out.precision = ( token.precision === void 0 ) ? 1 : token.precision;
|
|
out.width = token.width;
|
|
out.flags = token.flags || '';
|
|
out.mapping = token.mapping;
|
|
return out;
|
|
}
|
|
|
|
|
|
// MAIN //
|
|
|
|
/**
|
|
* Generates string from a token array by interpolating values.
|
|
*
|
|
* @param {Array} tokens - string parts and format identifier objects
|
|
* @param {Array} ...args - variable values
|
|
* @throws {TypeError} first argument must be an array
|
|
* @throws {Error} invalid flags
|
|
* @returns {string} formatted string
|
|
*
|
|
* @example
|
|
* var tokens = [ 'beep ', { 'specifier': 's' } ];
|
|
* var out = formatInterpolate( tokens, 'boop' );
|
|
* // returns 'beep boop'
|
|
*/
|
|
function formatInterpolate( tokens ) {
|
|
var hasPeriod;
|
|
var flags;
|
|
var token;
|
|
var flag;
|
|
var num;
|
|
var out;
|
|
var pos;
|
|
var i;
|
|
var j;
|
|
|
|
if ( !isArray( tokens ) ) {
|
|
throw new TypeError( 'invalid argument. First argument must be an array. Value: `' + tokens + '`.' );
|
|
}
|
|
out = '';
|
|
pos = 1;
|
|
for ( i = 0; i < tokens.length; i++ ) {
|
|
token = tokens[ i ];
|
|
if ( isString( token ) ) {
|
|
out += token;
|
|
} else {
|
|
hasPeriod = token.precision !== void 0;
|
|
token = initialize( token );
|
|
if ( !token.specifier ) {
|
|
throw new TypeError( 'invalid argument. Token is missing `specifier` property. Index: `'+ i +'`. Value: `' + token + '`.' );
|
|
}
|
|
if ( token.mapping ) {
|
|
pos = token.mapping;
|
|
}
|
|
flags = token.flags;
|
|
for ( j = 0; j < flags.length; j++ ) {
|
|
flag = flags.charAt( j );
|
|
switch ( flag ) {
|
|
case ' ':
|
|
token.sign = ' ';
|
|
break;
|
|
case '+':
|
|
token.sign = '+';
|
|
break;
|
|
case '-':
|
|
token.padRight = true;
|
|
token.padZeros = false;
|
|
break;
|
|
case '0':
|
|
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.
|
|
break;
|
|
case '#':
|
|
token.alternate = true;
|
|
break;
|
|
default:
|
|
throw new Error( 'invalid flag: ' + flag );
|
|
}
|
|
}
|
|
if ( token.width === '*' ) {
|
|
token.width = parseInt( arguments[ pos ], 10 );
|
|
pos += 1;
|
|
if ( isnan( token.width ) ) {
|
|
throw new TypeError( 'the argument for * width at position ' + pos + ' is not a number. Value: `' + token.width + '`.' );
|
|
}
|
|
if ( token.width < 0 ) {
|
|
token.padRight = true;
|
|
token.width = -token.width;
|
|
}
|
|
}
|
|
if ( hasPeriod ) {
|
|
if ( token.precision === '*' ) {
|
|
token.precision = parseInt( arguments[ pos ], 10 );
|
|
pos += 1;
|
|
if ( isnan( token.precision ) ) {
|
|
throw new TypeError( 'the argument for * precision at position ' + pos + ' is not a number. Value: `' + token.precision + '`.' );
|
|
}
|
|
if ( token.precision < 0 ) {
|
|
token.precision = 1;
|
|
hasPeriod = false;
|
|
}
|
|
}
|
|
}
|
|
token.arg = arguments[ pos ];
|
|
switch ( token.specifier ) {
|
|
case 'b':
|
|
case 'o':
|
|
case 'x':
|
|
case 'X':
|
|
case 'd':
|
|
case 'i':
|
|
case 'u':
|
|
// Case: %b (binary), %o (octal), %x, %X (hexadecimal), %d, %i (decimal), %u (unsigned decimal)
|
|
if ( hasPeriod ) {
|
|
token.padZeros = false;
|
|
}
|
|
token.arg = formatInteger( token );
|
|
break;
|
|
case 's':
|
|
// Case: %s (string)
|
|
token.maxWidth = ( hasPeriod ) ? token.precision : -1;
|
|
break;
|
|
case 'c':
|
|
// Case: %c (character)
|
|
if ( !isnan( token.arg ) ) {
|
|
num = parseInt( token.arg, 10 );
|
|
if ( num < 0 || num > 127 ) {
|
|
throw new Error( 'invalid character code. Value: ' + token.arg );
|
|
}
|
|
token.arg = ( isnan( num ) ) ?
|
|
String( token.arg ) :
|
|
fromCharCode( num );
|
|
}
|
|
break;
|
|
case 'e':
|
|
case 'E':
|
|
case 'f':
|
|
case 'F':
|
|
case 'g':
|
|
case 'G':
|
|
// Case: %e, %E (scientific notation), %f, %F (decimal floating point), %g, %G (uses the shorter of %e/E or %f/F)
|
|
if ( !hasPeriod ) {
|
|
token.precision = 6;
|
|
}
|
|
token.arg = formatDouble( token );
|
|
break;
|
|
default:
|
|
throw new Error( 'invalid specifier: ' + token.specifier );
|
|
}
|
|
// Fit argument into field width...
|
|
if ( token.maxWidth >= 0 && token.arg.length > token.maxWidth ) {
|
|
token.arg = token.arg.substring( 0, token.maxWidth );
|
|
}
|
|
if ( token.padZeros ) {
|
|
token.arg = zeroPad( token.arg, token.width || token.precision, token.padRight ); // eslint-disable-line max-len
|
|
} else if ( token.width ) {
|
|
token.arg = spacePad( token.arg, token.width, token.padRight );
|
|
}
|
|
out += token.arg || '';
|
|
pos += 1;
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
|
|
// EXPORTS //
|
|
|
|
module.exports = formatInterpolate;
|