@tmlmt/cooklang-parser 3.0.0-alpha.17 → 3.0.0-alpha.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +453 -181
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +185 -66
- package/dist/index.d.ts +185 -66
- package/dist/index.js +450 -179
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
package/dist/index.cjs
CHANGED
|
@@ -32,11 +32,9 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
32
32
|
// src/index.ts
|
|
33
33
|
var index_exports = {};
|
|
34
34
|
__export(index_exports, {
|
|
35
|
-
BadIndentationError: () => BadIndentationError,
|
|
36
35
|
CategoryConfig: () => CategoryConfig,
|
|
37
36
|
NoProductCatalogForCartError: () => NoProductCatalogForCartError,
|
|
38
37
|
NoShoppingListForCartError: () => NoShoppingListForCartError,
|
|
39
|
-
NoTabAsIndentError: () => NoTabAsIndentError,
|
|
40
38
|
Pantry: () => Pantry,
|
|
41
39
|
ProductCatalog: () => ProductCatalog,
|
|
42
40
|
Recipe: () => Recipe,
|
|
@@ -51,11 +49,14 @@ __export(index_exports, {
|
|
|
51
49
|
formatQuantityWithUnit: () => formatQuantityWithUnit,
|
|
52
50
|
formatSingleValue: () => formatSingleValue,
|
|
53
51
|
formatUnit: () => formatUnit,
|
|
52
|
+
getEffectiveChoices: () => getEffectiveChoices,
|
|
54
53
|
hasAlternatives: () => hasAlternatives,
|
|
55
54
|
isAlternativeSelected: () => isAlternativeSelected,
|
|
56
55
|
isAndGroup: () => isAndGroup,
|
|
57
56
|
isGroupedItem: () => isGroupedItem,
|
|
57
|
+
isSectionActive: () => isSectionActive,
|
|
58
58
|
isSimpleGroup: () => isSimpleGroup,
|
|
59
|
+
isStepActive: () => isStepActive,
|
|
59
60
|
renderFractionAsVulgar: () => renderFractionAsVulgar
|
|
60
61
|
});
|
|
61
62
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -334,8 +335,8 @@ var nonWordChar = "\\s@#~\\[\\]{(,;:!?";
|
|
|
334
335
|
var nonWordCharStrict = "\\s@#~\\[\\]{(,;:!?|";
|
|
335
336
|
var ingredientWithAlternativeRegex = d().literal("@").startNamedGroup("ingredientModifiers").anyOf("@\\-&?").zeroOrMore().endGroup().optional().startNamedGroup("ingredientRecipeAnchor").literal("./").endGroup().optional().startGroup().startGroup().startNamedGroup("mIngredientName").notAnyOf(nonWordChar).oneOrMore().startGroup().whitespace().oneOrMore().notAnyOf(nonWordChar).oneOrMore().endGroup().oneOrMore().endGroup().positiveLookahead("\\s*(?:\\{[^\\}]*\\}|\\([^)]*\\))").endGroup().or().startNamedGroup("sIngredientName").notAnyOf(nonWordChar).zeroOrMore().notAnyOf("\\." + nonWordChar).endGroup().endGroup().startGroup().literal("{").startNamedGroup("ingredientQuantityModifier").literal("=").exactly(1).endGroup().optional().startNamedGroup("ingredientQuantity").startGroup().notAnyOf("}|%").oneOrMore().endGroup().optional().startGroup().literal("%").notAnyOf("|}").oneOrMore().lazy().endGroup().optional().startGroup().literal("|").notAnyOf("}").oneOrMore().lazy().endGroup().zeroOrMore().endGroup().literal("}").endGroup().optional().startGroup().literal("(").startNamedGroup("ingredientPreparation").notAnyOf(")").oneOrMore().lazy().endGroup().literal(")").endGroup().optional().startGroup().literal("[").startNamedGroup("ingredientNote").notAnyOf("\\]").oneOrMore().lazy().endGroup().literal("]").endGroup().optional().startNamedGroup("ingredientAlternative").startGroup().literal("|").startGroup().anyOf("@\\-&?").zeroOrMore().endGroup().optional().startGroup().literal("./").endGroup().optional().startGroup().startGroup().startGroup().notAnyOf(nonWordChar).oneOrMore().startGroup().whitespace().oneOrMore().notAnyOf(nonWordChar).oneOrMore().endGroup().oneOrMore().endGroup().positiveLookahead("\\s*(?:\\{[^\\}]*\\}|\\([^)]*\\))").endGroup().or().startGroup().notAnyOf(nonWordChar).oneOrMore().endGroup().endGroup().startGroup().literal("{").startGroup().literal("=").exactly(1).endGroup().optional().startGroup().notAnyOf("}%").oneOrMore().endGroup().optional().startGroup().literal("%").startGroup().notAnyOf("}").oneOrMore().lazy().endGroup().endGroup().optional().literal("}").endGroup().optional().startGroup().literal("(").startGroup().notAnyOf(")").oneOrMore().lazy().endGroup().literal(")").endGroup().optional().startGroup().literal("[").startGroup().notAnyOf("\\]").oneOrMore().lazy().endGroup().literal("]").endGroup().optional().endGroup().zeroOrMore().endGroup().toRegExp();
|
|
336
337
|
var inlineIngredientAlternativesRegex = new RegExp("\\|" + ingredientWithAlternativeRegex.source.slice(1));
|
|
337
|
-
var quantityAlternativeRegex = d().startNamedGroup("quantity").notAnyOf("}|%").oneOrMore().endGroup().optional().startGroup().literal("%").startNamedGroup("unit").notAnyOf("|}").oneOrMore().endGroup().endGroup().optional().startGroup().literal("|").startNamedGroup("alternative").startGroup().notAnyOf("}").oneOrMore().endGroup().zeroOrMore().endGroup().endGroup().optional().toRegExp();
|
|
338
|
-
var ingredientWithGroupKeyRegex = d().literal("@|").startNamedGroup("gIngredientGroupKey").notAnyOf(nonWordCharStrict + "/").oneOrMore().endGroup().startGroup().literal("/").startNamedGroup("gIngredientSubgroupKey").notAnyOf(nonWordCharStrict).oneOrMore().endGroup().endGroup().optional().literal("|").startNamedGroup("gIngredientModifiers").anyOf("@\\-&?").zeroOrMore().endGroup().optional().startNamedGroup("gIngredientRecipeAnchor").literal("./").endGroup().optional().startGroup().startGroup().startNamedGroup("gmIngredientName").notAnyOf(nonWordChar).oneOrMore().startGroup().whitespace().oneOrMore().notAnyOf(nonWordChar).oneOrMore().endGroup().oneOrMore().endGroup().positiveLookahead("\\s*(?:\\{[^\\}]*\\}|\\([^)]*\\))").endGroup().or().startNamedGroup("gsIngredientName").notAnyOf(nonWordChar).zeroOrMore().notAnyOf("\\." + nonWordChar).endGroup().endGroup().startGroup().literal("{").startNamedGroup("gIngredientQuantityModifier").literal("=").exactly(1).endGroup().optional().startNamedGroup("gIngredientQuantity").startGroup().notAnyOf("}|%").oneOrMore().endGroup().optional().startGroup().literal("%").notAnyOf("|}").oneOrMore().lazy().endGroup().optional().startGroup().literal("|").notAnyOf("}").oneOrMore().lazy().endGroup().zeroOrMore().endGroup().literal("}").endGroup().optional().startGroup().literal("(").startNamedGroup("gIngredientPreparation").notAnyOf(")").oneOrMore().lazy().endGroup().literal(")").endGroup().optional().toRegExp();
|
|
338
|
+
var quantityAlternativeRegex = d().startNamedGroup("quantity").notAnyOf("{}|%").oneOrMore().endGroup().optional().startGroup().literal("%").startNamedGroup("unit").notAnyOf("|}").oneOrMore().endGroup().endGroup().optional().startGroup().literal("|").startNamedGroup("alternative").startGroup().notAnyOf("}").oneOrMore().endGroup().zeroOrMore().endGroup().endGroup().optional().toRegExp();
|
|
339
|
+
var ingredientWithGroupKeyRegex = d().literal("@|").startNamedGroup("gIngredientGroupKey").notAnyOf(nonWordCharStrict + "/").oneOrMore().endGroup().startGroup().literal("/").startNamedGroup("gIngredientSubgroupKey").notAnyOf(nonWordCharStrict).oneOrMore().endGroup().endGroup().optional().literal("|").startNamedGroup("gIngredientModifiers").anyOf("@\\-&?").zeroOrMore().endGroup().optional().startNamedGroup("gIngredientRecipeAnchor").literal("./").endGroup().optional().startGroup().startGroup().startNamedGroup("gmIngredientName").notAnyOf(nonWordChar).oneOrMore().startGroup().whitespace().oneOrMore().notAnyOf(nonWordChar).oneOrMore().endGroup().oneOrMore().endGroup().positiveLookahead("\\s*(?:\\{[^\\}]*\\}|\\([^)]*\\))").endGroup().or().startNamedGroup("gsIngredientName").notAnyOf(nonWordChar).zeroOrMore().notAnyOf("\\." + nonWordChar).endGroup().endGroup().startGroup().literal("{").startNamedGroup("gIngredientQuantityModifier").literal("=").exactly(1).endGroup().optional().startNamedGroup("gIngredientQuantity").startGroup().notAnyOf("}|%").oneOrMore().endGroup().optional().startGroup().literal("%").notAnyOf("|}").oneOrMore().lazy().endGroup().optional().startGroup().literal("|").notAnyOf("}").oneOrMore().lazy().endGroup().zeroOrMore().endGroup().literal("}").endGroup().optional().startGroup().literal("(").startNamedGroup("gIngredientPreparation").notAnyOf(")").oneOrMore().lazy().endGroup().literal(")").endGroup().optional().startGroup().literal("[").startNamedGroup("gIngredientNote").notAnyOf("\\]").oneOrMore().lazy().endGroup().literal("]").endGroup().optional().toRegExp();
|
|
339
340
|
var ingredientAliasRegex = d().startAnchor().startNamedGroup("ingredientListName").notAnyOf("|").oneOrMore().endGroup().literal("|").startNamedGroup("ingredientDisplayName").notAnyOf("|").oneOrMore().endGroup().endAnchor().toRegExp();
|
|
340
341
|
var cookwareRegex = d().literal("#").startNamedGroup("cookwareModifiers").anyOf("\\-&?").zeroOrMore().endGroup().startGroup().startGroup().startNamedGroup("mCookwareName").notAnyOf(nonWordChar).oneOrMore().startGroup().whitespace().oneOrMore().notAnyOf(nonWordChar).oneOrMore().endGroup().oneOrMore().endGroup().positiveLookahead("\\s*(?:\\{[^\\}]*\\})").endGroup().or().startNamedGroup("sCookwareName").notAnyOf(nonWordChar).zeroOrMore().notAnyOf("\\." + nonWordChar).endGroup().endGroup().startGroup().literal("{").startNamedGroup("cookwareQuantity").anyCharacter().zeroOrMore().lazy().endGroup().literal("}").endGroup().optional().toRegExp();
|
|
341
342
|
var timerRegex = d().literal("~").startNamedGroup("timerName").anyCharacter().zeroOrMore().lazy().endGroup().literal("{").startNamedGroup("timerQuantity").anyCharacter().oneOrMore().lazy().endGroup().startGroup().literal("%").startNamedGroup("timerUnit").anyCharacter().oneOrMore().lazy().endGroup().endGroup().optional().literal("}").toRegExp();
|
|
@@ -350,11 +351,18 @@ var tokensRegex = new RegExp(
|
|
|
350
351
|
].map((r2) => r2.source).join("|"),
|
|
351
352
|
"gu"
|
|
352
353
|
);
|
|
353
|
-
var
|
|
354
|
-
var
|
|
355
|
-
var
|
|
356
|
-
|
|
357
|
-
|
|
354
|
+
var yieldPrefixPart = d().startAnchor().literal("yield").literal(":").anyOf(" ").zeroOrMore().startNamedGroup("servingsPrefix").nonWhitespace().startGroup().anyCharacter().zeroOrMore().lazy().nonWhitespace().endGroup().optional().endGroup().optional().anyOf(" ").zeroOrMore().toRegExp();
|
|
355
|
+
var yieldSuffixPart = d().anyOf(" ").zeroOrMore().startNamedGroup("servingsSuffix").nonWhitespace().startGroup().anyCharacter().zeroOrMore().nonWhitespace().endGroup().optional().endGroup().optional().anyOf(" ").zeroOrMore().endAnchor().toRegExp();
|
|
356
|
+
var yieldMetaValueWithUnitRegex = new RegExp(
|
|
357
|
+
yieldPrefixPart.source + arbitraryScalableRegex.source + yieldSuffixPart.source,
|
|
358
|
+
"m"
|
|
359
|
+
);
|
|
360
|
+
var yieldMetaValueAsQuantityRegex = d().startAnchor().literal("yield:").anyOf(" ").zeroOrMore().startNamedGroup("quantity").notAnyOf("{}|%\\n\\r").oneOrMore().endGroup().optional().startGroup().literal("%").startNamedGroup("unit").notAnyOf("\\n\\r|}").oneOrMore().endGroup().endGroup().optional().anyOf(" ").zeroOrMore().endAnchor().toRegExp();
|
|
361
|
+
var yieldMetaValueRegex = new RegExp(
|
|
362
|
+
[
|
|
363
|
+
yieldMetaValueWithUnitRegex.source,
|
|
364
|
+
yieldMetaValueAsQuantityRegex.source
|
|
365
|
+
].join("|"),
|
|
358
366
|
"m"
|
|
359
367
|
);
|
|
360
368
|
var commentRegex = d().literal("--").anyCharacter().zeroOrMore().global().toRegExp();
|
|
@@ -363,6 +371,7 @@ var shoppingListRegex = d().literal("[").startNamedGroup("name").anyCharacter().
|
|
|
363
371
|
var rangeRegex = d().startAnchor().digit().oneOrMore().startGroup().anyOf(".,/").exactly(1).digit().oneOrMore().endGroup().optional().literal("-").digit().oneOrMore().startGroup().anyOf(".,/").exactly(1).digit().oneOrMore().endGroup().optional().endAnchor().toRegExp();
|
|
364
372
|
var numberLikeRegex = d().startAnchor().digit().oneOrMore().startGroup().anyOf(".,/").exactly(1).digit().oneOrMore().endGroup().optional().endAnchor().toRegExp();
|
|
365
373
|
var floatRegex = d().startAnchor().digit().oneOrMore().startGroup().anyOf(".").exactly(1).digit().oneOrMore().endGroup().optional().endAnchor().toRegExp();
|
|
374
|
+
var variantTagRegex = d().startAnchor().literal("[").startNamedGroup("variantOptionalPrefix").literal("?").endGroup().optional().startNamedGroup("variantNames").notAnyOf("\\]").oneOrMore().endGroup().optional().literal("]").whitespace().zeroOrMore().toRegExp();
|
|
366
375
|
var mdEscaped = d().literal("\\").startCaptureGroup().anyOf("*_`").endGroup();
|
|
367
376
|
var mdInlineCode = d().literal("`").startCaptureGroup().notAnyOf("`").oneOrMore().lazy().endGroup().literal("`");
|
|
368
377
|
var mdLink = d().literal("[").startCaptureGroup().notAnyOf("\\]").oneOrMore().lazy().endGroup().literal("](").startCaptureGroup().notAnyOf(")").oneOrMore().lazy().endGroup().literal(")");
|
|
@@ -938,7 +947,7 @@ var NoProductMatchError = class extends Error {
|
|
|
938
947
|
constructor(item_name, code) {
|
|
939
948
|
const messageMap = {
|
|
940
949
|
incompatibleUnits: `The units of the products in the catalogue are incompatible with ingredient ${item_name} in the shopping list.`,
|
|
941
|
-
noProduct:
|
|
950
|
+
noProduct: `No product was found linked to ingredient name ${item_name} in the shopping list`,
|
|
942
951
|
textValue: `Ingredient ${item_name} has a text value as quantity and can therefore not be matched with any product in the catalogue.`,
|
|
943
952
|
noQuantity: `Ingredient ${item_name} has no quantity and can therefore not be matched with any product in the catalogue.`,
|
|
944
953
|
textValue_incompatibleUnits: `Multiple alternative quantities were provided for ingredient ${item_name} in the shopping list but they were either text values or no product in catalog were found to have compatible units`
|
|
@@ -1417,9 +1426,12 @@ function flushPendingNote(section, noteItems) {
|
|
|
1417
1426
|
}
|
|
1418
1427
|
return noteItems;
|
|
1419
1428
|
}
|
|
1420
|
-
function flushPendingItems(section, items) {
|
|
1429
|
+
function flushPendingItems(section, items, stepVariants, stepOptional) {
|
|
1421
1430
|
if (items.length > 0) {
|
|
1422
|
-
|
|
1431
|
+
const step = { type: "step", items: [...items] };
|
|
1432
|
+
if (stepVariants) step.variants = stepVariants;
|
|
1433
|
+
if (stepOptional) step.optional = true;
|
|
1434
|
+
section.content.push(step);
|
|
1423
1435
|
items.length = 0;
|
|
1424
1436
|
return true;
|
|
1425
1437
|
}
|
|
@@ -1538,7 +1550,7 @@ function stringifyFixedValue(quantity) {
|
|
|
1538
1550
|
return String(quantity.value.decimal);
|
|
1539
1551
|
else return quantity.value.text;
|
|
1540
1552
|
}
|
|
1541
|
-
function
|
|
1553
|
+
function parseQuantityValue(input_str) {
|
|
1542
1554
|
const clean_str = String(input_str).trim();
|
|
1543
1555
|
if (rangeRegex.test(clean_str)) {
|
|
1544
1556
|
const range_parts = clean_str.split("-");
|
|
@@ -1552,12 +1564,12 @@ function parseQuantityWithUnit(input) {
|
|
|
1552
1564
|
const trimmed = input.trim();
|
|
1553
1565
|
const separatorIndex = trimmed.indexOf("%");
|
|
1554
1566
|
if (separatorIndex === -1) {
|
|
1555
|
-
return { value:
|
|
1567
|
+
return { value: parseQuantityValue(trimmed) };
|
|
1556
1568
|
}
|
|
1557
1569
|
const valuePart = trimmed.slice(0, separatorIndex).trim();
|
|
1558
1570
|
const unitPart = trimmed.slice(separatorIndex + 1).trim();
|
|
1559
1571
|
return {
|
|
1560
|
-
value:
|
|
1572
|
+
value: parseQuantityValue(valuePart),
|
|
1561
1573
|
unit: unitPart || void 0
|
|
1562
1574
|
};
|
|
1563
1575
|
}
|
|
@@ -1747,7 +1759,7 @@ function parseArbitraryQuantity(raw) {
|
|
|
1747
1759
|
"Arbitrary quantities must have a numerical value"
|
|
1748
1760
|
);
|
|
1749
1761
|
}
|
|
1750
|
-
const value =
|
|
1762
|
+
const value = parseQuantityValue(quantityMatch.groups.quantity);
|
|
1751
1763
|
const unit = quantityMatch.groups.unit;
|
|
1752
1764
|
if (!value || value.type === "fixed" && value.value.type === "text") {
|
|
1753
1765
|
throw new InvalidQuantityFormat(
|
|
@@ -1761,40 +1773,40 @@ function parseArbitraryQuantity(raw) {
|
|
|
1761
1773
|
if (unit) arbitrary.unit = unit;
|
|
1762
1774
|
return arbitrary;
|
|
1763
1775
|
}
|
|
1764
|
-
function
|
|
1765
|
-
const
|
|
1766
|
-
if (
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1776
|
+
function parseServingsMetaVar(content, varName) {
|
|
1777
|
+
const raw = parseSimpleMetaVar(content, varName);
|
|
1778
|
+
if (raw === void 0) return void 0;
|
|
1779
|
+
const num = Number(raw);
|
|
1780
|
+
if (isNaN(num)) {
|
|
1781
|
+
return { numericValue: 1, rawValue: raw };
|
|
1782
|
+
}
|
|
1783
|
+
return { numericValue: num, rawValue: num };
|
|
1784
|
+
}
|
|
1785
|
+
function parseYieldMetaVar(content) {
|
|
1786
|
+
const match = content.match(yieldMetaValueRegex);
|
|
1787
|
+
if (!match) return void 0;
|
|
1788
|
+
if (match.groups?.arbitraryQuantity) {
|
|
1789
|
+
const parsed = parseArbitraryQuantity(match.groups.arbitraryQuantity);
|
|
1790
|
+
const result = {
|
|
1771
1791
|
quantity: parsed.quantity
|
|
1772
1792
|
};
|
|
1773
|
-
if (parsed.unit)
|
|
1774
|
-
if (
|
|
1775
|
-
|
|
1793
|
+
if (parsed.unit) result.unit = parsed.unit;
|
|
1794
|
+
if (match.groups.servingsPrefix) {
|
|
1795
|
+
result.textBefore = match.groups.servingsPrefix;
|
|
1776
1796
|
}
|
|
1777
|
-
if (
|
|
1778
|
-
|
|
1797
|
+
if (match.groups.servingsSuffix) {
|
|
1798
|
+
result.textAfter = match.groups.servingsSuffix;
|
|
1779
1799
|
}
|
|
1780
|
-
return
|
|
1781
|
-
}
|
|
1782
|
-
const varMatch = content.match(scalingSimpleMetaValueRegex(varName));
|
|
1783
|
-
if (!varMatch) return void 0;
|
|
1784
|
-
if (isNaN(Number(varMatch[2]?.trim()))) {
|
|
1785
|
-
throw new Error("Scaling variables should be numbers");
|
|
1800
|
+
return result;
|
|
1786
1801
|
}
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
};
|
|
1794
|
-
if (varMatch[3]) {
|
|
1795
|
-
result.text = `${varMatch[3].trim()}`;
|
|
1802
|
+
if (match.groups?.quantity) {
|
|
1803
|
+
const result = {
|
|
1804
|
+
quantity: parseQuantityValue(match.groups.quantity)
|
|
1805
|
+
};
|
|
1806
|
+
if (match.groups.unit) result.unit = match.groups.unit;
|
|
1807
|
+
return result;
|
|
1796
1808
|
}
|
|
1797
|
-
return
|
|
1809
|
+
return void 0;
|
|
1798
1810
|
}
|
|
1799
1811
|
function parseListMetaVar(content, varName) {
|
|
1800
1812
|
const listMatch = content.match(
|
|
@@ -1912,12 +1924,12 @@ function parseAnyMetaVar(content, varName) {
|
|
|
1912
1924
|
if (simple) return parseMetadataValue(simple);
|
|
1913
1925
|
return void 0;
|
|
1914
1926
|
}
|
|
1915
|
-
function
|
|
1927
|
+
function getNumericValueFromYield(v) {
|
|
1916
1928
|
if (v.quantity.type === "fixed" && v.quantity.value.type !== "text") {
|
|
1917
1929
|
return getNumericValue(v.quantity.value);
|
|
1918
1930
|
}
|
|
1919
1931
|
if (v.quantity.type === "range") return getNumericValue(v.quantity.min);
|
|
1920
|
-
return
|
|
1932
|
+
return 1;
|
|
1921
1933
|
}
|
|
1922
1934
|
function extractMetadata(content) {
|
|
1923
1935
|
const metadata = {};
|
|
@@ -1963,7 +1975,8 @@ function extractMetadata(content) {
|
|
|
1963
1975
|
"yield",
|
|
1964
1976
|
"serves",
|
|
1965
1977
|
// List fields
|
|
1966
|
-
"tags"
|
|
1978
|
+
"tags",
|
|
1979
|
+
"variants"
|
|
1967
1980
|
]);
|
|
1968
1981
|
for (const metaVar of [
|
|
1969
1982
|
"title",
|
|
@@ -2041,15 +2054,22 @@ function extractMetadata(content) {
|
|
|
2041
2054
|
};
|
|
2042
2055
|
unitSystem = unitSystemMap[unitSystemRaw.toLowerCase()];
|
|
2043
2056
|
}
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2057
|
+
const yieldValue = parseYieldMetaVar(metadataContent);
|
|
2058
|
+
if (yieldValue) {
|
|
2059
|
+
metadata.yield = yieldValue;
|
|
2060
|
+
servings = getNumericValueFromYield(yieldValue);
|
|
2061
|
+
}
|
|
2062
|
+
for (const metaVar of ["serves", "servings"]) {
|
|
2063
|
+
const result = parseServingsMetaVar(metadataContent, metaVar);
|
|
2064
|
+
if (result !== void 0) {
|
|
2065
|
+
metadata[metaVar] = result.rawValue;
|
|
2066
|
+
servings = result.numericValue;
|
|
2049
2067
|
}
|
|
2050
2068
|
}
|
|
2051
2069
|
const tags = parseListMetaVar(metadataContent, "tags");
|
|
2052
2070
|
if (tags) metadata.tags = tags;
|
|
2071
|
+
const variants = parseListMetaVar(metadataContent, "variants");
|
|
2072
|
+
if (variants) metadata.variants = variants;
|
|
2053
2073
|
const allKeys = extractAllMetadataKeys(metadataContent);
|
|
2054
2074
|
for (const key of allKeys) {
|
|
2055
2075
|
if (handledKeys.has(key)) continue;
|
|
@@ -2324,7 +2344,7 @@ var ProductCatalog = class {
|
|
|
2324
2344
|
const sizeStrings = Array.isArray(size) ? size : [size];
|
|
2325
2345
|
const sizes = sizeStrings.map((sizeStr) => {
|
|
2326
2346
|
const sizeAndUnitRaw = sizeStr.split("%");
|
|
2327
|
-
const sizeParsed =
|
|
2347
|
+
const sizeParsed = parseQuantityValue(
|
|
2328
2348
|
sizeAndUnitRaw[0]
|
|
2329
2349
|
);
|
|
2330
2350
|
const productSize = { size: sizeParsed };
|
|
@@ -2437,8 +2457,10 @@ var Section = class {
|
|
|
2437
2457
|
/**
|
|
2438
2458
|
* Creates an instance of Section.
|
|
2439
2459
|
* @param name - The name of the section. Defaults to an empty string.
|
|
2460
|
+
* @param variants - Optional variant names for this section.
|
|
2461
|
+
* @param optional - Whether the section is optional.
|
|
2440
2462
|
*/
|
|
2441
|
-
constructor(name = "") {
|
|
2463
|
+
constructor(name = "", variants, optional) {
|
|
2442
2464
|
/**
|
|
2443
2465
|
* The name of the section. Can be an empty string for the default (first) section.
|
|
2444
2466
|
* @defaultValue `""`
|
|
@@ -2446,7 +2468,13 @@ var Section = class {
|
|
|
2446
2468
|
__publicField(this, "name");
|
|
2447
2469
|
/** An array of steps and notes that make up the content of the section. */
|
|
2448
2470
|
__publicField(this, "content", []);
|
|
2471
|
+
/** Optional list of variant names this section belongs to. */
|
|
2472
|
+
__publicField(this, "variants");
|
|
2473
|
+
/** Whether the section has been marked as optional ([?]) */
|
|
2474
|
+
__publicField(this, "optional");
|
|
2449
2475
|
this.name = name;
|
|
2476
|
+
if (variants) this.variants = variants;
|
|
2477
|
+
if (optional) this.optional = true;
|
|
2450
2478
|
}
|
|
2451
2479
|
/**
|
|
2452
2480
|
* Checks if the section is blank (has no name and no content).
|
|
@@ -2482,34 +2510,7 @@ function findCompatibleQuantityWithinList(list, quantity) {
|
|
|
2482
2510
|
}
|
|
2483
2511
|
|
|
2484
2512
|
// src/utils/general.ts
|
|
2485
|
-
var
|
|
2486
|
-
if (v === null || typeof v !== "object") {
|
|
2487
|
-
return v;
|
|
2488
|
-
}
|
|
2489
|
-
if (v instanceof Map) {
|
|
2490
|
-
return new Map(
|
|
2491
|
-
Array.from(v.entries()).map(([k, val]) => [
|
|
2492
|
-
legacyDeepClone(k),
|
|
2493
|
-
legacyDeepClone(val)
|
|
2494
|
-
])
|
|
2495
|
-
);
|
|
2496
|
-
}
|
|
2497
|
-
if (v instanceof Set) {
|
|
2498
|
-
return new Set(Array.from(v).map((val) => legacyDeepClone(val)));
|
|
2499
|
-
}
|
|
2500
|
-
if (v instanceof Date) {
|
|
2501
|
-
return new Date(v.getTime());
|
|
2502
|
-
}
|
|
2503
|
-
if (Array.isArray(v)) {
|
|
2504
|
-
return v.map((item) => legacyDeepClone(item));
|
|
2505
|
-
}
|
|
2506
|
-
const cloned = {};
|
|
2507
|
-
for (const key of Object.keys(v)) {
|
|
2508
|
-
cloned[key] = legacyDeepClone(v[key]);
|
|
2509
|
-
}
|
|
2510
|
-
return cloned;
|
|
2511
|
-
};
|
|
2512
|
-
var deepClone = (v) => typeof structuredClone === "function" ? structuredClone(v) : legacyDeepClone(v);
|
|
2513
|
+
var deepClone = (v) => structuredClone(v);
|
|
2513
2514
|
|
|
2514
2515
|
// src/quantities/alternatives.ts
|
|
2515
2516
|
function getEquivalentUnitsLists(...quantities) {
|
|
@@ -2788,6 +2789,47 @@ function addEquivalentsAndSimplify(quantities, system) {
|
|
|
2788
2789
|
return { and: regrouped.map(toPlainUnit) };
|
|
2789
2790
|
}
|
|
2790
2791
|
}
|
|
2792
|
+
function buildEquivalenceRatioMap(unitsLists) {
|
|
2793
|
+
const ratioMap = {};
|
|
2794
|
+
for (const list of unitsLists) {
|
|
2795
|
+
for (const equiv of list) {
|
|
2796
|
+
const equivValue = getAverageValue(equiv.quantity);
|
|
2797
|
+
for (const primary of list) {
|
|
2798
|
+
if (primary === equiv) continue;
|
|
2799
|
+
const primaryValue = getAverageValue(primary.quantity);
|
|
2800
|
+
const equivUnit = normalizeUnit(equiv.unit.name)?.name ?? equiv.unit.name;
|
|
2801
|
+
const primaryUnit = normalizeUnit(primary.unit.name)?.name ?? primary.unit.name;
|
|
2802
|
+
ratioMap[equivUnit] ?? (ratioMap[equivUnit] = {});
|
|
2803
|
+
ratioMap[equivUnit][primaryUnit] = equivValue / primaryValue;
|
|
2804
|
+
}
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2807
|
+
return ratioMap;
|
|
2808
|
+
}
|
|
2809
|
+
function recomputeEquivalents(primaries, ratioMap, equivUnits) {
|
|
2810
|
+
const equivalents = [];
|
|
2811
|
+
for (const equivUnit of equivUnits) {
|
|
2812
|
+
const ratios = ratioMap[normalizeUnit(equivUnit)?.name ?? equivUnit];
|
|
2813
|
+
let total = 0;
|
|
2814
|
+
for (const primary of primaries) {
|
|
2815
|
+
const pUnit = normalizeUnit(primary.unit ?? NO_UNIT)?.name ?? primary.unit ?? NO_UNIT;
|
|
2816
|
+
const ratio = ratios[pUnit];
|
|
2817
|
+
if (ratio === void 0) continue;
|
|
2818
|
+
const pValue = getAverageValue(primary.quantity);
|
|
2819
|
+
total += pValue * ratio;
|
|
2820
|
+
}
|
|
2821
|
+
if (total > 0) {
|
|
2822
|
+
equivalents.push({
|
|
2823
|
+
quantity: {
|
|
2824
|
+
type: "fixed",
|
|
2825
|
+
value: { type: "decimal", decimal: total }
|
|
2826
|
+
},
|
|
2827
|
+
...equivUnit !== "" && { unit: equivUnit }
|
|
2828
|
+
});
|
|
2829
|
+
}
|
|
2830
|
+
}
|
|
2831
|
+
return equivalents.length > 0 ? equivalents : void 0;
|
|
2832
|
+
}
|
|
2791
2833
|
|
|
2792
2834
|
// src/classes/recipe.ts
|
|
2793
2835
|
var import_big4 = __toESM(require("big.js"), 1);
|
|
@@ -2806,7 +2848,8 @@ var _Recipe = class _Recipe {
|
|
|
2806
2848
|
*/
|
|
2807
2849
|
__publicField(this, "choices", {
|
|
2808
2850
|
ingredientItems: /* @__PURE__ */ new Map(),
|
|
2809
|
-
ingredientGroups: /* @__PURE__ */ new Map()
|
|
2851
|
+
ingredientGroups: /* @__PURE__ */ new Map(),
|
|
2852
|
+
variants: []
|
|
2810
2853
|
});
|
|
2811
2854
|
/**
|
|
2812
2855
|
* The parsed recipe ingredients.
|
|
@@ -2911,7 +2954,7 @@ var _Recipe = class _Recipe {
|
|
|
2911
2954
|
let quantityMatch = quantityRaw.match(quantityAlternativeRegex);
|
|
2912
2955
|
const quantities = [];
|
|
2913
2956
|
while (quantityMatch?.groups) {
|
|
2914
|
-
const value = quantityMatch.groups.quantity ?
|
|
2957
|
+
const value = quantityMatch.groups.quantity ? parseQuantityValue(quantityMatch.groups.quantity) : void 0;
|
|
2915
2958
|
const unit = quantityMatch.groups.unit;
|
|
2916
2959
|
if (value) {
|
|
2917
2960
|
const newQuantity = { quantity: value };
|
|
@@ -3123,6 +3166,10 @@ var _Recipe = class _Recipe {
|
|
|
3123
3166
|
if (itemQuantity) {
|
|
3124
3167
|
Object.assign(alternative, itemQuantity);
|
|
3125
3168
|
}
|
|
3169
|
+
const note = groups.gIngredientNote?.trim();
|
|
3170
|
+
if (note) {
|
|
3171
|
+
alternative.note = note;
|
|
3172
|
+
}
|
|
3126
3173
|
const existingSubgroups = this.choices.ingredientGroups.get(groupKey);
|
|
3127
3174
|
const existingAlternativesFlat = existingSubgroups?.flat();
|
|
3128
3175
|
function upsertAlternativeToIngredient(ingredients, ingredientIdx, newAlternativeIdx) {
|
|
@@ -3216,6 +3263,8 @@ var _Recipe = class _Recipe {
|
|
|
3216
3263
|
/** @internal */
|
|
3217
3264
|
collectQuantityGroups(options) {
|
|
3218
3265
|
const { section, step, choices } = options || {};
|
|
3266
|
+
const activeVariant = choices?.variant;
|
|
3267
|
+
const isDefaultVariant = activeVariant === void 0 || activeVariant === "*";
|
|
3219
3268
|
const sectionsToProcess = section !== void 0 ? (() => {
|
|
3220
3269
|
const idx = typeof section === "number" ? section : this.sections.indexOf(section);
|
|
3221
3270
|
return idx >= 0 && idx < this.sections.length ? [this.sections[idx]] : [];
|
|
@@ -3223,12 +3272,29 @@ var _Recipe = class _Recipe {
|
|
|
3223
3272
|
const ingredientGroups = /* @__PURE__ */ new Map();
|
|
3224
3273
|
const selectedIndices = /* @__PURE__ */ new Set();
|
|
3225
3274
|
const referencedIndices = /* @__PURE__ */ new Set();
|
|
3275
|
+
const dynamicOptionalIndices = /* @__PURE__ */ new Set();
|
|
3226
3276
|
for (const currentSection of sectionsToProcess) {
|
|
3277
|
+
if (currentSection.variants) {
|
|
3278
|
+
if (isDefaultVariant) {
|
|
3279
|
+
if (!currentSection.variants.includes("*")) continue;
|
|
3280
|
+
} else {
|
|
3281
|
+
if (!currentSection.variants.includes(activeVariant)) continue;
|
|
3282
|
+
}
|
|
3283
|
+
}
|
|
3227
3284
|
const allSteps = currentSection.content.filter(
|
|
3228
3285
|
(item) => item.type === "step"
|
|
3229
3286
|
);
|
|
3287
|
+
const isOptionalSection = currentSection.optional === true;
|
|
3230
3288
|
const stepsToProcess = step === void 0 ? allSteps : typeof step === "number" ? step >= 0 && step < allSteps.length ? [allSteps[step]] : [] : allSteps.includes(step) ? [step] : [];
|
|
3231
3289
|
for (const currentStep of stepsToProcess) {
|
|
3290
|
+
if (currentStep.variants) {
|
|
3291
|
+
if (isDefaultVariant) {
|
|
3292
|
+
if (!currentStep.variants.includes("*")) continue;
|
|
3293
|
+
} else {
|
|
3294
|
+
if (!currentStep.variants.includes(activeVariant)) continue;
|
|
3295
|
+
}
|
|
3296
|
+
}
|
|
3297
|
+
const isOptionalStep = currentStep.optional === true || isOptionalSection;
|
|
3232
3298
|
for (const item of currentStep.items.filter(
|
|
3233
3299
|
(item2) => item2.type === "ingredient"
|
|
3234
3300
|
)) {
|
|
@@ -3240,18 +3306,55 @@ var _Recipe = class _Recipe {
|
|
|
3240
3306
|
if (isGrouped) {
|
|
3241
3307
|
const groupChoice = choices?.ingredientGroups?.get(item.group);
|
|
3242
3308
|
hasExplicitChoice = groupChoice !== void 0;
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3309
|
+
if (!hasExplicitChoice && !isDefaultVariant) {
|
|
3310
|
+
const matchingSubgroupIdx = groupSubgroups?.findIndex(
|
|
3311
|
+
(sg) => sg.some(
|
|
3312
|
+
(alt) => alt.note && alt.note.toLowerCase().includes(activeVariant.toLowerCase())
|
|
3313
|
+
)
|
|
3314
|
+
);
|
|
3315
|
+
if (matchingSubgroupIdx !== void 0 && matchingSubgroupIdx >= 0) {
|
|
3316
|
+
const matchedSubgroup = groupSubgroups[matchingSubgroupIdx];
|
|
3317
|
+
isSelected = matchedSubgroup.some(
|
|
3318
|
+
(alt) => alt.itemId === item.id
|
|
3319
|
+
);
|
|
3320
|
+
hasExplicitChoice = true;
|
|
3321
|
+
selectedAltIndex = 0;
|
|
3322
|
+
} else {
|
|
3323
|
+
const targetSubgroupIndex = 0;
|
|
3324
|
+
const selectedSubgroup = groupSubgroups?.[targetSubgroupIndex];
|
|
3325
|
+
isSelected = selectedSubgroup.some(
|
|
3326
|
+
(alt) => alt.itemId === item.id
|
|
3327
|
+
);
|
|
3328
|
+
}
|
|
3329
|
+
} else {
|
|
3330
|
+
const targetSubgroupIndex = groupChoice ?? 0;
|
|
3331
|
+
const selectedSubgroup = groupSubgroups?.[targetSubgroupIndex];
|
|
3332
|
+
isSelected = selectedSubgroup?.some((alt) => alt.itemId === item.id) ?? false;
|
|
3333
|
+
}
|
|
3246
3334
|
} else {
|
|
3247
3335
|
const itemChoice = choices?.ingredientItems?.get(item.id);
|
|
3248
3336
|
hasExplicitChoice = itemChoice !== void 0;
|
|
3249
|
-
|
|
3337
|
+
if (!hasExplicitChoice && !isDefaultVariant) {
|
|
3338
|
+
const matchingIndices = item.alternatives.map((alt, idx) => ({ alt, idx })).filter(
|
|
3339
|
+
({ alt }) => alt.note && alt.note.toLowerCase().includes(activeVariant.toLowerCase())
|
|
3340
|
+
).map(({ idx }) => idx);
|
|
3341
|
+
if (matchingIndices.length > 0) {
|
|
3342
|
+
selectedAltIndex = matchingIndices[0];
|
|
3343
|
+
hasExplicitChoice = true;
|
|
3344
|
+
} else {
|
|
3345
|
+
selectedAltIndex = itemChoice ?? 0;
|
|
3346
|
+
}
|
|
3347
|
+
} else {
|
|
3348
|
+
selectedAltIndex = itemChoice ?? 0;
|
|
3349
|
+
}
|
|
3250
3350
|
isSelected = true;
|
|
3251
3351
|
}
|
|
3252
3352
|
const alternative = item.alternatives[selectedAltIndex];
|
|
3253
3353
|
if (!alternative || !isSelected) continue;
|
|
3254
3354
|
selectedIndices.add(alternative.index);
|
|
3355
|
+
if (isOptionalStep) {
|
|
3356
|
+
dynamicOptionalIndices.add(alternative.index);
|
|
3357
|
+
}
|
|
3255
3358
|
const allAltsFlat = isGrouped ? groupSubgroups.flat() : item.alternatives;
|
|
3256
3359
|
for (const alt of allAltsFlat) {
|
|
3257
3360
|
referencedIndices.add(alt.index);
|
|
@@ -3357,7 +3460,12 @@ var _Recipe = class _Recipe {
|
|
|
3357
3460
|
}
|
|
3358
3461
|
}
|
|
3359
3462
|
}
|
|
3360
|
-
return {
|
|
3463
|
+
return {
|
|
3464
|
+
ingredientGroups,
|
|
3465
|
+
selectedIndices,
|
|
3466
|
+
referencedIndices,
|
|
3467
|
+
dynamicOptionalIndices
|
|
3468
|
+
};
|
|
3361
3469
|
}
|
|
3362
3470
|
/**
|
|
3363
3471
|
* Gets the raw (unprocessed) quantity groups for each ingredient, before
|
|
@@ -3376,12 +3484,21 @@ var _Recipe = class _Recipe {
|
|
|
3376
3484
|
* ```
|
|
3377
3485
|
*/
|
|
3378
3486
|
getRawQuantityGroups(options) {
|
|
3379
|
-
const {
|
|
3487
|
+
const {
|
|
3488
|
+
ingredientGroups,
|
|
3489
|
+
selectedIndices,
|
|
3490
|
+
referencedIndices,
|
|
3491
|
+
dynamicOptionalIndices
|
|
3492
|
+
} = this.collectQuantityGroups(options);
|
|
3380
3493
|
const result = [];
|
|
3381
3494
|
for (let index = 0; index < this.ingredients.length; index++) {
|
|
3382
3495
|
if (!referencedIndices.has(index)) continue;
|
|
3383
3496
|
const orig = this.ingredients[index];
|
|
3384
3497
|
const usedAsPrimary = selectedIndices.has(index);
|
|
3498
|
+
let flags = orig.flags;
|
|
3499
|
+
if (dynamicOptionalIndices.has(index) && !flags?.includes("optional")) {
|
|
3500
|
+
flags = [...flags ?? [], "optional"];
|
|
3501
|
+
}
|
|
3385
3502
|
const quantities = [];
|
|
3386
3503
|
if (usedAsPrimary) {
|
|
3387
3504
|
const groupsForIng = ingredientGroups.get(index);
|
|
@@ -3394,7 +3511,7 @@ var _Recipe = class _Recipe {
|
|
|
3394
3511
|
result.push({
|
|
3395
3512
|
name: orig.name,
|
|
3396
3513
|
...usedAsPrimary && { usedAsPrimary: true },
|
|
3397
|
-
...
|
|
3514
|
+
...flags && { flags },
|
|
3398
3515
|
quantities
|
|
3399
3516
|
});
|
|
3400
3517
|
}
|
|
@@ -3428,15 +3545,24 @@ var _Recipe = class _Recipe {
|
|
|
3428
3545
|
* ```
|
|
3429
3546
|
*/
|
|
3430
3547
|
getIngredientQuantities(options) {
|
|
3431
|
-
const {
|
|
3548
|
+
const {
|
|
3549
|
+
ingredientGroups,
|
|
3550
|
+
selectedIndices,
|
|
3551
|
+
referencedIndices,
|
|
3552
|
+
dynamicOptionalIndices
|
|
3553
|
+
} = this.collectQuantityGroups(options);
|
|
3432
3554
|
const result = [];
|
|
3433
3555
|
for (let index = 0; index < this.ingredients.length; index++) {
|
|
3434
3556
|
if (!referencedIndices.has(index)) continue;
|
|
3435
3557
|
const orig = this.ingredients[index];
|
|
3558
|
+
let flags = orig.flags;
|
|
3559
|
+
if (dynamicOptionalIndices.has(index) && !flags?.includes("optional")) {
|
|
3560
|
+
flags = [...flags ?? [], "optional"];
|
|
3561
|
+
}
|
|
3436
3562
|
const ing = {
|
|
3437
3563
|
name: orig.name,
|
|
3438
3564
|
...orig.preparation && { preparation: orig.preparation },
|
|
3439
|
-
...
|
|
3565
|
+
...flags && { flags },
|
|
3440
3566
|
...orig.extras && { extras: orig.extras }
|
|
3441
3567
|
};
|
|
3442
3568
|
if (selectedIndices.has(index)) {
|
|
@@ -3487,6 +3613,59 @@ var _Recipe = class _Recipe {
|
|
|
3487
3613
|
}
|
|
3488
3614
|
return result;
|
|
3489
3615
|
}
|
|
3616
|
+
/**
|
|
3617
|
+
* Returns the list of cookware items that are used in the active variant.
|
|
3618
|
+
* Cookware in steps/sections not matching the active variant are excluded.
|
|
3619
|
+
* Hidden cookware is always excluded.
|
|
3620
|
+
*
|
|
3621
|
+
* @param options - Options for filtering:
|
|
3622
|
+
* - `choices`: The choices to apply (only `variant` is used)
|
|
3623
|
+
* @returns Array of Cookware objects referenced by active steps
|
|
3624
|
+
*
|
|
3625
|
+
* @example
|
|
3626
|
+
* ```typescript
|
|
3627
|
+
* // Get all cookware for the default variant
|
|
3628
|
+
* const cookware = recipe.getCookwareForVariant();
|
|
3629
|
+
*
|
|
3630
|
+
* // Get cookware for a specific variant
|
|
3631
|
+
* const veganCookware = recipe.getCookwareForVariant({ choices: { variant: 'vegan' } });
|
|
3632
|
+
* ```
|
|
3633
|
+
*/
|
|
3634
|
+
getCookwareForVariant(options) {
|
|
3635
|
+
const { choices } = options || {};
|
|
3636
|
+
const activeVariant = choices?.variant;
|
|
3637
|
+
const isDefaultVariant = activeVariant === void 0 || activeVariant === "*";
|
|
3638
|
+
const cookwareIndices = /* @__PURE__ */ new Set();
|
|
3639
|
+
for (const currentSection of this.sections) {
|
|
3640
|
+
if (currentSection.variants) {
|
|
3641
|
+
if (isDefaultVariant) {
|
|
3642
|
+
if (!currentSection.variants.includes("*")) continue;
|
|
3643
|
+
} else {
|
|
3644
|
+
if (!currentSection.variants.includes(activeVariant)) continue;
|
|
3645
|
+
}
|
|
3646
|
+
}
|
|
3647
|
+
const allSteps = currentSection.content.filter(
|
|
3648
|
+
(item) => item.type === "step"
|
|
3649
|
+
);
|
|
3650
|
+
for (const currentStep of allSteps) {
|
|
3651
|
+
if (currentStep.variants) {
|
|
3652
|
+
if (isDefaultVariant) {
|
|
3653
|
+
if (!currentStep.variants.includes("*")) continue;
|
|
3654
|
+
} else {
|
|
3655
|
+
if (!currentStep.variants.includes(activeVariant)) continue;
|
|
3656
|
+
}
|
|
3657
|
+
}
|
|
3658
|
+
for (const item of currentStep.items) {
|
|
3659
|
+
if (item.type === "cookware") {
|
|
3660
|
+
cookwareIndices.add(item.index);
|
|
3661
|
+
}
|
|
3662
|
+
}
|
|
3663
|
+
}
|
|
3664
|
+
}
|
|
3665
|
+
return this.cookware.filter(
|
|
3666
|
+
(cw, idx) => cookwareIndices.has(idx) && !cw.flags?.includes("hidden")
|
|
3667
|
+
);
|
|
3668
|
+
}
|
|
3490
3669
|
/**
|
|
3491
3670
|
* Parses a recipe from a string.
|
|
3492
3671
|
* @param content - The recipe content to parse.
|
|
@@ -3502,9 +3681,14 @@ var _Recipe = class _Recipe {
|
|
|
3502
3681
|
const items = [];
|
|
3503
3682
|
let noteText = "";
|
|
3504
3683
|
let inNote = false;
|
|
3684
|
+
let stepVariants;
|
|
3685
|
+
let stepOptional;
|
|
3686
|
+
const discoveredVariants = /* @__PURE__ */ new Set();
|
|
3505
3687
|
for (const line of cleanContent) {
|
|
3506
3688
|
if (line.trim().length === 0) {
|
|
3507
|
-
flushPendingItems(section, items);
|
|
3689
|
+
flushPendingItems(section, items, stepVariants, stepOptional);
|
|
3690
|
+
stepVariants = void 0;
|
|
3691
|
+
stepOptional = void 0;
|
|
3508
3692
|
flushPendingNote(
|
|
3509
3693
|
section,
|
|
3510
3694
|
noteText ? this._parseNoteText(noteText) : []
|
|
@@ -3515,26 +3699,42 @@ var _Recipe = class _Recipe {
|
|
|
3515
3699
|
continue;
|
|
3516
3700
|
}
|
|
3517
3701
|
if (line.startsWith("=")) {
|
|
3518
|
-
flushPendingItems(section, items);
|
|
3702
|
+
flushPendingItems(section, items, stepVariants, stepOptional);
|
|
3703
|
+
stepVariants = void 0;
|
|
3704
|
+
stepOptional = void 0;
|
|
3519
3705
|
flushPendingNote(
|
|
3520
3706
|
section,
|
|
3521
3707
|
noteText ? this._parseNoteText(noteText) : []
|
|
3522
3708
|
);
|
|
3523
3709
|
noteText = "";
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3710
|
+
let sectionName = line.replace(/^=+|=+$/g, "").trim();
|
|
3711
|
+
let sectionVariants;
|
|
3712
|
+
let sectionOptional;
|
|
3713
|
+
const sectionVarMatch = sectionName.match(variantTagRegex);
|
|
3714
|
+
if (sectionVarMatch?.groups) {
|
|
3715
|
+
const isOptionalPrefix = sectionVarMatch.groups.variantOptionalPrefix === "?";
|
|
3716
|
+
const names = (sectionVarMatch.groups.variantNames ?? "").split(",").map((n2) => n2.trim()).filter((n2) => n2.length > 0);
|
|
3717
|
+
if (names.length > 0) {
|
|
3718
|
+
sectionVariants = names;
|
|
3719
|
+
for (const v of names) discoveredVariants.add(v);
|
|
3720
|
+
}
|
|
3721
|
+
if (isOptionalPrefix) {
|
|
3722
|
+
sectionOptional = true;
|
|
3529
3723
|
}
|
|
3530
|
-
|
|
3724
|
+
sectionName = sectionName.slice(sectionVarMatch[0].length).trim();
|
|
3531
3725
|
}
|
|
3726
|
+
if (!section.isBlank()) {
|
|
3727
|
+
this.sections.push(section);
|
|
3728
|
+
}
|
|
3729
|
+
section = new Section(sectionName, sectionVariants, sectionOptional);
|
|
3532
3730
|
blankLineBefore = true;
|
|
3533
3731
|
inNote = false;
|
|
3534
3732
|
continue;
|
|
3535
3733
|
}
|
|
3536
3734
|
if (blankLineBefore && line.startsWith(">")) {
|
|
3537
|
-
flushPendingItems(section, items);
|
|
3735
|
+
flushPendingItems(section, items, stepVariants, stepOptional);
|
|
3736
|
+
stepVariants = void 0;
|
|
3737
|
+
stepOptional = void 0;
|
|
3538
3738
|
noteText = line.substring(1).trim();
|
|
3539
3739
|
inNote = true;
|
|
3540
3740
|
blankLineBefore = false;
|
|
@@ -3549,11 +3749,31 @@ var _Recipe = class _Recipe {
|
|
|
3549
3749
|
blankLineBefore = false;
|
|
3550
3750
|
continue;
|
|
3551
3751
|
}
|
|
3752
|
+
let currentLine = line;
|
|
3753
|
+
if (items.length === 0) {
|
|
3754
|
+
const varMatch = currentLine.match(variantTagRegex);
|
|
3755
|
+
if (varMatch?.groups) {
|
|
3756
|
+
const isOptionalPrefix = varMatch.groups.variantOptionalPrefix === "?";
|
|
3757
|
+
const names = (varMatch.groups.variantNames ?? "").split(",").map((n2) => n2.trim()).filter((n2) => n2.length > 0);
|
|
3758
|
+
if (names.length > 0) {
|
|
3759
|
+
stepVariants = names;
|
|
3760
|
+
for (const v of names) discoveredVariants.add(v);
|
|
3761
|
+
}
|
|
3762
|
+
if (isOptionalPrefix) {
|
|
3763
|
+
stepOptional = true;
|
|
3764
|
+
}
|
|
3765
|
+
currentLine = currentLine.slice(varMatch[0].length);
|
|
3766
|
+
if (currentLine.trim().length === 0) {
|
|
3767
|
+
blankLineBefore = false;
|
|
3768
|
+
continue;
|
|
3769
|
+
}
|
|
3770
|
+
}
|
|
3771
|
+
}
|
|
3552
3772
|
let cursor = 0;
|
|
3553
|
-
for (const match of
|
|
3773
|
+
for (const match of currentLine.matchAll(tokensRegex)) {
|
|
3554
3774
|
const idx = match.index;
|
|
3555
3775
|
if (idx > cursor) {
|
|
3556
|
-
items.push(...parseMarkdownSegments(
|
|
3776
|
+
items.push(...parseMarkdownSegments(currentLine.slice(cursor, idx)));
|
|
3557
3777
|
}
|
|
3558
3778
|
const groups = match.groups;
|
|
3559
3779
|
if (groups.mIngredientName || groups.sIngredientName) {
|
|
@@ -3572,7 +3792,7 @@ var _Recipe = class _Recipe {
|
|
|
3572
3792
|
if (modifiers !== void 0 && modifiers.includes("-")) {
|
|
3573
3793
|
flags.push("hidden");
|
|
3574
3794
|
}
|
|
3575
|
-
const quantity = quantityRaw ?
|
|
3795
|
+
const quantity = quantityRaw ? parseQuantityValue(quantityRaw) : void 0;
|
|
3576
3796
|
const newCookware = {
|
|
3577
3797
|
name
|
|
3578
3798
|
};
|
|
@@ -3604,7 +3824,7 @@ var _Recipe = class _Recipe {
|
|
|
3604
3824
|
throw new Error("Timer missing unit");
|
|
3605
3825
|
}
|
|
3606
3826
|
const name = groups.timerName || void 0;
|
|
3607
|
-
const duration =
|
|
3827
|
+
const duration = parseQuantityValue(durationStr);
|
|
3608
3828
|
const timerObj = {
|
|
3609
3829
|
name,
|
|
3610
3830
|
duration,
|
|
@@ -3614,16 +3834,21 @@ var _Recipe = class _Recipe {
|
|
|
3614
3834
|
}
|
|
3615
3835
|
cursor = idx + match[0].length;
|
|
3616
3836
|
}
|
|
3617
|
-
if (cursor <
|
|
3618
|
-
items.push(...parseMarkdownSegments(
|
|
3837
|
+
if (cursor < currentLine.length) {
|
|
3838
|
+
items.push(...parseMarkdownSegments(currentLine.slice(cursor)));
|
|
3619
3839
|
}
|
|
3620
3840
|
blankLineBefore = false;
|
|
3621
3841
|
}
|
|
3622
|
-
flushPendingItems(section, items);
|
|
3842
|
+
flushPendingItems(section, items, stepVariants, stepOptional);
|
|
3623
3843
|
flushPendingNote(section, noteText ? this._parseNoteText(noteText) : []);
|
|
3624
3844
|
if (!section.isBlank()) {
|
|
3625
3845
|
this.sections.push(section);
|
|
3626
3846
|
}
|
|
3847
|
+
const metaVariants = this.metadata.variants ?? [];
|
|
3848
|
+
const allVariants = /* @__PURE__ */ new Set([...metaVariants, ...discoveredVariants]);
|
|
3849
|
+
if (allVariants.size > 0) {
|
|
3850
|
+
this.choices.variants = [...allVariants];
|
|
3851
|
+
}
|
|
3627
3852
|
this._populateIngredientQuantities();
|
|
3628
3853
|
}
|
|
3629
3854
|
/**
|
|
@@ -3732,9 +3957,15 @@ var _Recipe = class _Recipe {
|
|
|
3732
3957
|
}
|
|
3733
3958
|
newRecipe._populateIngredientQuantities();
|
|
3734
3959
|
newRecipe.servings = (0, import_big4.default)(originalServings).times(factor).toNumber();
|
|
3735
|
-
for (const metaVar of ["servings", "
|
|
3736
|
-
if (newRecipe.metadata[metaVar]
|
|
3737
|
-
|
|
3960
|
+
for (const metaVar of ["servings", "serves"]) {
|
|
3961
|
+
if (typeof newRecipe.metadata[metaVar] === "number") {
|
|
3962
|
+
newRecipe.metadata[metaVar] = (0, import_big4.default)(newRecipe.metadata[metaVar]).times(factor).toNumber();
|
|
3963
|
+
}
|
|
3964
|
+
}
|
|
3965
|
+
if (newRecipe.metadata.yield && this.metadata.yield) {
|
|
3966
|
+
const original = this.metadata.yield;
|
|
3967
|
+
if (original.quantity.type === "fixed" && original.quantity.value.type === "text") {
|
|
3968
|
+
} else {
|
|
3738
3969
|
const scaledQuantity = multiplyQuantityValue(
|
|
3739
3970
|
original.quantity,
|
|
3740
3971
|
factor
|
|
@@ -3749,8 +3980,7 @@ var _Recipe = class _Recipe {
|
|
|
3749
3980
|
if (optimized.unit) scaled.unit = optimized.unit;
|
|
3750
3981
|
if (original.textBefore) scaled.textBefore = original.textBefore;
|
|
3751
3982
|
if (original.textAfter) scaled.textAfter = original.textAfter;
|
|
3752
|
-
|
|
3753
|
-
newRecipe.metadata[metaVar] = scaled;
|
|
3983
|
+
newRecipe.metadata.yield = scaled;
|
|
3754
3984
|
}
|
|
3755
3985
|
}
|
|
3756
3986
|
return newRecipe;
|
|
@@ -3934,7 +4164,11 @@ var _Recipe = class _Recipe {
|
|
|
3934
4164
|
newRecipe.metadata = deepClone(this.metadata);
|
|
3935
4165
|
newRecipe.ingredients = deepClone(this.ingredients);
|
|
3936
4166
|
newRecipe.sections = this.sections.map((section) => {
|
|
3937
|
-
const newSection = new Section(
|
|
4167
|
+
const newSection = new Section(
|
|
4168
|
+
section.name,
|
|
4169
|
+
section.variants,
|
|
4170
|
+
section.optional
|
|
4171
|
+
);
|
|
3938
4172
|
newSection.content = deepClone(section.content);
|
|
3939
4173
|
return newSection;
|
|
3940
4174
|
});
|
|
@@ -3963,7 +4197,7 @@ __publicField(_Recipe, "subgroupIndices", /* @__PURE__ */ new WeakMap());
|
|
|
3963
4197
|
var Recipe = _Recipe;
|
|
3964
4198
|
|
|
3965
4199
|
// src/classes/shopping_list.ts
|
|
3966
|
-
var ShoppingList = class
|
|
4200
|
+
var ShoppingList = class {
|
|
3967
4201
|
/**
|
|
3968
4202
|
* Creates a new ShoppingList instance
|
|
3969
4203
|
* @param categoryConfigStr - The category configuration to parse.
|
|
@@ -4058,7 +4292,7 @@ var ShoppingList = class _ShoppingList {
|
|
|
4058
4292
|
}
|
|
4059
4293
|
}
|
|
4060
4294
|
if (numericEntries.length > 1) {
|
|
4061
|
-
const ratioMap =
|
|
4295
|
+
const ratioMap = buildEquivalenceRatioMap(
|
|
4062
4296
|
getEquivalentUnitsLists(...numericEntries)
|
|
4063
4297
|
);
|
|
4064
4298
|
if (Object.keys(ratioMap).length > 0) {
|
|
@@ -4115,10 +4349,12 @@ var ShoppingList = class _ShoppingList {
|
|
|
4115
4349
|
const pantryHasUnit = pantryExtended.unit !== void 0 && pantryExtended.unit.name !== "";
|
|
4116
4350
|
const ratioMap = this.equivalenceRatios.get(ingredient.name);
|
|
4117
4351
|
const unitMismatch = leafHasUnit !== pantryHasUnit && ratioMap !== void 0;
|
|
4352
|
+
const leafDef = normalizeUnit(leaf.unit);
|
|
4353
|
+
const pantryDef = normalizeUnit(pantryExtended.unit?.name);
|
|
4118
4354
|
if (unitMismatch) {
|
|
4119
4355
|
const leafUnit = leaf.unit ?? NO_UNIT;
|
|
4120
4356
|
const pantryUnit = pantryExtended.unit?.name ?? NO_UNIT;
|
|
4121
|
-
const ratioFromPantry = ratioMap[leafUnit]?.[pantryUnit];
|
|
4357
|
+
const ratioFromPantry = ratioMap[normalizeUnit(leafUnit)?.name ?? leafUnit]?.[normalizeUnit(pantryUnit)?.name ?? pantryUnit];
|
|
4122
4358
|
if (ratioFromPantry !== void 0) {
|
|
4123
4359
|
const pantryValue = getAverageValue(pantryExtended.quantity);
|
|
4124
4360
|
const leafValue = getAverageValue(ingredientExtended.quantity);
|
|
@@ -4133,7 +4369,7 @@ var ShoppingList = class _ShoppingList {
|
|
|
4133
4369
|
type: "fixed",
|
|
4134
4370
|
value: { type: "decimal", decimal: remainingLeafValue }
|
|
4135
4371
|
};
|
|
4136
|
-
const consumedInPantryUnits =
|
|
4372
|
+
const consumedInPantryUnits = subtracted / ratioFromPantry;
|
|
4137
4373
|
const remainingPantryValue = Math.max(
|
|
4138
4374
|
pantryValue - consumedInPantryUnits,
|
|
4139
4375
|
0
|
|
@@ -4150,9 +4386,10 @@ var ShoppingList = class _ShoppingList {
|
|
|
4150
4386
|
};
|
|
4151
4387
|
continue;
|
|
4152
4388
|
}
|
|
4389
|
+
} else {
|
|
4390
|
+
continue;
|
|
4153
4391
|
}
|
|
4154
|
-
}
|
|
4155
|
-
try {
|
|
4392
|
+
} else if (leafDef && pantryDef && areUnitsConvertible(leafDef, pantryDef) || (leaf.unit ?? "").toLowerCase() === (pantryExtended.unit?.name ?? "").toLowerCase()) {
|
|
4156
4393
|
const remaining = subtractQuantities(
|
|
4157
4394
|
ingredientExtended,
|
|
4158
4395
|
pantryExtended,
|
|
@@ -4167,7 +4404,47 @@ var ShoppingList = class _ShoppingList {
|
|
|
4167
4404
|
const updated = toPlainUnit(remaining);
|
|
4168
4405
|
leaf.quantity = updated.quantity;
|
|
4169
4406
|
leaf.unit = updated.unit;
|
|
4170
|
-
}
|
|
4407
|
+
} else if (ratioMap) {
|
|
4408
|
+
const canonicalLeaf = normalizeUnit(leaf.unit)?.name ?? leaf.unit;
|
|
4409
|
+
const leafValue = getAverageValue(ingredientExtended.quantity);
|
|
4410
|
+
const pantryValue = getAverageValue(pantryExtended.quantity);
|
|
4411
|
+
if (typeof leafValue === "number" && typeof pantryValue === "number" && pantryDef) {
|
|
4412
|
+
for (const [equivUnit, ratios] of Object.entries(ratioMap)) {
|
|
4413
|
+
const ratio = ratios[canonicalLeaf];
|
|
4414
|
+
if (ratio === void 0) continue;
|
|
4415
|
+
const equivDef = normalizeUnit(equivUnit);
|
|
4416
|
+
if (!equivDef || !areUnitsConvertible(equivDef, pantryDef))
|
|
4417
|
+
continue;
|
|
4418
|
+
const pantryInEquiv = pantryValue * getToBase(pantryDef) / getToBase(equivDef);
|
|
4419
|
+
const pantryInLeafUnits = pantryInEquiv / ratio;
|
|
4420
|
+
const subtracted = Math.min(pantryInLeafUnits, leafValue);
|
|
4421
|
+
const remainingLeafValue = Math.max(
|
|
4422
|
+
leafValue - pantryInLeafUnits,
|
|
4423
|
+
0
|
|
4424
|
+
);
|
|
4425
|
+
leaf.quantity = {
|
|
4426
|
+
type: "fixed",
|
|
4427
|
+
value: { type: "decimal", decimal: remainingLeafValue }
|
|
4428
|
+
};
|
|
4429
|
+
const consumedInEquiv = subtracted * ratio;
|
|
4430
|
+
const consumedInPantryUnits = consumedInEquiv * getToBase(equivDef) / getToBase(pantryDef);
|
|
4431
|
+
const remainingPantryValue = Math.max(
|
|
4432
|
+
pantryValue - consumedInPantryUnits,
|
|
4433
|
+
0
|
|
4434
|
+
);
|
|
4435
|
+
pantryExtended = {
|
|
4436
|
+
quantity: {
|
|
4437
|
+
type: "fixed",
|
|
4438
|
+
value: {
|
|
4439
|
+
type: "decimal",
|
|
4440
|
+
decimal: remainingPantryValue
|
|
4441
|
+
}
|
|
4442
|
+
},
|
|
4443
|
+
...pantryExtended.unit && { unit: pantryExtended.unit }
|
|
4444
|
+
};
|
|
4445
|
+
break;
|
|
4446
|
+
}
|
|
4447
|
+
}
|
|
4171
4448
|
}
|
|
4172
4449
|
}
|
|
4173
4450
|
if ("and" in entry) {
|
|
@@ -4178,8 +4455,8 @@ var ShoppingList = class _ShoppingList {
|
|
|
4178
4455
|
entry.and.push(...nonZero);
|
|
4179
4456
|
const ratioMap = this.equivalenceRatios.get(ingredient.name);
|
|
4180
4457
|
if (entry.equivalents && ratioMap) {
|
|
4181
|
-
const equivUnits = entry.equivalents.map((e2) => e2.unit
|
|
4182
|
-
entry.equivalents =
|
|
4458
|
+
const equivUnits = entry.equivalents.map((e2) => e2.unit);
|
|
4459
|
+
entry.equivalents = recomputeEquivalents(
|
|
4183
4460
|
entry.and,
|
|
4184
4461
|
ratioMap,
|
|
4185
4462
|
equivUnits
|
|
@@ -4190,17 +4467,17 @@ var ShoppingList = class _ShoppingList {
|
|
|
4190
4467
|
ingredient.quantities[i2] = {
|
|
4191
4468
|
quantity: single.quantity,
|
|
4192
4469
|
...single.unit && { unit: single.unit },
|
|
4193
|
-
...entry.equivalents && { equivalents: entry.equivalents }
|
|
4194
|
-
...entry.alternatives && { alternatives: entry.alternatives }
|
|
4470
|
+
...entry.equivalents && { equivalents: entry.equivalents }
|
|
4195
4471
|
};
|
|
4196
4472
|
}
|
|
4197
4473
|
} else if ("equivalents" in entry && entry.equivalents) {
|
|
4198
4474
|
const ratioMap = this.equivalenceRatios.get(ingredient.name);
|
|
4199
4475
|
if (ratioMap) {
|
|
4200
4476
|
const equivUnits = entry.equivalents.map(
|
|
4201
|
-
(e2) => e2.unit
|
|
4477
|
+
(e2) => e2.unit
|
|
4478
|
+
// equivalents always have units
|
|
4202
4479
|
);
|
|
4203
|
-
const recomputed =
|
|
4480
|
+
const recomputed = recomputeEquivalents(
|
|
4204
4481
|
[entry],
|
|
4205
4482
|
ratioMap,
|
|
4206
4483
|
equivUnits
|
|
@@ -4223,57 +4500,6 @@ var ShoppingList = class _ShoppingList {
|
|
|
4223
4500
|
}
|
|
4224
4501
|
this.resultingPantry = clonedPantry;
|
|
4225
4502
|
}
|
|
4226
|
-
/**
|
|
4227
|
-
* Builds a ratio map from equivalence lists.
|
|
4228
|
-
* For each equivalence list, stores ratio = equiv_value / primary_value
|
|
4229
|
-
* for every pair of units, so equivalents can be recomputed after
|
|
4230
|
-
* pantry subtraction modifies primary quantities.
|
|
4231
|
-
*/
|
|
4232
|
-
static buildEquivalenceRatioMap(unitsLists) {
|
|
4233
|
-
const ratioMap = {};
|
|
4234
|
-
for (const list of unitsLists) {
|
|
4235
|
-
for (const equiv of list) {
|
|
4236
|
-
const equivValue = getAverageValue(equiv.quantity);
|
|
4237
|
-
for (const primary of list) {
|
|
4238
|
-
if (primary === equiv) continue;
|
|
4239
|
-
const primaryValue = getAverageValue(primary.quantity);
|
|
4240
|
-
const equivUnit = equiv.unit.name;
|
|
4241
|
-
const primaryUnit = primary.unit.name;
|
|
4242
|
-
ratioMap[equivUnit] ?? (ratioMap[equivUnit] = {});
|
|
4243
|
-
ratioMap[equivUnit][primaryUnit] = equivValue / primaryValue;
|
|
4244
|
-
}
|
|
4245
|
-
}
|
|
4246
|
-
}
|
|
4247
|
-
return ratioMap;
|
|
4248
|
-
}
|
|
4249
|
-
/**
|
|
4250
|
-
* Recomputes equivalent quantities from current primary values and stored ratios.
|
|
4251
|
-
* For each equivalent unit in equivUnits, new_value = Σ (primary_value × ratio[equivUnit][primaryUnit]).
|
|
4252
|
-
* Returns undefined if all equivalents compute to zero.
|
|
4253
|
-
*/
|
|
4254
|
-
static recomputeEquivalents(primaries, ratioMap, equivUnits) {
|
|
4255
|
-
const equivalents = [];
|
|
4256
|
-
for (const equivUnit of equivUnits) {
|
|
4257
|
-
const ratios = ratioMap[equivUnit];
|
|
4258
|
-
let total = 0;
|
|
4259
|
-
for (const primary of primaries) {
|
|
4260
|
-
const pUnit = primary.unit ?? NO_UNIT;
|
|
4261
|
-
const ratio = ratios[pUnit];
|
|
4262
|
-
const pValue = getAverageValue(primary.quantity);
|
|
4263
|
-
total += pValue * ratio;
|
|
4264
|
-
}
|
|
4265
|
-
if (total > 0) {
|
|
4266
|
-
equivalents.push({
|
|
4267
|
-
quantity: {
|
|
4268
|
-
type: "fixed",
|
|
4269
|
-
value: { type: "decimal", decimal: total }
|
|
4270
|
-
},
|
|
4271
|
-
...equivUnit !== "" && { unit: equivUnit }
|
|
4272
|
-
});
|
|
4273
|
-
}
|
|
4274
|
-
}
|
|
4275
|
-
return equivalents.length > 0 ? equivalents : void 0;
|
|
4276
|
-
}
|
|
4277
4503
|
/**
|
|
4278
4504
|
* Adds a recipe to the shopping list, then automatically
|
|
4279
4505
|
* recalculates the quantities and recategorize the ingredients.
|
|
@@ -4807,13 +5033,53 @@ function isAlternativeSelected(recipe, choices, item, alternativeIndex) {
|
|
|
4807
5033
|
const selectedIndex = choices?.ingredientItems?.get(item.id);
|
|
4808
5034
|
return alternativeIndex === selectedIndex;
|
|
4809
5035
|
}
|
|
5036
|
+
function isSectionActive(section, variant) {
|
|
5037
|
+
if (!section.variants) return true;
|
|
5038
|
+
const isDefault = variant === void 0 || variant === "*";
|
|
5039
|
+
if (isDefault) {
|
|
5040
|
+
return section.variants.includes("*");
|
|
5041
|
+
}
|
|
5042
|
+
return section.variants.includes(variant);
|
|
5043
|
+
}
|
|
5044
|
+
function isStepActive(step, variant) {
|
|
5045
|
+
if (!step.variants) return true;
|
|
5046
|
+
const isDefault = variant === void 0 || variant === "*";
|
|
5047
|
+
if (isDefault) {
|
|
5048
|
+
return step.variants.includes("*");
|
|
5049
|
+
}
|
|
5050
|
+
return step.variants.includes(variant);
|
|
5051
|
+
}
|
|
5052
|
+
function getEffectiveChoices(recipe, variant) {
|
|
5053
|
+
const choices = { variant };
|
|
5054
|
+
if (variant === void 0 || variant === "*") return choices;
|
|
5055
|
+
const variantLower = variant.toLowerCase();
|
|
5056
|
+
for (const [itemId, alternatives] of recipe.choices.ingredientItems) {
|
|
5057
|
+
const matchIdx = alternatives.findIndex(
|
|
5058
|
+
(alt) => alt.note && alt.note.toLowerCase().includes(variantLower)
|
|
5059
|
+
);
|
|
5060
|
+
if (matchIdx >= 0) {
|
|
5061
|
+
if (!choices.ingredientItems) choices.ingredientItems = /* @__PURE__ */ new Map();
|
|
5062
|
+
choices.ingredientItems.set(itemId, matchIdx);
|
|
5063
|
+
}
|
|
5064
|
+
}
|
|
5065
|
+
for (const [groupId, subgroups] of recipe.choices.ingredientGroups) {
|
|
5066
|
+
const matchIdx = subgroups.findIndex(
|
|
5067
|
+
(sg) => sg.some(
|
|
5068
|
+
(alt) => alt.note && alt.note.toLowerCase().includes(variantLower)
|
|
5069
|
+
)
|
|
5070
|
+
);
|
|
5071
|
+
if (matchIdx >= 0) {
|
|
5072
|
+
if (!choices.ingredientGroups) choices.ingredientGroups = /* @__PURE__ */ new Map();
|
|
5073
|
+
choices.ingredientGroups.set(groupId, matchIdx);
|
|
5074
|
+
}
|
|
5075
|
+
}
|
|
5076
|
+
return choices;
|
|
5077
|
+
}
|
|
4810
5078
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4811
5079
|
0 && (module.exports = {
|
|
4812
|
-
BadIndentationError,
|
|
4813
5080
|
CategoryConfig,
|
|
4814
5081
|
NoProductCatalogForCartError,
|
|
4815
5082
|
NoShoppingListForCartError,
|
|
4816
|
-
NoTabAsIndentError,
|
|
4817
5083
|
Pantry,
|
|
4818
5084
|
ProductCatalog,
|
|
4819
5085
|
Recipe,
|
|
@@ -4828,11 +5094,14 @@ function isAlternativeSelected(recipe, choices, item, alternativeIndex) {
|
|
|
4828
5094
|
formatQuantityWithUnit,
|
|
4829
5095
|
formatSingleValue,
|
|
4830
5096
|
formatUnit,
|
|
5097
|
+
getEffectiveChoices,
|
|
4831
5098
|
hasAlternatives,
|
|
4832
5099
|
isAlternativeSelected,
|
|
4833
5100
|
isAndGroup,
|
|
4834
5101
|
isGroupedItem,
|
|
5102
|
+
isSectionActive,
|
|
4835
5103
|
isSimpleGroup,
|
|
5104
|
+
isStepActive,
|
|
4836
5105
|
renderFractionAsVulgar
|
|
4837
5106
|
});
|
|
4838
5107
|
/* v8 ignore else -- @preserve */
|
|
@@ -4845,6 +5114,9 @@ function isAlternativeSelected(recipe, choices, item, alternativeIndex) {
|
|
|
4845
5114
|
// v8 ignore if -- @preserve: defensive type guard
|
|
4846
5115
|
/* v8 ignore if -- @preserve */
|
|
4847
5116
|
// v8 ignore next -- @preserve
|
|
5117
|
+
// v8 ignore else -- @preserve: text quantities never reach the equivalence path
|
|
4848
5118
|
// v8 ignore else --@preserve: defensive type guard
|
|
4849
5119
|
// v8 ignore else -- @preserve: detection if
|
|
5120
|
+
/* v8 ignore else -- @preserve: only act when there are matches */
|
|
5121
|
+
/* v8 ignore else -- @preserve: initialization pattern */
|
|
4850
5122
|
//# sourceMappingURL=index.cjs.map
|