@tmlmt/cooklang-parser 3.0.0-alpha.15 → 3.0.0-alpha.17
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 +200 -88
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +51 -16
- package/dist/index.d.ts +51 -16
- package/dist/index.js +200 -88
- package/dist/index.js.map +1 -1
- package/package.json +11 -11
package/dist/index.cjs
CHANGED
|
@@ -330,13 +330,12 @@ var nestedMetaVarRegex = (varName) => new RegExp(
|
|
|
330
330
|
"m"
|
|
331
331
|
);
|
|
332
332
|
var metadataRegex = d().literal("---").newline().startCaptureGroup().anyCharacter().zeroOrMore().optional().endGroup().newline().literal("---").dotAll().toRegExp();
|
|
333
|
-
var scalingMetaValueRegex = (varName) => d().startAnchor().literal(varName).literal(":").anyOf("\\t ").zeroOrMore().startCaptureGroup().startCaptureGroup().notAnyOf(",\\n").oneOrMore().endGroup().startGroup().literal(",").whitespace().zeroOrMore().startCaptureGroup().anyCharacter().oneOrMore().endGroup().endGroup().optional().endGroup().endAnchor().multiline().toRegExp();
|
|
334
333
|
var nonWordChar = "\\s@#~\\[\\]{(,;:!?";
|
|
335
334
|
var nonWordCharStrict = "\\s@#~\\[\\]{(,;:!?|";
|
|
336
335
|
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();
|
|
337
336
|
var inlineIngredientAlternativesRegex = new RegExp("\\|" + ingredientWithAlternativeRegex.source.slice(1));
|
|
338
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();
|
|
339
|
-
var ingredientWithGroupKeyRegex = d().literal("@|").startNamedGroup("gIngredientGroupKey").notAnyOf(nonWordCharStrict).oneOrMore().endGroup().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 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();
|
|
340
339
|
var ingredientAliasRegex = d().startAnchor().startNamedGroup("ingredientListName").notAnyOf("|").oneOrMore().endGroup().literal("|").startNamedGroup("ingredientDisplayName").notAnyOf("|").oneOrMore().endGroup().endAnchor().toRegExp();
|
|
341
340
|
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();
|
|
342
341
|
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();
|
|
@@ -351,6 +350,13 @@ var tokensRegex = new RegExp(
|
|
|
351
350
|
].map((r2) => r2.source).join("|"),
|
|
352
351
|
"gu"
|
|
353
352
|
);
|
|
353
|
+
var servingsPrefixPart = (varName) => d().startAnchor().literal(varName).literal(":").anyOf(" ").zeroOrMore().startNamedGroup("servingsPrefix").nonWhitespace().startGroup().anyCharacter().zeroOrMore().lazy().nonWhitespace().endGroup().optional().endGroup().optional().anyOf(" ").zeroOrMore().toRegExp();
|
|
354
|
+
var servingsSuffixPart = d().anyOf(" ").zeroOrMore().startNamedGroup("servingsSuffix").nonWhitespace().startGroup().anyCharacter().zeroOrMore().nonWhitespace().endGroup().optional().endGroup().optional().anyOf(" ").zeroOrMore().endAnchor().toRegExp();
|
|
355
|
+
var scalingSimpleMetaValueRegex = (varName) => d().startAnchor().literal(varName).literal(":").anyOf("\\t ").zeroOrMore().startCaptureGroup().startCaptureGroup().notAnyOf(",\\n").oneOrMore().endGroup().startGroup().literal(",").anyOf("\\t ").zeroOrMore().startCaptureGroup().anyCharacter().oneOrMore().endGroup().optional().endGroup().optional().endGroup().endAnchor().multiline().toRegExp();
|
|
356
|
+
var scalingMetaValueWithUnitRegex = (varName) => new RegExp(
|
|
357
|
+
servingsPrefixPart(varName).source + arbitraryScalableRegex.source + servingsSuffixPart.source,
|
|
358
|
+
"m"
|
|
359
|
+
);
|
|
354
360
|
var commentRegex = d().literal("--").anyCharacter().zeroOrMore().global().toRegExp();
|
|
355
361
|
var blockCommentRegex = d().literal("[-").anyCharacter().zeroOrMore().lazy().literal("-]").whitespace().zeroOrMore().global().toRegExp();
|
|
356
362
|
var shoppingListRegex = d().literal("[").startNamedGroup("name").anyCharacter().oneOrMore().endGroup().literal("]").newline().startNamedGroup("items").anyCharacter().zeroOrMore().lazy().endGroup().startGroup().newline().newline().or().endAnchor().endGroup().global().toRegExp();
|
|
@@ -1334,17 +1340,21 @@ var flattenPlainUnitGroup = (summed) => {
|
|
|
1334
1340
|
}
|
|
1335
1341
|
};
|
|
1336
1342
|
function applyBestUnit(q, system) {
|
|
1337
|
-
|
|
1343
|
+
const extended = { quantity: q.quantity };
|
|
1344
|
+
if (q.unit) {
|
|
1345
|
+
extended.unit = typeof q.unit === "string" ? { name: q.unit } : q.unit;
|
|
1346
|
+
}
|
|
1347
|
+
if (!extended.unit?.name) {
|
|
1338
1348
|
return q;
|
|
1339
1349
|
}
|
|
1340
|
-
const unitDef = resolveUnit(
|
|
1350
|
+
const unitDef = resolveUnit(extended.unit.name);
|
|
1341
1351
|
if (unitDef.type === "other") {
|
|
1342
1352
|
return q;
|
|
1343
1353
|
}
|
|
1344
|
-
if (
|
|
1354
|
+
if (extended.quantity.type === "fixed" && extended.quantity.value.type === "text") {
|
|
1345
1355
|
return q;
|
|
1346
1356
|
}
|
|
1347
|
-
const avgValue = getAverageValue(
|
|
1357
|
+
const avgValue = getAverageValue(extended.quantity);
|
|
1348
1358
|
const effectiveSystem = system ?? (["metric", "JP"].includes(unitDef.system) ? unitDef.system : "US");
|
|
1349
1359
|
const toBase = getToBase(unitDef, effectiveSystem);
|
|
1350
1360
|
const valueInBase = avgValue * toBase;
|
|
@@ -1354,22 +1364,22 @@ function applyBestUnit(q, system) {
|
|
|
1354
1364
|
effectiveSystem,
|
|
1355
1365
|
[unitDef]
|
|
1356
1366
|
);
|
|
1357
|
-
const originalCanonicalName = normalizeUnit(
|
|
1367
|
+
const originalCanonicalName = normalizeUnit(extended.unit.name)?.name;
|
|
1358
1368
|
if (bestUnit.name === originalCanonicalName) {
|
|
1359
1369
|
return q;
|
|
1360
1370
|
}
|
|
1361
1371
|
const formattedValue = formatOutputValue(bestValue, bestUnit);
|
|
1362
|
-
if (
|
|
1372
|
+
if (extended.quantity.type === "range") {
|
|
1363
1373
|
const bestToBase = getToBase(bestUnit, effectiveSystem);
|
|
1364
|
-
const minValue = getNumericValue(
|
|
1365
|
-
const maxValue = getNumericValue(
|
|
1374
|
+
const minValue = getNumericValue(extended.quantity.min) * toBase / bestToBase;
|
|
1375
|
+
const maxValue = getNumericValue(extended.quantity.max) * toBase / bestToBase;
|
|
1366
1376
|
return {
|
|
1367
1377
|
quantity: {
|
|
1368
1378
|
type: "range",
|
|
1369
1379
|
min: formatOutputValue(minValue, bestUnit),
|
|
1370
1380
|
max: formatOutputValue(maxValue, bestUnit)
|
|
1371
1381
|
},
|
|
1372
|
-
unit: { name: bestUnit.name }
|
|
1382
|
+
unit: typeof q.unit === "string" ? bestUnit.name : { name: bestUnit.name }
|
|
1373
1383
|
};
|
|
1374
1384
|
}
|
|
1375
1385
|
return {
|
|
@@ -1377,7 +1387,7 @@ function applyBestUnit(q, system) {
|
|
|
1377
1387
|
type: "fixed",
|
|
1378
1388
|
value: formattedValue
|
|
1379
1389
|
},
|
|
1380
|
-
unit: { name: bestUnit.name }
|
|
1390
|
+
unit: typeof q.unit === "string" ? bestUnit.name : { name: bestUnit.name }
|
|
1381
1391
|
};
|
|
1382
1392
|
}
|
|
1383
1393
|
function subtractQuantities(q1, q2, options = {}) {
|
|
@@ -1729,13 +1739,62 @@ function parseBlockScalarMetaVar(content, varName) {
|
|
|
1729
1739
|
}
|
|
1730
1740
|
return stripped.replace(/\n\n/g, "\0").replace(/\n/g, " ").replace(/\0/g, "\n");
|
|
1731
1741
|
}
|
|
1742
|
+
function parseArbitraryQuantity(raw) {
|
|
1743
|
+
const quantityMatch = raw.trim().match(quantityAlternativeRegex);
|
|
1744
|
+
if (!quantityMatch?.groups) {
|
|
1745
|
+
throw new InvalidQuantityFormat(
|
|
1746
|
+
raw,
|
|
1747
|
+
"Arbitrary quantities must have a numerical value"
|
|
1748
|
+
);
|
|
1749
|
+
}
|
|
1750
|
+
const value = parseQuantityInput(quantityMatch.groups.quantity);
|
|
1751
|
+
const unit = quantityMatch.groups.unit;
|
|
1752
|
+
if (!value || value.type === "fixed" && value.value.type === "text") {
|
|
1753
|
+
throw new InvalidQuantityFormat(
|
|
1754
|
+
raw,
|
|
1755
|
+
"Arbitrary quantities must have a numerical value"
|
|
1756
|
+
);
|
|
1757
|
+
}
|
|
1758
|
+
const arbitrary = {
|
|
1759
|
+
quantity: value
|
|
1760
|
+
};
|
|
1761
|
+
if (unit) arbitrary.unit = unit;
|
|
1762
|
+
return arbitrary;
|
|
1763
|
+
}
|
|
1732
1764
|
function parseScalingMetaVar(content, varName) {
|
|
1733
|
-
const
|
|
1765
|
+
const complexMatch = content.match(scalingMetaValueWithUnitRegex(varName));
|
|
1766
|
+
if (complexMatch?.groups?.arbitraryQuantity) {
|
|
1767
|
+
const parsed = parseArbitraryQuantity(
|
|
1768
|
+
complexMatch.groups.arbitraryQuantity
|
|
1769
|
+
);
|
|
1770
|
+
const result2 = {
|
|
1771
|
+
quantity: parsed.quantity
|
|
1772
|
+
};
|
|
1773
|
+
if (parsed.unit) result2.unit = parsed.unit;
|
|
1774
|
+
if (complexMatch.groups.servingsPrefix) {
|
|
1775
|
+
result2.textBefore = complexMatch.groups.servingsPrefix;
|
|
1776
|
+
}
|
|
1777
|
+
if (complexMatch.groups.servingsSuffix) {
|
|
1778
|
+
result2.textAfter = complexMatch.groups.servingsSuffix;
|
|
1779
|
+
}
|
|
1780
|
+
return result2;
|
|
1781
|
+
}
|
|
1782
|
+
const varMatch = content.match(scalingSimpleMetaValueRegex(varName));
|
|
1734
1783
|
if (!varMatch) return void 0;
|
|
1735
1784
|
if (isNaN(Number(varMatch[2]?.trim()))) {
|
|
1736
1785
|
throw new Error("Scaling variables should be numbers");
|
|
1737
1786
|
}
|
|
1738
|
-
|
|
1787
|
+
const numericValue = Number(varMatch[2]?.trim());
|
|
1788
|
+
const result = {
|
|
1789
|
+
quantity: {
|
|
1790
|
+
type: "fixed",
|
|
1791
|
+
value: { type: "decimal", decimal: numericValue }
|
|
1792
|
+
}
|
|
1793
|
+
};
|
|
1794
|
+
if (varMatch[3]) {
|
|
1795
|
+
result.text = `${varMatch[3].trim()}`;
|
|
1796
|
+
}
|
|
1797
|
+
return result;
|
|
1739
1798
|
}
|
|
1740
1799
|
function parseListMetaVar(content, varName) {
|
|
1741
1800
|
const listMatch = content.match(
|
|
@@ -1853,6 +1912,13 @@ function parseAnyMetaVar(content, varName) {
|
|
|
1853
1912
|
if (simple) return parseMetadataValue(simple);
|
|
1854
1913
|
return void 0;
|
|
1855
1914
|
}
|
|
1915
|
+
function getNumericValueFromMetaVar(v) {
|
|
1916
|
+
if (v.quantity.type === "fixed" && v.quantity.value.type !== "text") {
|
|
1917
|
+
return getNumericValue(v.quantity.value);
|
|
1918
|
+
}
|
|
1919
|
+
if (v.quantity.type === "range") return getNumericValue(v.quantity.min);
|
|
1920
|
+
return 0;
|
|
1921
|
+
}
|
|
1856
1922
|
function extractMetadata(content) {
|
|
1857
1923
|
const metadata = {};
|
|
1858
1924
|
let servings = void 0;
|
|
@@ -1977,9 +2043,9 @@ function extractMetadata(content) {
|
|
|
1977
2043
|
}
|
|
1978
2044
|
for (const metaVar of ["servings", "yield", "serves"]) {
|
|
1979
2045
|
const scalingMetaValue = parseScalingMetaVar(metadataContent, metaVar);
|
|
1980
|
-
if (scalingMetaValue
|
|
1981
|
-
metadata[metaVar] = scalingMetaValue
|
|
1982
|
-
servings = scalingMetaValue
|
|
2046
|
+
if (scalingMetaValue) {
|
|
2047
|
+
metadata[metaVar] = scalingMetaValue;
|
|
2048
|
+
servings = getNumericValueFromMetaVar(scalingMetaValue);
|
|
1983
2049
|
}
|
|
1984
2050
|
}
|
|
1985
2051
|
const tags = parseListMetaVar(metadataContent, "tags");
|
|
@@ -2771,6 +2837,7 @@ var _Recipe = class _Recipe {
|
|
|
2771
2837
|
*/
|
|
2772
2838
|
__publicField(this, "servings");
|
|
2773
2839
|
_Recipe.itemCounts.set(this, 0);
|
|
2840
|
+
_Recipe.subgroupIndices.set(this, /* @__PURE__ */ new Map());
|
|
2774
2841
|
if (content) {
|
|
2775
2842
|
this.parse(content);
|
|
2776
2843
|
}
|
|
@@ -2806,27 +2873,17 @@ var _Recipe = class _Recipe {
|
|
|
2806
2873
|
*/
|
|
2807
2874
|
_parseArbitraryScalable(regexMatchGroups, intoArray) {
|
|
2808
2875
|
if (!regexMatchGroups || !regexMatchGroups.arbitraryQuantity) return;
|
|
2809
|
-
const
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
const arbitrary = {
|
|
2821
|
-
quantity: value
|
|
2822
|
-
};
|
|
2823
|
-
if (name) arbitrary.name = name;
|
|
2824
|
-
if (unit) arbitrary.unit = unit;
|
|
2825
|
-
intoArray.push({
|
|
2826
|
-
type: "arbitrary",
|
|
2827
|
-
index: this.arbitraries.push(arbitrary) - 1
|
|
2828
|
-
});
|
|
2829
|
-
}
|
|
2876
|
+
const parsed = parseArbitraryQuantity(regexMatchGroups.arbitraryQuantity);
|
|
2877
|
+
const name = regexMatchGroups.arbitraryName || void 0;
|
|
2878
|
+
const arbitrary = {
|
|
2879
|
+
quantity: parsed.quantity
|
|
2880
|
+
};
|
|
2881
|
+
if (name) arbitrary.name = name;
|
|
2882
|
+
if (parsed.unit) arbitrary.unit = parsed.unit;
|
|
2883
|
+
intoArray.push({
|
|
2884
|
+
type: "arbitrary",
|
|
2885
|
+
index: this.arbitraries.push(arbitrary) - 1
|
|
2886
|
+
});
|
|
2830
2887
|
}
|
|
2831
2888
|
/**
|
|
2832
2889
|
* Parses text for arbitrary scalables and returns NoteItem array.
|
|
@@ -2998,6 +3055,7 @@ var _Recipe = class _Recipe {
|
|
|
2998
3055
|
if (!match?.groups) return;
|
|
2999
3056
|
const groups = match.groups;
|
|
3000
3057
|
const groupKey = groups.gIngredientGroupKey;
|
|
3058
|
+
const subgroupKey = groups.gIngredientSubgroupKey;
|
|
3001
3059
|
let name = groups.gmIngredientName || groups.gsIngredientName;
|
|
3002
3060
|
const preparation = groups.gIngredientPreparation;
|
|
3003
3061
|
const modifiers = groups.gIngredientModifiers;
|
|
@@ -3065,7 +3123,8 @@ var _Recipe = class _Recipe {
|
|
|
3065
3123
|
if (itemQuantity) {
|
|
3066
3124
|
Object.assign(alternative, itemQuantity);
|
|
3067
3125
|
}
|
|
3068
|
-
const
|
|
3126
|
+
const existingSubgroups = this.choices.ingredientGroups.get(groupKey);
|
|
3127
|
+
const existingAlternativesFlat = existingSubgroups?.flat();
|
|
3069
3128
|
function upsertAlternativeToIngredient(ingredients, ingredientIdx, newAlternativeIdx) {
|
|
3070
3129
|
const ingredient = ingredients[ingredientIdx];
|
|
3071
3130
|
if (ingredient) {
|
|
@@ -3076,8 +3135,8 @@ var _Recipe = class _Recipe {
|
|
|
3076
3135
|
}
|
|
3077
3136
|
}
|
|
3078
3137
|
}
|
|
3079
|
-
if (
|
|
3080
|
-
for (const alt of
|
|
3138
|
+
if (existingAlternativesFlat) {
|
|
3139
|
+
for (const alt of existingAlternativesFlat) {
|
|
3081
3140
|
upsertAlternativeToIngredient(this.ingredients, alt.index, idxInList);
|
|
3082
3141
|
upsertAlternativeToIngredient(this.ingredients, idxInList, alt.index);
|
|
3083
3142
|
}
|
|
@@ -3089,14 +3148,35 @@ var _Recipe = class _Recipe {
|
|
|
3089
3148
|
group: groupKey,
|
|
3090
3149
|
alternatives: [alternative]
|
|
3091
3150
|
};
|
|
3151
|
+
if (subgroupKey !== void 0) {
|
|
3152
|
+
newItem.subgroup = subgroupKey;
|
|
3153
|
+
}
|
|
3092
3154
|
items.push(newItem);
|
|
3093
3155
|
const choiceAlternative = deepClone(alternative);
|
|
3094
3156
|
choiceAlternative.itemId = id;
|
|
3095
3157
|
const existingChoice = this.choices.ingredientGroups.get(groupKey);
|
|
3158
|
+
const sgMap = _Recipe.subgroupIndices.get(this);
|
|
3096
3159
|
if (!existingChoice) {
|
|
3097
|
-
this.choices.ingredientGroups.set(groupKey, [choiceAlternative]);
|
|
3160
|
+
this.choices.ingredientGroups.set(groupKey, [[choiceAlternative]]);
|
|
3161
|
+
if (subgroupKey !== void 0) {
|
|
3162
|
+
sgMap.set(groupKey, /* @__PURE__ */ new Map([[subgroupKey, 0]]));
|
|
3163
|
+
}
|
|
3164
|
+
} else if (subgroupKey !== void 0) {
|
|
3165
|
+
const groupSgMap = sgMap.get(groupKey);
|
|
3166
|
+
const existingIdx = groupSgMap?.get(subgroupKey);
|
|
3167
|
+
if (existingIdx !== void 0) {
|
|
3168
|
+
existingChoice[existingIdx].push(choiceAlternative);
|
|
3169
|
+
} else {
|
|
3170
|
+
const newIdx = existingChoice.length;
|
|
3171
|
+
existingChoice.push([choiceAlternative]);
|
|
3172
|
+
if (!groupSgMap) {
|
|
3173
|
+
sgMap.set(groupKey, /* @__PURE__ */ new Map([[subgroupKey, newIdx]]));
|
|
3174
|
+
} else {
|
|
3175
|
+
groupSgMap.set(subgroupKey, newIdx);
|
|
3176
|
+
}
|
|
3177
|
+
}
|
|
3098
3178
|
} else {
|
|
3099
|
-
existingChoice.push(choiceAlternative);
|
|
3179
|
+
existingChoice.push([choiceAlternative]);
|
|
3100
3180
|
}
|
|
3101
3181
|
}
|
|
3102
3182
|
/**
|
|
@@ -3153,15 +3233,16 @@ var _Recipe = class _Recipe {
|
|
|
3153
3233
|
(item2) => item2.type === "ingredient"
|
|
3154
3234
|
)) {
|
|
3155
3235
|
const isGrouped = "group" in item && item.group !== void 0;
|
|
3156
|
-
const
|
|
3236
|
+
const groupSubgroups = isGrouped ? this.choices.ingredientGroups.get(item.group) : void 0;
|
|
3157
3237
|
let selectedAltIndex = 0;
|
|
3158
|
-
let isSelected
|
|
3159
|
-
let hasExplicitChoice
|
|
3238
|
+
let isSelected;
|
|
3239
|
+
let hasExplicitChoice;
|
|
3160
3240
|
if (isGrouped) {
|
|
3161
3241
|
const groupChoice = choices?.ingredientGroups?.get(item.group);
|
|
3162
3242
|
hasExplicitChoice = groupChoice !== void 0;
|
|
3163
|
-
const
|
|
3164
|
-
|
|
3243
|
+
const targetSubgroupIndex = groupChoice ?? 0;
|
|
3244
|
+
const selectedSubgroup = groupSubgroups?.[targetSubgroupIndex];
|
|
3245
|
+
isSelected = selectedSubgroup?.some((alt) => alt.itemId === item.id) ?? false;
|
|
3165
3246
|
} else {
|
|
3166
3247
|
const itemChoice = choices?.ingredientItems?.get(item.id);
|
|
3167
3248
|
hasExplicitChoice = itemChoice !== void 0;
|
|
@@ -3171,8 +3252,8 @@ var _Recipe = class _Recipe {
|
|
|
3171
3252
|
const alternative = item.alternatives[selectedAltIndex];
|
|
3172
3253
|
if (!alternative || !isSelected) continue;
|
|
3173
3254
|
selectedIndices.add(alternative.index);
|
|
3174
|
-
const
|
|
3175
|
-
for (const alt of
|
|
3255
|
+
const allAltsFlat = isGrouped ? groupSubgroups.flat() : item.alternatives;
|
|
3256
|
+
for (const alt of allAltsFlat) {
|
|
3176
3257
|
referencedIndices.add(alt.index);
|
|
3177
3258
|
}
|
|
3178
3259
|
if (!alternative.quantity) continue;
|
|
@@ -3184,10 +3265,34 @@ var _Recipe = class _Recipe {
|
|
|
3184
3265
|
};
|
|
3185
3266
|
const quantityEntry = alternative.equivalents?.length ? { or: [baseQty, ...alternative.equivalents] } : baseQty;
|
|
3186
3267
|
let alternativeRefs;
|
|
3187
|
-
if (!hasExplicitChoice &&
|
|
3188
|
-
|
|
3189
|
-
(
|
|
3190
|
-
)
|
|
3268
|
+
if (!hasExplicitChoice && groupSubgroups && groupSubgroups.length > 1) {
|
|
3269
|
+
const currentSubgroupIdx = groupSubgroups.findIndex(
|
|
3270
|
+
(sg) => sg.some((alt) => alt.itemId === item.id)
|
|
3271
|
+
);
|
|
3272
|
+
alternativeRefs = groupSubgroups.filter((_, idx) => idx !== currentSubgroupIdx).flatMap(
|
|
3273
|
+
(subgroup) => subgroup.map((otherAlt) => {
|
|
3274
|
+
const ref = {
|
|
3275
|
+
index: otherAlt.index
|
|
3276
|
+
};
|
|
3277
|
+
if (otherAlt.quantity) {
|
|
3278
|
+
const altQty = {
|
|
3279
|
+
quantity: otherAlt.quantity,
|
|
3280
|
+
...otherAlt.unit && {
|
|
3281
|
+
unit: otherAlt.unit.name
|
|
3282
|
+
},
|
|
3283
|
+
...otherAlt.equivalents && {
|
|
3284
|
+
equivalents: otherAlt.equivalents.map(
|
|
3285
|
+
(eq) => toPlainUnit(eq)
|
|
3286
|
+
)
|
|
3287
|
+
}
|
|
3288
|
+
};
|
|
3289
|
+
ref.quantities = [altQty];
|
|
3290
|
+
}
|
|
3291
|
+
return ref;
|
|
3292
|
+
})
|
|
3293
|
+
);
|
|
3294
|
+
} else if (!hasExplicitChoice && !isGrouped && allAltsFlat.length > 1) {
|
|
3295
|
+
alternativeRefs = allAltsFlat.filter((alt) => alt.index !== alternative.index).map((otherAlt) => {
|
|
3191
3296
|
const ref = { index: otherAlt.index };
|
|
3192
3297
|
if (otherAlt.quantity) {
|
|
3193
3298
|
const altQty = {
|
|
@@ -3605,8 +3710,10 @@ var _Recipe = class _Recipe {
|
|
|
3605
3710
|
}
|
|
3606
3711
|
}
|
|
3607
3712
|
}
|
|
3608
|
-
for (const
|
|
3609
|
-
|
|
3713
|
+
for (const subgroups of newRecipe.choices.ingredientGroups.values()) {
|
|
3714
|
+
for (const subgroup of subgroups) {
|
|
3715
|
+
scaleAlternativesBy(subgroup, factor);
|
|
3716
|
+
}
|
|
3610
3717
|
}
|
|
3611
3718
|
for (const alternatives of newRecipe.choices.ingredientItems.values()) {
|
|
3612
3719
|
scaleAlternativesBy(alternatives, factor);
|
|
@@ -3616,37 +3723,34 @@ var _Recipe = class _Recipe {
|
|
|
3616
3723
|
arbitrary.quantity,
|
|
3617
3724
|
factor
|
|
3618
3725
|
);
|
|
3726
|
+
const optimized = applyBestUnit(
|
|
3727
|
+
{ quantity: arbitrary.quantity, unit: arbitrary.unit },
|
|
3728
|
+
unitSystem
|
|
3729
|
+
);
|
|
3730
|
+
arbitrary.quantity = optimized.quantity;
|
|
3731
|
+
arbitrary.unit = optimized.unit;
|
|
3619
3732
|
}
|
|
3620
3733
|
newRecipe._populateIngredientQuantities();
|
|
3621
3734
|
newRecipe.servings = (0, import_big4.default)(originalServings).times(factor).toNumber();
|
|
3622
|
-
|
|
3623
|
-
if (
|
|
3624
|
-
const
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
(0, import_big4.default)(servingsValue).times(factor).toNumber()
|
|
3629
|
-
);
|
|
3630
|
-
}
|
|
3631
|
-
}
|
|
3632
|
-
if (newRecipe.metadata.yield && this.metadata.yield) {
|
|
3633
|
-
if (floatRegex.test(String(this.metadata.yield).replace(",", ".").trim())) {
|
|
3634
|
-
const yieldValue = parseFloat(
|
|
3635
|
-
String(this.metadata.yield).replace(",", ".")
|
|
3735
|
+
for (const metaVar of ["servings", "yield", "serves"]) {
|
|
3736
|
+
if (newRecipe.metadata[metaVar] && this.metadata[metaVar]) {
|
|
3737
|
+
const original = this.metadata[metaVar];
|
|
3738
|
+
const scaledQuantity = multiplyQuantityValue(
|
|
3739
|
+
original.quantity,
|
|
3740
|
+
factor
|
|
3636
3741
|
);
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
}
|
|
3641
|
-
}
|
|
3642
|
-
if (newRecipe.metadata.serves && this.metadata.serves) {
|
|
3643
|
-
if (floatRegex.test(String(this.metadata.serves).replace(",", ".").trim())) {
|
|
3644
|
-
const servesValue = parseFloat(
|
|
3645
|
-
String(this.metadata.serves).replace(",", ".")
|
|
3646
|
-
);
|
|
3647
|
-
newRecipe.metadata.serves = String(
|
|
3648
|
-
(0, import_big4.default)(servesValue).times(factor).toNumber()
|
|
3742
|
+
const optimized = applyBestUnit(
|
|
3743
|
+
{ quantity: scaledQuantity, unit: original.unit },
|
|
3744
|
+
unitSystem
|
|
3649
3745
|
);
|
|
3746
|
+
const scaled = {
|
|
3747
|
+
quantity: optimized.quantity
|
|
3748
|
+
};
|
|
3749
|
+
if (optimized.unit) scaled.unit = optimized.unit;
|
|
3750
|
+
if (original.textBefore) scaled.textBefore = original.textBefore;
|
|
3751
|
+
if (original.textAfter) scaled.textAfter = original.textAfter;
|
|
3752
|
+
if (original.text) scaled.text = original.text;
|
|
3753
|
+
newRecipe.metadata[metaVar] = scaled;
|
|
3650
3754
|
}
|
|
3651
3755
|
}
|
|
3652
3756
|
return newRecipe;
|
|
@@ -3796,8 +3900,10 @@ var _Recipe = class _Recipe {
|
|
|
3796
3900
|
}
|
|
3797
3901
|
}
|
|
3798
3902
|
}
|
|
3799
|
-
for (const
|
|
3800
|
-
|
|
3903
|
+
for (const subgroups of newRecipe.choices.ingredientGroups.values()) {
|
|
3904
|
+
for (const subgroup of subgroups) {
|
|
3905
|
+
convertAlternatives(subgroup);
|
|
3906
|
+
}
|
|
3801
3907
|
}
|
|
3802
3908
|
for (const alternatives of newRecipe.choices.ingredientItems.values()) {
|
|
3803
3909
|
convertAlternatives(alternatives);
|
|
@@ -3849,6 +3955,11 @@ __publicField(_Recipe, "unitSystems", /* @__PURE__ */ new WeakMap());
|
|
|
3849
3955
|
* Used for giving ID numbers to items during parsing.
|
|
3850
3956
|
*/
|
|
3851
3957
|
__publicField(_Recipe, "itemCounts", /* @__PURE__ */ new WeakMap());
|
|
3958
|
+
/**
|
|
3959
|
+
* External storage for subgroup index tracking during parsing.
|
|
3960
|
+
* Maps groupKey → subgroupKey → index within the subgroups array.
|
|
3961
|
+
*/
|
|
3962
|
+
__publicField(_Recipe, "subgroupIndices", /* @__PURE__ */ new WeakMap());
|
|
3852
3963
|
var Recipe = _Recipe;
|
|
3853
3964
|
|
|
3854
3965
|
// src/classes/shopping_list.ts
|
|
@@ -4686,10 +4797,10 @@ function isGroupedItem(item) {
|
|
|
4686
4797
|
function isAlternativeSelected(recipe, choices, item, alternativeIndex) {
|
|
4687
4798
|
if (item.group) {
|
|
4688
4799
|
const selectedIndex2 = choices?.ingredientGroups?.get(item.group);
|
|
4689
|
-
const
|
|
4690
|
-
if (
|
|
4691
|
-
const
|
|
4692
|
-
return
|
|
4800
|
+
const groupSubgroups = recipe.choices.ingredientGroups.get(item.group);
|
|
4801
|
+
if (groupSubgroups && selectedIndex2 !== void 0 && selectedIndex2 < groupSubgroups.length) {
|
|
4802
|
+
const selectedSubgroup = groupSubgroups[selectedIndex2];
|
|
4803
|
+
return selectedSubgroup?.some((alt) => alt.itemId === item.id);
|
|
4693
4804
|
}
|
|
4694
4805
|
return false;
|
|
4695
4806
|
}
|
|
@@ -4730,6 +4841,7 @@ function isAlternativeSelected(recipe, choices, item, alternativeIndex) {
|
|
|
4730
4841
|
// v8 ignore else -- @preserve
|
|
4731
4842
|
// v8 ignore if -- @preserve
|
|
4732
4843
|
/* v8 ignore else -- expliciting error type -- @preserve */
|
|
4844
|
+
/* v8 ignore next 4 -- @preserve: defensive guard; regex always matches */
|
|
4733
4845
|
// v8 ignore if -- @preserve: defensive type guard
|
|
4734
4846
|
/* v8 ignore if -- @preserve */
|
|
4735
4847
|
// v8 ignore next -- @preserve
|