Repository navigation
Expand file tree
/
Copy pathmod.ts
More file actions
165 lines (164 loc) · 6.53 KB
/
Copy pathmod.ts
File metadata and controls
165 lines (164 loc) · 6.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
import length from '@app/string/length.ts'
import slice from '@app/string/slice.ts'
/**
* Format a template with values.
* @description Substitutes placeholders in the template string.
* @param pattern - Template string or array of templates
* @param values - Replacement values for the template placeholders
* @returns Formatted strings matching the input shape
*/
export default function mod(
pattern: string | string[] | string[][],
values: (string | number) | (string | number)[] | (string | number)[][]
): string | string[] | string[][] {
if (
(Array.isArray(pattern) && Array.isArray(pattern[0])) ||
(Array.isArray(values) && Array.isArray(values[0]))
) {
const layered = Array.isArray(pattern) && Array.isArray(pattern[0])
? (pattern as string[][])
: null
const nested = Array.isArray(values) && Array.isArray(values[0])
? (values as (string | number)[][])
: null
if (
layered && nested && layered.length !== nested.length &&
layered.length !== 1 && nested.length !== 1
) {
throw new RangeError('operands could not be broadcast together')
}
return Array.from(
{
length: layered && nested
? (layered.length === 1 ? nested.length : layered.length)
: layered
? layered.length
: nested!.length
},
(_outer, index) =>
mod(
layered
? (layered[layered.length === 1 ? 0 : index] ?? [])
: Array.isArray(pattern)
? (pattern as string[])
: [pattern as string],
nested
? (nested[nested.length === 1 ? 0 : index] ?? '')
: (values as (string | number) | (string | number)[])
) as string[]
)
}
const patterns = Array.isArray(pattern) ? (pattern as string[]) : [pattern]
const items = Array.isArray(pattern) && Array.isArray(values)
? (values as (string | number)[])
: null
if (
items && patterns.length !== items.length && patterns.length !== 1 &&
items.length !== 1
) {
throw new RangeError('operands could not be broadcast together')
}
const result = Array.from(
{ length: items ? (patterns.length === 1 ? items.length : patterns.length) : patterns.length },
(_outer, index) => {
const payload = (items ? (items[items.length === 1 ? 0 : index] ?? '') : values) as
| (string | number)
| (string | number)[]
let cursor = 0
return (patterns[patterns.length === 1 ? 0 : index] ?? '').replace(
/%(?:%|([-+ 0]*)(\d+)?(?:\.(\d+))?([sdiouxXeEfFgG]))/g,
(match, flags, field, digits, letter) => {
if (match === '%%') {
return '%'
}
const source = Array.isArray(payload)
? payload[cursor++]
: cursor++ === 0
? payload
: undefined
if (source === undefined) {
throw new TypeError('not enough arguments for format string')
}
const specifier = letter as string
const precision = digits as string | undefined
let token: string
if (specifier === 's') {
token = precision !== undefined
? slice(String(source), 0, Number(precision))
: String(source)
} else {
if (typeof source !== 'number') {
throw new TypeError(`format specifier %${specifier} requires a number`)
}
const places = precision !== undefined ? Number(precision) : 6
if (specifier === 'd' || specifier === 'i' || specifier === 'u') {
const truncated = Math.trunc(source)
if (precision === undefined) {
token = String(truncated)
} else {
const digits = Math.abs(truncated).toString().padStart(places, '0')
token = truncated < 0 ? `-${digits}` : digits
}
} else if (specifier === 'o' || specifier === 'x' || specifier === 'X') {
if (!Number.isInteger(source)) {
throw new TypeError(`format specifier %${specifier} requires an integer`)
}
token = Math.trunc(source).toString(specifier === 'o' ? 8 : 16)
} else if (!Number.isFinite(source)) {
token = Number.isNaN(source) ? 'nan' : source < 0 ? '-inf' : 'inf'
} else if (specifier === 'f' || specifier === 'F') {
token = source.toFixed(places)
} else if (specifier === 'e' || specifier === 'E') {
token = source.toExponential(places).replace(/e([+-])(\d)$/, 'e$10$2')
} else {
const significant = Math.max(1, places)
const exponential = source.toExponential(significant - 1)
const exponent = Number(exponential.slice(exponential.indexOf('e') + 1))
token = source === 0
? (Object.is(source, -0) ? '-0' : '0')
: (exponent < -4 || exponent >= significant
? exponential
: source.toPrecision(significant))
.replace(/(\.\d*?[1-9])0+(?=e|$)|\.0+(?=e|$)/, '$1')
.replace(/e([+-])(\d)$/, 'e$10$2')
}
if (specifier === specifier.toUpperCase()) {
token = token.toUpperCase()
}
}
const options = (flags as string | undefined) ?? ''
const width = field !== undefined ? Number(field as string) : 0
if (specifier === 's') {
const count = length(token)
return width <= count
? token
: options.includes('-')
? token + ' '.repeat(width - count)
: ' '.repeat(width - count) + token
}
const negative = token.startsWith('-')
const magnitude = negative ? token.slice(1) : token
const sense = negative
? '-'
: token !== 'nan' && token !== 'NAN' && options.includes('+')
? '+'
: token !== 'nan' && token !== 'NAN' && options.includes(' ')
? ' '
: ''
const combined = sense + magnitude
if (width <= combined.length) {
return combined
}
if (options.includes('-')) {
return combined.padEnd(width, ' ')
}
if (options.includes('0') && typeof source === 'number' && Number.isFinite(source)) {
return sense + magnitude.padStart(width - sense.length, '0')
}
return combined.padStart(width, ' ')
}
)
}
)
return !Array.isArray(pattern) ? result[0]! : result
}