UNPKG

alacritty-theme-switch

Version:
754 lines (753 loc) 25.1 kB
// Copyright 2018-2025 the Deno authors. MIT license. // This module is browser compatible. var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var _Scanner_whitespace, _Scanner_position, _Scanner_source; import { deepMerge } from "../../collections/1.1.3/deep_merge.js"; /** * Copy of `import { isLeap } from "@std/datetime";` because it cannot be impoted as long as it is unstable. */ function isLeap(yearNumber) { return ((yearNumber % 4 === 0 && yearNumber % 100 !== 0) || yearNumber % 400 === 0); } export class Scanner { constructor(source) { _Scanner_whitespace.set(this, /[ \t]/); _Scanner_position.set(this, 0); _Scanner_source.set(this, void 0); __classPrivateFieldSet(this, _Scanner_source, source, "f"); } get position() { return __classPrivateFieldGet(this, _Scanner_position, "f"); } get source() { return __classPrivateFieldGet(this, _Scanner_source, "f"); } /** * Get current character * @param index - relative index from current position */ char(index = 0) { return __classPrivateFieldGet(this, _Scanner_source, "f")[__classPrivateFieldGet(this, _Scanner_position, "f") + index] ?? ""; } /** * Get sliced string * @param start - start position relative from current position * @param end - end position relative from current position */ slice(start, end) { return __classPrivateFieldGet(this, _Scanner_source, "f").slice(__classPrivateFieldGet(this, _Scanner_position, "f") + start, __classPrivateFieldGet(this, _Scanner_position, "f") + end); } /** * Move position to next */ next(count = 1) { __classPrivateFieldSet(this, _Scanner_position, __classPrivateFieldGet(this, _Scanner_position, "f") + count, "f"); } skipWhitespaces() { while (__classPrivateFieldGet(this, _Scanner_whitespace, "f").test(this.char()) && !this.eof()) { this.next(); } // Invalid if current char is other kinds of whitespace if (!this.isCurrentCharEOL() && /\s/.test(this.char())) { const escaped = "\\u" + this.char().charCodeAt(0).toString(16); const position = __classPrivateFieldGet(this, _Scanner_position, "f"); throw new SyntaxError(`Cannot parse the TOML: It contains invalid whitespace at position '${position}': \`${escaped}\``); } } nextUntilChar(options = { skipComments: true }) { while (!this.eof()) { const char = this.char(); if (__classPrivateFieldGet(this, _Scanner_whitespace, "f").test(char) || this.isCurrentCharEOL()) { this.next(); } else if (options.skipComments && this.char() === "#") { // entering comment while (!this.isCurrentCharEOL() && !this.eof()) { this.next(); } } else { break; } } } /** * Position reached EOF or not */ eof() { return __classPrivateFieldGet(this, _Scanner_position, "f") >= __classPrivateFieldGet(this, _Scanner_source, "f").length; } isCurrentCharEOL() { return this.char() === "\n" || this.startsWith("\r\n"); } startsWith(searchString) { return __classPrivateFieldGet(this, _Scanner_source, "f").startsWith(searchString, __classPrivateFieldGet(this, _Scanner_position, "f")); } match(regExp) { if (!regExp.sticky) { throw new Error(`RegExp ${regExp} does not have a sticky 'y' flag`); } regExp.lastIndex = __classPrivateFieldGet(this, _Scanner_position, "f"); return __classPrivateFieldGet(this, _Scanner_source, "f").match(regExp); } } _Scanner_whitespace = new WeakMap(), _Scanner_position = new WeakMap(), _Scanner_source = new WeakMap(); // ----------------------- // Utilities // ----------------------- function success(body) { return { ok: true, body }; } function failure() { return { ok: false }; } /** * Creates a nested object from the keys and values. * * e.g. `unflat(["a", "b", "c"], 1)` returns `{ a: { b: { c: 1 } } }` */ export function unflat(keys, values = { __proto__: null }) { return keys.reduceRight((acc, key) => ({ [key]: acc }), values); } function isObject(value) { return typeof value === "object" && value !== null; } function getTargetValue(target, keys) { const key = keys[0]; if (!key) { throw new Error("Cannot parse the TOML: key length is not a positive number"); } return target[key]; } function deepAssignTable(target, table) { const { keys, type, value } = table; const currentValue = getTargetValue(target, keys); if (currentValue === undefined) { return Object.assign(target, unflat(keys, value)); } if (Array.isArray(currentValue)) { const last = currentValue.at(-1); deepAssign(last, { type, keys: keys.slice(1), value }); return target; } if (isObject(currentValue)) { deepAssign(currentValue, { type, keys: keys.slice(1), value }); return target; } throw new Error("Unexpected assign"); } function deepAssignTableArray(target, table) { const { type, keys, value } = table; const currentValue = getTargetValue(target, keys); if (currentValue === undefined) { return Object.assign(target, unflat(keys, [value])); } if (Array.isArray(currentValue)) { if (table.keys.length === 1) { currentValue.push(value); } else { const last = currentValue.at(-1); deepAssign(last, { type: table.type, keys: table.keys.slice(1), value: table.value, }); } return target; } if (isObject(currentValue)) { deepAssign(currentValue, { type, keys: keys.slice(1), value }); return target; } throw new Error("Unexpected assign"); } export function deepAssign(target, body) { switch (body.type) { case "Block": return deepMerge(target, body.value); case "Table": return deepAssignTable(target, body); case "TableArray": return deepAssignTableArray(target, body); } } // --------------------------------- // Parser combinators and generators // --------------------------------- // deno-lint-ignore no-explicit-any function or(parsers) { return (scanner) => { for (const parse of parsers) { const result = parse(scanner); if (result.ok) return result; } return failure(); }; } /** Join the parse results of the given parser into an array. * * If the parser fails at the first attempt, it will return an empty array. */ function join(parser, separator) { const Separator = character(separator); return (scanner) => { const out = []; const first = parser(scanner); if (!first.ok) return success(out); out.push(first.body); while (!scanner.eof()) { if (!Separator(scanner).ok) break; const result = parser(scanner); if (!result.ok) { throw new SyntaxError(`Invalid token after "${separator}"`); } out.push(result.body); } return success(out); }; } /** Join the parse results of the given parser into an array. * * This requires the parser to succeed at least once. */ function join1(parser, separator) { const Separator = character(separator); return (scanner) => { const first = parser(scanner); if (!first.ok) return failure(); const out = [first.body]; while (!scanner.eof()) { if (!Separator(scanner).ok) break; const result = parser(scanner); if (!result.ok) { throw new SyntaxError(`Invalid token after "${separator}"`); } out.push(result.body); } return success(out); }; } function kv(keyParser, separator, valueParser) { const Separator = character(separator); return (scanner) => { const position = scanner.position; const key = keyParser(scanner); if (!key.ok) return failure(); const sep = Separator(scanner); if (!sep.ok) { throw new SyntaxError(`key/value pair doesn't have "${separator}"`); } const value = valueParser(scanner); if (!value.ok) { const lineEndIndex = scanner.source.indexOf("\n", scanner.position); const endPosition = lineEndIndex > 0 ? lineEndIndex : scanner.source.length; const line = scanner.source.slice(position, endPosition); throw new SyntaxError(`Cannot parse value on line '${line}'`); } return success(unflat(key.body, value.body)); }; } function merge(parser) { return (scanner) => { const result = parser(scanner); if (!result.ok) return failure(); let body = { __proto__: null }; for (const record of result.body) { if (typeof record === "object" && record !== null) { body = deepMerge(body, record); } } return success(body); }; } function repeat(parser) { return (scanner) => { const body = []; while (!scanner.eof()) { const result = parser(scanner); if (!result.ok) break; body.push(result.body); scanner.nextUntilChar(); } if (body.length === 0) return failure(); return success(body); }; } function surround(left, parser, right) { const Left = character(left); const Right = character(right); return (scanner) => { if (!Left(scanner).ok) { return failure(); } const result = parser(scanner); if (!result.ok) { throw new SyntaxError(`Invalid token after "${left}"`); } if (!Right(scanner).ok) { throw new SyntaxError(`Not closed by "${right}" after started with "${left}"`); } return success(result.body); }; } function character(str) { return (scanner) => { scanner.skipWhitespaces(); if (!scanner.startsWith(str)) return failure(); scanner.next(str.length); scanner.skipWhitespaces(); return success(undefined); }; } // ----------------------- // Parser components // ----------------------- const BARE_KEY_REGEXP = /[A-Za-z0-9_-]+/y; export function bareKey(scanner) { scanner.skipWhitespaces(); const key = scanner.match(BARE_KEY_REGEXP)?.[0]; if (!key) return failure(); scanner.next(key.length); return success(key); } function escapeSequence(scanner) { if (scanner.char() !== "\\") return failure(); scanner.next(); // See https://toml.io/en/v1.0.0-rc.3#string switch (scanner.char()) { case "b": scanner.next(); return success("\b"); case "t": scanner.next(); return success("\t"); case "n": scanner.next(); return success("\n"); case "f": scanner.next(); return success("\f"); case "r": scanner.next(); return success("\r"); case "u": case "U": { // Unicode character const codePointLen = scanner.char() === "u" ? 4 : 6; const codePoint = parseInt("0x" + scanner.slice(1, 1 + codePointLen), 16); const str = String.fromCodePoint(codePoint); scanner.next(codePointLen + 1); return success(str); } case '"': scanner.next(); return success('"'); case "\\": scanner.next(); return success("\\"); default: throw new SyntaxError(`Invalid escape sequence: \\${scanner.char()}`); } } export function basicString(scanner) { scanner.skipWhitespaces(); if (scanner.char() !== '"') return failure(); scanner.next(); const acc = []; while (scanner.char() !== '"' && !scanner.eof()) { if (scanner.char() === "\n") { throw new SyntaxError("Single-line string cannot contain EOL"); } const escapedChar = escapeSequence(scanner); if (escapedChar.ok) { acc.push(escapedChar.body); } else { acc.push(scanner.char()); scanner.next(); } } if (scanner.eof()) { throw new SyntaxError(`Single-line string is not closed:\n${acc.join("")}`); } scanner.next(); // skip last '"" return success(acc.join("")); } export function literalString(scanner) { scanner.skipWhitespaces(); if (scanner.char() !== "'") return failure(); scanner.next(); const acc = []; while (scanner.char() !== "'" && !scanner.eof()) { if (scanner.char() === "\n") { throw new SyntaxError("Single-line string cannot contain EOL"); } acc.push(scanner.char()); scanner.next(); } if (scanner.eof()) { throw new SyntaxError(`Single-line string is not closed:\n${acc.join("")}`); } scanner.next(); // skip last "'" return success(acc.join("")); } export function multilineBasicString(scanner) { scanner.skipWhitespaces(); if (!scanner.startsWith('"""')) return failure(); scanner.next(3); if (scanner.char() === "\n") { // The first newline (LF) is trimmed scanner.next(); } else if (scanner.startsWith("\r\n")) { // The first newline (CRLF) is trimmed scanner.next(2); } const acc = []; while (!scanner.startsWith('"""') && !scanner.eof()) { // line ending backslash if (scanner.startsWith("\\\n")) { scanner.next(); scanner.nextUntilChar({ skipComments: false }); continue; } else if (scanner.startsWith("\\\r\n")) { scanner.next(); scanner.nextUntilChar({ skipComments: false }); continue; } const escapedChar = escapeSequence(scanner); if (escapedChar.ok) { acc.push(escapedChar.body); } else { acc.push(scanner.char()); scanner.next(); } } if (scanner.eof()) { throw new SyntaxError(`Multi-line string is not closed:\n${acc.join("")}`); } // if ends with 4 `"`, push the fist `"` to string if (scanner.char(3) === '"') { acc.push('"'); scanner.next(); } scanner.next(3); // skip last '"""" return success(acc.join("")); } export function multilineLiteralString(scanner) { scanner.skipWhitespaces(); if (!scanner.startsWith("'''")) return failure(); scanner.next(3); if (scanner.char() === "\n") { // The first newline (LF) is trimmed scanner.next(); } else if (scanner.startsWith("\r\n")) { // The first newline (CRLF) is trimmed scanner.next(2); } const acc = []; while (!scanner.startsWith("'''") && !scanner.eof()) { acc.push(scanner.char()); scanner.next(); } if (scanner.eof()) { throw new SyntaxError(`Multi-line string is not closed:\n${acc.join("")}`); } // if ends with 4 `'`, push the fist `'` to string if (scanner.char(3) === "'") { acc.push("'"); scanner.next(); } scanner.next(3); // skip last "'''" return success(acc.join("")); } const BOOLEAN_REGEXP = /(?:true|false)\b/y; export function boolean(scanner) { scanner.skipWhitespaces(); const match = scanner.match(BOOLEAN_REGEXP); if (!match) return failure(); const string = match[0]; scanner.next(string.length); const value = string === "true"; return success(value); } const INFINITY_MAP = new Map([ ["inf", Infinity], ["+inf", Infinity], ["-inf", -Infinity], ]); const INFINITY_REGEXP = /[+-]?inf\b/y; export function infinity(scanner) { scanner.skipWhitespaces(); const match = scanner.match(INFINITY_REGEXP); if (!match) return failure(); const string = match[0]; scanner.next(string.length); const value = INFINITY_MAP.get(string); return success(value); } const NAN_REGEXP = /[+-]?nan\b/y; export function nan(scanner) { scanner.skipWhitespaces(); const match = scanner.match(NAN_REGEXP); if (!match) return failure(); const string = match[0]; scanner.next(string.length); const value = NaN; return success(value); } export const dottedKey = join1(or([bareKey, basicString, literalString]), "."); const BINARY_REGEXP = /0b[01]+(?:_[01]+)*\b/y; export function binary(scanner) { scanner.skipWhitespaces(); const match = scanner.match(BINARY_REGEXP)?.[0]; if (!match) return failure(); scanner.next(match.length); const value = match.slice(2).replaceAll("_", ""); const number = parseInt(value, 2); return isNaN(number) ? failure() : success(number); } const OCTAL_REGEXP = /0o[0-7]+(?:_[0-7]+)*\b/y; export function octal(scanner) { scanner.skipWhitespaces(); const match = scanner.match(OCTAL_REGEXP)?.[0]; if (!match) return failure(); scanner.next(match.length); const value = match.slice(2).replaceAll("_", ""); const number = parseInt(value, 8); return isNaN(number) ? failure() : success(number); } const HEX_REGEXP = /0x[0-9a-f]+(?:_[0-9a-f]+)*\b/yi; export function hex(scanner) { scanner.skipWhitespaces(); const match = scanner.match(HEX_REGEXP)?.[0]; if (!match) return failure(); scanner.next(match.length); const value = match.slice(2).replaceAll("_", ""); const number = parseInt(value, 16); return isNaN(number) ? failure() : success(number); } const INTEGER_REGEXP = /[+-]?(?:0|[1-9][0-9]*(?:_[0-9]+)*)\b/y; export function integer(scanner) { scanner.skipWhitespaces(); const match = scanner.match(INTEGER_REGEXP)?.[0]; if (!match) return failure(); scanner.next(match.length); const value = match.replaceAll("_", ""); const int = parseInt(value, 10); return success(int); } const FLOAT_REGEXP = /[+-]?(?:0|[1-9][0-9]*(?:_[0-9]+)*)(?:\.[0-9]+(?:_[0-9]+)*)?(?:e[+-]?[0-9]+(?:_[0-9]+)*)?\b/yi; export function float(scanner) { scanner.skipWhitespaces(); const match = scanner.match(FLOAT_REGEXP)?.[0]; if (!match) return failure(); scanner.next(match.length); const value = match.replaceAll("_", ""); const float = parseFloat(value); if (isNaN(float)) return failure(); return success(float); } const DATE_TIME_REGEXP = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})(?:[ 0-9TZ.:+-]+)?\b/y; export function dateTime(scanner) { scanner.skipWhitespaces(); const match = scanner.match(DATE_TIME_REGEXP); if (!match) return failure(); const string = match[0]; scanner.next(string.length); const groups = match.groups; // special case if month is February if (groups.month == "02") { const days = parseInt(groups.day); if (days > 29) { throw new SyntaxError(`Invalid date string "${match}"`); } const year = parseInt(groups.year); if (days > 28 && !isLeap(year)) { throw new SyntaxError(`Invalid date string "${match}"`); } } const date = new Date(string.trim()); // invalid date if (isNaN(date.getTime())) { throw new SyntaxError(`Invalid date string "${match}"`); } return success(date); } const LOCAL_TIME_REGEXP = /(\d{2}):(\d{2}):(\d{2})(?:\.[0-9]+)?\b/y; export function localTime(scanner) { scanner.skipWhitespaces(); const match = scanner.match(LOCAL_TIME_REGEXP)?.[0]; if (!match) return failure(); scanner.next(match.length); return success(match); } export function arrayValue(scanner) { scanner.skipWhitespaces(); if (scanner.char() !== "[") return failure(); scanner.next(); const array = []; while (!scanner.eof()) { scanner.nextUntilChar(); const result = value(scanner); if (!result.ok) break; array.push(result.body); scanner.skipWhitespaces(); // may have a next item, but trailing comma is allowed at array if (scanner.char() !== ",") break; scanner.next(); } scanner.nextUntilChar(); if (scanner.char() !== "]") throw new SyntaxError("Array is not closed"); scanner.next(); return success(array); } export function inlineTable(scanner) { scanner.nextUntilChar(); if (scanner.char(1) === "}") { scanner.next(2); return success({ __proto__: null }); } const pairs = surround("{", join(pair, ","), "}")(scanner); if (!pairs.ok) return failure(); let table = { __proto__: null }; for (const pair of pairs.body) { table = deepMerge(table, pair); } return success(table); } export const value = or([ multilineBasicString, multilineLiteralString, basicString, literalString, boolean, infinity, nan, dateTime, localTime, binary, octal, hex, float, integer, arrayValue, inlineTable, ]); export const pair = kv(dottedKey, "=", value); export function block(scanner) { scanner.nextUntilChar(); const result = merge(repeat(pair))(scanner); if (result.ok) return success({ type: "Block", value: result.body }); return failure(); } export const tableHeader = surround("[", dottedKey, "]"); export function table(scanner) { scanner.nextUntilChar(); const header = tableHeader(scanner); if (!header.ok) return failure(); scanner.nextUntilChar(); const b = block(scanner); return success({ type: "Table", keys: header.body, value: b.ok ? b.body.value : { __proto__: null }, }); } export const tableArrayHeader = surround("[[", dottedKey, "]]"); export function tableArray(scanner) { scanner.nextUntilChar(); const header = tableArrayHeader(scanner); if (!header.ok) return failure(); scanner.nextUntilChar(); const b = block(scanner); return success({ type: "TableArray", keys: header.body, value: b.ok ? b.body.value : { __proto__: null }, }); } export function toml(scanner) { const blocks = repeat(or([block, tableArray, table]))(scanner); if (!blocks.ok) return success({ __proto__: null }); const body = blocks.body.reduce(deepAssign, { __proto__: null }); return success(body); } function createParseErrorMessage(scanner, message) { const string = scanner.source.slice(0, scanner.position); const lines = string.split("\n"); const row = lines.length; const column = lines.at(-1)?.length ?? 0; return `Parse error on line ${row}, column ${column}: ${message}`; } export function parserFactory(parser) { return (tomlString) => { const scanner = new Scanner(tomlString); try { const result = parser(scanner); if (result.ok && scanner.eof()) return result.body; const message = `Unexpected character: "${scanner.char()}"`; throw new SyntaxError(createParseErrorMessage(scanner, message)); } catch (error) { if (error instanceof Error) { throw new SyntaxError(createParseErrorMessage(scanner, error.message)); } const message = "Invalid error type caught"; throw new SyntaxError(createParseErrorMessage(scanner, message)); } }; }