UNPKG

@alexanderson1993/cooklang-ts

Version:

Cooklang-TS is a TypeScript library for parsing and manipulating Cooklang recipes.

208 lines 8.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const tokens_1 = require("./tokens"); class Parser { constructor(options) { this.defaultUnits = ""; this.defaultCookwareAmount = options?.defaultCookwareAmount ?? 1; this.defaultIngredientAmount = options?.defaultIngredientAmount ?? "some"; this.includeStepNumber = options?.includeStepNumber ?? false; } parse(source) { const ingredients = []; const cookwares = []; const metadata = {}; const steps = []; const shoppingList = {}; source = source.replace(tokens_1.comment, "").replace(tokens_1.blockComment, " "); for (let match of source.matchAll(tokens_1.shoppingList)) { const groups = createNamedGroups(match, "shoppingList"); if (!groups?.name) continue; shoppingList[groups.name] = parseShoppingListCategory(groups.items || ""); source = source.substring(0, match.index || 0); +source.substring((match.index || 0) + match[0].length); } const lines = source.split(/\r?\n/).filter((l) => l.trim().length > 0); let stepNumber = 0; stepLoop: for (let i = 0; i < lines.length; i++) { const line = lines[i]; const step = []; let pos = 0; for (let match of line.matchAll(tokens_1.tokens)) { const metadataGroups = createNamedGroups(match, "metadata"); if (metadataGroups?.key && metadataGroups.value) { metadata[metadataGroups.key.trim()] = metadataGroups.value.trim(); continue stepLoop; } if (pos < (match.index || 0)) { step.push({ type: "text", value: line.substring(pos, match.index), }); } const sIngredientGroup = createNamedGroups(match, "singleWordIngredient"); if (sIngredientGroup?.sIngredientName) { const ingredient = { type: "ingredient", name: sIngredientGroup.sIngredientName, quantity: this.defaultIngredientAmount, units: this.defaultUnits, }; if (this.includeStepNumber) ingredient.step = stepNumber; ingredients.push(ingredient); step.push(ingredient); } const mIngredientGroup = createNamedGroups(match, "multiwordIngredient"); if (mIngredientGroup?.mIngredientName) { const ingredient = { type: "ingredient", name: mIngredientGroup.mIngredientName, quantity: parseQuantity(mIngredientGroup.mIngredientQuantity) ?? this.defaultIngredientAmount, units: parseUnits(mIngredientGroup.mIngredientUnits) ?? this.defaultUnits, ...(mIngredientGroup.mIngredientPreparation ? { preparation: mIngredientGroup.mIngredientPreparation } : null), ...(mIngredientGroup.mIngredientReference ? { reference: mIngredientGroup.mIngredientReference } : null), }; if (this.includeStepNumber) ingredient.step = stepNumber; ingredients.push(ingredient); step.push(ingredient); } const sCookwareGroup = createNamedGroups(match, "singleWordCookware"); if (sCookwareGroup?.sCookwareName) { const cookware = { type: "cookware", name: sCookwareGroup.sCookwareName, quantity: this.defaultCookwareAmount, }; if (this.includeStepNumber) cookware.step = stepNumber; cookwares.push(cookware); step.push(cookware); } const mCookwareGroup = createNamedGroups(match, "multiwordCookware"); if (mCookwareGroup?.mCookwareName) { const cookware = { type: "cookware", name: mCookwareGroup?.mCookwareName, quantity: parseQuantity(mCookwareGroup?.mCookwareQuantity) ?? this.defaultCookwareAmount, }; if (this.includeStepNumber) cookware.step = stepNumber; cookwares.push(cookware); step.push(cookware); } const timerGroup = createNamedGroups(match, "timer"); if (timerGroup?.timerQuantity) { step.push({ type: "timer", name: timerGroup.timerName, quantity: parseQuantity(timerGroup.timerQuantity) ?? 0, units: parseUnits(timerGroup.timerUnits) ?? this.defaultUnits, }); } pos = (match.index || 0) + match[0].length; } if (pos < line.length) { step.push({ type: "text", value: line.substring(pos), }); } if (step.length > 0) { steps.push(step); stepNumber++; } } return { ingredients, cookwares, metadata, steps, shoppingList }; } } exports.default = Parser; function parseQuantity(quantity) { if (!quantity || quantity.trim() === "") { return undefined; } quantity = quantity.trim(); const [left, right] = quantity.split("/"); const [numLeft, numRight] = [Number(left), Number(right)]; if (right && isNaN(numRight)) return quantity; if (!isNaN(numLeft) && !numRight) return numLeft; else if (!isNaN(numLeft) && !isNaN(numRight) && !(left.startsWith("0") || right.startsWith("0"))) return numLeft / numRight; return quantity.trim(); } function parseUnits(units) { if (!units || units.trim() === "") { return undefined; } return units.trim(); } function parseShoppingListCategory(items) { const list = []; for (let item of items.split("\n")) { item = item.trim(); if (item == "") continue; const [name, synonym] = item.split("|"); list.push({ name: name.trim(), synonym: synonym?.trim() || "", }); } return list; } function createNamedGroups(match, type) { if (!match) return null; const groupMappings = { metadata: { key: 1, value: 2, }, multiwordIngredient: { mIngredientName: 3, mIngredientQuantity: 4, mIngredientUnits: 5, mIngredientPreparation: 6, mIngredientReference: 7, }, singleWordIngredient: { sIngredientName: 8, }, multiwordCookware: { mCookwareName: 9, mCookwareQuantity: 10, }, singleWordCookware: { sCookwareName: 11, }, timer: { timerName: 12, timerQuantity: 13, timerUnits: 14, }, shoppingList: { name: 15, items: 16, }, }; const mapping = groupMappings[type]; if (!mapping) { throw new Error(`Unknown regex type: ${type}`); } const groups = {}; for (const [groupName, index] of Object.entries(mapping)) { if (match[index] !== undefined) { groups[groupName] = match[index]; } } return groups; } //# sourceMappingURL=Parser.js.map