1 |
|
2 |
|
3 |
|
4 | 'use strict'
|
5 |
|
6 | const { inspect } = require('util')
|
7 |
|
8 |
|
9 | module.exports = function sub(pattern, ...values) {
|
10 | let regex = /%(?:(%)|(-)?(\*)?(?:\((\w+)\))?([A-Za-z]))/g
|
11 |
|
12 | let result = pattern.replace(regex, function (_, is_literal, is_left_align, is_padded, name, format) {
|
13 | if (is_literal) return '%'
|
14 |
|
15 | let padded_count = 0
|
16 | if (is_padded) {
|
17 | if (values.length === 0) throw new TypeError('not enough arguments for format string')
|
18 | padded_count = values.shift()
|
19 | if (!Number.isInteger(padded_count)) throw new TypeError('* wants int')
|
20 | }
|
21 |
|
22 | let str
|
23 | if (name !== undefined) {
|
24 | let dict = values[0]
|
25 | if (typeof dict !== 'object' || dict === null) throw new TypeError('format requires a mapping')
|
26 | if (!(name in dict)) throw new TypeError(`no such key: '${name}'`)
|
27 | str = dict[name]
|
28 | } else {
|
29 | if (values.length === 0) throw new TypeError('not enough arguments for format string')
|
30 | str = values.shift()
|
31 | }
|
32 |
|
33 | switch (format) {
|
34 | case 's':
|
35 | str = String(str)
|
36 | break
|
37 | case 'r':
|
38 | str = inspect(str)
|
39 | break
|
40 | case 'd':
|
41 | case 'i':
|
42 | if (typeof str !== 'number') {
|
43 | throw new TypeError(`%${format} format: a number is required, not ${typeof str}`)
|
44 | }
|
45 | str = String(str.toFixed(0))
|
46 | break
|
47 | default:
|
48 | throw new TypeError(`unsupported format character '${format}'`)
|
49 | }
|
50 |
|
51 | if (padded_count > 0) {
|
52 | return is_left_align ? str.padEnd(padded_count) : str.padStart(padded_count)
|
53 | } else {
|
54 | return str
|
55 | }
|
56 | })
|
57 |
|
58 | if (values.length) {
|
59 | if (values.length === 1 && typeof values[0] === 'object' && values[0] !== null) {
|
60 |
|
61 | } else {
|
62 | throw new TypeError('not all arguments converted during string formatting')
|
63 | }
|
64 | }
|
65 |
|
66 | return result
|
67 | }
|