1 | import balanced from 'balanced-match'
|
2 |
|
3 | const escSlash = '\0SLASH' + Math.random() + '\0'
|
4 | const escOpen = '\0OPEN' + Math.random() + '\0'
|
5 | const escClose = '\0CLOSE' + Math.random() + '\0'
|
6 | const escComma = '\0COMMA' + Math.random() + '\0'
|
7 | const escPeriod = '\0PERIOD' + Math.random() + '\0'
|
8 | const escSlashPattern = new RegExp(escSlash, 'g')
|
9 | const escOpenPattern = new RegExp(escOpen, 'g')
|
10 | const escClosePattern = new RegExp(escClose, 'g')
|
11 | const escCommaPattern = new RegExp(escComma, 'g')
|
12 | const escPeriodPattern = new RegExp(escPeriod, 'g')
|
13 | const slashPattern = /\\\\/g
|
14 | const openPattern = /\\{/g
|
15 | const closePattern = /\\}/g
|
16 | const commaPattern = /\\,/g
|
17 | const periodPattern = /\\./g
|
18 |
|
19 |
|
20 |
|
21 |
|
22 | function numeric (str) {
|
23 | return !isNaN(str)
|
24 | ? parseInt(str, 10)
|
25 | : str.charCodeAt(0)
|
26 | }
|
27 |
|
28 |
|
29 |
|
30 |
|
31 | function escapeBraces (str) {
|
32 | return str.replace(slashPattern, escSlash)
|
33 | .replace(openPattern, escOpen)
|
34 | .replace(closePattern, escClose)
|
35 | .replace(commaPattern, escComma)
|
36 | .replace(periodPattern, escPeriod)
|
37 | }
|
38 |
|
39 |
|
40 |
|
41 |
|
42 | function unescapeBraces (str) {
|
43 | return str.replace(escSlashPattern, '\\')
|
44 | .replace(escOpenPattern, '{')
|
45 | .replace(escClosePattern, '}')
|
46 | .replace(escCommaPattern, ',')
|
47 | .replace(escPeriodPattern, '.')
|
48 | }
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 | function parseCommaParts (str) {
|
57 | if (!str) { return [''] }
|
58 |
|
59 | const parts = []
|
60 | const m = balanced('{', '}', str)
|
61 |
|
62 | if (!m) { return str.split(',') }
|
63 |
|
64 | const { pre, body, post } = m
|
65 | const p = pre.split(',')
|
66 |
|
67 | p[p.length - 1] += '{' + body + '}'
|
68 | const postParts = parseCommaParts(post)
|
69 | if (post.length) {
|
70 | p[p.length - 1] += postParts.shift()
|
71 | p.push.apply(p, postParts)
|
72 | }
|
73 |
|
74 | parts.push.apply(parts, p)
|
75 |
|
76 | return parts
|
77 | }
|
78 |
|
79 |
|
80 |
|
81 |
|
82 | export default function expandTop (str) {
|
83 | if (!str) { return [] }
|
84 |
|
85 |
|
86 |
|
87 |
|
88 |
|
89 |
|
90 |
|
91 | if (str.slice(0, 2) === '{}') {
|
92 | str = '\\{\\}' + str.slice(2)
|
93 | }
|
94 |
|
95 | return expand(escapeBraces(str), true).map(unescapeBraces)
|
96 | }
|
97 |
|
98 |
|
99 |
|
100 |
|
101 | function embrace (str) {
|
102 | return '{' + str + '}'
|
103 | }
|
104 |
|
105 |
|
106 |
|
107 |
|
108 | function isPadded (el) {
|
109 | return /^-?0\d/.test(el)
|
110 | }
|
111 |
|
112 |
|
113 |
|
114 |
|
115 |
|
116 | function lte (i, y) {
|
117 | return i <= y
|
118 | }
|
119 |
|
120 |
|
121 |
|
122 |
|
123 |
|
124 | function gte (i, y) {
|
125 | return i >= y
|
126 | }
|
127 |
|
128 |
|
129 |
|
130 |
|
131 |
|
132 | function expand (str, isTop) {
|
133 |
|
134 | const expansions = []
|
135 |
|
136 | const m = balanced('{', '}', str)
|
137 | if (!m) return [str]
|
138 |
|
139 |
|
140 | const pre = m.pre
|
141 | const post = m.post.length
|
142 | ? expand(m.post, false)
|
143 | : ['']
|
144 |
|
145 | if (/\$$/.test(m.pre)) {
|
146 | for (let k = 0; k < post.length; k++) {
|
147 | const expansion = pre + '{' + m.body + '}' + post[k]
|
148 | expansions.push(expansion)
|
149 | }
|
150 | } else {
|
151 | const isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body)
|
152 | const isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body)
|
153 | const isSequence = isNumericSequence || isAlphaSequence
|
154 | const isOptions = m.body.indexOf(',') >= 0
|
155 | if (!isSequence && !isOptions) {
|
156 |
|
157 | if (m.post.match(/,.*\}/)) {
|
158 | str = m.pre + '{' + m.body + escClose + m.post
|
159 | return expand(str)
|
160 | }
|
161 | return [str]
|
162 | }
|
163 |
|
164 | let n
|
165 | if (isSequence) {
|
166 | n = m.body.split(/\.\./)
|
167 | } else {
|
168 | n = parseCommaParts(m.body)
|
169 | if (n.length === 1) {
|
170 |
|
171 | n = expand(n[0], false).map(embrace)
|
172 | if (n.length === 1) {
|
173 | return post.map(function (p) {
|
174 | return m.pre + n[0] + p
|
175 | })
|
176 | }
|
177 | }
|
178 | }
|
179 |
|
180 |
|
181 |
|
182 | let N
|
183 |
|
184 | if (isSequence) {
|
185 | const x = numeric(n[0])
|
186 | const y = numeric(n[1])
|
187 | const width = Math.max(n[0].length, n[1].length)
|
188 | let incr = n.length === 3
|
189 | ? Math.abs(numeric(n[2]))
|
190 | : 1
|
191 | let test = lte
|
192 | const reverse = y < x
|
193 | if (reverse) {
|
194 | incr *= -1
|
195 | test = gte
|
196 | }
|
197 | const pad = n.some(isPadded)
|
198 |
|
199 | N = []
|
200 |
|
201 | for (let i = x; test(i, y); i += incr) {
|
202 | let c
|
203 | if (isAlphaSequence) {
|
204 | c = String.fromCharCode(i)
|
205 | if (c === '\\') { c = '' }
|
206 | } else {
|
207 | c = String(i)
|
208 | if (pad) {
|
209 | const need = width - c.length
|
210 | if (need > 0) {
|
211 | const z = new Array(need + 1).join('0')
|
212 | if (i < 0) { c = '-' + z + c.slice(1) } else { c = z + c }
|
213 | }
|
214 | }
|
215 | }
|
216 | N.push(c)
|
217 | }
|
218 | } else {
|
219 | N = []
|
220 |
|
221 | for (let j = 0; j < n.length; j++) {
|
222 | N.push.apply(N, expand(n[j], false))
|
223 | }
|
224 | }
|
225 |
|
226 | for (let j = 0; j < N.length; j++) {
|
227 | for (let k = 0; k < post.length; k++) {
|
228 | const expansion = pre + N[j] + post[k]
|
229 | if (!isTop || isSequence || expansion) { expansions.push(expansion) }
|
230 | }
|
231 | }
|
232 | }
|
233 |
|
234 | return expansions
|
235 | }
|