@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.js
CHANGED
|
@@ -272,13 +272,12 @@ var nestedMetaVarRegex = (varName) => new RegExp(
|
|
|
272
272
|
"m"
|
|
273
273
|
);
|
|
274
274
|
var metadataRegex = d().literal("---").newline().startCaptureGroup().anyCharacter().zeroOrMore().optional().endGroup().newline().literal("---").dotAll().toRegExp();
|
|
275
|
-
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();
|
|
276
275
|
var nonWordChar = "\\s@#~\\[\\]{(,;:!?";
|
|
277
276
|
var nonWordCharStrict = "\\s@#~\\[\\]{(,;:!?|";
|
|
278
277
|
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();
|
|
279
278
|
var inlineIngredientAlternativesRegex = new RegExp("\\|" + ingredientWithAlternativeRegex.source.slice(1));
|
|
280
279
|
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();
|
|
281
|
-
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();
|
|
280
|
+
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();
|
|
282
281
|
var ingredientAliasRegex = d().startAnchor().startNamedGroup("ingredientListName").notAnyOf("|").oneOrMore().endGroup().literal("|").startNamedGroup("ingredientDisplayName").notAnyOf("|").oneOrMore().endGroup().endAnchor().toRegExp();
|
|
283
282
|
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();
|
|
284
283
|
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();
|
|
@@ -293,6 +292,13 @@ var tokensRegex = new RegExp(
|
|
|
293
292
|
].map((r2) => r2.source).join("|"),
|
|
294
293
|
"gu"
|
|
295
294
|
);
|
|
295
|
+
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();
|
|
296
|
+
var servingsSuffixPart = d().anyOf(" ").zeroOrMore().startNamedGroup("servingsSuffix").nonWhitespace().startGroup().anyCharacter().zeroOrMore().nonWhitespace().endGroup().optional().endGroup().optional().anyOf(" ").zeroOrMore().endAnchor().toRegExp();
|
|
297
|
+
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();
|
|
298
|
+
var scalingMetaValueWithUnitRegex = (varName) => new RegExp(
|
|
299
|
+
servingsPrefixPart(varName).source + arbitraryScalableRegex.source + servingsSuffixPart.source,
|
|
300
|
+
"m"
|
|
301
|
+
);
|
|
296
302
|
var commentRegex = d().literal("--").anyCharacter().zeroOrMore().global().toRegExp();
|
|
297
303
|
var blockCommentRegex = d().literal("[-").anyCharacter().zeroOrMore().lazy().literal("-]").whitespace().zeroOrMore().global().toRegExp();
|
|
298
304
|
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();
|
|
@@ -1276,17 +1282,21 @@ var flattenPlainUnitGroup = (summed) => {
|
|
|
1276
1282
|
}
|
|
1277
1283
|
};
|
|
1278
1284
|
function applyBestUnit(q, system) {
|
|
1279
|
-
|
|
1285
|
+
const extended = { quantity: q.quantity };
|
|
1286
|
+
if (q.unit) {
|
|
1287
|
+
extended.unit = typeof q.unit === "string" ? { name: q.unit } : q.unit;
|
|
1288
|
+
}
|
|
1289
|
+
if (!extended.unit?.name) {
|
|
1280
1290
|
return q;
|
|
1281
1291
|
}
|
|
1282
|
-
const unitDef = resolveUnit(
|
|
1292
|
+
const unitDef = resolveUnit(extended.unit.name);
|
|
1283
1293
|
if (unitDef.type === "other") {
|
|
1284
1294
|
return q;
|
|
1285
1295
|
}
|
|
1286
|
-
if (
|
|
1296
|
+
if (extended.quantity.type === "fixed" && extended.quantity.value.type === "text") {
|
|
1287
1297
|
return q;
|
|
1288
1298
|
}
|
|
1289
|
-
const avgValue = getAverageValue(
|
|
1299
|
+
const avgValue = getAverageValue(extended.quantity);
|
|
1290
1300
|
const effectiveSystem = system ?? (["metric", "JP"].includes(unitDef.system) ? unitDef.system : "US");
|
|
1291
1301
|
const toBase = getToBase(unitDef, effectiveSystem);
|
|
1292
1302
|
const valueInBase = avgValue * toBase;
|
|
@@ -1296,22 +1306,22 @@ function applyBestUnit(q, system) {
|
|
|
1296
1306
|
effectiveSystem,
|
|
1297
1307
|
[unitDef]
|
|
1298
1308
|
);
|
|
1299
|
-
const originalCanonicalName = normalizeUnit(
|
|
1309
|
+
const originalCanonicalName = normalizeUnit(extended.unit.name)?.name;
|
|
1300
1310
|
if (bestUnit.name === originalCanonicalName) {
|
|
1301
1311
|
return q;
|
|
1302
1312
|
}
|
|
1303
1313
|
const formattedValue = formatOutputValue(bestValue, bestUnit);
|
|
1304
|
-
if (
|
|
1314
|
+
if (extended.quantity.type === "range") {
|
|
1305
1315
|
const bestToBase = getToBase(bestUnit, effectiveSystem);
|
|
1306
|
-
const minValue = getNumericValue(
|
|
1307
|
-
const maxValue = getNumericValue(
|
|
1316
|
+
const minValue = getNumericValue(extended.quantity.min) * toBase / bestToBase;
|
|
1317
|
+
const maxValue = getNumericValue(extended.quantity.max) * toBase / bestToBase;
|
|
1308
1318
|
return {
|
|
1309
1319
|
quantity: {
|
|
1310
1320
|
type: "range",
|
|
1311
1321
|
min: formatOutputValue(minValue, bestUnit),
|
|
1312
1322
|
max: formatOutputValue(maxValue, bestUnit)
|
|
1313
1323
|
},
|
|
1314
|
-
unit: { name: bestUnit.name }
|
|
1324
|
+
unit: typeof q.unit === "string" ? bestUnit.name : { name: bestUnit.name }
|
|
1315
1325
|
};
|
|
1316
1326
|
}
|
|
1317
1327
|
return {
|
|
@@ -1319,7 +1329,7 @@ function applyBestUnit(q, system) {
|
|
|
1319
1329
|
type: "fixed",
|
|
1320
1330
|
value: formattedValue
|
|
1321
1331
|
},
|
|
1322
|
-
unit: { name: bestUnit.name }
|
|
1332
|
+
unit: typeof q.unit === "string" ? bestUnit.name : { name: bestUnit.name }
|
|
1323
1333
|
};
|
|
1324
1334
|
}
|
|
1325
1335
|
function subtractQuantities(q1, q2, options = {}) {
|
|
@@ -1671,13 +1681,62 @@ function parseBlockScalarMetaVar(content, varName) {
|
|
|
1671
1681
|
}
|
|
1672
1682
|
return stripped.replace(/\n\n/g, "\0").replace(/\n/g, " ").replace(/\0/g, "\n");
|
|
1673
1683
|
}
|
|
1684
|
+
function parseArbitraryQuantity(raw) {
|
|
1685
|
+
const quantityMatch = raw.trim().match(quantityAlternativeRegex);
|
|
1686
|
+
if (!quantityMatch?.groups) {
|
|
1687
|
+
throw new InvalidQuantityFormat(
|
|
1688
|
+
raw,
|
|
1689
|
+
"Arbitrary quantities must have a numerical value"
|
|
1690
|
+
);
|
|
1691
|
+
}
|
|
1692
|
+
const value = parseQuantityInput(quantityMatch.groups.quantity);
|
|
1693
|
+
const unit = quantityMatch.groups.unit;
|
|
1694
|
+
if (!value || value.type === "fixed" && value.value.type === "text") {
|
|
1695
|
+
throw new InvalidQuantityFormat(
|
|
1696
|
+
raw,
|
|
1697
|
+
"Arbitrary quantities must have a numerical value"
|
|
1698
|
+
);
|
|
1699
|
+
}
|
|
1700
|
+
const arbitrary = {
|
|
1701
|
+
quantity: value
|
|
1702
|
+
};
|
|
1703
|
+
if (unit) arbitrary.unit = unit;
|
|
1704
|
+
return arbitrary;
|
|
1705
|
+
}
|
|
1674
1706
|
function parseScalingMetaVar(content, varName) {
|
|
1675
|
-
const
|
|
1707
|
+
const complexMatch = content.match(scalingMetaValueWithUnitRegex(varName));
|
|
1708
|
+
if (complexMatch?.groups?.arbitraryQuantity) {
|
|
1709
|
+
const parsed = parseArbitraryQuantity(
|
|
1710
|
+
complexMatch.groups.arbitraryQuantity
|
|
1711
|
+
);
|
|
1712
|
+
const result2 = {
|
|
1713
|
+
quantity: parsed.quantity
|
|
1714
|
+
};
|
|
1715
|
+
if (parsed.unit) result2.unit = parsed.unit;
|
|
1716
|
+
if (complexMatch.groups.servingsPrefix) {
|
|
1717
|
+
result2.textBefore = complexMatch.groups.servingsPrefix;
|
|
1718
|
+
}
|
|
1719
|
+
if (complexMatch.groups.servingsSuffix) {
|
|
1720
|
+
result2.textAfter = complexMatch.groups.servingsSuffix;
|
|
1721
|
+
}
|
|
1722
|
+
return result2;
|
|
1723
|
+
}
|
|
1724
|
+
const varMatch = content.match(scalingSimpleMetaValueRegex(varName));
|
|
1676
1725
|
if (!varMatch) return void 0;
|
|
1677
1726
|
if (isNaN(Number(varMatch[2]?.trim()))) {
|
|
1678
1727
|
throw new Error("Scaling variables should be numbers");
|
|
1679
1728
|
}
|
|
1680
|
-
|
|
1729
|
+
const numericValue = Number(varMatch[2]?.trim());
|
|
1730
|
+
const result = {
|
|
1731
|
+
quantity: {
|
|
1732
|
+
type: "fixed",
|
|
1733
|
+
value: { type: "decimal", decimal: numericValue }
|
|
1734
|
+
}
|
|
1735
|
+
};
|
|
1736
|
+
if (varMatch[3]) {
|
|
1737
|
+
result.text = `${varMatch[3].trim()}`;
|
|
1738
|
+
}
|
|
1739
|
+
return result;
|
|
1681
1740
|
}
|
|
1682
1741
|
function parseListMetaVar(content, varName) {
|
|
1683
1742
|
const listMatch = content.match(
|
|
@@ -1795,6 +1854,13 @@ function parseAnyMetaVar(content, varName) {
|
|
|
1795
1854
|
if (simple) return parseMetadataValue(simple);
|
|
1796
1855
|
return void 0;
|
|
1797
1856
|
}
|
|
1857
|
+
function getNumericValueFromMetaVar(v) {
|
|
1858
|
+
if (v.quantity.type === "fixed" && v.quantity.value.type !== "text") {
|
|
1859
|
+
return getNumericValue(v.quantity.value);
|
|
1860
|
+
}
|
|
1861
|
+
if (v.quantity.type === "range") return getNumericValue(v.quantity.min);
|
|
1862
|
+
return 0;
|
|
1863
|
+
}
|
|
1798
1864
|
function extractMetadata(content) {
|
|
1799
1865
|
const metadata = {};
|
|
1800
1866
|
let servings = void 0;
|
|
@@ -1919,9 +1985,9 @@ function extractMetadata(content) {
|
|
|
1919
1985
|
}
|
|
1920
1986
|
for (const metaVar of ["servings", "yield", "serves"]) {
|
|
1921
1987
|
const scalingMetaValue = parseScalingMetaVar(metadataContent, metaVar);
|
|
1922
|
-
if (scalingMetaValue
|
|
1923
|
-
metadata[metaVar] = scalingMetaValue
|
|
1924
|
-
servings = scalingMetaValue
|
|
1988
|
+
if (scalingMetaValue) {
|
|
1989
|
+
metadata[metaVar] = scalingMetaValue;
|
|
1990
|
+
servings = getNumericValueFromMetaVar(scalingMetaValue);
|
|
1925
1991
|
}
|
|
1926
1992
|
}
|
|
1927
1993
|
const tags = parseListMetaVar(metadataContent, "tags");
|
|
@@ -2713,6 +2779,7 @@ var _Recipe = class _Recipe {
|
|
|
2713
2779
|
*/
|
|
2714
2780
|
__publicField(this, "servings");
|
|
2715
2781
|
_Recipe.itemCounts.set(this, 0);
|
|
2782
|
+
_Recipe.subgroupIndices.set(this, /* @__PURE__ */ new Map());
|
|
2716
2783
|
if (content) {
|
|
2717
2784
|
this.parse(content);
|
|
2718
2785
|
}
|
|
@@ -2748,27 +2815,17 @@ var _Recipe = class _Recipe {
|
|
|
2748
2815
|
*/
|
|
2749
2816
|
_parseArbitraryScalable(regexMatchGroups, intoArray) {
|
|
2750
2817
|
if (!regexMatchGroups || !regexMatchGroups.arbitraryQuantity) return;
|
|
2751
|
-
const
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
const arbitrary = {
|
|
2763
|
-
quantity: value
|
|
2764
|
-
};
|
|
2765
|
-
if (name) arbitrary.name = name;
|
|
2766
|
-
if (unit) arbitrary.unit = unit;
|
|
2767
|
-
intoArray.push({
|
|
2768
|
-
type: "arbitrary",
|
|
2769
|
-
index: this.arbitraries.push(arbitrary) - 1
|
|
2770
|
-
});
|
|
2771
|
-
}
|
|
2818
|
+
const parsed = parseArbitraryQuantity(regexMatchGroups.arbitraryQuantity);
|
|
2819
|
+
const name = regexMatchGroups.arbitraryName || void 0;
|
|
2820
|
+
const arbitrary = {
|
|
2821
|
+
quantity: parsed.quantity
|
|
2822
|
+
};
|
|
2823
|
+
if (name) arbitrary.name = name;
|
|
2824
|
+
if (parsed.unit) arbitrary.unit = parsed.unit;
|
|
2825
|
+
intoArray.push({
|
|
2826
|
+
type: "arbitrary",
|
|
2827
|
+
index: this.arbitraries.push(arbitrary) - 1
|
|
2828
|
+
});
|
|
2772
2829
|
}
|
|
2773
2830
|
/**
|
|
2774
2831
|
* Parses text for arbitrary scalables and returns NoteItem array.
|
|
@@ -2940,6 +2997,7 @@ var _Recipe = class _Recipe {
|
|
|
2940
2997
|
if (!match?.groups) return;
|
|
2941
2998
|
const groups = match.groups;
|
|
2942
2999
|
const groupKey = groups.gIngredientGroupKey;
|
|
3000
|
+
const subgroupKey = groups.gIngredientSubgroupKey;
|
|
2943
3001
|
let name = groups.gmIngredientName || groups.gsIngredientName;
|
|
2944
3002
|
const preparation = groups.gIngredientPreparation;
|
|
2945
3003
|
const modifiers = groups.gIngredientModifiers;
|
|
@@ -3007,7 +3065,8 @@ var _Recipe = class _Recipe {
|
|
|
3007
3065
|
if (itemQuantity) {
|
|
3008
3066
|
Object.assign(alternative, itemQuantity);
|
|
3009
3067
|
}
|
|
3010
|
-
const
|
|
3068
|
+
const existingSubgroups = this.choices.ingredientGroups.get(groupKey);
|
|
3069
|
+
const existingAlternativesFlat = existingSubgroups?.flat();
|
|
3011
3070
|
function upsertAlternativeToIngredient(ingredients, ingredientIdx, newAlternativeIdx) {
|
|
3012
3071
|
const ingredient = ingredients[ingredientIdx];
|
|
3013
3072
|
if (ingredient) {
|
|
@@ -3018,8 +3077,8 @@ var _Recipe = class _Recipe {
|
|
|
3018
3077
|
}
|
|
3019
3078
|
}
|
|
3020
3079
|
}
|
|
3021
|
-
if (
|
|
3022
|
-
for (const alt of
|
|
3080
|
+
if (existingAlternativesFlat) {
|
|
3081
|
+
for (const alt of existingAlternativesFlat) {
|
|
3023
3082
|
upsertAlternativeToIngredient(this.ingredients, alt.index, idxInList);
|
|
3024
3083
|
upsertAlternativeToIngredient(this.ingredients, idxInList, alt.index);
|
|
3025
3084
|
}
|
|
@@ -3031,14 +3090,35 @@ var _Recipe = class _Recipe {
|
|
|
3031
3090
|
group: groupKey,
|
|
3032
3091
|
alternatives: [alternative]
|
|
3033
3092
|
};
|
|
3093
|
+
if (subgroupKey !== void 0) {
|
|
3094
|
+
newItem.subgroup = subgroupKey;
|
|
3095
|
+
}
|
|
3034
3096
|
items.push(newItem);
|
|
3035
3097
|
const choiceAlternative = deepClone(alternative);
|
|
3036
3098
|
choiceAlternative.itemId = id;
|
|
3037
3099
|
const existingChoice = this.choices.ingredientGroups.get(groupKey);
|
|
3100
|
+
const sgMap = _Recipe.subgroupIndices.get(this);
|
|
3038
3101
|
if (!existingChoice) {
|
|
3039
|
-
this.choices.ingredientGroups.set(groupKey, [choiceAlternative]);
|
|
3102
|
+
this.choices.ingredientGroups.set(groupKey, [[choiceAlternative]]);
|
|
3103
|
+
if (subgroupKey !== void 0) {
|
|
3104
|
+
sgMap.set(groupKey, /* @__PURE__ */ new Map([[subgroupKey, 0]]));
|
|
3105
|
+
}
|
|
3106
|
+
} else if (subgroupKey !== void 0) {
|
|
3107
|
+
const groupSgMap = sgMap.get(groupKey);
|
|
3108
|
+
const existingIdx = groupSgMap?.get(subgroupKey);
|
|
3109
|
+
if (existingIdx !== void 0) {
|
|
3110
|
+
existingChoice[existingIdx].push(choiceAlternative);
|
|
3111
|
+
} else {
|
|
3112
|
+
const newIdx = existingChoice.length;
|
|
3113
|
+
existingChoice.push([choiceAlternative]);
|
|
3114
|
+
if (!groupSgMap) {
|
|
3115
|
+
sgMap.set(groupKey, /* @__PURE__ */ new Map([[subgroupKey, newIdx]]));
|
|
3116
|
+
} else {
|
|
3117
|
+
groupSgMap.set(subgroupKey, newIdx);
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
3040
3120
|
} else {
|
|
3041
|
-
existingChoice.push(choiceAlternative);
|
|
3121
|
+
existingChoice.push([choiceAlternative]);
|
|
3042
3122
|
}
|
|
3043
3123
|
}
|
|
3044
3124
|
/**
|
|
@@ -3095,15 +3175,16 @@ var _Recipe = class _Recipe {
|
|
|
3095
3175
|
(item2) => item2.type === "ingredient"
|
|
3096
3176
|
)) {
|
|
3097
3177
|
const isGrouped = "group" in item && item.group !== void 0;
|
|
3098
|
-
const
|
|
3178
|
+
const groupSubgroups = isGrouped ? this.choices.ingredientGroups.get(item.group) : void 0;
|
|
3099
3179
|
let selectedAltIndex = 0;
|
|
3100
|
-
let isSelected
|
|
3101
|
-
let hasExplicitChoice
|
|
3180
|
+
let isSelected;
|
|
3181
|
+
let hasExplicitChoice;
|
|
3102
3182
|
if (isGrouped) {
|
|
3103
3183
|
const groupChoice = choices?.ingredientGroups?.get(item.group);
|
|
3104
3184
|
hasExplicitChoice = groupChoice !== void 0;
|
|
3105
|
-
const
|
|
3106
|
-
|
|
3185
|
+
const targetSubgroupIndex = groupChoice ?? 0;
|
|
3186
|
+
const selectedSubgroup = groupSubgroups?.[targetSubgroupIndex];
|
|
3187
|
+
isSelected = selectedSubgroup?.some((alt) => alt.itemId === item.id) ?? false;
|
|
3107
3188
|
} else {
|
|
3108
3189
|
const itemChoice = choices?.ingredientItems?.get(item.id);
|
|
3109
3190
|
hasExplicitChoice = itemChoice !== void 0;
|
|
@@ -3113,8 +3194,8 @@ var _Recipe = class _Recipe {
|
|
|
3113
3194
|
const alternative = item.alternatives[selectedAltIndex];
|
|
3114
3195
|
if (!alternative || !isSelected) continue;
|
|
3115
3196
|
selectedIndices.add(alternative.index);
|
|
3116
|
-
const
|
|
3117
|
-
for (const alt of
|
|
3197
|
+
const allAltsFlat = isGrouped ? groupSubgroups.flat() : item.alternatives;
|
|
3198
|
+
for (const alt of allAltsFlat) {
|
|
3118
3199
|
referencedIndices.add(alt.index);
|
|
3119
3200
|
}
|
|
3120
3201
|
if (!alternative.quantity) continue;
|
|
@@ -3126,10 +3207,34 @@ var _Recipe = class _Recipe {
|
|
|
3126
3207
|
};
|
|
3127
3208
|
const quantityEntry = alternative.equivalents?.length ? { or: [baseQty, ...alternative.equivalents] } : baseQty;
|
|
3128
3209
|
let alternativeRefs;
|
|
3129
|
-
if (!hasExplicitChoice &&
|
|
3130
|
-
|
|
3131
|
-
(
|
|
3132
|
-
)
|
|
3210
|
+
if (!hasExplicitChoice && groupSubgroups && groupSubgroups.length > 1) {
|
|
3211
|
+
const currentSubgroupIdx = groupSubgroups.findIndex(
|
|
3212
|
+
(sg) => sg.some((alt) => alt.itemId === item.id)
|
|
3213
|
+
);
|
|
3214
|
+
alternativeRefs = groupSubgroups.filter((_, idx) => idx !== currentSubgroupIdx).flatMap(
|
|
3215
|
+
(subgroup) => subgroup.map((otherAlt) => {
|
|
3216
|
+
const ref = {
|
|
3217
|
+
index: otherAlt.index
|
|
3218
|
+
};
|
|
3219
|
+
if (otherAlt.quantity) {
|
|
3220
|
+
const altQty = {
|
|
3221
|
+
quantity: otherAlt.quantity,
|
|
3222
|
+
...otherAlt.unit && {
|
|
3223
|
+
unit: otherAlt.unit.name
|
|
3224
|
+
},
|
|
3225
|
+
...otherAlt.equivalents && {
|
|
3226
|
+
equivalents: otherAlt.equivalents.map(
|
|
3227
|
+
(eq) => toPlainUnit(eq)
|
|
3228
|
+
)
|
|
3229
|
+
}
|
|
3230
|
+
};
|
|
3231
|
+
ref.quantities = [altQty];
|
|
3232
|
+
}
|
|
3233
|
+
return ref;
|
|
3234
|
+
})
|
|
3235
|
+
);
|
|
3236
|
+
} else if (!hasExplicitChoice && !isGrouped && allAltsFlat.length > 1) {
|
|
3237
|
+
alternativeRefs = allAltsFlat.filter((alt) => alt.index !== alternative.index).map((otherAlt) => {
|
|
3133
3238
|
const ref = { index: otherAlt.index };
|
|
3134
3239
|
if (otherAlt.quantity) {
|
|
3135
3240
|
const altQty = {
|
|
@@ -3547,8 +3652,10 @@ var _Recipe = class _Recipe {
|
|
|
3547
3652
|
}
|
|
3548
3653
|
}
|
|
3549
3654
|
}
|
|
3550
|
-
for (const
|
|
3551
|
-
|
|
3655
|
+
for (const subgroups of newRecipe.choices.ingredientGroups.values()) {
|
|
3656
|
+
for (const subgroup of subgroups) {
|
|
3657
|
+
scaleAlternativesBy(subgroup, factor);
|
|
3658
|
+
}
|
|
3552
3659
|
}
|
|
3553
3660
|
for (const alternatives of newRecipe.choices.ingredientItems.values()) {
|
|
3554
3661
|
scaleAlternativesBy(alternatives, factor);
|
|
@@ -3558,37 +3665,34 @@ var _Recipe = class _Recipe {
|
|
|
3558
3665
|
arbitrary.quantity,
|
|
3559
3666
|
factor
|
|
3560
3667
|
);
|
|
3668
|
+
const optimized = applyBestUnit(
|
|
3669
|
+
{ quantity: arbitrary.quantity, unit: arbitrary.unit },
|
|
3670
|
+
unitSystem
|
|
3671
|
+
);
|
|
3672
|
+
arbitrary.quantity = optimized.quantity;
|
|
3673
|
+
arbitrary.unit = optimized.unit;
|
|
3561
3674
|
}
|
|
3562
3675
|
newRecipe._populateIngredientQuantities();
|
|
3563
3676
|
newRecipe.servings = Big4(originalServings).times(factor).toNumber();
|
|
3564
|
-
|
|
3565
|
-
if (
|
|
3566
|
-
const
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
Big4(servingsValue).times(factor).toNumber()
|
|
3571
|
-
);
|
|
3572
|
-
}
|
|
3573
|
-
}
|
|
3574
|
-
if (newRecipe.metadata.yield && this.metadata.yield) {
|
|
3575
|
-
if (floatRegex.test(String(this.metadata.yield).replace(",", ".").trim())) {
|
|
3576
|
-
const yieldValue = parseFloat(
|
|
3577
|
-
String(this.metadata.yield).replace(",", ".")
|
|
3677
|
+
for (const metaVar of ["servings", "yield", "serves"]) {
|
|
3678
|
+
if (newRecipe.metadata[metaVar] && this.metadata[metaVar]) {
|
|
3679
|
+
const original = this.metadata[metaVar];
|
|
3680
|
+
const scaledQuantity = multiplyQuantityValue(
|
|
3681
|
+
original.quantity,
|
|
3682
|
+
factor
|
|
3578
3683
|
);
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
}
|
|
3583
|
-
}
|
|
3584
|
-
if (newRecipe.metadata.serves && this.metadata.serves) {
|
|
3585
|
-
if (floatRegex.test(String(this.metadata.serves).replace(",", ".").trim())) {
|
|
3586
|
-
const servesValue = parseFloat(
|
|
3587
|
-
String(this.metadata.serves).replace(",", ".")
|
|
3588
|
-
);
|
|
3589
|
-
newRecipe.metadata.serves = String(
|
|
3590
|
-
Big4(servesValue).times(factor).toNumber()
|
|
3684
|
+
const optimized = applyBestUnit(
|
|
3685
|
+
{ quantity: scaledQuantity, unit: original.unit },
|
|
3686
|
+
unitSystem
|
|
3591
3687
|
);
|
|
3688
|
+
const scaled = {
|
|
3689
|
+
quantity: optimized.quantity
|
|
3690
|
+
};
|
|
3691
|
+
if (optimized.unit) scaled.unit = optimized.unit;
|
|
3692
|
+
if (original.textBefore) scaled.textBefore = original.textBefore;
|
|
3693
|
+
if (original.textAfter) scaled.textAfter = original.textAfter;
|
|
3694
|
+
if (original.text) scaled.text = original.text;
|
|
3695
|
+
newRecipe.metadata[metaVar] = scaled;
|
|
3592
3696
|
}
|
|
3593
3697
|
}
|
|
3594
3698
|
return newRecipe;
|
|
@@ -3738,8 +3842,10 @@ var _Recipe = class _Recipe {
|
|
|
3738
3842
|
}
|
|
3739
3843
|
}
|
|
3740
3844
|
}
|
|
3741
|
-
for (const
|
|
3742
|
-
|
|
3845
|
+
for (const subgroups of newRecipe.choices.ingredientGroups.values()) {
|
|
3846
|
+
for (const subgroup of subgroups) {
|
|
3847
|
+
convertAlternatives(subgroup);
|
|
3848
|
+
}
|
|
3743
3849
|
}
|
|
3744
3850
|
for (const alternatives of newRecipe.choices.ingredientItems.values()) {
|
|
3745
3851
|
convertAlternatives(alternatives);
|
|
@@ -3791,6 +3897,11 @@ __publicField(_Recipe, "unitSystems", /* @__PURE__ */ new WeakMap());
|
|
|
3791
3897
|
* Used for giving ID numbers to items during parsing.
|
|
3792
3898
|
*/
|
|
3793
3899
|
__publicField(_Recipe, "itemCounts", /* @__PURE__ */ new WeakMap());
|
|
3900
|
+
/**
|
|
3901
|
+
* External storage for subgroup index tracking during parsing.
|
|
3902
|
+
* Maps groupKey → subgroupKey → index within the subgroups array.
|
|
3903
|
+
*/
|
|
3904
|
+
__publicField(_Recipe, "subgroupIndices", /* @__PURE__ */ new WeakMap());
|
|
3794
3905
|
var Recipe = _Recipe;
|
|
3795
3906
|
|
|
3796
3907
|
// src/classes/shopping_list.ts
|
|
@@ -4628,10 +4739,10 @@ function isGroupedItem(item) {
|
|
|
4628
4739
|
function isAlternativeSelected(recipe, choices, item, alternativeIndex) {
|
|
4629
4740
|
if (item.group) {
|
|
4630
4741
|
const selectedIndex2 = choices?.ingredientGroups?.get(item.group);
|
|
4631
|
-
const
|
|
4632
|
-
if (
|
|
4633
|
-
const
|
|
4634
|
-
return
|
|
4742
|
+
const groupSubgroups = recipe.choices.ingredientGroups.get(item.group);
|
|
4743
|
+
if (groupSubgroups && selectedIndex2 !== void 0 && selectedIndex2 < groupSubgroups.length) {
|
|
4744
|
+
const selectedSubgroup = groupSubgroups[selectedIndex2];
|
|
4745
|
+
return selectedSubgroup?.some((alt) => alt.itemId === item.id);
|
|
4635
4746
|
}
|
|
4636
4747
|
return false;
|
|
4637
4748
|
}
|
|
@@ -4671,6 +4782,7 @@ export {
|
|
|
4671
4782
|
// v8 ignore else -- @preserve
|
|
4672
4783
|
// v8 ignore if -- @preserve
|
|
4673
4784
|
/* v8 ignore else -- expliciting error type -- @preserve */
|
|
4785
|
+
/* v8 ignore next 4 -- @preserve: defensive guard; regex always matches */
|
|
4674
4786
|
// v8 ignore if -- @preserve: defensive type guard
|
|
4675
4787
|
/* v8 ignore if -- @preserve */
|
|
4676
4788
|
// v8 ignore next -- @preserve
|