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;
 |