markdown-magic 2.6.1 → 3.0.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.
Files changed (61) hide show
  1. package/README.md +6 -10
  2. package/cli.js +5 -82
  3. package/lib/block-parser-js.test.js +179 -0
  4. package/lib/block-parser.js +389 -0
  5. package/lib/{utils/new-parser.test.js → block-parser.test.js} +168 -50
  6. package/lib/cli.js +234 -0
  7. package/lib/cli.test.js +409 -0
  8. package/lib/defaults.js +12 -0
  9. package/lib/index.js +319 -184
  10. package/lib/index.test.js +11 -0
  11. package/lib/options-parser.js +498 -0
  12. package/lib/options-parser.test.js +1237 -0
  13. package/lib/process-contents.js +330 -0
  14. package/lib/process-file.js +34 -0
  15. package/lib/transforms/code.js +67 -22
  16. package/lib/transforms/file.js +13 -10
  17. package/lib/transforms/remote.js +9 -6
  18. package/lib/transforms/toc.js +136 -64
  19. package/lib/transforms/wordCount.js +5 -0
  20. package/lib/utils/fs.js +340 -0
  21. package/lib/utils/fs.test.js +268 -0
  22. package/lib/utils/html-to-json/compat.js +42 -0
  23. package/lib/utils/html-to-json/format.js +64 -0
  24. package/lib/utils/html-to-json/index.js +37 -0
  25. package/lib/utils/html-to-json/lexer.js +345 -0
  26. package/lib/utils/html-to-json/parser.js +146 -0
  27. package/lib/utils/html-to-json/stringify.js +37 -0
  28. package/lib/utils/html-to-json/tags.js +171 -0
  29. package/lib/utils/index.js +19 -0
  30. package/{cli-utils.js → lib/utils/load-config.js} +2 -6
  31. package/lib/utils/logs.js +89 -0
  32. package/lib/utils/md/filters.js +20 -0
  33. package/lib/utils/md/find-code-blocks.js +80 -0
  34. package/lib/utils/md/find-date.js +32 -0
  35. package/lib/utils/md/find-frontmatter.js +94 -0
  36. package/lib/utils/md/find-frontmatter.test.js +17 -0
  37. package/lib/utils/md/find-html-tags.js +105 -0
  38. package/lib/utils/md/find-images.js +102 -0
  39. package/lib/utils/md/find-links.js +202 -0
  40. package/lib/utils/md/find-unmatched-html-tags.js +33 -0
  41. package/lib/utils/md/fixtures/2022-01-22-date-in-filename.md +14 -0
  42. package/lib/utils/md/fixtures/file-with-frontmatter.md +32 -0
  43. package/lib/utils/md/fixtures/file-with-links.md +143 -0
  44. package/lib/utils/md/md.test.js +37 -0
  45. package/lib/utils/md/parse.js +122 -0
  46. package/lib/utils/md/utils.js +19 -0
  47. package/lib/utils/regex-timeout.js +83 -0
  48. package/lib/utils/regex.js +38 -5
  49. package/lib/utils/remoteRequest.js +54 -0
  50. package/lib/utils/syntax.js +79 -0
  51. package/lib/utils/text.js +260 -0
  52. package/lib/utils/text.test.js +311 -0
  53. package/package.json +25 -25
  54. package/index.js +0 -46
  55. package/lib/processFile.js +0 -154
  56. package/lib/transforms/index.js +0 -114
  57. package/lib/updateContents.js +0 -125
  58. package/lib/utils/_md.test.js +0 -63
  59. package/lib/utils/new-parser.js +0 -412
  60. package/lib/utils/weird-parse.js +0 -230
  61. package/lib/utils/weird-parse.test.js +0 -217
@@ -0,0 +1,498 @@
1
+ const { parseJSON } = require('json-alexander')
2
+
3
+ // alt approach maybe https://github.com/etienne-dldc/literal-parser
4
+ const RESERVED = '__private'
5
+ const SPACES = '__SPACE__'
6
+ const NEWLINE = '__NEWLINE__'
7
+ const BREAK = '__OPT_BREAK__'
8
+ const SURROUNDING_QUOTES = /^("|'|`)|("|'|`)$/g
9
+ const STARTS_WITH_VALID_CHAR = /^[A-Za-z0-9_]/
10
+ const ARRAY_REGEX = /^\[(.*)\]$/
11
+ const OBJECT_REGEX = /^\{(.*)\}$/
12
+ const TRAILING_COMMAS = /,+$/
13
+ const TRAILING_ARRAY_COMMAS = /(?:,*[^\S]*)+?]$/
14
+ // https://regex101.com/r/cy7mLe/1
15
+ const TRAILING_OBJECT_COMMAS = /(?:,*[^\S]*)*?}$/
16
+
17
+ function isArrayLike(str) {
18
+ if (typeof str !== 'string') return false
19
+ return Boolean(ARRAY_REGEX.test(str))
20
+ }
21
+
22
+ function isObjectLike(str) {
23
+ if (typeof str !== 'string') return false
24
+ return Boolean(OBJECT_REGEX.test(str))
25
+ }
26
+
27
+ function convert(value) {
28
+ // console.log(typeof value)
29
+ // console.log('convert preparse value', value)
30
+ if (value === 'false') {
31
+ return false
32
+ }
33
+ if (value === 'true') {
34
+ return true
35
+ }
36
+
37
+ const isNumber = Number(value)
38
+ if (typeof isNumber === 'number' && !isNaN(isNumber)) {
39
+ return isNumber
40
+ }
41
+
42
+ try {
43
+ /* Trim trailing commas in arrays */
44
+ if (isArrayLike(value)) {
45
+ value = value.replace(TRAILING_ARRAY_COMMAS, ']')
46
+ } else if (isObjectLike(value)) {
47
+ /* Trim trailing commas in object */
48
+ value = value.replace(TRAILING_OBJECT_COMMAS, '}')
49
+ }
50
+ const val = parseJSON(value) // last attempt to format an array like [ one, two ]
51
+ // console.log('parseJSON val', val)
52
+ // if (typeof val === 'string' && val.match(/^\[/) && val.match(/\]$/)) {
53
+ // const inner = val.match(/^\[(.*)\]/)
54
+ // if (inner && inner[1]) {
55
+ // const newVal = inner[1].split(', ').map((x) => {
56
+ // return convert(x.trim())
57
+ // })
58
+ // return newVal
59
+ // }
60
+ // }
61
+ // console.log('post parse', value)
62
+ return val
63
+ } catch (err) {
64
+ // console.log('parse error', err)
65
+ // console.log('json val', value)
66
+ /* Convert array looking string into values */
67
+ if (typeof value === 'string' && ARRAY_REGEX.test(value)) {
68
+ const inner = value.match(ARRAY_REGEX)
69
+ if (inner && inner[1]) {
70
+ const composeValue = inner[1]
71
+ .replace(TRAILING_COMMAS, '') // remove dangling commas JSON alt MATCH_DANGLING_COMMAS /}(,[^}]*?)]}?$/
72
+ .split(',')
73
+ .reduce((acc, curr) => {
74
+ const open = (curr.match(/{/g) || []).length
75
+ const close = (curr.match(/}/g) || []).length
76
+ const arrayOpen = (curr.match(/\[/g) || []).length
77
+ const arrayClose = (curr.match(/\]/g) || []).length
78
+ acc.objectOpenCount += open
79
+ acc.objectCloseCount += close
80
+ acc.arrayOpenCount += arrayOpen
81
+ acc.arrayCloseCount += arrayClose
82
+ const sealObject = acc.objectOpenCount > 0 && acc.objectOpenCount === acc.objectCloseCount
83
+ const sealArray = acc.arrayOpenCount > 0 && acc.arrayOpenCount === acc.arrayCloseCount
84
+
85
+ if (acc.objectOpenCount > 0 && !sealObject || acc.arrayOpenCount > 0 && !sealArray) {
86
+ // if (curr.match(/:|{/)) {
87
+ return {
88
+ ...acc,
89
+ next: acc.next + curr + ','
90
+ }
91
+ }
92
+
93
+ if (sealObject || sealArray) {
94
+ return {
95
+ ...acc,
96
+ ...(!sealObject) ? {} : {
97
+ objectOpenCount: 0,
98
+ objectCloseCount: 0,
99
+ },
100
+ ...(!sealArray) ? {} : {
101
+ arrayOpenCount: 0,
102
+ arrayCloseCount: 0,
103
+ },
104
+ next: '',
105
+ values: acc.values.concat(acc.next + curr)
106
+ }
107
+ }
108
+
109
+ // default
110
+ return {
111
+ ...acc,
112
+ values: acc.values.concat(curr)
113
+ }
114
+ }, {
115
+ next: '',
116
+ values: [],
117
+ arrayOpenCount: 0,
118
+ arrayCloseCount: 0,
119
+ objectOpenCount: 0,
120
+ objectCloseCount: 0,
121
+ })
122
+ // console.log('composeValue', composeValue)
123
+ if (composeValue.values.length) {
124
+ const newVal = composeValue.values.map((x) => {
125
+ // console.log('x', x)
126
+ return convert(x.trim())
127
+ })
128
+ return newVal
129
+ }
130
+ }
131
+ }
132
+
133
+ /* Fix fallthrough strings remove surrounding strings
134
+ if (value.startsWith('"') && value.endsWith('"')) {
135
+ return value.replace(/^"|"$/g, '')
136
+ }
137
+ if (value.startsWith("'") && value.endsWith("'")) {
138
+ return value.replace(/^'|'$/g, '')
139
+ }
140
+ */
141
+ }
142
+ return value
143
+ }
144
+
145
+
146
+ function fixSpaceStrings(val) {
147
+ // console.log('val', val)
148
+ if (typeof val === 'string') {
149
+ return val
150
+ .replace(/__SPACE__/g, ' ')
151
+ .replace(/__NEWLINE__/g, '\n')
152
+ }
153
+ return val
154
+ }
155
+
156
+ /**
157
+ * Parse string of key value options
158
+ * @param {string} input - string of options. Can be multiline
159
+ * @returns {Record<string, any>}
160
+ */
161
+ function optionsParse(input) {
162
+ if (typeof input === 'undefined' || input === null || input === '') {
163
+ return {}
164
+ }
165
+ // https://regex101.com/r/bx8DXm/1/ Match everything but spaces/newlines
166
+ // var pattern = /("|'|{)[^"}]+("|'|})|(\S+)/g
167
+ var pattern = /(\S+)/g
168
+
169
+ //var y = /("|{)[^"}]+("|})|([^\r\n\t\f\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]+)/gm
170
+
171
+ const cleanLines = input
172
+ //.replace(/\n/g, NEWLINE)
173
+ // Remove JS comment blocks and single line comments https://regex101.com/r/XKHU18/2 | alt https://regex101.com/r/ywd8TT/1
174
+ .replace(/\s+\/\*[\s\S]*?\*\/|\s+\/\/.*$/gm, '')
175
+ /* Temporarily replace newlines with placeholder */
176
+ .replace(/\n(?=(?:(?:[^"]*(?:")){2})*[^"]*(?:")[^"]*$)/g, NEWLINE)
177
+ .replace(/\n(?=(?:(?:[^']*(?:')){2})*[^']*(?:')[^']*$)/g, NEWLINE)
178
+ .replace(/\n(?=(?:(?:[^`]*(?:`)){2})*[^`]*(?:`)[^`]*$)/g, NEWLINE)
179
+ /* Temporarily replace spaces with placeholder */
180
+ // bob="co ol" steve="c ool" --> add temp spaces
181
+ .replace(/\s(?=(?:(?:[^"]*(?:")){2})*[^"]*(?:")[^"]*$)/g, SPACES)
182
+ // bob='co ol' steve='c ool' --> add temp spaces
183
+ .replace(/\s(?=(?:(?:[^']*(?:')){2})*[^']*(?:')[^']*$)/g, SPACES)
184
+ // bob=`co ol` steve=`c ool` --> add temp spaces
185
+ .replace(/\s(?=(?:(?:[^`]*(?:`)){2})*[^`]*(?:`)[^`]*$)/g, SPACES)
186
+ // .replace(/ /g, SPACES)
187
+ // matchspaces inside quotes https://regex101.com/r/DqJ4TD/1
188
+ // .replace(/\s+(?=(?:(?:[^"']*(?:"|')){2})*[^"']*(?:"|')[^"']*$)/g, SPACES)
189
+ .split(/\n/)
190
+ .filter((line) => {
191
+ /* Trim single line comments like:
192
+ // xyz
193
+ ## abc
194
+ /* foo bar * /
195
+ */
196
+ return !line.trim().match(/^(\/\/+|\/\*+|#+)/gm)
197
+ })
198
+ // .map((x) => x.trim().replace(/ /g, SPACES)) // c="tons of" weird inner quotes"
199
+ .join('\n')
200
+ //.replace(/ /g, SPACES)
201
+ //console.log('cleanLines', cleanLines)
202
+
203
+ var lines = cleanLines
204
+ .replace(/__SPACE__([a-zA-Z]*)=/g, `${BREAK}$1=`)
205
+ // Fix out of option new line replacements https://regex101.com/r/ttlXyt/1
206
+ .replace(/__NEWLINE__(?:__SPACE__)*__OPT_BREAK__/g, BREAK)
207
+ .match(pattern)
208
+ .map(fixSpaceStrings)
209
+ /* if BREAK split string again */
210
+ .map((item) => {
211
+ return item.split(BREAK)
212
+ }).flat()
213
+
214
+ // console.log('lines', lines)
215
+ var isEnding = /(['"}\]]|true,?|false,?)$/
216
+ // var isEnding = /(['"}\]]|true,?|false,?|[A-Za-z0-9"']+,?)$/ // false positive on arrays
217
+ var isKeyValNoQuotes = /^[A-Za-z]+=[A-Za-z0-9!*_\-\/\\]/
218
+ //var isKeyValNoQuotes = /^[A-Za-z]+=\S+/
219
+
220
+ // @TODO Get real json matcher
221
+ var isJsonLike = /[{[]/
222
+
223
+ const values = lines.reduce((acc, curr, i) => {
224
+ let alreadyAdded = false
225
+ const isLastLoop = lines.length === (i + 1)
226
+ const nextItem = lines[i + 1] || ''
227
+ const hasText = curr.match(STARTS_WITH_VALID_CHAR)
228
+ /*
229
+ console.log('___________________')
230
+ console.log('isLastLoop', isLastLoop)
231
+ console.log('RESERVED', acc[RESERVED])
232
+ console.log("current item", `|${curr}|`)
233
+ console.log('next item ', `|${nextItem}|`)
234
+ console.log('===================')
235
+ /** */
236
+
237
+ /* If has no = its a true boolean. e.g isThingy */
238
+ if (hasText && acc[RESERVED].match(STARTS_WITH_VALID_CHAR) && !isValuePair(acc[RESERVED])) {
239
+ // console.log('xxxxxxx', acc[RESERVED])
240
+ acc[acc[RESERVED]] = true
241
+ acc[RESERVED] = ''
242
+ // return fails, need to refine which runs first
243
+ // return acc
244
+ }
245
+
246
+ /* If has no = its a true boolean
247
+ if (
248
+ hasText
249
+ && !curr.match(isEnding)
250
+ && acc[RESERVED].match(isEnding)
251
+ && !acc[RESERVED].match(isJsonLike)
252
+ ) {
253
+ console.log('end', curr)
254
+ // console.log('acc[RESERVED]', acc[RESERVED])
255
+ const kv = getKeyAndValueFromString(acc[RESERVED], 'boolean')
256
+ if (kv) {
257
+ acc[kv.key] = kv.value
258
+ acc[RESERVED] = ''
259
+ return acc
260
+ }
261
+ }
262
+ */
263
+
264
+ if (!acc[RESERVED].match(/^[A-Za-z]+={+/) && isValuePair(curr) && curr.match(isEnding)) {
265
+ const kv = getKeyAndValueFromString(curr, 'one')
266
+ // console.log('kv', kv)
267
+ if (kv) {
268
+ // console.log(`ADDED`, kv)
269
+ acc[kv.key] = kv.value
270
+ return acc
271
+ }
272
+ }
273
+
274
+ if (curr.match(isKeyValNoQuotes)) {
275
+ // console.log('curr', curr)
276
+ const kv = getKeyAndValueFromString(curr, 'curr.match(isKeyValNoQuotes)')
277
+ // console.log('no quotes')
278
+ if (kv) {
279
+ acc[kv.key] = kv.value
280
+ return acc
281
+ }
282
+ }
283
+
284
+ const updated = acc[RESERVED] + curr
285
+ // console.log('SET reserve', `"${updated}"`)
286
+ if (!updated.match(isEnding) && updated.match(isKeyValNoQuotes)) {
287
+ // console.log('UPDATED HERE', updated)
288
+ const kv = getKeyAndValueFromString(updated, 'fall')
289
+ if (kv) {
290
+ acc[kv.key] = kv.value
291
+ acc[RESERVED] = ''
292
+ return acc
293
+ }
294
+ } else {
295
+ // Handle trailing option commas funny='what', funky="cool", weird=what, case
296
+ if (!updated.match(isJsonLike) && updated.match(TRAILING_COMMAS)) {
297
+ const kv = getKeyAndValueFromString(updated.replace(TRAILING_COMMAS, ''), 'commas')
298
+ acc[kv.key] = kv.value
299
+ acc[RESERVED] = ''
300
+ return acc
301
+ }
302
+ // console.log('ALREADy', updated)
303
+ alreadyAdded = true
304
+ acc[RESERVED] = updated
305
+ //return acc
306
+ }
307
+
308
+ if (
309
+ acc[RESERVED].match(isEnding)
310
+ && nextItem.match(/^[A-Za-z0-9_-]/)
311
+ && isBalanced(acc[RESERVED]) // If value is balanced brackets {[()]}
312
+ ) {
313
+ const kv = getKeyAndValueFromString(acc[RESERVED], 'xxxx')
314
+ if (kv) {
315
+ acc[kv.key] = kv.value
316
+ acc[RESERVED] = ''
317
+ return acc
318
+ }
319
+ }
320
+
321
+ // If ends in number foo=2 or bar=3, but ignore foo=[2 and foo=[{2
322
+ if (isValuePair(curr) && curr.match(/\d,?$/) && !curr.match(/=\{?\[/)) {
323
+ const kv = getKeyAndValueFromString(acc[RESERVED], 'numberMatch')
324
+ if (kv) {
325
+ acc[kv.key] = kv.value
326
+ acc[RESERVED] = ''
327
+ return acc
328
+ }
329
+ }
330
+
331
+ if (!isLastLoop) {
332
+ return acc
333
+ }
334
+
335
+ // If last loop and still no match and looks like KV. Parse it
336
+ if (isLastLoop) {
337
+ // console.log("acc[RESERVED]", acc[RESERVED])
338
+ // console.log('acc[RESERVED] + curr', acc[RESERVED] + curr)
339
+ /* If value empty but __private have accumulated values */
340
+ if (acc[RESERVED]) {
341
+ // if (acc[RESERVED] && (acc[RESERVED].match(isEnding) || isValuePair(acc[RESERVED]))) {
342
+ const valueToCheck = (curr.match(isEnding) && !alreadyAdded) ? acc[RESERVED] + curr : acc[RESERVED]
343
+ // console.log('valueToCheck', valueToCheck)
344
+ const kv = getKeyAndValueFromString(valueToCheck, 'lastLoop')
345
+ if (kv) {
346
+ acc[kv.key] = kv.value
347
+ acc[RESERVED] = ''
348
+ }
349
+ }
350
+ }
351
+
352
+ return acc
353
+ }, {
354
+ [RESERVED]: '',
355
+ })
356
+
357
+ // console.log('values', values)
358
+ delete values[RESERVED]
359
+
360
+ return values
361
+ }
362
+
363
+ function isValuePair(str) {
364
+ return str.match(/=/) // && !str.match(/=\[/)
365
+ }
366
+
367
+ // https://melvingeorge.me/blog/check-if-string-contain-emojis-javascript OR https://www.npmjs.com/package/emoji-regex
368
+ function hasEmoji(str) {
369
+ const regexExp = /^(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])/gi;
370
+ return regexExp.test(str)
371
+ }
372
+
373
+ /**
374
+ * Verify brackets are balanced
375
+ * @param {string} str - string with code
376
+ * @return {Boolean}
377
+ */
378
+ function isBalanced(str) {
379
+ return !str.split('').reduce((uptoPrevChar, thisChar) => {
380
+ if (thisChar === '(' || thisChar === '{' || thisChar === '[') {
381
+ return ++uptoPrevChar
382
+ } else if (thisChar === ')' || thisChar === '}' || thisChar === ']') {
383
+ return --uptoPrevChar
384
+ }
385
+ return uptoPrevChar
386
+ }, 0)
387
+ }
388
+
389
+ function getKeyAndValueFromString(string, callLocation) {
390
+ /*
391
+ console.log(`getKeyAndValueFromString from ${callLocation}`)
392
+ console.log(`|${string}|`)
393
+ /** */
394
+ if (!string) return
395
+ // const keyValueRegex = /([A-Za-z-_$]+)=['{"]?(.*)['}"]?/g
396
+ // const match = keyValueRegex.exec(string)
397
+ // if (!match) {
398
+ //   return
399
+ // }
400
+ // console.log('getKeyAndValueFromString')
401
+ const [key] = string.split('=')
402
+ /* If no key or key starts with --- */
403
+ if (!key || key.charAt(0) === '-' || hasEmoji(key)) {
404
+ return
405
+ }
406
+ // console.log('string', string)
407
+
408
+ // console.log('values', values)
409
+ /* If no value, isThing === true */
410
+ const hasEqual = string.indexOf('=') > -1
411
+ if (!hasEqual) {
412
+ return {
413
+ key,
414
+ value: true,
415
+ }
416
+ }
417
+
418
+ let value = string.substring(string.indexOf('=') + 1)
419
+ // Trim trailing commas
420
+ .replace(TRAILING_COMMAS, '')
421
+
422
+ /*
423
+ console.log('key', key)
424
+ console.log('value', value)
425
+ /** */
426
+
427
+
428
+ const leadingCurleyBrackets = value.match(/^{{2,}/)
429
+ const trailingCurleyBrackets = value.match(/}{2,}$/)
430
+ if (leadingCurleyBrackets && trailingCurleyBrackets) {
431
+ const len = leadingCurleyBrackets[0].length <= trailingCurleyBrackets[0].length ? leadingCurleyBrackets : trailingCurleyBrackets
432
+ const trimLength = len[0].length
433
+ const trimLeading = new RegExp(`^{{${trimLength}}`)
434
+ const trimTrailing = new RegExp(`}{${trimLength}}$`)
435
+ if (trimLength) {
436
+ value = value
437
+ // Trim extra leading brackets
438
+ .replace(trimLeading, '{')
439
+ // Trim extra trailing brackets
440
+ .replace(trimTrailing, '}')
441
+ }
442
+ }
443
+
444
+ // console.log('xvalue', value)
445
+ // let value = value
446
+ // .replace(/^{{2,}/, '{')
447
+ // .replace(/}{2,}$/, '}')
448
+ // .replace(/^\[{2,}/, '[')
449
+ // .replace(/\]{2,}$/, ']')
450
+
451
+ // If Doesn't look like JSON object
452
+ if (value.match(/^{[^:,]*}/)) {
453
+ value = removeSurroundingBrackets(value)
454
+ // If looks like array in brackets {[ thing, thing, thing ]}
455
+ } else if (value.match(/^{\s*\[\s*[^:]*\s*\]\s*\}/)) {
456
+ // Match { [ one, two ,3,4 ]   }
457
+ value = removeSurroundingBrackets(value)
458
+ // If matches {` stuff `} & {[ stuff ]}
459
+ } else if (value.match(/^{(?:`|\[)([\s\S]*?)(?:`|\])}$/)) {
460
+ value = removeSurroundingBrackets(value)
461
+ }
462
+ // console.log('value', value)
463
+
464
+ /* Check if remaining value is surrounded by quotes */
465
+ const surroundingQuotes = value.match(SURROUNDING_QUOTES) || []
466
+ const hasSurroundingQuotes = surroundingQuotes.length === 2 && (surroundingQuotes[0] === surroundingQuotes[1])
467
+ /*
468
+ console.log('surroundingQuotes', surroundingQuotes)
469
+ console.log('hasSurroundingQuotes', hasSurroundingQuotes)
470
+ /** */
471
+
472
+ // console.log('yvalue', value)
473
+ return {
474
+ key,
475
+ value: hasSurroundingQuotes ? value.replace(SURROUNDING_QUOTES, '') : convert(value),
476
+ }
477
+ }
478
+
479
+ function removeSurroundingBrackets(val) {
480
+ return val.replace(/^{/, '').replace(/}$/, '')
481
+ }
482
+
483
+ /**
484
+ * Parse string of key value options. Template tag version
485
+ * @param {string} input - string of options. Can be multiline
486
+ * @returns {Record<string, any>}
487
+ */
488
+ function options(input = '', ...substitutions) {
489
+ let str = String.raw(input, ...substitutions)
490
+ return optionsParse(str)
491
+ }
492
+
493
+ module.exports = {
494
+ isArrayLike,
495
+ isObjectLike,
496
+ optionsParse,
497
+ options
498
+ }