yarnspinner2js
Version:
Converts a Yarn Spinner script string to a js object
393 lines (380 loc) • 10.4 kB
JavaScript
;
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
});