@tmlmt/cooklang-parser 3.0.0-alpha.19 → 3.0.0-alpha.21
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 +194 -139
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +41 -54
- package/dist/index.d.ts +41 -54
- package/dist/index.js +194 -139
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -276,7 +276,7 @@ var nonWordChar = "\\s@#~\\[\\]{(,;:!?";
|
|
|
276
276
|
var nonWordCharStrict = "\\s@#~\\[\\]{(,;:!?|";
|
|
277
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();
|
|
278
278
|
var inlineIngredientAlternativesRegex = new RegExp("\\|" + ingredientWithAlternativeRegex.source.slice(1));
|
|
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();
|
|
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();
|
|
280
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().startGroup().literal("[").startNamedGroup("gIngredientNote").notAnyOf("\\]").oneOrMore().lazy().endGroup().literal("]").endGroup().optional().toRegExp();
|
|
281
281
|
var ingredientAliasRegex = d().startAnchor().startNamedGroup("ingredientListName").notAnyOf("|").oneOrMore().endGroup().literal("|").startNamedGroup("ingredientDisplayName").notAnyOf("|").oneOrMore().endGroup().endAnchor().toRegExp();
|
|
282
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();
|
|
@@ -292,11 +292,18 @@ var tokensRegex = new RegExp(
|
|
|
292
292
|
].map((r2) => r2.source).join("|"),
|
|
293
293
|
"gu"
|
|
294
294
|
);
|
|
295
|
-
var
|
|
296
|
-
var
|
|
297
|
-
var
|
|
298
|
-
|
|
299
|
-
|
|
295
|
+
var yieldPrefixPart = d().startAnchor().literal("yield").literal(":").anyOf(" ").zeroOrMore().startNamedGroup("servingsPrefix").nonWhitespace().startGroup().anyCharacter().zeroOrMore().lazy().nonWhitespace().endGroup().optional().endGroup().optional().anyOf(" ").zeroOrMore().toRegExp();
|
|
296
|
+
var yieldSuffixPart = d().anyOf(" ").zeroOrMore().startNamedGroup("servingsSuffix").nonWhitespace().startGroup().anyCharacter().zeroOrMore().nonWhitespace().endGroup().optional().endGroup().optional().anyOf(" ").zeroOrMore().endAnchor().toRegExp();
|
|
297
|
+
var yieldMetaValueWithUnitRegex = new RegExp(
|
|
298
|
+
yieldPrefixPart.source + arbitraryScalableRegex.source + yieldSuffixPart.source,
|
|
299
|
+
"m"
|
|
300
|
+
);
|
|
301
|
+
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();
|
|
302
|
+
var yieldMetaValueRegex = new RegExp(
|
|
303
|
+
[
|
|
304
|
+
yieldMetaValueWithUnitRegex.source,
|
|
305
|
+
yieldMetaValueAsQuantityRegex.source
|
|
306
|
+
].join("|"),
|
|
300
307
|
"m"
|
|
301
308
|
);
|
|
302
309
|
var commentRegex = d().literal("--").anyCharacter().zeroOrMore().global().toRegExp();
|
|
@@ -305,7 +312,7 @@ var shoppingListRegex = d().literal("[").startNamedGroup("name").anyCharacter().
|
|
|
305
312
|
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();
|
|
306
313
|
var numberLikeRegex = d().startAnchor().digit().oneOrMore().startGroup().anyOf(".,/").exactly(1).digit().oneOrMore().endGroup().optional().endAnchor().toRegExp();
|
|
307
314
|
var floatRegex = d().startAnchor().digit().oneOrMore().startGroup().anyOf(".").exactly(1).digit().oneOrMore().endGroup().optional().endAnchor().toRegExp();
|
|
308
|
-
var variantTagRegex = d().startAnchor().literal("[").startNamedGroup("variantOptionalPrefix").literal("?").endGroup().optional().startNamedGroup("variantNames").notAnyOf("\\]").
|
|
315
|
+
var variantTagRegex = d().startAnchor().literal("[").startNamedGroup("variantOptionalPrefix").literal("?").endGroup().optional().startNamedGroup("variantNames").notAnyOf("\\]").oneOrMore().endGroup().optional().literal("]").whitespace().zeroOrMore().toRegExp();
|
|
309
316
|
var mdEscaped = d().literal("\\").startCaptureGroup().anyOf("*_`").endGroup();
|
|
310
317
|
var mdInlineCode = d().literal("`").startCaptureGroup().notAnyOf("`").oneOrMore().lazy().endGroup().literal("`");
|
|
311
318
|
var mdLink = d().literal("[").startCaptureGroup().notAnyOf("\\]").oneOrMore().lazy().endGroup().literal("](").startCaptureGroup().notAnyOf(")").oneOrMore().lazy().endGroup().literal(")");
|
|
@@ -881,7 +888,7 @@ var NoProductMatchError = class extends Error {
|
|
|
881
888
|
constructor(item_name, code) {
|
|
882
889
|
const messageMap = {
|
|
883
890
|
incompatibleUnits: `The units of the products in the catalogue are incompatible with ingredient ${item_name} in the shopping list.`,
|
|
884
|
-
noProduct:
|
|
891
|
+
noProduct: `No product was found linked to ingredient name ${item_name} in the shopping list`,
|
|
885
892
|
textValue: `Ingredient ${item_name} has a text value as quantity and can therefore not be matched with any product in the catalogue.`,
|
|
886
893
|
noQuantity: `Ingredient ${item_name} has no quantity and can therefore not be matched with any product in the catalogue.`,
|
|
887
894
|
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`
|
|
@@ -1484,7 +1491,7 @@ function stringifyFixedValue(quantity) {
|
|
|
1484
1491
|
return String(quantity.value.decimal);
|
|
1485
1492
|
else return quantity.value.text;
|
|
1486
1493
|
}
|
|
1487
|
-
function
|
|
1494
|
+
function parseQuantityValue(input_str) {
|
|
1488
1495
|
const clean_str = String(input_str).trim();
|
|
1489
1496
|
if (rangeRegex.test(clean_str)) {
|
|
1490
1497
|
const range_parts = clean_str.split("-");
|
|
@@ -1498,12 +1505,12 @@ function parseQuantityWithUnit(input) {
|
|
|
1498
1505
|
const trimmed = input.trim();
|
|
1499
1506
|
const separatorIndex = trimmed.indexOf("%");
|
|
1500
1507
|
if (separatorIndex === -1) {
|
|
1501
|
-
return { value:
|
|
1508
|
+
return { value: parseQuantityValue(trimmed) };
|
|
1502
1509
|
}
|
|
1503
1510
|
const valuePart = trimmed.slice(0, separatorIndex).trim();
|
|
1504
1511
|
const unitPart = trimmed.slice(separatorIndex + 1).trim();
|
|
1505
1512
|
return {
|
|
1506
|
-
value:
|
|
1513
|
+
value: parseQuantityValue(valuePart),
|
|
1507
1514
|
unit: unitPart || void 0
|
|
1508
1515
|
};
|
|
1509
1516
|
}
|
|
@@ -1693,7 +1700,7 @@ function parseArbitraryQuantity(raw) {
|
|
|
1693
1700
|
"Arbitrary quantities must have a numerical value"
|
|
1694
1701
|
);
|
|
1695
1702
|
}
|
|
1696
|
-
const value =
|
|
1703
|
+
const value = parseQuantityValue(quantityMatch.groups.quantity);
|
|
1697
1704
|
const unit = quantityMatch.groups.unit;
|
|
1698
1705
|
if (!value || value.type === "fixed" && value.value.type === "text") {
|
|
1699
1706
|
throw new InvalidQuantityFormat(
|
|
@@ -1707,40 +1714,40 @@ function parseArbitraryQuantity(raw) {
|
|
|
1707
1714
|
if (unit) arbitrary.unit = unit;
|
|
1708
1715
|
return arbitrary;
|
|
1709
1716
|
}
|
|
1710
|
-
function
|
|
1711
|
-
const
|
|
1712
|
-
if (
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
+
function parseServingsMetaVar(content, varName) {
|
|
1718
|
+
const raw = parseSimpleMetaVar(content, varName);
|
|
1719
|
+
if (raw === void 0) return void 0;
|
|
1720
|
+
const num = Number(raw);
|
|
1721
|
+
if (isNaN(num)) {
|
|
1722
|
+
return { numericValue: 1, rawValue: raw };
|
|
1723
|
+
}
|
|
1724
|
+
return { numericValue: num, rawValue: num };
|
|
1725
|
+
}
|
|
1726
|
+
function parseYieldMetaVar(content) {
|
|
1727
|
+
const match = content.match(yieldMetaValueRegex);
|
|
1728
|
+
if (!match) return void 0;
|
|
1729
|
+
if (match.groups?.arbitraryQuantity) {
|
|
1730
|
+
const parsed = parseArbitraryQuantity(match.groups.arbitraryQuantity);
|
|
1731
|
+
const result = {
|
|
1717
1732
|
quantity: parsed.quantity
|
|
1718
1733
|
};
|
|
1719
|
-
if (parsed.unit)
|
|
1720
|
-
if (
|
|
1721
|
-
|
|
1734
|
+
if (parsed.unit) result.unit = parsed.unit;
|
|
1735
|
+
if (match.groups.servingsPrefix) {
|
|
1736
|
+
result.textBefore = match.groups.servingsPrefix;
|
|
1722
1737
|
}
|
|
1723
|
-
if (
|
|
1724
|
-
|
|
1738
|
+
if (match.groups.servingsSuffix) {
|
|
1739
|
+
result.textAfter = match.groups.servingsSuffix;
|
|
1725
1740
|
}
|
|
1726
|
-
return
|
|
1727
|
-
}
|
|
1728
|
-
const varMatch = content.match(scalingSimpleMetaValueRegex(varName));
|
|
1729
|
-
if (!varMatch) return void 0;
|
|
1730
|
-
if (isNaN(Number(varMatch[2]?.trim()))) {
|
|
1731
|
-
throw new Error("Scaling variables should be numbers");
|
|
1741
|
+
return result;
|
|
1732
1742
|
}
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
};
|
|
1740
|
-
if (varMatch[3]) {
|
|
1741
|
-
result.text = `${varMatch[3].trim()}`;
|
|
1743
|
+
if (match.groups?.quantity) {
|
|
1744
|
+
const result = {
|
|
1745
|
+
quantity: parseQuantityValue(match.groups.quantity)
|
|
1746
|
+
};
|
|
1747
|
+
if (match.groups.unit) result.unit = match.groups.unit;
|
|
1748
|
+
return result;
|
|
1742
1749
|
}
|
|
1743
|
-
return
|
|
1750
|
+
return void 0;
|
|
1744
1751
|
}
|
|
1745
1752
|
function parseListMetaVar(content, varName) {
|
|
1746
1753
|
const listMatch = content.match(
|
|
@@ -1858,12 +1865,12 @@ function parseAnyMetaVar(content, varName) {
|
|
|
1858
1865
|
if (simple) return parseMetadataValue(simple);
|
|
1859
1866
|
return void 0;
|
|
1860
1867
|
}
|
|
1861
|
-
function
|
|
1868
|
+
function getNumericValueFromYield(v) {
|
|
1862
1869
|
if (v.quantity.type === "fixed" && v.quantity.value.type !== "text") {
|
|
1863
1870
|
return getNumericValue(v.quantity.value);
|
|
1864
1871
|
}
|
|
1865
1872
|
if (v.quantity.type === "range") return getNumericValue(v.quantity.min);
|
|
1866
|
-
return
|
|
1873
|
+
return 1;
|
|
1867
1874
|
}
|
|
1868
1875
|
function extractMetadata(content) {
|
|
1869
1876
|
const metadata = {};
|
|
@@ -1988,11 +1995,16 @@ function extractMetadata(content) {
|
|
|
1988
1995
|
};
|
|
1989
1996
|
unitSystem = unitSystemMap[unitSystemRaw.toLowerCase()];
|
|
1990
1997
|
}
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1998
|
+
const yieldValue = parseYieldMetaVar(metadataContent);
|
|
1999
|
+
if (yieldValue) {
|
|
2000
|
+
metadata.yield = yieldValue;
|
|
2001
|
+
servings = getNumericValueFromYield(yieldValue);
|
|
2002
|
+
}
|
|
2003
|
+
for (const metaVar of ["serves", "servings"]) {
|
|
2004
|
+
const result = parseServingsMetaVar(metadataContent, metaVar);
|
|
2005
|
+
if (result !== void 0) {
|
|
2006
|
+
metadata[metaVar] = result.rawValue;
|
|
2007
|
+
servings = result.numericValue;
|
|
1996
2008
|
}
|
|
1997
2009
|
}
|
|
1998
2010
|
const tags = parseListMetaVar(metadataContent, "tags");
|
|
@@ -2273,7 +2285,7 @@ var ProductCatalog = class {
|
|
|
2273
2285
|
const sizeStrings = Array.isArray(size) ? size : [size];
|
|
2274
2286
|
const sizes = sizeStrings.map((sizeStr) => {
|
|
2275
2287
|
const sizeAndUnitRaw = sizeStr.split("%");
|
|
2276
|
-
const sizeParsed =
|
|
2288
|
+
const sizeParsed = parseQuantityValue(
|
|
2277
2289
|
sizeAndUnitRaw[0]
|
|
2278
2290
|
);
|
|
2279
2291
|
const productSize = { size: sizeParsed };
|
|
@@ -2416,7 +2428,7 @@ var Section = class {
|
|
|
2416
2428
|
};
|
|
2417
2429
|
|
|
2418
2430
|
// src/quantities/alternatives.ts
|
|
2419
|
-
import
|
|
2431
|
+
import Big4 from "big.js";
|
|
2420
2432
|
|
|
2421
2433
|
// src/units/lookup.ts
|
|
2422
2434
|
function findListWithCompatibleQuantity(list, quantity) {
|
|
@@ -2439,10 +2451,14 @@ function findCompatibleQuantityWithinList(list, quantity) {
|
|
|
2439
2451
|
}
|
|
2440
2452
|
|
|
2441
2453
|
// src/utils/general.ts
|
|
2454
|
+
import Big3 from "big.js";
|
|
2442
2455
|
var legacyDeepClone = (v) => {
|
|
2443
2456
|
if (v === null || typeof v !== "object") {
|
|
2444
2457
|
return v;
|
|
2445
2458
|
}
|
|
2459
|
+
if (v instanceof Big3) {
|
|
2460
|
+
return new Big3(v);
|
|
2461
|
+
}
|
|
2446
2462
|
if (v instanceof Map) {
|
|
2447
2463
|
return new Map(
|
|
2448
2464
|
Array.from(v.entries()).map(([k, val]) => [
|
|
@@ -2452,7 +2468,9 @@ var legacyDeepClone = (v) => {
|
|
|
2452
2468
|
);
|
|
2453
2469
|
}
|
|
2454
2470
|
if (v instanceof Set) {
|
|
2455
|
-
return new Set(
|
|
2471
|
+
return new Set(
|
|
2472
|
+
Array.from(v).map((val) => legacyDeepClone(val))
|
|
2473
|
+
);
|
|
2456
2474
|
}
|
|
2457
2475
|
if (v instanceof Date) {
|
|
2458
2476
|
return new Date(v.getTime());
|
|
@@ -2466,7 +2484,7 @@ var legacyDeepClone = (v) => {
|
|
|
2466
2484
|
}
|
|
2467
2485
|
return cloned;
|
|
2468
2486
|
};
|
|
2469
|
-
var deepClone = (v) =>
|
|
2487
|
+
var deepClone = (v) => legacyDeepClone(v);
|
|
2470
2488
|
|
|
2471
2489
|
// src/quantities/alternatives.ts
|
|
2472
2490
|
function getEquivalentUnitsLists(...quantities) {
|
|
@@ -2692,7 +2710,7 @@ function regroupQuantitiesAndExpandEquivalents(sum, unitsLists, system) {
|
|
|
2692
2710
|
return main.reduce((acc, v) => {
|
|
2693
2711
|
const mainInList = findCompatibleQuantityWithinList(list, v);
|
|
2694
2712
|
const conversionRatio = getBaseUnitRatio(v, mainInList);
|
|
2695
|
-
const valueInOriginalUnit =
|
|
2713
|
+
const valueInOriginalUnit = Big4(getAverageValue(v.quantity)).times(
|
|
2696
2714
|
conversionRatio
|
|
2697
2715
|
);
|
|
2698
2716
|
const newValue = {
|
|
@@ -2704,7 +2722,7 @@ function regroupQuantitiesAndExpandEquivalents(sum, unitsLists, system) {
|
|
|
2704
2722
|
decimal: valueInOriginalUnit.toNumber()
|
|
2705
2723
|
}
|
|
2706
2724
|
},
|
|
2707
|
-
|
|
2725
|
+
Big4(getAverageValue(equiv.quantity)).div(
|
|
2708
2726
|
getAverageValue(mainInList.quantity)
|
|
2709
2727
|
)
|
|
2710
2728
|
)
|
|
@@ -2745,9 +2763,50 @@ function addEquivalentsAndSimplify(quantities, system) {
|
|
|
2745
2763
|
return { and: regrouped.map(toPlainUnit) };
|
|
2746
2764
|
}
|
|
2747
2765
|
}
|
|
2766
|
+
function buildEquivalenceRatioMap(unitsLists) {
|
|
2767
|
+
const ratioMap = {};
|
|
2768
|
+
for (const list of unitsLists) {
|
|
2769
|
+
for (const equiv of list) {
|
|
2770
|
+
const equivValue = getAverageValue(equiv.quantity);
|
|
2771
|
+
for (const primary of list) {
|
|
2772
|
+
if (primary === equiv) continue;
|
|
2773
|
+
const primaryValue = getAverageValue(primary.quantity);
|
|
2774
|
+
const equivUnit = normalizeUnit(equiv.unit.name)?.name ?? equiv.unit.name;
|
|
2775
|
+
const primaryUnit = normalizeUnit(primary.unit.name)?.name ?? primary.unit.name;
|
|
2776
|
+
ratioMap[equivUnit] ?? (ratioMap[equivUnit] = {});
|
|
2777
|
+
ratioMap[equivUnit][primaryUnit] = equivValue / primaryValue;
|
|
2778
|
+
}
|
|
2779
|
+
}
|
|
2780
|
+
}
|
|
2781
|
+
return ratioMap;
|
|
2782
|
+
}
|
|
2783
|
+
function recomputeEquivalents(primaries, ratioMap, equivUnits) {
|
|
2784
|
+
const equivalents = [];
|
|
2785
|
+
for (const equivUnit of equivUnits) {
|
|
2786
|
+
const ratios = ratioMap[normalizeUnit(equivUnit)?.name ?? equivUnit];
|
|
2787
|
+
let total = 0;
|
|
2788
|
+
for (const primary of primaries) {
|
|
2789
|
+
const pUnit = normalizeUnit(primary.unit ?? NO_UNIT)?.name ?? primary.unit ?? NO_UNIT;
|
|
2790
|
+
const ratio = ratios[pUnit];
|
|
2791
|
+
if (ratio === void 0) continue;
|
|
2792
|
+
const pValue = getAverageValue(primary.quantity);
|
|
2793
|
+
total += pValue * ratio;
|
|
2794
|
+
}
|
|
2795
|
+
if (total > 0) {
|
|
2796
|
+
equivalents.push({
|
|
2797
|
+
quantity: {
|
|
2798
|
+
type: "fixed",
|
|
2799
|
+
value: { type: "decimal", decimal: total }
|
|
2800
|
+
},
|
|
2801
|
+
...equivUnit !== "" && { unit: equivUnit }
|
|
2802
|
+
});
|
|
2803
|
+
}
|
|
2804
|
+
}
|
|
2805
|
+
return equivalents.length > 0 ? equivalents : void 0;
|
|
2806
|
+
}
|
|
2748
2807
|
|
|
2749
2808
|
// src/classes/recipe.ts
|
|
2750
|
-
import
|
|
2809
|
+
import Big5 from "big.js";
|
|
2751
2810
|
var _Recipe = class _Recipe {
|
|
2752
2811
|
/**
|
|
2753
2812
|
* Creates a new Recipe instance.
|
|
@@ -2869,7 +2928,7 @@ var _Recipe = class _Recipe {
|
|
|
2869
2928
|
let quantityMatch = quantityRaw.match(quantityAlternativeRegex);
|
|
2870
2929
|
const quantities = [];
|
|
2871
2930
|
while (quantityMatch?.groups) {
|
|
2872
|
-
const value = quantityMatch.groups.quantity ?
|
|
2931
|
+
const value = quantityMatch.groups.quantity ? parseQuantityValue(quantityMatch.groups.quantity) : void 0;
|
|
2873
2932
|
const unit = quantityMatch.groups.unit;
|
|
2874
2933
|
if (value) {
|
|
2875
2934
|
const newQuantity = { quantity: value };
|
|
@@ -3237,7 +3296,9 @@ var _Recipe = class _Recipe {
|
|
|
3237
3296
|
} else {
|
|
3238
3297
|
const targetSubgroupIndex = 0;
|
|
3239
3298
|
const selectedSubgroup = groupSubgroups?.[targetSubgroupIndex];
|
|
3240
|
-
isSelected = selectedSubgroup
|
|
3299
|
+
isSelected = selectedSubgroup.some(
|
|
3300
|
+
(alt) => alt.itemId === item.id
|
|
3301
|
+
);
|
|
3241
3302
|
}
|
|
3242
3303
|
} else {
|
|
3243
3304
|
const targetSubgroupIndex = groupChoice ?? 0;
|
|
@@ -3636,16 +3697,10 @@ var _Recipe = class _Recipe {
|
|
|
3636
3697
|
}
|
|
3637
3698
|
sectionName = sectionName.slice(sectionVarMatch[0].length).trim();
|
|
3638
3699
|
}
|
|
3639
|
-
if (
|
|
3640
|
-
section
|
|
3641
|
-
if (sectionVariants) section.variants = sectionVariants;
|
|
3642
|
-
if (sectionOptional) section.optional = true;
|
|
3643
|
-
} else {
|
|
3644
|
-
if (!section.isBlank()) {
|
|
3645
|
-
this.sections.push(section);
|
|
3646
|
-
}
|
|
3647
|
-
section = new Section(sectionName, sectionVariants, sectionOptional);
|
|
3700
|
+
if (!section.isBlank()) {
|
|
3701
|
+
this.sections.push(section);
|
|
3648
3702
|
}
|
|
3703
|
+
section = new Section(sectionName, sectionVariants, sectionOptional);
|
|
3649
3704
|
blankLineBefore = true;
|
|
3650
3705
|
inNote = false;
|
|
3651
3706
|
continue;
|
|
@@ -3711,7 +3766,7 @@ var _Recipe = class _Recipe {
|
|
|
3711
3766
|
if (modifiers !== void 0 && modifiers.includes("-")) {
|
|
3712
3767
|
flags.push("hidden");
|
|
3713
3768
|
}
|
|
3714
|
-
const quantity = quantityRaw ?
|
|
3769
|
+
const quantity = quantityRaw ? parseQuantityValue(quantityRaw) : void 0;
|
|
3715
3770
|
const newCookware = {
|
|
3716
3771
|
name
|
|
3717
3772
|
};
|
|
@@ -3743,7 +3798,7 @@ var _Recipe = class _Recipe {
|
|
|
3743
3798
|
throw new Error("Timer missing unit");
|
|
3744
3799
|
}
|
|
3745
3800
|
const name = groups.timerName || void 0;
|
|
3746
|
-
const duration =
|
|
3801
|
+
const duration = parseQuantityValue(durationStr);
|
|
3747
3802
|
const timerObj = {
|
|
3748
3803
|
name,
|
|
3749
3804
|
duration,
|
|
@@ -3783,7 +3838,7 @@ var _Recipe = class _Recipe {
|
|
|
3783
3838
|
if (originalServings === void 0 || originalServings === 0) {
|
|
3784
3839
|
originalServings = 1;
|
|
3785
3840
|
}
|
|
3786
|
-
const factor =
|
|
3841
|
+
const factor = Big5(newServings).div(originalServings);
|
|
3787
3842
|
return this.scaleBy(factor);
|
|
3788
3843
|
}
|
|
3789
3844
|
/**
|
|
@@ -3802,7 +3857,7 @@ var _Recipe = class _Recipe {
|
|
|
3802
3857
|
function scaleAlternativesBy(alternatives, factor2) {
|
|
3803
3858
|
for (const alternative of alternatives) {
|
|
3804
3859
|
if (alternative.quantity) {
|
|
3805
|
-
const scaleFactor = alternative.scalable ?
|
|
3860
|
+
const scaleFactor = alternative.scalable ? Big5(factor2) : 1;
|
|
3806
3861
|
if (alternative.quantity.type !== "fixed" || alternative.quantity.value.type !== "text") {
|
|
3807
3862
|
alternative.quantity = multiplyQuantityValue(
|
|
3808
3863
|
alternative.quantity,
|
|
@@ -3875,10 +3930,16 @@ var _Recipe = class _Recipe {
|
|
|
3875
3930
|
arbitrary.unit = optimized.unit;
|
|
3876
3931
|
}
|
|
3877
3932
|
newRecipe._populateIngredientQuantities();
|
|
3878
|
-
newRecipe.servings =
|
|
3879
|
-
for (const metaVar of ["servings", "
|
|
3880
|
-
if (newRecipe.metadata[metaVar]
|
|
3881
|
-
|
|
3933
|
+
newRecipe.servings = Big5(originalServings).times(factor).toNumber();
|
|
3934
|
+
for (const metaVar of ["servings", "serves"]) {
|
|
3935
|
+
if (typeof newRecipe.metadata[metaVar] === "number") {
|
|
3936
|
+
newRecipe.metadata[metaVar] = Big5(newRecipe.metadata[metaVar]).times(factor).toNumber();
|
|
3937
|
+
}
|
|
3938
|
+
}
|
|
3939
|
+
if (newRecipe.metadata.yield && this.metadata.yield) {
|
|
3940
|
+
const original = this.metadata.yield;
|
|
3941
|
+
if (original.quantity.type === "fixed" && original.quantity.value.type === "text") {
|
|
3942
|
+
} else {
|
|
3882
3943
|
const scaledQuantity = multiplyQuantityValue(
|
|
3883
3944
|
original.quantity,
|
|
3884
3945
|
factor
|
|
@@ -3893,8 +3954,7 @@ var _Recipe = class _Recipe {
|
|
|
3893
3954
|
if (optimized.unit) scaled.unit = optimized.unit;
|
|
3894
3955
|
if (original.textBefore) scaled.textBefore = original.textBefore;
|
|
3895
3956
|
if (original.textAfter) scaled.textAfter = original.textAfter;
|
|
3896
|
-
|
|
3897
|
-
newRecipe.metadata[metaVar] = scaled;
|
|
3957
|
+
newRecipe.metadata.yield = scaled;
|
|
3898
3958
|
}
|
|
3899
3959
|
}
|
|
3900
3960
|
return newRecipe;
|
|
@@ -4111,7 +4171,7 @@ __publicField(_Recipe, "subgroupIndices", /* @__PURE__ */ new WeakMap());
|
|
|
4111
4171
|
var Recipe = _Recipe;
|
|
4112
4172
|
|
|
4113
4173
|
// src/classes/shopping_list.ts
|
|
4114
|
-
var ShoppingList = class
|
|
4174
|
+
var ShoppingList = class {
|
|
4115
4175
|
/**
|
|
4116
4176
|
* Creates a new ShoppingList instance
|
|
4117
4177
|
* @param categoryConfigStr - The category configuration to parse.
|
|
@@ -4206,7 +4266,7 @@ var ShoppingList = class _ShoppingList {
|
|
|
4206
4266
|
}
|
|
4207
4267
|
}
|
|
4208
4268
|
if (numericEntries.length > 1) {
|
|
4209
|
-
const ratioMap =
|
|
4269
|
+
const ratioMap = buildEquivalenceRatioMap(
|
|
4210
4270
|
getEquivalentUnitsLists(...numericEntries)
|
|
4211
4271
|
);
|
|
4212
4272
|
if (Object.keys(ratioMap).length > 0) {
|
|
@@ -4263,10 +4323,12 @@ var ShoppingList = class _ShoppingList {
|
|
|
4263
4323
|
const pantryHasUnit = pantryExtended.unit !== void 0 && pantryExtended.unit.name !== "";
|
|
4264
4324
|
const ratioMap = this.equivalenceRatios.get(ingredient.name);
|
|
4265
4325
|
const unitMismatch = leafHasUnit !== pantryHasUnit && ratioMap !== void 0;
|
|
4326
|
+
const leafDef = normalizeUnit(leaf.unit);
|
|
4327
|
+
const pantryDef = normalizeUnit(pantryExtended.unit?.name);
|
|
4266
4328
|
if (unitMismatch) {
|
|
4267
4329
|
const leafUnit = leaf.unit ?? NO_UNIT;
|
|
4268
4330
|
const pantryUnit = pantryExtended.unit?.name ?? NO_UNIT;
|
|
4269
|
-
const ratioFromPantry = ratioMap[leafUnit]?.[pantryUnit];
|
|
4331
|
+
const ratioFromPantry = ratioMap[normalizeUnit(leafUnit)?.name ?? leafUnit]?.[normalizeUnit(pantryUnit)?.name ?? pantryUnit];
|
|
4270
4332
|
if (ratioFromPantry !== void 0) {
|
|
4271
4333
|
const pantryValue = getAverageValue(pantryExtended.quantity);
|
|
4272
4334
|
const leafValue = getAverageValue(ingredientExtended.quantity);
|
|
@@ -4281,7 +4343,7 @@ var ShoppingList = class _ShoppingList {
|
|
|
4281
4343
|
type: "fixed",
|
|
4282
4344
|
value: { type: "decimal", decimal: remainingLeafValue }
|
|
4283
4345
|
};
|
|
4284
|
-
const consumedInPantryUnits =
|
|
4346
|
+
const consumedInPantryUnits = subtracted / ratioFromPantry;
|
|
4285
4347
|
const remainingPantryValue = Math.max(
|
|
4286
4348
|
pantryValue - consumedInPantryUnits,
|
|
4287
4349
|
0
|
|
@@ -4298,9 +4360,10 @@ var ShoppingList = class _ShoppingList {
|
|
|
4298
4360
|
};
|
|
4299
4361
|
continue;
|
|
4300
4362
|
}
|
|
4363
|
+
} else {
|
|
4364
|
+
continue;
|
|
4301
4365
|
}
|
|
4302
|
-
}
|
|
4303
|
-
try {
|
|
4366
|
+
} else if (leafDef && pantryDef && areUnitsConvertible(leafDef, pantryDef) || (leaf.unit ?? "").toLowerCase() === (pantryExtended.unit?.name ?? "").toLowerCase()) {
|
|
4304
4367
|
const remaining = subtractQuantities(
|
|
4305
4368
|
ingredientExtended,
|
|
4306
4369
|
pantryExtended,
|
|
@@ -4315,7 +4378,47 @@ var ShoppingList = class _ShoppingList {
|
|
|
4315
4378
|
const updated = toPlainUnit(remaining);
|
|
4316
4379
|
leaf.quantity = updated.quantity;
|
|
4317
4380
|
leaf.unit = updated.unit;
|
|
4318
|
-
}
|
|
4381
|
+
} else if (ratioMap) {
|
|
4382
|
+
const canonicalLeaf = normalizeUnit(leaf.unit)?.name ?? leaf.unit;
|
|
4383
|
+
const leafValue = getAverageValue(ingredientExtended.quantity);
|
|
4384
|
+
const pantryValue = getAverageValue(pantryExtended.quantity);
|
|
4385
|
+
if (typeof leafValue === "number" && typeof pantryValue === "number" && pantryDef) {
|
|
4386
|
+
for (const [equivUnit, ratios] of Object.entries(ratioMap)) {
|
|
4387
|
+
const ratio = ratios[canonicalLeaf];
|
|
4388
|
+
if (ratio === void 0) continue;
|
|
4389
|
+
const equivDef = normalizeUnit(equivUnit);
|
|
4390
|
+
if (!equivDef || !areUnitsConvertible(equivDef, pantryDef))
|
|
4391
|
+
continue;
|
|
4392
|
+
const pantryInEquiv = pantryValue * getToBase(pantryDef) / getToBase(equivDef);
|
|
4393
|
+
const pantryInLeafUnits = pantryInEquiv / ratio;
|
|
4394
|
+
const subtracted = Math.min(pantryInLeafUnits, leafValue);
|
|
4395
|
+
const remainingLeafValue = Math.max(
|
|
4396
|
+
leafValue - pantryInLeafUnits,
|
|
4397
|
+
0
|
|
4398
|
+
);
|
|
4399
|
+
leaf.quantity = {
|
|
4400
|
+
type: "fixed",
|
|
4401
|
+
value: { type: "decimal", decimal: remainingLeafValue }
|
|
4402
|
+
};
|
|
4403
|
+
const consumedInEquiv = subtracted * ratio;
|
|
4404
|
+
const consumedInPantryUnits = consumedInEquiv * getToBase(equivDef) / getToBase(pantryDef);
|
|
4405
|
+
const remainingPantryValue = Math.max(
|
|
4406
|
+
pantryValue - consumedInPantryUnits,
|
|
4407
|
+
0
|
|
4408
|
+
);
|
|
4409
|
+
pantryExtended = {
|
|
4410
|
+
quantity: {
|
|
4411
|
+
type: "fixed",
|
|
4412
|
+
value: {
|
|
4413
|
+
type: "decimal",
|
|
4414
|
+
decimal: remainingPantryValue
|
|
4415
|
+
}
|
|
4416
|
+
},
|
|
4417
|
+
...pantryExtended.unit && { unit: pantryExtended.unit }
|
|
4418
|
+
};
|
|
4419
|
+
break;
|
|
4420
|
+
}
|
|
4421
|
+
}
|
|
4319
4422
|
}
|
|
4320
4423
|
}
|
|
4321
4424
|
if ("and" in entry) {
|
|
@@ -4326,8 +4429,8 @@ var ShoppingList = class _ShoppingList {
|
|
|
4326
4429
|
entry.and.push(...nonZero);
|
|
4327
4430
|
const ratioMap = this.equivalenceRatios.get(ingredient.name);
|
|
4328
4431
|
if (entry.equivalents && ratioMap) {
|
|
4329
|
-
const equivUnits = entry.equivalents.map((e2) => e2.unit
|
|
4330
|
-
entry.equivalents =
|
|
4432
|
+
const equivUnits = entry.equivalents.map((e2) => e2.unit);
|
|
4433
|
+
entry.equivalents = recomputeEquivalents(
|
|
4331
4434
|
entry.and,
|
|
4332
4435
|
ratioMap,
|
|
4333
4436
|
equivUnits
|
|
@@ -4338,17 +4441,17 @@ var ShoppingList = class _ShoppingList {
|
|
|
4338
4441
|
ingredient.quantities[i2] = {
|
|
4339
4442
|
quantity: single.quantity,
|
|
4340
4443
|
...single.unit && { unit: single.unit },
|
|
4341
|
-
...entry.equivalents && { equivalents: entry.equivalents }
|
|
4342
|
-
...entry.alternatives && { alternatives: entry.alternatives }
|
|
4444
|
+
...entry.equivalents && { equivalents: entry.equivalents }
|
|
4343
4445
|
};
|
|
4344
4446
|
}
|
|
4345
4447
|
} else if ("equivalents" in entry && entry.equivalents) {
|
|
4346
4448
|
const ratioMap = this.equivalenceRatios.get(ingredient.name);
|
|
4347
4449
|
if (ratioMap) {
|
|
4348
4450
|
const equivUnits = entry.equivalents.map(
|
|
4349
|
-
(e2) => e2.unit
|
|
4451
|
+
(e2) => e2.unit
|
|
4452
|
+
// equivalents always have units
|
|
4350
4453
|
);
|
|
4351
|
-
const recomputed =
|
|
4454
|
+
const recomputed = recomputeEquivalents(
|
|
4352
4455
|
[entry],
|
|
4353
4456
|
ratioMap,
|
|
4354
4457
|
equivUnits
|
|
@@ -4371,57 +4474,6 @@ var ShoppingList = class _ShoppingList {
|
|
|
4371
4474
|
}
|
|
4372
4475
|
this.resultingPantry = clonedPantry;
|
|
4373
4476
|
}
|
|
4374
|
-
/**
|
|
4375
|
-
* Builds a ratio map from equivalence lists.
|
|
4376
|
-
* For each equivalence list, stores ratio = equiv_value / primary_value
|
|
4377
|
-
* for every pair of units, so equivalents can be recomputed after
|
|
4378
|
-
* pantry subtraction modifies primary quantities.
|
|
4379
|
-
*/
|
|
4380
|
-
static buildEquivalenceRatioMap(unitsLists) {
|
|
4381
|
-
const ratioMap = {};
|
|
4382
|
-
for (const list of unitsLists) {
|
|
4383
|
-
for (const equiv of list) {
|
|
4384
|
-
const equivValue = getAverageValue(equiv.quantity);
|
|
4385
|
-
for (const primary of list) {
|
|
4386
|
-
if (primary === equiv) continue;
|
|
4387
|
-
const primaryValue = getAverageValue(primary.quantity);
|
|
4388
|
-
const equivUnit = equiv.unit.name;
|
|
4389
|
-
const primaryUnit = primary.unit.name;
|
|
4390
|
-
ratioMap[equivUnit] ?? (ratioMap[equivUnit] = {});
|
|
4391
|
-
ratioMap[equivUnit][primaryUnit] = equivValue / primaryValue;
|
|
4392
|
-
}
|
|
4393
|
-
}
|
|
4394
|
-
}
|
|
4395
|
-
return ratioMap;
|
|
4396
|
-
}
|
|
4397
|
-
/**
|
|
4398
|
-
* Recomputes equivalent quantities from current primary values and stored ratios.
|
|
4399
|
-
* For each equivalent unit in equivUnits, new_value = Σ (primary_value × ratio[equivUnit][primaryUnit]).
|
|
4400
|
-
* Returns undefined if all equivalents compute to zero.
|
|
4401
|
-
*/
|
|
4402
|
-
static recomputeEquivalents(primaries, ratioMap, equivUnits) {
|
|
4403
|
-
const equivalents = [];
|
|
4404
|
-
for (const equivUnit of equivUnits) {
|
|
4405
|
-
const ratios = ratioMap[equivUnit];
|
|
4406
|
-
let total = 0;
|
|
4407
|
-
for (const primary of primaries) {
|
|
4408
|
-
const pUnit = primary.unit ?? NO_UNIT;
|
|
4409
|
-
const ratio = ratios[pUnit];
|
|
4410
|
-
const pValue = getAverageValue(primary.quantity);
|
|
4411
|
-
total += pValue * ratio;
|
|
4412
|
-
}
|
|
4413
|
-
if (total > 0) {
|
|
4414
|
-
equivalents.push({
|
|
4415
|
-
quantity: {
|
|
4416
|
-
type: "fixed",
|
|
4417
|
-
value: { type: "decimal", decimal: total }
|
|
4418
|
-
},
|
|
4419
|
-
...equivUnit !== "" && { unit: equivUnit }
|
|
4420
|
-
});
|
|
4421
|
-
}
|
|
4422
|
-
}
|
|
4423
|
-
return equivalents.length > 0 ? equivalents : void 0;
|
|
4424
|
-
}
|
|
4425
4477
|
/**
|
|
4426
4478
|
* Adds a recipe to the shopping list, then automatically
|
|
4427
4479
|
* recalculates the quantities and recategorize the ingredients.
|
|
@@ -5035,6 +5087,9 @@ export {
|
|
|
5035
5087
|
// v8 ignore if -- @preserve: defensive type guard
|
|
5036
5088
|
/* v8 ignore if -- @preserve */
|
|
5037
5089
|
// v8 ignore next -- @preserve
|
|
5090
|
+
// v8 ignore else -- @preserve: text quantities never reach the equivalence path
|
|
5038
5091
|
// v8 ignore else --@preserve: defensive type guard
|
|
5039
5092
|
// v8 ignore else -- @preserve: detection if
|
|
5093
|
+
/* v8 ignore else -- @preserve: only act when there are matches */
|
|
5094
|
+
/* v8 ignore else -- @preserve: initialization pattern */
|
|
5040
5095
|
//# sourceMappingURL=index.js.map
|