UNPKG

yarnspinner2js

Version:

Converts a Yarn Spinner script string to a js object

393 lines (380 loc) 10.4 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { parseYarnSpinner: () => parseYarnSpinner }); module.exports = __toCommonJS(src_exports); // src/utils/index.ts function countIndents(str) { var _a; return ((_a = str.match(/^\s+/)) == null ? void 0 : _a[0].length) || 0; } function lineIsEmpty(str) { return !str.trim(); } function lineIsComment(str) { return /^\s*\/\//.test(str); } function lineIsOption(str) { return /^\s*->/.test(str); } function lineIsIfBlockStart(str) { return /^\s*<<if/.test(str); } function lineIsVariable(str) { return /^\s*<<(declare|set)/.test(str); } function lineIsJump(str) { return /^\s*<<jump/.test(str); } function lineIsCommand(str) { return /^\s*<</.test(str); } // src/utils/createStringsIter.ts function createStringsIter(arr) { let index = 0; return { next() { if (index >= arr.length) return { done: true, value: void 0 }; return { done: false, value: arr[index++] }; }, stepBack() { index--; if (index < 0) index = 0; }, [Symbol.iterator]: function() { return this; }, getLine() { return arr[index]; } }; } // src/utils/strings.ts var escapingChars = { "\\": "L34KLFdfg3", "#": "jt5437teWY", "/": "kdf8438hjf", "[": "asLS8345KL", "]": "fsdkgDf768", ">": "dK48fkK20G", "<": "F7gi8f3Jk0" }; function normalizeString(str) { return _settings.normalizeText ? str.trim().replace(/\s+/g, " ") : str; } function extractID(str) { const [_str, id] = str.split("#"); return [_str, id ? normalizeString(id) : ""]; } function clearComment(str) { return str.split("//")[0]; } function extractCondition(str) { const reg = str.match(/(<<if(.+)>>)\s*$/); if (!(reg == null ? void 0 : reg.length)) { return [str, ""]; } str = str.replace(reg == null ? void 0 : reg[1], ""); const condition = normalizeString(reg == null ? void 0 : reg[2]); return [str, condition]; } function hideEscapingChars(str) { for (const [k, v] of Object.entries(escapingChars)) { str = str.split("\\" + k).join(v); } return str; } function showEscapingChars(str) { for (const [k, v] of Object.entries(escapingChars)) { str = str.split(v).join(k); } return str; } // src/parseSpeech.ts function parseSpeech(str) { str = hideEscapingChars(str); const [_speech, id] = extractID(str); const [speech, condition] = extractCondition(_speech); const [name, text] = extractSpeech(speech); return { type: "speech", name, text, condition, id }; } function extractSpeech(speech) { speech = speech.trim(); let name = ""; let text = speech; const splitIndex = speech.indexOf(":"); if (splitIndex !== -1) { name = speech.substring(0, splitIndex); text = speech.substring(splitIndex + 1); } if (text.startsWith(" ")) text = text.substring(1); name = showEscapingChars(name); text = showEscapingChars(text); return [normalizeString(name), normalizeString(text)]; } // src/parseVariable.ts var declarePrefixLength = 10; var setPrefixLength = 6; var postfixPosition = -2; function parseVariable(str) { str = str.trim(); let prefixLength = declarePrefixLength; let type = "declare"; if (str.startsWith("<<set")) { prefixLength = setPrefixLength; type = "set"; } let separator = " = "; let operator = ""; if (/<<set/.test(str) && /\sto\s/.test(str)) { separator = " to "; } else if (str.startsWith("<<set") && str.includes(" += ")) { separator = " += "; operator = " + "; } else if (str.startsWith("<<set") && str.includes(" -= ")) { separator = " -= "; operator = " - "; } else if (str.startsWith("<<set") && str.includes(" *= ")) { separator = " *= "; operator = " * "; } else if (str.startsWith("<<set") && str.includes(" /= ")) { separator = " /= "; operator = " / "; } else if (str.startsWith("<<set") && str.includes(" %= ")) { separator = " %= "; operator = " % "; } else if (!/\s=\s/.test(str)) { throw new SyntaxError("Missing assignment operator at" + str); } const splitIndex = str.indexOf(separator); const name = str.slice(prefixLength, splitIndex); let value = str.slice(splitIndex + separator.length, postfixPosition); if (operator) { value = name + operator + "(" + value + ")"; } value = normalizeString(value); return { type, name, value }; } // src/parseOptionsBlock.ts function parseOptionsBlock(strings) { const options = { type: "options-block", options: [] }; const indents = countIndents(strings.getLine()); for (const str of strings) { strings.stepBack(); if (!lineIsOption(str) || countIndents(str) < indents) break; const option = parseOption(strings); options.options.push(option); } return options; } function parseOption(strings) { const str = strings.next().value; const [_text, id] = extractOptionTextAndId(str); const [text, condition] = extractCondition(_text); const option = { type: "option", text: normalizeString(text), id, condition, body: [] }; const indents = countIndents(str); option.body = parseStrings(strings, (s) => { if (countIndents(s) > indents) return false; strings.stepBack(); return true; }); return option; } function extractOptionTextAndId(option) { const prefixLength = 2; const text = option.trim().substring(prefixLength); return extractID(text); } // src/parseIfBlock.ts function parseIfBlock(strings) { const ifBlock = { type: "if-block", items: [] }; for (const str of strings) { if (str.includes("<<endif>>")) break; const item = { type: "if-block-item", condition: "", body: [] }; if (str.includes("<<if") || str.includes("<<elseif")) { item.condition = parseCondition(str); } else if (!str.includes("<<else>>")) { throw new SyntaxError( `The condition block must contain only lines <<elseif>>, <<else>>, <<endif>> and nested lines. Line: "${str}"` ); } item.body = parseStrings(strings, (_str) => ifBlockOver(_str, strings)); ifBlock.items.push(item); } return ifBlock; } function parseCondition(str) { var _a; const condition = ((_a = str.match(/<<(if|elseif)(.*)>>/)) == null ? void 0 : _a[2]) || ""; if (!condition.trim()) { throw new SyntaxError(`Condition not found. Line: "${str}"`); } return normalizeString(condition); } function ifBlockOver(str, strings) { if (str.includes("<<else>>") || str.includes("<<elseif") || str.includes("<<endif>>")) { strings.stepBack(); return true; } return false; } // src/parseJump.ts function parseJump(str) { return { type: "jump", to: str.trim().slice(6, -2).trim() }; } // src/parseCommand.ts function parseCommand(str) { var _a; str = str.trim(); const name = ((_a = str.match(/<<\w+/)) == null ? void 0 : _a[0].slice(2)) || ""; return { type: "command", name, parameters: str.slice(name.length + 2, -2).trim().split(/\s+/) }; } // src/parseBody.ts function parseBody(bodyRaw) { const nodeBody = createStringsIter(bodyRaw.split("\n")); return parseStrings(nodeBody); } function parseStrings(strings, isOver) { const body = []; for (let str of strings) { if (lineIsEmpty(str) || lineIsComment(str)) continue; if (isOver == null ? void 0 : isOver(str)) break; str = clearComment(str); let line; if (lineIsIfBlockStart(str)) { strings.stepBack(); line = parseIfBlock(strings); } else if (lineIsVariable(str)) { line = parseVariable(str); } else if (lineIsJump(str)) { line = parseJump(str); } else if (lineIsCommand(str)) { line = parseCommand(str); } else if (lineIsOption(str)) { strings.stepBack(); line = parseOptionsBlock(strings); } else { line = parseSpeech(str); } line && body.push(line); } return body; } // src/parseYarnSpinner.ts var _settings = { ignoreHeaderParameters: [""], normalizeText: true }; function parseYarnSpinner(yarnRaw, settings) { Object.assign(_settings, settings); const nodes = []; const nodesRaw = yarnRaw.split("\n==="); for (const nodeRaw of nodesRaw) { if (!nodeRaw.trim()) continue; const node = { title: "", parameters: {}, body: [] }; const [headerRaw, bodyRaw] = splitNode(nodeRaw); [node.title, node.parameters] = parseNodeHeader(headerRaw); node.body = parseBody(bodyRaw); nodes.push(node); } return nodes; } function splitNode(nodeRaw) { if (!/---\r?\n/.test(nodeRaw)) { throw new SyntaxError("One of the nodes has no delimiter"); } const [headerRaw, bodyRaw, ...nodeParts] = nodeRaw.split("\n---"); if (nodeParts.length || !headerRaw.trim()) { throw new SyntaxError("One of the nodes has no header"); } return [headerRaw, bodyRaw]; } function parseNodeHeader(headerRaw) { var _a; const params = {}; let title = ""; const headerStrings = headerRaw.split("\n").map((s) => s.trim()); for (const string of headerStrings) { const [key, value] = string.split(":").map((i) => i.trim()); if (key === "title") { title = value; continue; } if (!key || ((_a = _settings.ignoreHeaderParameters) == null ? void 0 : _a.includes(key))) continue; key && (params[key] = value); } if (!title) { throw new SyntaxError("One of the nodes has no title"); } return [title, params]; } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { parseYarnSpinner });