"use strict"; var slice = [].slice; /** * @fileOverview The sprintf for js script from Alexandru Marasteanu at diveintojavascript.com * @module ink/strings/sprintf * * @copyright Copyright (c) Alexandru Marasteanu <alexaholic [at) gmail (dot] com> All rights reserved. * @see http://www.diveintojavascript.com/projects/javascript-sprintf * @author Alexandru Marasteanu <hello at alexei dot ro> * */ var sprintf = ( function() { function get_type( variable ) { return Object.prototype.toString.call( variable ).slice( 8, -1 ).toLowerCase(); } function str_repeat( input, multiplier ) { for ( var output = []; multiplier > 0; output[ --multiplier ] = input ) {/* do nothing */ } //noinspection JSHint return output.join( '' ); } var str_format = function() { if ( !str_format.cache.hasOwnProperty( arguments[ 0 ] ) ) { str_format.cache[ arguments[ 0 ] ] = str_format.parse( arguments[ 0 ] ); } return str_format.format.call( null, str_format.cache[ arguments[ 0 ] ], arguments ); }; str_format.format = function( parse_tree, argv ) { var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length; for ( i = 0; i < tree_length; i++ ) { node_type = get_type( parse_tree[ i ] ); if ( node_type === 'string' ) { output.push( parse_tree[ i ] ); } else if ( node_type === 'array' ) { match = parse_tree[ i ]; // convenience purposes only if ( match[ 2 ] ) { // keyword argument arg = argv[ cursor ]; for ( k = 0; k < match[ 2 ].length; k++ ) { if ( !arg.hasOwnProperty( match[ 2 ][ k ] ) ) { throw ( sprintf( '[sprintf] property "%s" does not exist', match[ 2 ][ k ] ) ); } arg = arg[ match[ 2 ][ k ] ]; } } else if ( match[ 1 ] ) { // positional argument (explicit) arg = argv[ match[ 1 ] ]; } else { // positional argument (implicit) arg = argv[ cursor++ ]; } if ( /[^s]/.test( match[ 8 ] ) && ( get_type( arg ) !== 'number' ) ) { throw ( sprintf( '[sprintf] expecting number but found %s', get_type( arg ) ) ); } switch ( match[ 8 ] ) { case 'b': arg = arg.toString( 2 ); break; case 'c': arg = String.fromCharCode( arg ); break; case 'd': arg = parseInt( arg, 10 ); break; case 'e': arg = match[ 7 ] ? arg.toExponential( match[ 7 ] ) : arg.toExponential(); break; case 'f': arg = match[ 7 ] ? parseFloat( arg ).toFixed( match[ 7 ] ) : parseFloat( arg ); break; case 'o': arg = arg.toString( 8 ); break; case 's': arg = ( ( arg = String( arg ) ) && match[ 7 ] ? arg.substring( 0, match[ 7 ] ) : arg ); break; case 'u': arg = Math.abs( arg ); break; case 'x': arg = arg.toString( 16 ); break; case 'X': arg = arg.toString( 16 ).toUpperCase(); break; } arg = ( /[def]/.test( match[ 8 ] ) && match[ 3 ] && arg >= 0 ? '+' + arg : arg ); pad_character = match[ 4 ] ? match[ 4 ] === '0' ? '0' : match[ 4 ].charAt( 1 ) : ' '; pad_length = match[ 6 ] - String( arg ).length; pad = match[ 6 ] ? str_repeat( pad_character, pad_length ) : ''; output.push( match[ 5 ] ? arg + pad : pad + arg ); } } return output.join( '' ); }; str_format.cache = {}; str_format.parse = function( fmt ) { var _fmt = fmt, match = [], parse_tree = [], arg_names = 0; while ( _fmt ) { if ( ( match = /^[^\x25]+/.exec( _fmt ) ) !== null ) { parse_tree.push( match[ 0 ] ); } else if ( ( match = /^\x25{2}/.exec( _fmt ) ) !== null ) { parse_tree.push( '%' ); } else if ( ( match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/ .exec( _fmt ) ) !== null ) { if ( match[ 2 ] ) { // noinspection JSDeclarationsAtScopeStart { var field_list = [], replacement_field = match[ 2 ], field_match = []; if ( ( field_match = /^([a-z_][a-z_\d]*)/i.exec( replacement_field ) ) !== null ) { field_list.push( field_match[ 1 ] ); while ( ( replacement_field = replacement_field.substring( field_match[ 0 ].length ) ) !== '' ) { if ( ( field_match = /^\.([a-z_][a-z_\d]*)/i.exec( replacement_field ) ) !== null ) { field_list.push( field_match[ 1 ] ); } else if ( ( field_match = /^\[(\d+)\]/.exec( replacement_field ) ) !== null ) { field_list.push( field_match[ 1 ] ); } else { throw ( '[sprintf] huh?' ); } } } else { throw ( '[sprintf] huh?' ); } match[ 2 ] = field_list; } } else { //noinspection JSHint arg_names |= 2; } if ( arg_names === 3 ) { throw ( '[sprintf] mixing positional and named placeholders is not (yet) supported' ); } parse_tree.push( match ); } else { throw ( '[sprintf] huh?' ); } _fmt = _fmt.substring( match[ 0 ].length ); } return parse_tree; }; return str_format; } )(); var vsprintf = function( fmt, argv ) { argv.unshift( fmt ); return sprintf.apply( null, argv ); }; /** * sprintf() for JavaScript 0.7-beta1 sprintf() for JavaScript is a complete open source JavaScript sprintf * implementation. * * It's prototype is simple: * * string sprintf(string format , [mixed arg1 [, mixed arg2 [ ,...]]]); * * The placeholders in the format string are marked by "%" and are followed by one or more of these elements, in this order: * * + An optional "+" sign that forces to preceed the result with a plus or minus sign on numeric values. By default, * only the "-" sign is used on negative numbers. * + An optional padding specifier that says what character to use for padding (if specified). Possible values are 0 * or any other character * precedeed by a '. The default is to pad with spaces. * + An optional "-" sign, that causes sprintf to left-align the result of this placeholder. The default is to * right-align the result. * + An optional number, that says how many characters the result should have. If the value to be returned is shorter * than this number, the result will be padded. * + An optional precision modifier, consisting of a "." (dot) followed by a number, that says how many digits should * be displayed for floating point numbers. When used on a string, it causes the result to be truncated. * + A type specifier that can be any of: * * + % — print a literal "%" character * + b — print an integer as a binary number * + c — print an integer as the character with that ASCII value * + d — print an integer as a signed decimal number * + e — print a float as scientific notation * + u — print an integer as an unsigned decimal number * + f — print a float as is * + o — print an integer as an octal number * + s — print a string as is * + x — print an integer as a hexadecimal number (lower-case) * + X — print an integer as a hexadecimal number (upper-case) * * You can also swap the arguments. That is, the order of the placeholders doesn't have to match the order of the * arguments. You can do that by simply indicating in the format string which arguments the placeholders refer to: * sprintf('%2$s %3$s a %1$s', 'cracker', 'Polly', 'wants'); * * And, of course, you can repeat the placeholders without having to increase the number of arguments. * * * Format strings may contain replacement fields rather than positional placeholders. Instead of referring to a certain * argument, you can now refer to a certain key within an object. Replacement fields are surrounded by rounded * parentheses () and begin with a keyword that refers to a key: * var user = { * name : 'Dolly' * }; * sprintf( 'Hello %(name)s', user ); // Hello Dolly * Keywords in replacement fields can be optionally followed by any number of keywords or indexes: * var users = [ { name : 'Dolly' * }, { name : 'Molly' * }, { * name : 'Polly' * } ]; * * sprintf( 'Hello %(users[0].name)s, %(users[1].name)s and %(users[2].name)s', { users : users * } ); // Hello Dolly, Molly and Polly @param {string} * fmt The format string * @param {...*} argv The formatters * @returns {string} * @method * @author Alexandru Marasteanu * @copyright Copyright (c) Alexandru Marasteanu <alexaholic [at) gmail (dot] com>. All rights reserved. * @note mixing positional and named placeholders is not (yet) supported * @see http://www.diveintojavascript.com/projects/javascript-sprintf * @example * var strings = require("ink-strings"); * strings.sprintf( "Hello %1s, it is me, %2s", "Doctor", "Dalek Caan" ); * -> "Hello Doctor, it is me, Dalek Caan" * * strings.sprintf( "%1b", 24 ); * -> "11000" * * strings.sprintf( "%1c", 74 ); * -> "J" * * strings.sprintf( "%1d", 40 ); * -> "40" * * strings.sprintf( "%1d", 40 * -1 ); * -> "-40" * * strings.sprintf( "%1e", 40 * 1000000 ); * -> "4e+7" * * strings.sprintf( "%1u", 40 * -1 ); * -> "40" * * strings.sprintf( "%1f", 40.23498765 ); * -> "40.23498765" * * strings.sprintf( "%1o", 24 ); * -> "30" * * strings.sprintf( "%(name)s %(occupation)s", { name : "doctor", occupation : "timelord" } ); -> "doctor timelord" * */ exports.sprintf = sprintf; /** * vsprintf() is the same as sprintf() except that it accepts an array of arguments, rather than a variable number of * arguments * @method * @author Alexandru Marasteanu * @copyright Copyright (c) Alexandru Marasteanu <alexaholic [at) gmail (dot] com>. All rights reserved. * @param {string} * fmt The format string * @param {array.<string>} argv The formatters * @returns {string} */ exports.vsprintf = vsprintf;