@tannin/sprintf 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,5 +1,4 @@
1
- `@tannin/sprintf`
2
- =================
1
+ # `@tannin/sprintf`
3
2
 
4
3
  Given a format string, returns string with arguments interpolatation. Arguments can either be provided directly via function arguments spread, or with an array as the second argument.
5
4
 
@@ -37,7 +36,7 @@ Using positional arguments:
37
36
  ```js
38
37
  import sprintf from '@tannin/sprintf';
39
38
 
40
- sprintf( 'Hello %s!', 'world' );
39
+ sprintf('Hello %s!', 'world');
41
40
  // ⇒ 'Hello world!'
42
41
  ```
43
42
 
@@ -46,13 +45,14 @@ Using named arguments:
46
45
  ```js
47
46
  import sprintf from '@tannin/sprintf';
48
47
 
49
- sprintf( 'Hello %(place)s! From %(name)s.', { place: 'world', name: 'Andrew' } )
48
+ sprintf('Hello %(place)s! From %(name)s.', { place: 'world', name: 'Andrew' });
50
49
  // ⇒ 'Hello world! From Andrew.'
51
50
  ```
52
51
 
53
52
  ## Type coercions and default values
54
53
 
55
54
  When replacing numeric types (`%d` and `%f`), values will be coerced to numeric values, and default to 0:
55
+
56
56
  - `sprintf( '%d', 123 )` returns `'123'`
57
57
  - `sprintf( '%d', '123' )` returns `'123'`
58
58
  - `sprintf( '%d', 'string' )` returns `'0'`
@@ -61,6 +61,7 @@ When replacing numeric types (`%d` and `%f`), values will be coerced to numeric
61
61
  - `sprintf( '%d', undefined )` returns `'0'`
62
62
 
63
63
  When replacing string types (`%s`), values will be coerced to strings, and nullish values will be replaced with `''`:
64
+
64
65
  - `sprintf( '%s', 'string' )` returns `'string'`
65
66
  - `sprintf( '%s', 0 )` returns `'0'`
66
67
  - `sprintf( '%s', false )` returns `'false'`
@@ -69,6 +70,6 @@ When replacing string types (`%s`), values will be coerced to strings, and nulli
69
70
 
70
71
  ## License
71
72
 
72
- Copyright 2019-2020 Andrew Duthie
73
+ Copyright 2019-2025 Andrew Duthie
73
74
 
74
75
  Released under the [MIT License](https://opensource.org/licenses/MIT).
package/build/index.js CHANGED
@@ -31,14 +31,14 @@
31
31
  *
32
32
  * @type {RegExp}
33
33
  */
34
- var PATTERN = /%(((\d+)\$)|(\(([$_a-zA-Z][$_a-zA-Z0-9]*)\)))?[ +0#-]*\d*(\.(\d+|\*))?(ll|[lhqL])?([cduxXefgsp%])/g;
34
+ var PATTERN =
35
+ /%(((\d+)\$)|(\(([$_a-zA-Z][$_a-zA-Z0-9]*)\)))?[ +0#-]*\d*(\.(\d+|\*))?(ll|[lhqL])?([cduxXefgsp%])/g;
35
36
  // ▲ ▲ ▲ ▲ ▲ ▲ ▲ type
36
37
  // │ │ │ │ │ └ Length (unsupported)
37
38
  // │ │ │ │ └ Precision / max width
38
39
  // │ │ │ └ Min width (unsupported)
39
40
  // │ │ └ Flags (unsupported)
40
41
  // └ Index └ Name (for named arguments)
41
-
42
42
  /**
43
43
  * Given a format string, returns string with arguments interpolatation.
44
44
  * Arguments can either be provided directly via function arguments spread, or
@@ -54,84 +54,135 @@ var PATTERN = /%(((\d+)\$)|(\(([$_a-zA-Z][$_a-zA-Z0-9]*)\)))?[ +0#-]*\d*(\.(\d+|
54
54
  * sprintf( 'Hello %s!', 'world' );
55
55
  * // ⇒ 'Hello world!'
56
56
  * ```
57
+ * @template {string} T
58
+ * @overload
59
+ * @param {T} string - string printf format string
60
+ * @param {...import('../types').SprintfArgs<T>} args - arguments to interpolate
57
61
  *
58
- * @param {string} string printf format string
59
- * @param {...string|string[]|Object<string,string>} [args] String arguments.
62
+ * @return {string} Formatted string.
63
+ */
64
+
65
+ /**
66
+ * Given a format string, returns string with arguments interpolatation.
67
+ * Arguments can either be provided directly via function arguments spread, or
68
+ * with an array as the second argument.
69
+ *
70
+ * @see https://en.wikipedia.org/wiki/Printf_format_string
71
+ *
72
+ * @example
73
+ *
74
+ * ```js
75
+ * import sprintf from '@tannin/sprintf';
76
+ *
77
+ * sprintf( 'Hello %s!', 'world' );
78
+ * // ⇒ 'Hello world!'
79
+ * ```
80
+ * @template {string} T
81
+ * @overload
82
+ * @param {T} string - string printf format string
83
+ * @param {import('../types').SprintfArgs<T>} args - arguments to interpolate
60
84
  *
61
85
  * @return {string} Formatted string.
62
86
  */
63
- function sprintf( string, args ) {
64
- var i;
65
87
 
66
- if ( ! Array.isArray( args ) ) {
67
- // Construct a copy of arguments from index one, used for replace
68
- // function placeholder substitution.
69
- args = new Array( arguments.length - 1 );
70
- for ( i = 1; i < arguments.length; i++ ) {
71
- args[ i - 1 ] = arguments[ i ];
72
- }
88
+ /**
89
+ * Given a format string, returns string with arguments interpolatation.
90
+ * Arguments can either be provided directly via function arguments spread, or
91
+ * with an array as the second argument.
92
+ *
93
+ * @see https://en.wikipedia.org/wiki/Printf_format_string
94
+ *
95
+ * @example
96
+ *
97
+ * ```js
98
+ * import sprintf from '@tannin/sprintf';
99
+ *
100
+ * sprintf( 'Hello %s!', 'world' );
101
+ * // ⇒ 'Hello world!'
102
+ * ```
103
+ * @template {string} T
104
+ * @param {T} string - string printf format string
105
+ * @param {...import('../types').SprintfArgs<T>} args - arguments to interpolate
106
+ *
107
+ * @return {string} Formatted string.
108
+ */
109
+ function sprintf(string, ...args) {
110
+ var i = 0;
111
+ if (Array.isArray(args[0])) {
112
+ args = /** @type {import('../types').SprintfArgs<T>[]} */ (
113
+ /** @type {unknown} */ args[0]
114
+ );
73
115
  }
74
116
 
75
- i = 1;
76
-
77
- return string.replace( PATTERN, function() {
78
- var index, name, precision, type, value;
117
+ return string.replace(PATTERN, function () {
118
+ var index,
119
+ // name needs to be documented as `string | undefined` else value will have tpye unknown.
120
+ /**
121
+ * Name of the argument to substitute, if any.
122
+ *
123
+ * @type {string | undefined}
124
+ */
125
+ name,
126
+ precision,
127
+ type,
128
+ value;
79
129
 
80
- index = arguments[ 3 ];
81
- name = arguments[ 5 ];
82
- precision = arguments[ 7 ];
83
- type = arguments[ 9 ];
130
+ index = arguments[3];
131
+ name = arguments[5];
132
+ precision = arguments[7];
133
+ type = arguments[9];
84
134
 
85
135
  // There's no placeholder substitution in the explicit "%", meaning it
86
136
  // is not necessary to increment argument index.
87
- if ( type === '%' ) {
137
+ if (type === '%') {
88
138
  return '%';
89
139
  }
90
140
 
91
141
  // Asterisk precision determined by peeking / shifting next argument.
92
- if ( precision === '*' ) {
93
- precision = args[ i - 1 ];
142
+ if (precision === '*') {
143
+ precision = args[i];
94
144
  i++;
95
145
  }
96
146
 
97
- if ( name !== undefined ) {
98
- // If it's a named argument, use name.
99
- if ( args[ 0 ] && typeof args[ 0 ] === 'object' &&
100
- args[ 0 ].hasOwnProperty( name ) ) {
101
- value = args[ 0 ][ name ];
102
- }
103
- } else {
147
+ if (name === undefined) {
104
148
  // If not a positional argument, use counter value.
105
- if ( index === undefined ) {
106
- index = i;
149
+ if (index === undefined) {
150
+ index = i + 1;
107
151
  }
108
152
 
109
153
  i++;
110
154
 
111
155
  // Positional argument.
112
- value = args[ index - 1 ];
156
+ value = args[index - 1];
157
+ } else if (
158
+ args[0] &&
159
+ typeof args[0] === 'object' &&
160
+ args[0].hasOwnProperty(name)
161
+ ) {
162
+ // If it's a named argument, use name.
163
+ value = args[0][name];
113
164
  }
114
165
 
115
166
  // Parse as type.
116
- if ( type === 'f' ) {
117
- value = parseFloat( value ) || 0;
118
- } else if ( type === 'd' ) {
119
- value = parseInt( value ) || 0;
167
+ if (type === 'f') {
168
+ value = parseFloat(value) || 0;
169
+ } else if (type === 'd') {
170
+ value = parseInt(value) || 0;
120
171
  }
121
172
 
122
173
  // Apply precision.
123
- if ( precision !== undefined ) {
124
- if ( type === 'f' ) {
125
- value = value.toFixed( precision );
126
- } else if ( type === 's' ) {
127
- value = value.substr( 0, precision );
174
+ if (precision !== undefined) {
175
+ if (type === 'f') {
176
+ value = value.toFixed(precision);
177
+ } else if (type === 's') {
178
+ value = value.substr(0, precision);
128
179
  }
129
180
  }
130
181
 
131
182
  // To avoid "undefined" concatenation, return empty string if no
132
183
  // placeholder substitution can be performed.
133
184
  return value !== undefined && value !== null ? value : '';
134
- } );
185
+ });
135
186
  }
136
187
 
137
188
  module.exports = sprintf;
package/dist/sprintf.js CHANGED
@@ -32,14 +32,14 @@ var sprintf = (function () {
32
32
  *
33
33
  * @type {RegExp}
34
34
  */
35
- var PATTERN = /%(((\d+)\$)|(\(([$_a-zA-Z][$_a-zA-Z0-9]*)\)))?[ +0#-]*\d*(\.(\d+|\*))?(ll|[lhqL])?([cduxXefgsp%])/g;
35
+ var PATTERN =
36
+ /%(((\d+)\$)|(\(([$_a-zA-Z][$_a-zA-Z0-9]*)\)))?[ +0#-]*\d*(\.(\d+|\*))?(ll|[lhqL])?([cduxXefgsp%])/g;
36
37
  // ▲ ▲ ▲ ▲ ▲ ▲ ▲ type
37
38
  // │ │ │ │ │ └ Length (unsupported)
38
39
  // │ │ │ │ └ Precision / max width
39
40
  // │ │ │ └ Min width (unsupported)
40
41
  // │ │ └ Flags (unsupported)
41
42
  // └ Index └ Name (for named arguments)
42
-
43
43
  /**
44
44
  * Given a format string, returns string with arguments interpolatation.
45
45
  * Arguments can either be provided directly via function arguments spread, or
@@ -55,86 +55,137 @@ var sprintf = (function () {
55
55
  * sprintf( 'Hello %s!', 'world' );
56
56
  * // ⇒ 'Hello world!'
57
57
  * ```
58
+ * @template {string} T
59
+ * @overload
60
+ * @param {T} string - string printf format string
61
+ * @param {...import('../types').SprintfArgs<T>} args - arguments to interpolate
62
+ *
63
+ * @return {string} Formatted string.
64
+ */
65
+
66
+ /**
67
+ * Given a format string, returns string with arguments interpolatation.
68
+ * Arguments can either be provided directly via function arguments spread, or
69
+ * with an array as the second argument.
70
+ *
71
+ * @see https://en.wikipedia.org/wiki/Printf_format_string
58
72
  *
59
- * @param {string} string printf format string
60
- * @param {...string|string[]|Object<string,string>} [args] String arguments.
73
+ * @example
74
+ *
75
+ * ```js
76
+ * import sprintf from '@tannin/sprintf';
77
+ *
78
+ * sprintf( 'Hello %s!', 'world' );
79
+ * // ⇒ 'Hello world!'
80
+ * ```
81
+ * @template {string} T
82
+ * @overload
83
+ * @param {T} string - string printf format string
84
+ * @param {import('../types').SprintfArgs<T>} args - arguments to interpolate
61
85
  *
62
86
  * @return {string} Formatted string.
63
87
  */
64
- function sprintf( string, args ) {
65
- var i;
66
88
 
67
- if ( ! Array.isArray( args ) ) {
68
- // Construct a copy of arguments from index one, used for replace
69
- // function placeholder substitution.
70
- args = new Array( arguments.length - 1 );
71
- for ( i = 1; i < arguments.length; i++ ) {
72
- args[ i - 1 ] = arguments[ i ];
73
- }
89
+ /**
90
+ * Given a format string, returns string with arguments interpolatation.
91
+ * Arguments can either be provided directly via function arguments spread, or
92
+ * with an array as the second argument.
93
+ *
94
+ * @see https://en.wikipedia.org/wiki/Printf_format_string
95
+ *
96
+ * @example
97
+ *
98
+ * ```js
99
+ * import sprintf from '@tannin/sprintf';
100
+ *
101
+ * sprintf( 'Hello %s!', 'world' );
102
+ * // ⇒ 'Hello world!'
103
+ * ```
104
+ * @template {string} T
105
+ * @param {T} string - string printf format string
106
+ * @param {...import('../types').SprintfArgs<T>} args - arguments to interpolate
107
+ *
108
+ * @return {string} Formatted string.
109
+ */
110
+ function sprintf(string, ...args) {
111
+ var i = 0;
112
+ if (Array.isArray(args[0])) {
113
+ args = /** @type {import('../types').SprintfArgs<T>[]} */ (
114
+ /** @type {unknown} */ args[0]
115
+ );
74
116
  }
75
117
 
76
- i = 1;
77
-
78
- return string.replace( PATTERN, function() {
79
- var index, name, precision, type, value;
80
-
81
- index = arguments[ 3 ];
82
- name = arguments[ 5 ];
83
- precision = arguments[ 7 ];
84
- type = arguments[ 9 ];
118
+ return string.replace(PATTERN, function () {
119
+ var index,
120
+ // name needs to be documented as `string | undefined` else value will have tpye unknown.
121
+ /**
122
+ * Name of the argument to substitute, if any.
123
+ *
124
+ * @type {string | undefined}
125
+ */
126
+ name,
127
+ precision,
128
+ type,
129
+ value;
130
+
131
+ index = arguments[3];
132
+ name = arguments[5];
133
+ precision = arguments[7];
134
+ type = arguments[9];
85
135
 
86
136
  // There's no placeholder substitution in the explicit "%", meaning it
87
137
  // is not necessary to increment argument index.
88
- if ( type === '%' ) {
138
+ if (type === '%') {
89
139
  return '%';
90
140
  }
91
141
 
92
142
  // Asterisk precision determined by peeking / shifting next argument.
93
- if ( precision === '*' ) {
94
- precision = args[ i - 1 ];
143
+ if (precision === '*') {
144
+ precision = args[i];
95
145
  i++;
96
146
  }
97
147
 
98
- if ( name !== undefined ) {
99
- // If it's a named argument, use name.
100
- if ( args[ 0 ] && typeof args[ 0 ] === 'object' &&
101
- args[ 0 ].hasOwnProperty( name ) ) {
102
- value = args[ 0 ][ name ];
103
- }
104
- } else {
148
+ if (name === undefined) {
105
149
  // If not a positional argument, use counter value.
106
- if ( index === undefined ) {
107
- index = i;
150
+ if (index === undefined) {
151
+ index = i + 1;
108
152
  }
109
153
 
110
154
  i++;
111
155
 
112
156
  // Positional argument.
113
- value = args[ index - 1 ];
157
+ value = args[index - 1];
158
+ } else if (
159
+ args[0] &&
160
+ typeof args[0] === 'object' &&
161
+ args[0].hasOwnProperty(name)
162
+ ) {
163
+ // If it's a named argument, use name.
164
+ value = args[0][name];
114
165
  }
115
166
 
116
167
  // Parse as type.
117
- if ( type === 'f' ) {
118
- value = parseFloat( value ) || 0;
119
- } else if ( type === 'd' ) {
120
- value = parseInt( value ) || 0;
168
+ if (type === 'f') {
169
+ value = parseFloat(value) || 0;
170
+ } else if (type === 'd') {
171
+ value = parseInt(value) || 0;
121
172
  }
122
173
 
123
174
  // Apply precision.
124
- if ( precision !== undefined ) {
125
- if ( type === 'f' ) {
126
- value = value.toFixed( precision );
127
- } else if ( type === 's' ) {
128
- value = value.substr( 0, precision );
175
+ if (precision !== undefined) {
176
+ if (type === 'f') {
177
+ value = value.toFixed(precision);
178
+ } else if (type === 's') {
179
+ value = value.substr(0, precision);
129
180
  }
130
181
  }
131
182
 
132
183
  // To avoid "undefined" concatenation, return empty string if no
133
184
  // placeholder substitution can be performed.
134
185
  return value !== undefined && value !== null ? value : '';
135
- } );
186
+ });
136
187
  }
137
188
 
138
189
  return sprintf;
139
190
 
140
- }());
191
+ })();
@@ -1 +1 @@
1
- var sprintf=function(){"use strict";var t=/%(((\d+)\$)|(\(([$_a-zA-Z][$_a-zA-Z0-9]*)\)))?[ +0#-]*\d*(\.(\d+|\*))?(ll|[lhqL])?([cduxXefgsp%])/g;return function(r,o){var i;if(!Array.isArray(o))for(o=new Array(arguments.length-1),i=1;i<arguments.length;i++)o[i-1]=arguments[i];return i=1,r.replace(t,function(){var r,t,e,n,a;return r=arguments[3],t=arguments[5],e=arguments[7],"%"===(n=arguments[9])?"%":("*"===e&&(e=o[i-1],i++),void 0!==t?o[0]&&"object"==typeof o[0]&&o[0].hasOwnProperty(t)&&(a=o[0][t]):(void 0===r&&(r=i),i++,a=o[r-1]),"f"===n?a=parseFloat(a)||0:"d"===n&&(a=parseInt(a)||0),void 0!==e&&("f"===n?a=a.toFixed(e):"s"===n&&(a=a.substr(0,e))),null!=a?a:"")})}}();
1
+ var sprintf=function(){"use strict";var r=/%(((\d+)\$)|(\(([$_a-zA-Z][$_a-zA-Z0-9]*)\)))?[ +0#-]*\d*(\.(\d+|\*))?(ll|[lhqL])?([cduxXefgsp%])/g;return function(t,...a){var e=0;return Array.isArray(a[0])&&(a=a[0]),t.replace(r,(function(){var r,t,n,o,s;return r=arguments[3],t=arguments[5],"%"===(o=arguments[9])?"%":("*"===(n=arguments[7])&&(n=a[e],e++),void 0===t?(void 0===r&&(r=e+1),e++,s=a[r-1]):a[0]&&"object"==typeof a[0]&&a[0].hasOwnProperty(t)&&(s=a[0][t]),"f"===o?s=parseFloat(s)||0:"d"===o&&(s=parseInt(s)||0),void 0!==n&&("f"===o?s=s.toFixed(n):"s"===o&&(s=s.substr(0,n))),null!=s?s:"")}))}}();
package/package.json CHANGED
@@ -1,34 +1,41 @@
1
1
  {
2
- "name": "@tannin/sprintf",
3
- "version": "1.2.0",
4
- "description": "printf string formatter",
5
- "main": "build/index.js",
6
- "module": "index.js",
7
- "types": "index.d.ts",
8
- "keywords": [],
9
- "author": {
10
- "name": "Andrew Duthie",
11
- "email": "andrew@andrewduthie.com",
12
- "url": "https://andrewduthie.com"
13
- },
14
- "homepage": "https://github.com/aduth/tannin",
15
- "repository": {
16
- "type": "git",
17
- "url": "https://github.com/aduth/tannin.git",
18
- "directory": "packages/sprintf"
19
- },
20
- "bugs": {
21
- "url": "https://github.com/aduth/tannin/issues"
22
- },
23
- "license": "MIT",
24
- "files": [
25
- "index.js",
26
- "index.d.ts",
27
- "build",
28
- "dist"
29
- ],
30
- "publishConfig": {
31
- "access": "public"
32
- },
33
- "gitHead": "cd1c7447843df7751c4abd1b92aee03fe56bfba4"
2
+ "name": "@tannin/sprintf",
3
+ "version": "1.3.0",
4
+ "type": "module",
5
+ "description": "printf string formatter",
6
+ "main": "build/index.js",
7
+ "module": "src/index.js",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./src/index.js",
11
+ "require": "./build/index.js"
12
+ }
13
+ },
14
+ "types": "src/index.d.ts",
15
+ "keywords": [],
16
+ "author": {
17
+ "name": "Andrew Duthie",
18
+ "email": "andrew@andrewduthie.com",
19
+ "url": "https://andrewduthie.com"
20
+ },
21
+ "homepage": "https://github.com/aduth/tannin",
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "https://github.com/aduth/tannin.git",
25
+ "directory": "packages/sprintf"
26
+ },
27
+ "bugs": {
28
+ "url": "https://github.com/aduth/tannin/issues"
29
+ },
30
+ "license": "MIT",
31
+ "files": [
32
+ "src/index.js",
33
+ "src/index.d.ts",
34
+ "types/index.d.ts",
35
+ "build",
36
+ "dist"
37
+ ],
38
+ "publishConfig": {
39
+ "access": "public"
40
+ }
34
41
  }
package/src/index.d.ts ADDED
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Given a format string, returns string with arguments interpolatation.
3
+ * Arguments can either be provided directly via function arguments spread, or
4
+ * with an array as the second argument.
5
+ *
6
+ * @see https://en.wikipedia.org/wiki/Printf_format_string
7
+ *
8
+ * @example
9
+ *
10
+ * ```js
11
+ * import sprintf from '@tannin/sprintf';
12
+ *
13
+ * sprintf( 'Hello %s!', 'world' );
14
+ * // ⇒ 'Hello world!'
15
+ * ```
16
+ * @template {string} T
17
+ * @overload
18
+ * @param {T} string - string printf format string
19
+ * @param {...import('../types').SprintfArgs<T>} args - arguments to interpolate
20
+ *
21
+ * @return {string} Formatted string.
22
+ */
23
+ export default function sprintf<T extends string>(string: T, ...args: import("../types").SprintfArgs<T>): string;
24
+ /**
25
+ * Given a format string, returns string with arguments interpolatation.
26
+ * Arguments can either be provided directly via function arguments spread, or
27
+ * with an array as the second argument.
28
+ *
29
+ * @see https://en.wikipedia.org/wiki/Printf_format_string
30
+ *
31
+ * @example
32
+ *
33
+ * ```js
34
+ * import sprintf from '@tannin/sprintf';
35
+ *
36
+ * sprintf( 'Hello %s!', 'world' );
37
+ * // ⇒ 'Hello world!'
38
+ * ```
39
+ * @template {string} T
40
+ * @overload
41
+ * @param {T} string - string printf format string
42
+ * @param {import('../types').SprintfArgs<T>} args - arguments to interpolate
43
+ *
44
+ * @return {string} Formatted string.
45
+ */
46
+ export default function sprintf<T extends string>(string: T, args: import("../types").SprintfArgs<T>): string;
@@ -29,14 +29,14 @@
29
29
  *
30
30
  * @type {RegExp}
31
31
  */
32
- var PATTERN = /%(((\d+)\$)|(\(([$_a-zA-Z][$_a-zA-Z0-9]*)\)))?[ +0#-]*\d*(\.(\d+|\*))?(ll|[lhqL])?([cduxXefgsp%])/g;
32
+ var PATTERN =
33
+ /%(((\d+)\$)|(\(([$_a-zA-Z][$_a-zA-Z0-9]*)\)))?[ +0#-]*\d*(\.(\d+|\*))?(ll|[lhqL])?([cduxXefgsp%])/g;
33
34
  // ▲ ▲ ▲ ▲ ▲ ▲ ▲ type
34
35
  // │ │ │ │ │ └ Length (unsupported)
35
36
  // │ │ │ │ └ Precision / max width
36
37
  // │ │ │ └ Min width (unsupported)
37
38
  // │ │ └ Flags (unsupported)
38
39
  // └ Index └ Name (for named arguments)
39
-
40
40
  /**
41
41
  * Given a format string, returns string with arguments interpolatation.
42
42
  * Arguments can either be provided directly via function arguments spread, or
@@ -52,82 +52,133 @@ var PATTERN = /%(((\d+)\$)|(\(([$_a-zA-Z][$_a-zA-Z0-9]*)\)))?[ +0#-]*\d*(\.(\d+|
52
52
  * sprintf( 'Hello %s!', 'world' );
53
53
  * // ⇒ 'Hello world!'
54
54
  * ```
55
+ * @template {string} T
56
+ * @overload
57
+ * @param {T} string - string printf format string
58
+ * @param {...import('../types').SprintfArgs<T>} args - arguments to interpolate
55
59
  *
56
- * @param {string} string printf format string
57
- * @param {...string|string[]|Object<string,string>} [args] String arguments.
60
+ * @return {string} Formatted string.
61
+ */
62
+
63
+ /**
64
+ * Given a format string, returns string with arguments interpolatation.
65
+ * Arguments can either be provided directly via function arguments spread, or
66
+ * with an array as the second argument.
67
+ *
68
+ * @see https://en.wikipedia.org/wiki/Printf_format_string
69
+ *
70
+ * @example
71
+ *
72
+ * ```js
73
+ * import sprintf from '@tannin/sprintf';
74
+ *
75
+ * sprintf( 'Hello %s!', 'world' );
76
+ * // ⇒ 'Hello world!'
77
+ * ```
78
+ * @template {string} T
79
+ * @overload
80
+ * @param {T} string - string printf format string
81
+ * @param {import('../types').SprintfArgs<T>} args - arguments to interpolate
58
82
  *
59
83
  * @return {string} Formatted string.
60
84
  */
61
- export default function sprintf( string, args ) {
62
- var i;
63
85
 
64
- if ( ! Array.isArray( args ) ) {
65
- // Construct a copy of arguments from index one, used for replace
66
- // function placeholder substitution.
67
- args = new Array( arguments.length - 1 );
68
- for ( i = 1; i < arguments.length; i++ ) {
69
- args[ i - 1 ] = arguments[ i ];
70
- }
86
+ /**
87
+ * Given a format string, returns string with arguments interpolatation.
88
+ * Arguments can either be provided directly via function arguments spread, or
89
+ * with an array as the second argument.
90
+ *
91
+ * @see https://en.wikipedia.org/wiki/Printf_format_string
92
+ *
93
+ * @example
94
+ *
95
+ * ```js
96
+ * import sprintf from '@tannin/sprintf';
97
+ *
98
+ * sprintf( 'Hello %s!', 'world' );
99
+ * // ⇒ 'Hello world!'
100
+ * ```
101
+ * @template {string} T
102
+ * @param {T} string - string printf format string
103
+ * @param {...import('../types').SprintfArgs<T>} args - arguments to interpolate
104
+ *
105
+ * @return {string} Formatted string.
106
+ */
107
+ export default function sprintf(string, ...args) {
108
+ var i = 0;
109
+ if (Array.isArray(args[0])) {
110
+ args = /** @type {import('../types').SprintfArgs<T>[]} */ (
111
+ /** @type {unknown} */ args[0]
112
+ );
71
113
  }
72
114
 
73
- i = 1;
74
-
75
- return string.replace( PATTERN, function() {
76
- var index, name, precision, type, value;
115
+ return string.replace(PATTERN, function () {
116
+ var index,
117
+ // name needs to be documented as `string | undefined` else value will have tpye unknown.
118
+ /**
119
+ * Name of the argument to substitute, if any.
120
+ *
121
+ * @type {string | undefined}
122
+ */
123
+ name,
124
+ precision,
125
+ type,
126
+ value;
77
127
 
78
- index = arguments[ 3 ];
79
- name = arguments[ 5 ];
80
- precision = arguments[ 7 ];
81
- type = arguments[ 9 ];
128
+ index = arguments[3];
129
+ name = arguments[5];
130
+ precision = arguments[7];
131
+ type = arguments[9];
82
132
 
83
133
  // There's no placeholder substitution in the explicit "%", meaning it
84
134
  // is not necessary to increment argument index.
85
- if ( type === '%' ) {
135
+ if (type === '%') {
86
136
  return '%';
87
137
  }
88
138
 
89
139
  // Asterisk precision determined by peeking / shifting next argument.
90
- if ( precision === '*' ) {
91
- precision = args[ i - 1 ];
140
+ if (precision === '*') {
141
+ precision = args[i];
92
142
  i++;
93
143
  }
94
144
 
95
- if ( name !== undefined ) {
96
- // If it's a named argument, use name.
97
- if ( args[ 0 ] && typeof args[ 0 ] === 'object' &&
98
- args[ 0 ].hasOwnProperty( name ) ) {
99
- value = args[ 0 ][ name ];
100
- }
101
- } else {
145
+ if (name === undefined) {
102
146
  // If not a positional argument, use counter value.
103
- if ( index === undefined ) {
104
- index = i;
147
+ if (index === undefined) {
148
+ index = i + 1;
105
149
  }
106
150
 
107
151
  i++;
108
152
 
109
153
  // Positional argument.
110
- value = args[ index - 1 ];
154
+ value = args[index - 1];
155
+ } else if (
156
+ args[0] &&
157
+ typeof args[0] === 'object' &&
158
+ args[0].hasOwnProperty(name)
159
+ ) {
160
+ // If it's a named argument, use name.
161
+ value = args[0][name];
111
162
  }
112
163
 
113
164
  // Parse as type.
114
- if ( type === 'f' ) {
115
- value = parseFloat( value ) || 0;
116
- } else if ( type === 'd' ) {
117
- value = parseInt( value ) || 0;
165
+ if (type === 'f') {
166
+ value = parseFloat(value) || 0;
167
+ } else if (type === 'd') {
168
+ value = parseInt(value) || 0;
118
169
  }
119
170
 
120
171
  // Apply precision.
121
- if ( precision !== undefined ) {
122
- if ( type === 'f' ) {
123
- value = value.toFixed( precision );
124
- } else if ( type === 's' ) {
125
- value = value.substr( 0, precision );
172
+ if (precision !== undefined) {
173
+ if (type === 'f') {
174
+ value = value.toFixed(precision);
175
+ } else if (type === 's') {
176
+ value = value.substr(0, precision);
126
177
  }
127
178
  }
128
179
 
129
180
  // To avoid "undefined" concatenation, return empty string if no
130
181
  // placeholder substitution can be performed.
131
182
  return value !== undefined && value !== null ? value : '';
132
- } );
183
+ });
133
184
  }
@@ -0,0 +1,87 @@
1
+ type Specifiers = {
2
+ s: string;
3
+ d: number;
4
+ f: number;
5
+ };
6
+ type S = keyof Specifiers;
7
+
8
+ type BuildTuple<L extends number, T extends any[] = []> = T['length'] extends L
9
+ ? T
10
+ : BuildTuple<L, [any, ...T]>;
11
+
12
+ type Subtract1<N extends number> = BuildTuple<N> extends [any, ...infer Rest]
13
+ ? Rest['length']
14
+ : never;
15
+
16
+ type StripEscapedPercents<T extends string> =
17
+ T extends `${infer Head}%%${infer Tail}`
18
+ ? `${Head}${StripEscapedPercents<Tail>}`
19
+ : T;
20
+
21
+ type HasNamedPlaceholders<T extends string> =
22
+ StripEscapedPercents<T> extends `${any}%(${string})${S}${string}`
23
+ ? true
24
+ : false;
25
+
26
+ type HasPositionalPlaceholders<T extends string> =
27
+ StripEscapedPercents<T> extends `${any}%${number}$${S}${string}`
28
+ ? true
29
+ : false;
30
+
31
+ type HasUnnamedPlaceholders<T extends string> =
32
+ StripEscapedPercents<T> extends `${any}%${S}${string}` ? true : false;
33
+
34
+ type HasDynamicPrecisionPlaceholders<T extends string> =
35
+ StripEscapedPercents<T> extends `${any}%.*${S}${any}` ? true : false;
36
+
37
+ type HasStaticPrecisionPlaceholders<T extends string> =
38
+ StripEscapedPercents<T> extends `${any}%.${number}${S}${any}` ? true : false;
39
+
40
+ type ExtractNamedPlaceholders<T extends string> =
41
+ StripEscapedPercents<T> extends `${any}%(${infer Key})${infer Spec}${infer Rest}`
42
+ ? Spec extends S
43
+ ? { [K in Key]: Specifiers[Spec] } & ExtractNamedPlaceholders<Rest>
44
+ : never
45
+ : {};
46
+
47
+ type ExtractPositionalPlaceholders<T extends string> =
48
+ StripEscapedPercents<T> extends `${any}%${infer Index extends number}$${infer Spec}${infer Rest}`
49
+ ? Spec extends S
50
+ ? {
51
+ [K in Subtract1<Index>]: Specifiers[Spec];
52
+ } & ExtractPositionalPlaceholders<Rest>
53
+ : ExtractPositionalPlaceholders<Rest>
54
+ : unknown[];
55
+
56
+ type ExtractStaticPrecisionPlaceholders<T extends string> =
57
+ StripEscapedPercents<T> extends `${any}%.${infer Precision extends number}${infer Spec}${infer Rest}`
58
+ ? Spec extends S
59
+ ? [Specifiers[Spec], ...ExtractStaticPrecisionPlaceholders<Rest>]
60
+ : never
61
+ : [];
62
+
63
+ type ExtractDynamicPrecisionPlaceholder<T extends string> =
64
+ StripEscapedPercents<T> extends `${any}%.*${infer Spec}${infer Rest}`
65
+ ? Spec extends S
66
+ ? [number, Specifiers[Spec], ...ExtractDynamicPrecisionPlaceholder<Rest>]
67
+ : never
68
+ : [];
69
+
70
+ type ExtractUnnamedPlaceholders<T extends string> =
71
+ StripEscapedPercents<T> extends `${any}%${infer Spec}${infer Rest}`
72
+ ? Spec extends S
73
+ ? [Specifiers[Spec], ...ExtractUnnamedPlaceholders<Rest>]
74
+ : never
75
+ : [];
76
+
77
+ export type SprintfArgs<T extends string> = HasNamedPlaceholders<T> extends true
78
+ ? [values: ExtractNamedPlaceholders<T>]
79
+ : HasDynamicPrecisionPlaceholders<T> extends true
80
+ ? ExtractDynamicPrecisionPlaceholder<T>
81
+ : HasStaticPrecisionPlaceholders<T> extends true
82
+ ? ExtractStaticPrecisionPlaceholders<T>
83
+ : HasPositionalPlaceholders<T> extends true
84
+ ? ExtractPositionalPlaceholders<T>
85
+ : HasUnnamedPlaceholders<T> extends true
86
+ ? ExtractUnnamedPlaceholders<T>
87
+ : [];
package/CHANGELOG.md DELETED
@@ -1,31 +0,0 @@
1
- ## 1.2.0 (2020-03-07)
2
-
3
- New Features
4
-
5
- - Add TypeScript definitions
6
-
7
- ## 1.1.0 (2019-11-27)
8
-
9
- New Features
10
-
11
- - Add support for named arguments
12
-
13
- Bugfix
14
-
15
- - Fix handling of falsy values, notably 0
16
-
17
- ## 1.0.2 (2019-03-07)
18
-
19
- Internal
20
-
21
- - Add `repository.directory` to `package.json`
22
-
23
- ## 1.0.1 (2019-01-19)
24
-
25
- Documentation
26
-
27
- - Correct documentation unpkg links
28
-
29
- Internal
30
-
31
- - Use Lerna for managing mono-repo
package/LICENSE.md DELETED
@@ -1,7 +0,0 @@
1
- [The MIT License (MIT)](https://opensource.org/licenses/MIT)
2
-
3
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
-
5
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
-
7
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/index.d.ts DELETED
@@ -1,24 +0,0 @@
1
- /**
2
- * Given a format string, returns string with arguments interpolatation.
3
- * Arguments can either be provided directly via function arguments spread, or
4
- * with an array as the second argument.
5
- *
6
- * @see https://en.wikipedia.org/wiki/Printf_format_string
7
- *
8
- * @example
9
- *
10
- * ```js
11
- * import sprintf from '@tannin/sprintf';
12
- *
13
- * sprintf( 'Hello %s!', 'world' );
14
- * // ⇒ 'Hello world!'
15
- * ```
16
- *
17
- * @param {string} string printf format string
18
- * @param {...string|string[]|Object<string,string>} [args] String arguments.
19
- *
20
- * @return {string} Formatted string.
21
- */
22
- export default function sprintf(string: string, ...args: (string | string[] | {
23
- [x: string]: string;
24
- })[]): string;