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 |
x9 x9 x9 x9 x9 x9 x9 x9 x9 x63 x63 x63 x63 x63 x854 x1189 x1229 x1245 x1245 x1229 x1229 x1229 x1189 x1189 x854 x892 x892 x892 x892 x892 x892 x892 x892 x854 x898 x898 x898 x854 x898 x4490 x898 x898 x854 x89 x91 x91 x77 x63 |
// Imports
import type { Nullable } from "@libs/typing"
/** Tokens. */
const tokens = {
"(": ")",
"[": "]",
"{": "}",
'"': '"',
"'": "'",
"`": "`",
} as Record<PropertyKey, string>
/**
* Capture content between delimiters.
*
* ```ts
* const { captured, match } = capture("foo {{ bar }} baz")!
* console.assert(captured === "bar")
* console.assert(match === "{{ bar }}")
* ```
*
* @author Simon Lecoq (lowlighter)
* @license MIT
*/
export function capture(string: string, offset = 0): Nullable<{ a: number; b: number; match: string; captured: string; triple: boolean }> {
const stack = []
let a = NaN
let d = 2
let quoted = false
for (let i = offset; i < string.length; i++) {
// Start capturing upon meeting mustache opening
if (Number.isNaN(a)) {
if ((string[i] === "{") && (string[i + 1] === "{")) {
if (string[i + 2] === "{") {
d = 3
}
a = i
i += d - 1
}
continue
}
// Close capturing on mustache closing (stack must be empty)
if ((!stack.length) && (string[i] === "}") && (string[i + 1] === "}") && ((d === 2) || (string[i + 2] === "}"))) {
return {
a,
b: i + d,
match: string.slice(a, i + d),
captured: string.slice(a + d, i).trim(),
triple: d === 3,
}
}
// Close group on closing token
if (string[i] === tokens[stack.at(-1)!]) {
stack.pop()
continue
}
// Open group on opening token
if ((!quoted) && (string[i] in tokens)) {
stack.push(string[i])
quoted = ["'", '"', "`"].includes(string[i])
continue
}
}
// Throw on mustache unclosed
if (!Number.isNaN(a)) {
throw new SyntaxError(`Unclosed expression, unterminated expression at: ${a}\n${string.slice(a)}`)
}
return null
}
|