awesome-string
Version:
The ultimate JavaScript string library
187 lines (186 loc) • 7.62 kB
JavaScript
import coerceToString from 'helper/string/coerce_to_string';
import { REGEXP_CONVERSION_SPECIFICATION } from 'helper/reg_exp/const';
import ReplacementIndex from 'helper/format/replacement/index';
import replacementMatch from 'helper/format/replacement/match';
/**
* Produces a string according to `format`.
*
* <div id="sprintf-format" class="smaller">
* `format` string is composed of zero or more directives: ordinary characters (not <code>%</code>), which are copied unchanged
* to the output string and <i>conversion specifications</i>, each of which results in fetching zero or more subsequent
* arguments. <br/> <br/>
*
* Each <b>conversion specification</b> is introduced by the character <code>%</code>, and ends with a <b>conversion
* specifier</b>. In between there may be (in this order) zero or more <b>flags</b>, an optional <b>minimum field width</b>
* and an optional <b>precision</b>.<br/>
* The syntax is: <b>ConversionSpecification</b> = <b>"%"</b> { <b>Flags</b> }
* [ <b>MinimumFieldWidth</b> ] [ <b>Precision</b> ] <b>ConversionSpecifier</b>, where curly braces { } denote repetition
* and square brackets [ ] optionality. <br/><br/>
*
* By default, the arguments are used in the given order.<br/>
* For argument numbering and swapping, `%m$` (where `m` is a number indicating the argument order)
* is used instead of `%` to specify explicitly which argument is taken. For instance `%1$s` fetches the 1st argument,
* `%2$s` the 2nd and so on, no matter what position the conversion specification has in `format`.
* <br/><br/>
*
* <b>The flags</b><br/>
* The character <code>%</code> is followed by zero or more of the following flags:<br/>
* <table class="light-params">
* <tr>
* <td><code>+</code></td>
* <td>
* A sign (<code>+</code> or <code>-</code>) should always be placed before a number produced by a
* signed conversion. By default a sign is used only for negative numbers.
* </td>
* </tr>
* <tr>
* <td><code>0</code></td>
* <td>The value should be zero padded.</td>
* </tr>
* <tr>
* <td><code>␣</code></td>
* <td>(a space) The value should be space padded.</td>
* </tr>
* <tr>
* <td><code>'</code></td>
* <td>Indicates alternate padding character, specified by prefixing it with a single quote <code>'</code>.</td>
* </tr>
* <tr>
* <td><code>-</code></td>
* <td>The converted value is to be left adjusted on the field boundary (the default is right justification).</td>
* </tr>
* </table>
*
* <b>The minimum field width</b><br/>
* An optional decimal digit string (with nonzero first digit) specifying a minimum field width. If the converted
* value has fewer characters than the field width, it will be padded with spaces on the left (or right, if the
* left-adjustment flag has been given).<br/><br/>
*
* <b>The precision</b><br/>
* An optional precision, in the form of a period `.` followed by an optional decimal digit string.<br/>
* This gives the number of digits to appear after the radix character for `e`, `E`, `f` and `F` conversions, the
* maximum number of significant digits for `g` and `G` conversions or the maximum number of characters to be printed
* from a string for `s` conversion.<br/><br/>
*
* <b>The conversion specifier</b><br/>
* A specifier that mentions what type the argument should be treated as:
*
* <table class="light-params">
* <tr>
* <td>`s`</td>
* <td>The string argument is treated as and presented as a string.</td>
* </tr>
* <tr>
* <td>`d` `i`</td>
* <td>The integer argument is converted to signed decimal notation.</td>
* </tr>
* <tr>
* <td>`b`</td>
* <td>The unsigned integer argument is converted to unsigned binary.</td>
* </tr>
* <tr>
* <td>`c`</td>
* <td>The unsigned integer argument is converted to an ASCII character with that number.</td>
* </tr>
* <tr>
* <td>`o`</td>
* <td>The unsigned integer argument is converted to unsigned octal.</td>
* </tr>
* <tr>
* <td>`u`</td>
* <td>The unsigned integer argument is converted to unsigned decimal.</td>
* </tr>
* <tr>
* <td>`x` `X`</td>
* <td>The unsigned integer argument is converted to unsigned hexadecimal. The letters `abcdef` are used for `x`
* conversions; the letters `ABCDEF` are used for `X` conversions.</td>
* </tr>
* <tr>
* <td>`f`</td>
* <td>
* The float argument is rounded and converted to decimal notation in the style `[-]ddd.ddd`, where the number of
* digits after the decimal-point character is equal to the precision specification. If the precision is missing,
* it is taken as 6; if the precision is explicitly zero, no decimal-point character appears.
* If a decimal point appears, at least one digit appears before it.
* </td>
* </tr>
* <tr>
* <td>`e` `E`</td>
* <td>
* The float argument is rounded and converted in the style `[-]d.ddde±dd`, where there is one digit
* before the decimal-point character and the number of digits after it is equal to the precision. If
* the precision is missing, it is taken as `6`; if the precision is zero, no decimal-point character
* appears. An `E` conversion uses the letter `E` (rather than `e`) to introduce the exponent.
* </td>
* </tr>
* <tr>
* <td>`g` `G`</td>
* <td>
* The float argument is converted in style `f` or `e` (or `F` or `E` for `G` conversions). The precision specifies
* the number of significant digits. If the precision is missing, `6` digits are given; if the
* precision is zero, it is treated as `1`. Style `e` is used if the exponent from its conversion is less
* than `-6` or greater than or equal to the precision. Trailing zeros are removed from the fractional
* part of the result; a decimal point appears only if it is followed by at least one digit.
* </td>
* </tr>
* <tr>
* <td>`%`</td>
* <td>A literal `%` is written. No argument is converted. The complete conversion specification is `%%`.</td>
* </tr>
*
* </table>
* </div>
*
* @function sprintf
* @static
* @since 1.0.0
* @memberOf Format
* @param {string} [format=''] The format string.
* @param {...*} replacements The replacements to produce the string.
* @return {string} Returns the produced string.
* @example
* as.sprintf('%s, %s!', 'Hello', 'World');
* // => 'Hello World!'
*
* as.sprintf('%s costs $%d', 'coffee', 2);
* // => 'coffee costs $2'
*
* as.sprintf('%1$s %2$s %1$s %2$s, watcha gonna %3$s', 'bad', 'boys', 'do')
* // => 'bad boys bad boys, watcha gonna do'
*
* as.sprintf('% 6s', 'bird');
* // => ' bird'
*
* as.sprintf('% -6s', 'crab');
* // => 'crab '
*
* as.sprintf("%'*5s", 'cat');
* // => '**cat'
*
* as.sprintf("%'*-6s", 'duck');
* // => 'duck**'
*
* as.sprintf('%d %i %+d', 15, -2, 25);
* // => '15 -2 +25'
*
* as.sprintf("%06d", 15);
* // => '000015'
*
* as.sprintf('0b%b 0o%o 0x%X', 12, 9, 155);
* // => '0b1100 0o11 0x9B'
*
* as.sprintf('%.2f', 10.469);
* // => '10.47'
*
* as.sprintf('%.2e %g', 100.5, 0.455);
* // => '1.01e+2 0.455'
*
*/
export default function sprintf(format, ...replacements) {
const formatString = coerceToString(format);
if (formatString === '') {
return formatString;
}
const boundReplacementMatch = replacementMatch.bind(undefined, new ReplacementIndex(), replacements);
return formatString.replace(REGEXP_CONVERSION_SPECIFICATION, boundReplacementMatch);
}