@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.cjs
CHANGED
|
@@ -335,7 +335,7 @@ var nonWordChar = "\\s@#~\\[\\]{(,;:!?";
|
|
|
335
335
|
var nonWordCharStrict = "\\s@#~\\[\\]{(,;:!?|";
|
|
336
336
|
var ingredientWithAlternativeRegex = d().literal("@").startNamedGroup("ingredientModifiers").anyOf("@\\-&?").zeroOrMore().endGroup().optional().startNamedGroup("ingredientRecipeAnchor").literal("./").endGroup().optional().startGroup().startGroup().startNamedGroup("mIngredientName").notAnyOf(nonWordChar).oneOrMore().startGroup().whitespace().oneOrMore().notAnyOf(nonWordChar).oneOrMore().endGroup().oneOrMore().endGroup().positiveLookahead("\\s*(?:\\{[^\\}]*\\}|\\([^)]*\\))").endGroup().or().startNamedGroup("sIngredientName").notAnyOf(nonWordChar).zeroOrMore().notAnyOf("\\." + nonWordChar).endGroup().endGroup().startGroup().literal("{").startNamedGroup("ingredientQuantityModifier").literal("=").exactly(1).endGroup().optional().startNamedGroup("ingredientQuantity").startGroup().notAnyOf("}|%").oneOrMore().endGroup().optional().startGroup().literal("%").notAnyOf("|}").oneOrMore().lazy().endGroup().optional().startGroup().literal("|").notAnyOf("}").oneOrMore().lazy().endGroup().zeroOrMore().endGroup().literal("}").endGroup().optional().startGroup().literal("(").startNamedGroup("ingredientPreparation").notAnyOf(")").oneOrMore().lazy().endGroup().literal(")").endGroup().optional().startGroup().literal("[").startNamedGroup("ingredientNote").notAnyOf("\\]").oneOrMore().lazy().endGroup().literal("]").endGroup().optional().startNamedGroup("ingredientAlternative").startGroup().literal("|").startGroup().anyOf("@\\-&?").zeroOrMore().endGroup().optional().startGroup().literal("./").endGroup().optional().startGroup().startGroup().startGroup().notAnyOf(nonWordChar).oneOrMore().startGroup().whitespace().oneOrMore().notAnyOf(nonWordChar).oneOrMore().endGroup().oneOrMore().endGroup().positiveLookahead("\\s*(?:\\{[^\\}]*\\}|\\([^)]*\\))").endGroup().or().startGroup().notAnyOf(nonWordChar).oneOrMore().endGroup().endGroup().startGroup().literal("{").startGroup().literal("=").exactly(1).endGroup().optional().startGroup().notAnyOf("}%").oneOrMore().endGroup().optional().startGroup().literal("%").startGroup().notAnyOf("}").oneOrMore().lazy().endGroup().endGroup().optional().literal("}").endGroup().optional().startGroup().literal("(").startGroup().notAnyOf(")").oneOrMore().lazy().endGroup().literal(")").endGroup().optional().startGroup().literal("[").startGroup().notAnyOf("\\]").oneOrMore().lazy().endGroup().literal("]").endGroup().optional().endGroup().zeroOrMore().endGroup().toRegExp();
|
|
337
337
|
var inlineIngredientAlternativesRegex = new RegExp("\\|" + ingredientWithAlternativeRegex.source.slice(1));
|
|
338
|
-
var quantityAlternativeRegex = d().startNamedGroup("quantity").notAnyOf("}|%").oneOrMore().endGroup().optional().startGroup().literal("%").startNamedGroup("unit").notAnyOf("|}").oneOrMore().endGroup().endGroup().optional().startGroup().literal("|").startNamedGroup("alternative").startGroup().notAnyOf("}").oneOrMore().endGroup().zeroOrMore().endGroup().endGroup().optional().toRegExp();
|
|
338
|
+
var quantityAlternativeRegex = d().startNamedGroup("quantity").notAnyOf("{}|%").oneOrMore().endGroup().optional().startGroup().literal("%").startNamedGroup("unit").notAnyOf("|}").oneOrMore().endGroup().endGroup().optional().startGroup().literal("|").startNamedGroup("alternative").startGroup().notAnyOf("}").oneOrMore().endGroup().zeroOrMore().endGroup().endGroup().optional().toRegExp();
|
|
339
339
|
var ingredientWithGroupKeyRegex = d().literal("@|").startNamedGroup("gIngredientGroupKey").notAnyOf(nonWordCharStrict + "/").oneOrMore().endGroup().startGroup().literal("/").startNamedGroup("gIngredientSubgroupKey").notAnyOf(nonWordCharStrict).oneOrMore().endGroup().endGroup().optional().literal("|").startNamedGroup("gIngredientModifiers").anyOf("@\\-&?").zeroOrMore().endGroup().optional().startNamedGroup("gIngredientRecipeAnchor").literal("./").endGroup().optional().startGroup().startGroup().startNamedGroup("gmIngredientName").notAnyOf(nonWordChar).oneOrMore().startGroup().whitespace().oneOrMore().notAnyOf(nonWordChar).oneOrMore().endGroup().oneOrMore().endGroup().positiveLookahead("\\s*(?:\\{[^\\}]*\\}|\\([^)]*\\))").endGroup().or().startNamedGroup("gsIngredientName").notAnyOf(nonWordChar).zeroOrMore().notAnyOf("\\." + nonWordChar).endGroup().endGroup().startGroup().literal("{").startNamedGroup("gIngredientQuantityModifier").literal("=").exactly(1).endGroup().optional().startNamedGroup("gIngredientQuantity").startGroup().notAnyOf("}|%").oneOrMore().endGroup().optional().startGroup().literal("%").notAnyOf("|}").oneOrMore().lazy().endGroup().optional().startGroup().literal("|").notAnyOf("}").oneOrMore().lazy().endGroup().zeroOrMore().endGroup().literal("}").endGroup().optional().startGroup().literal("(").startNamedGroup("gIngredientPreparation").notAnyOf(")").oneOrMore().lazy().endGroup().literal(")").endGroup().optional().startGroup().literal("[").startNamedGroup("gIngredientNote").notAnyOf("\\]").oneOrMore().lazy().endGroup().literal("]").endGroup().optional().toRegExp();
|
|
340
340
|
var ingredientAliasRegex = d().startAnchor().startNamedGroup("ingredientListName").notAnyOf("|").oneOrMore().endGroup().literal("|").startNamedGroup("ingredientDisplayName").notAnyOf("|").oneOrMore().endGroup().endAnchor().toRegExp();
|
|
341
341
|
var cookwareRegex = d().literal("#").startNamedGroup("cookwareModifiers").anyOf("\\-&?").zeroOrMore().endGroup().startGroup().startGroup().startNamedGroup("mCookwareName").notAnyOf(nonWordChar).oneOrMore().startGroup().whitespace().oneOrMore().notAnyOf(nonWordChar).oneOrMore().endGroup().oneOrMore().endGroup().positiveLookahead("\\s*(?:\\{[^\\}]*\\})").endGroup().or().startNamedGroup("sCookwareName").notAnyOf(nonWordChar).zeroOrMore().notAnyOf("\\." + nonWordChar).endGroup().endGroup().startGroup().literal("{").startNamedGroup("cookwareQuantity").anyCharacter().zeroOrMore().lazy().endGroup().literal("}").endGroup().optional().toRegExp();
|
|
@@ -351,11 +351,18 @@ var tokensRegex = new RegExp(
|
|
|
351
351
|
].map((r2) => r2.source).join("|"),
|
|
352
352
|
"gu"
|
|
353
353
|
);
|
|
354
|
-
var
|
|
355
|
-
var
|
|
356
|
-
var
|
|
357
|
-
|
|
358
|
-
|
|
354
|
+
var yieldPrefixPart = d().startAnchor().literal("yield").literal(":").anyOf(" ").zeroOrMore().startNamedGroup("servingsPrefix").nonWhitespace().startGroup().anyCharacter().zeroOrMore().lazy().nonWhitespace().endGroup().optional().endGroup().optional().anyOf(" ").zeroOrMore().toRegExp();
|
|
355
|
+
var yieldSuffixPart = d().anyOf(" ").zeroOrMore().startNamedGroup("servingsSuffix").nonWhitespace().startGroup().anyCharacter().zeroOrMore().nonWhitespace().endGroup().optional().endGroup().optional().anyOf(" ").zeroOrMore().endAnchor().toRegExp();
|
|
356
|
+
var yieldMetaValueWithUnitRegex = new RegExp(
|
|
357
|
+
yieldPrefixPart.source + arbitraryScalableRegex.source + yieldSuffixPart.source,
|
|
358
|
+
"m"
|
|
359
|
+
);
|
|
360
|
+
var yieldMetaValueAsQuantityRegex = d().startAnchor().literal("yield:").anyOf(" ").zeroOrMore().startNamedGroup("quantity").notAnyOf("{}|%\\n\\r").oneOrMore().endGroup().optional().startGroup().literal("%").startNamedGroup("unit").notAnyOf("\\n\\r|}").oneOrMore().endGroup().endGroup().optional().anyOf(" ").zeroOrMore().endAnchor().toRegExp();
|
|
361
|
+
var yieldMetaValueRegex = new RegExp(
|
|
362
|
+
[
|
|
363
|
+
yieldMetaValueWithUnitRegex.source,
|
|
364
|
+
yieldMetaValueAsQuantityRegex.source
|
|
365
|
+
].join("|"),
|
|
359
366
|
"m"
|
|
360
367
|
);
|
|
361
368
|
var commentRegex = d().literal("--").anyCharacter().zeroOrMore().global().toRegExp();
|
|
@@ -364,7 +371,7 @@ var shoppingListRegex = d().literal("[").startNamedGroup("name").anyCharacter().
|
|
|
364
371
|
var rangeRegex = d().startAnchor().digit().oneOrMore().startGroup().anyOf(".,/").exactly(1).digit().oneOrMore().endGroup().optional().literal("-").digit().oneOrMore().startGroup().anyOf(".,/").exactly(1).digit().oneOrMore().endGroup().optional().endAnchor().toRegExp();
|
|
365
372
|
var numberLikeRegex = d().startAnchor().digit().oneOrMore().startGroup().anyOf(".,/").exactly(1).digit().oneOrMore().endGroup().optional().endAnchor().toRegExp();
|
|
366
373
|
var floatRegex = d().startAnchor().digit().oneOrMore().startGroup().anyOf(".").exactly(1).digit().oneOrMore().endGroup().optional().endAnchor().toRegExp();
|
|
367
|
-
var variantTagRegex = d().startAnchor().literal("[").startNamedGroup("variantOptionalPrefix").literal("?").endGroup().optional().startNamedGroup("variantNames").notAnyOf("\\]").
|
|
374
|
+
var variantTagRegex = d().startAnchor().literal("[").startNamedGroup("variantOptionalPrefix").literal("?").endGroup().optional().startNamedGroup("variantNames").notAnyOf("\\]").oneOrMore().endGroup().optional().literal("]").whitespace().zeroOrMore().toRegExp();
|
|
368
375
|
var mdEscaped = d().literal("\\").startCaptureGroup().anyOf("*_`").endGroup();
|
|
369
376
|
var mdInlineCode = d().literal("`").startCaptureGroup().notAnyOf("`").oneOrMore().lazy().endGroup().literal("`");
|
|
370
377
|
var mdLink = d().literal("[").startCaptureGroup().notAnyOf("\\]").oneOrMore().lazy().endGroup().literal("](").startCaptureGroup().notAnyOf(")").oneOrMore().lazy().endGroup().literal(")");
|
|
@@ -940,7 +947,7 @@ var NoProductMatchError = class extends Error {
|
|
|
940
947
|
constructor(item_name, code) {
|
|
941
948
|
const messageMap = {
|
|
942
949
|
incompatibleUnits: `The units of the products in the catalogue are incompatible with ingredient ${item_name} in the shopping list.`,
|
|
943
|
-
noProduct:
|
|
950
|
+
noProduct: `No product was found linked to ingredient name ${item_name} in the shopping list`,
|
|
944
951
|
textValue: `Ingredient ${item_name} has a text value as quantity and can therefore not be matched with any product in the catalogue.`,
|
|
945
952
|
noQuantity: `Ingredient ${item_name} has no quantity and can therefore not be matched with any product in the catalogue.`,
|
|
946
953
|
textValue_incompatibleUnits: `Multiple alternative quantities were provided for ingredient ${item_name} in the shopping list but they were either text values or no product in catalog were found to have compatible units`
|
|
@@ -1543,7 +1550,7 @@ function stringifyFixedValue(quantity) {
|
|
|
1543
1550
|
return String(quantity.value.decimal);
|
|
1544
1551
|
else return quantity.value.text;
|
|
1545
1552
|
}
|
|
1546
|
-
function
|
|
1553
|
+
function parseQuantityValue(input_str) {
|
|
1547
1554
|
const clean_str = String(input_str).trim();
|
|
1548
1555
|
if (rangeRegex.test(clean_str)) {
|
|
1549
1556
|
const range_parts = clean_str.split("-");
|
|
@@ -1557,12 +1564,12 @@ function parseQuantityWithUnit(input) {
|
|
|
1557
1564
|
const trimmed = input.trim();
|
|
1558
1565
|
const separatorIndex = trimmed.indexOf("%");
|
|
1559
1566
|
if (separatorIndex === -1) {
|
|
1560
|
-
return { value:
|
|
1567
|
+
return { value: parseQuantityValue(trimmed) };
|
|
1561
1568
|
}
|
|
1562
1569
|
const valuePart = trimmed.slice(0, separatorIndex).trim();
|
|
1563
1570
|
const unitPart = trimmed.slice(separatorIndex + 1).trim();
|
|
1564
1571
|
return {
|
|
1565
|
-
value:
|
|
1572
|
+
value: parseQuantityValue(valuePart),
|
|
1566
1573
|
unit: unitPart || void 0
|
|
1567
1574
|
};
|
|
1568
1575
|
}
|
|
@@ -1752,7 +1759,7 @@ function parseArbitraryQuantity(raw) {
|
|
|
1752
1759
|
"Arbitrary quantities must have a numerical value"
|
|
1753
1760
|
);
|
|
1754
1761
|
}
|
|
1755
|
-
const value =
|
|
1762
|
+
const value = parseQuantityValue(quantityMatch.groups.quantity);
|
|
1756
1763
|
const unit = quantityMatch.groups.unit;
|
|
1757
1764
|
if (!value || value.type === "fixed" && value.value.type === "text") {
|
|
1758
1765
|
throw new InvalidQuantityFormat(
|
|
@@ -1766,40 +1773,40 @@ function parseArbitraryQuantity(raw) {
|
|
|
1766
1773
|
if (unit) arbitrary.unit = unit;
|
|
1767
1774
|
return arbitrary;
|
|
1768
1775
|
}
|
|
1769
|
-
function
|
|
1770
|
-
const
|
|
1771
|
-
if (
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
+
function parseServingsMetaVar(content, varName) {
|
|
1777
|
+
const raw = parseSimpleMetaVar(content, varName);
|
|
1778
|
+
if (raw === void 0) return void 0;
|
|
1779
|
+
const num = Number(raw);
|
|
1780
|
+
if (isNaN(num)) {
|
|
1781
|
+
return { numericValue: 1, rawValue: raw };
|
|
1782
|
+
}
|
|
1783
|
+
return { numericValue: num, rawValue: num };
|
|
1784
|
+
}
|
|
1785
|
+
function parseYieldMetaVar(content) {
|
|
1786
|
+
const match = content.match(yieldMetaValueRegex);
|
|
1787
|
+
if (!match) return void 0;
|
|
1788
|
+
if (match.groups?.arbitraryQuantity) {
|
|
1789
|
+
const parsed = parseArbitraryQuantity(match.groups.arbitraryQuantity);
|
|
1790
|
+
const result = {
|
|
1776
1791
|
quantity: parsed.quantity
|
|
1777
1792
|
};
|
|
1778
|
-
if (parsed.unit)
|
|
1779
|
-
if (
|
|
1780
|
-
|
|
1793
|
+
if (parsed.unit) result.unit = parsed.unit;
|
|
1794
|
+
if (match.groups.servingsPrefix) {
|
|
1795
|
+
result.textBefore = match.groups.servingsPrefix;
|
|
1781
1796
|
}
|
|
1782
|
-
if (
|
|
1783
|
-
|
|
1797
|
+
if (match.groups.servingsSuffix) {
|
|
1798
|
+
result.textAfter = match.groups.servingsSuffix;
|
|
1784
1799
|
}
|
|
1785
|
-
return
|
|
1786
|
-
}
|
|
1787
|
-
const varMatch = content.match(scalingSimpleMetaValueRegex(varName));
|
|
1788
|
-
if (!varMatch) return void 0;
|
|
1789
|
-
if (isNaN(Number(varMatch[2]?.trim()))) {
|
|
1790
|
-
throw new Error("Scaling variables should be numbers");
|
|
1800
|
+
return result;
|
|
1791
1801
|
}
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
};
|
|
1799
|
-
if (varMatch[3]) {
|
|
1800
|
-
result.text = `${varMatch[3].trim()}`;
|
|
1802
|
+
if (match.groups?.quantity) {
|
|
1803
|
+
const result = {
|
|
1804
|
+
quantity: parseQuantityValue(match.groups.quantity)
|
|
1805
|
+
};
|
|
1806
|
+
if (match.groups.unit) result.unit = match.groups.unit;
|
|
1807
|
+
return result;
|
|
1801
1808
|
}
|
|
1802
|
-
return
|
|
1809
|
+
return void 0;
|
|
1803
1810
|
}
|
|
1804
1811
|
function parseListMetaVar(content, varName) {
|
|
1805
1812
|
const listMatch = content.match(
|
|
@@ -1917,12 +1924,12 @@ function parseAnyMetaVar(content, varName) {
|
|
|
1917
1924
|
if (simple) return parseMetadataValue(simple);
|
|
1918
1925
|
return void 0;
|
|
1919
1926
|
}
|
|
1920
|
-
function
|
|
1927
|
+
function getNumericValueFromYield(v) {
|
|
1921
1928
|
if (v.quantity.type === "fixed" && v.quantity.value.type !== "text") {
|
|
1922
1929
|
return getNumericValue(v.quantity.value);
|
|
1923
1930
|
}
|
|
1924
1931
|
if (v.quantity.type === "range") return getNumericValue(v.quantity.min);
|
|
1925
|
-
return
|
|
1932
|
+
return 1;
|
|
1926
1933
|
}
|
|
1927
1934
|
function extractMetadata(content) {
|
|
1928
1935
|
const metadata = {};
|
|
@@ -2047,11 +2054,16 @@ function extractMetadata(content) {
|
|
|
2047
2054
|
};
|
|
2048
2055
|
unitSystem = unitSystemMap[unitSystemRaw.toLowerCase()];
|
|
2049
2056
|
}
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2057
|
+
const yieldValue = parseYieldMetaVar(metadataContent);
|
|
2058
|
+
if (yieldValue) {
|
|
2059
|
+
metadata.yield = yieldValue;
|
|
2060
|
+
servings = getNumericValueFromYield(yieldValue);
|
|
2061
|
+
}
|
|
2062
|
+
for (const metaVar of ["serves", "servings"]) {
|
|
2063
|
+
const result = parseServingsMetaVar(metadataContent, metaVar);
|
|
2064
|
+
if (result !== void 0) {
|
|
2065
|
+
metadata[metaVar] = result.rawValue;
|
|
2066
|
+
servings = result.numericValue;
|
|
2055
2067
|
}
|
|
2056
2068
|
}
|
|
2057
2069
|
const tags = parseListMetaVar(metadataContent, "tags");
|
|
@@ -2332,7 +2344,7 @@ var ProductCatalog = class {
|
|
|
2332
2344
|
const sizeStrings = Array.isArray(size) ? size : [size];
|
|
2333
2345
|
const sizes = sizeStrings.map((sizeStr) => {
|
|
2334
2346
|
const sizeAndUnitRaw = sizeStr.split("%");
|
|
2335
|
-
const sizeParsed =
|
|
2347
|
+
const sizeParsed = parseQuantityValue(
|
|
2336
2348
|
sizeAndUnitRaw[0]
|
|
2337
2349
|
);
|
|
2338
2350
|
const productSize = { size: sizeParsed };
|
|
@@ -2475,7 +2487,7 @@ var Section = class {
|
|
|
2475
2487
|
};
|
|
2476
2488
|
|
|
2477
2489
|
// src/quantities/alternatives.ts
|
|
2478
|
-
var
|
|
2490
|
+
var import_big4 = __toESM(require("big.js"), 1);
|
|
2479
2491
|
|
|
2480
2492
|
// src/units/lookup.ts
|
|
2481
2493
|
function findListWithCompatibleQuantity(list, quantity) {
|
|
@@ -2498,10 +2510,14 @@ function findCompatibleQuantityWithinList(list, quantity) {
|
|
|
2498
2510
|
}
|
|
2499
2511
|
|
|
2500
2512
|
// src/utils/general.ts
|
|
2513
|
+
var import_big3 = __toESM(require("big.js"), 1);
|
|
2501
2514
|
var legacyDeepClone = (v) => {
|
|
2502
2515
|
if (v === null || typeof v !== "object") {
|
|
2503
2516
|
return v;
|
|
2504
2517
|
}
|
|
2518
|
+
if (v instanceof import_big3.default) {
|
|
2519
|
+
return new import_big3.default(v);
|
|
2520
|
+
}
|
|
2505
2521
|
if (v instanceof Map) {
|
|
2506
2522
|
return new Map(
|
|
2507
2523
|
Array.from(v.entries()).map(([k, val]) => [
|
|
@@ -2511,7 +2527,9 @@ var legacyDeepClone = (v) => {
|
|
|
2511
2527
|
);
|
|
2512
2528
|
}
|
|
2513
2529
|
if (v instanceof Set) {
|
|
2514
|
-
return new Set(
|
|
2530
|
+
return new Set(
|
|
2531
|
+
Array.from(v).map((val) => legacyDeepClone(val))
|
|
2532
|
+
);
|
|
2515
2533
|
}
|
|
2516
2534
|
if (v instanceof Date) {
|
|
2517
2535
|
return new Date(v.getTime());
|
|
@@ -2525,7 +2543,7 @@ var legacyDeepClone = (v) => {
|
|
|
2525
2543
|
}
|
|
2526
2544
|
return cloned;
|
|
2527
2545
|
};
|
|
2528
|
-
var deepClone = (v) =>
|
|
2546
|
+
var deepClone = (v) => legacyDeepClone(v);
|
|
2529
2547
|
|
|
2530
2548
|
// src/quantities/alternatives.ts
|
|
2531
2549
|
function getEquivalentUnitsLists(...quantities) {
|
|
@@ -2751,7 +2769,7 @@ function regroupQuantitiesAndExpandEquivalents(sum, unitsLists, system) {
|
|
|
2751
2769
|
return main.reduce((acc, v) => {
|
|
2752
2770
|
const mainInList = findCompatibleQuantityWithinList(list, v);
|
|
2753
2771
|
const conversionRatio = getBaseUnitRatio(v, mainInList);
|
|
2754
|
-
const valueInOriginalUnit = (0,
|
|
2772
|
+
const valueInOriginalUnit = (0, import_big4.default)(getAverageValue(v.quantity)).times(
|
|
2755
2773
|
conversionRatio
|
|
2756
2774
|
);
|
|
2757
2775
|
const newValue = {
|
|
@@ -2763,7 +2781,7 @@ function regroupQuantitiesAndExpandEquivalents(sum, unitsLists, system) {
|
|
|
2763
2781
|
decimal: valueInOriginalUnit.toNumber()
|
|
2764
2782
|
}
|
|
2765
2783
|
},
|
|
2766
|
-
(0,
|
|
2784
|
+
(0, import_big4.default)(getAverageValue(equiv.quantity)).div(
|
|
2767
2785
|
getAverageValue(mainInList.quantity)
|
|
2768
2786
|
)
|
|
2769
2787
|
)
|
|
@@ -2804,9 +2822,50 @@ function addEquivalentsAndSimplify(quantities, system) {
|
|
|
2804
2822
|
return { and: regrouped.map(toPlainUnit) };
|
|
2805
2823
|
}
|
|
2806
2824
|
}
|
|
2825
|
+
function buildEquivalenceRatioMap(unitsLists) {
|
|
2826
|
+
const ratioMap = {};
|
|
2827
|
+
for (const list of unitsLists) {
|
|
2828
|
+
for (const equiv of list) {
|
|
2829
|
+
const equivValue = getAverageValue(equiv.quantity);
|
|
2830
|
+
for (const primary of list) {
|
|
2831
|
+
if (primary === equiv) continue;
|
|
2832
|
+
const primaryValue = getAverageValue(primary.quantity);
|
|
2833
|
+
const equivUnit = normalizeUnit(equiv.unit.name)?.name ?? equiv.unit.name;
|
|
2834
|
+
const primaryUnit = normalizeUnit(primary.unit.name)?.name ?? primary.unit.name;
|
|
2835
|
+
ratioMap[equivUnit] ?? (ratioMap[equivUnit] = {});
|
|
2836
|
+
ratioMap[equivUnit][primaryUnit] = equivValue / primaryValue;
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
return ratioMap;
|
|
2841
|
+
}
|
|
2842
|
+
function recomputeEquivalents(primaries, ratioMap, equivUnits) {
|
|
2843
|
+
const equivalents = [];
|
|
2844
|
+
for (const equivUnit of equivUnits) {
|
|
2845
|
+
const ratios = ratioMap[normalizeUnit(equivUnit)?.name ?? equivUnit];
|
|
2846
|
+
let total = 0;
|
|
2847
|
+
for (const primary of primaries) {
|
|
2848
|
+
const pUnit = normalizeUnit(primary.unit ?? NO_UNIT)?.name ?? primary.unit ?? NO_UNIT;
|
|
2849
|
+
const ratio = ratios[pUnit];
|
|
2850
|
+
if (ratio === void 0) continue;
|
|
2851
|
+
const pValue = getAverageValue(primary.quantity);
|
|
2852
|
+
total += pValue * ratio;
|
|
2853
|
+
}
|
|
2854
|
+
if (total > 0) {
|
|
2855
|
+
equivalents.push({
|
|
2856
|
+
quantity: {
|
|
2857
|
+
type: "fixed",
|
|
2858
|
+
value: { type: "decimal", decimal: total }
|
|
2859
|
+
},
|
|
2860
|
+
...equivUnit !== "" && { unit: equivUnit }
|
|
2861
|
+
});
|
|
2862
|
+
}
|
|
2863
|
+
}
|
|
2864
|
+
return equivalents.length > 0 ? equivalents : void 0;
|
|
2865
|
+
}
|
|
2807
2866
|
|
|
2808
2867
|
// src/classes/recipe.ts
|
|
2809
|
-
var
|
|
2868
|
+
var import_big5 = __toESM(require("big.js"), 1);
|
|
2810
2869
|
var _Recipe = class _Recipe {
|
|
2811
2870
|
/**
|
|
2812
2871
|
* Creates a new Recipe instance.
|
|
@@ -2928,7 +2987,7 @@ var _Recipe = class _Recipe {
|
|
|
2928
2987
|
let quantityMatch = quantityRaw.match(quantityAlternativeRegex);
|
|
2929
2988
|
const quantities = [];
|
|
2930
2989
|
while (quantityMatch?.groups) {
|
|
2931
|
-
const value = quantityMatch.groups.quantity ?
|
|
2990
|
+
const value = quantityMatch.groups.quantity ? parseQuantityValue(quantityMatch.groups.quantity) : void 0;
|
|
2932
2991
|
const unit = quantityMatch.groups.unit;
|
|
2933
2992
|
if (value) {
|
|
2934
2993
|
const newQuantity = { quantity: value };
|
|
@@ -3296,7 +3355,9 @@ var _Recipe = class _Recipe {
|
|
|
3296
3355
|
} else {
|
|
3297
3356
|
const targetSubgroupIndex = 0;
|
|
3298
3357
|
const selectedSubgroup = groupSubgroups?.[targetSubgroupIndex];
|
|
3299
|
-
isSelected = selectedSubgroup
|
|
3358
|
+
isSelected = selectedSubgroup.some(
|
|
3359
|
+
(alt) => alt.itemId === item.id
|
|
3360
|
+
);
|
|
3300
3361
|
}
|
|
3301
3362
|
} else {
|
|
3302
3363
|
const targetSubgroupIndex = groupChoice ?? 0;
|
|
@@ -3695,16 +3756,10 @@ var _Recipe = class _Recipe {
|
|
|
3695
3756
|
}
|
|
3696
3757
|
sectionName = sectionName.slice(sectionVarMatch[0].length).trim();
|
|
3697
3758
|
}
|
|
3698
|
-
if (
|
|
3699
|
-
section
|
|
3700
|
-
if (sectionVariants) section.variants = sectionVariants;
|
|
3701
|
-
if (sectionOptional) section.optional = true;
|
|
3702
|
-
} else {
|
|
3703
|
-
if (!section.isBlank()) {
|
|
3704
|
-
this.sections.push(section);
|
|
3705
|
-
}
|
|
3706
|
-
section = new Section(sectionName, sectionVariants, sectionOptional);
|
|
3759
|
+
if (!section.isBlank()) {
|
|
3760
|
+
this.sections.push(section);
|
|
3707
3761
|
}
|
|
3762
|
+
section = new Section(sectionName, sectionVariants, sectionOptional);
|
|
3708
3763
|
blankLineBefore = true;
|
|
3709
3764
|
inNote = false;
|
|
3710
3765
|
continue;
|
|
@@ -3770,7 +3825,7 @@ var _Recipe = class _Recipe {
|
|
|
3770
3825
|
if (modifiers !== void 0 && modifiers.includes("-")) {
|
|
3771
3826
|
flags.push("hidden");
|
|
3772
3827
|
}
|
|
3773
|
-
const quantity = quantityRaw ?
|
|
3828
|
+
const quantity = quantityRaw ? parseQuantityValue(quantityRaw) : void 0;
|
|
3774
3829
|
const newCookware = {
|
|
3775
3830
|
name
|
|
3776
3831
|
};
|
|
@@ -3802,7 +3857,7 @@ var _Recipe = class _Recipe {
|
|
|
3802
3857
|
throw new Error("Timer missing unit");
|
|
3803
3858
|
}
|
|
3804
3859
|
const name = groups.timerName || void 0;
|
|
3805
|
-
const duration =
|
|
3860
|
+
const duration = parseQuantityValue(durationStr);
|
|
3806
3861
|
const timerObj = {
|
|
3807
3862
|
name,
|
|
3808
3863
|
duration,
|
|
@@ -3842,7 +3897,7 @@ var _Recipe = class _Recipe {
|
|
|
3842
3897
|
if (originalServings === void 0 || originalServings === 0) {
|
|
3843
3898
|
originalServings = 1;
|
|
3844
3899
|
}
|
|
3845
|
-
const factor = (0,
|
|
3900
|
+
const factor = (0, import_big5.default)(newServings).div(originalServings);
|
|
3846
3901
|
return this.scaleBy(factor);
|
|
3847
3902
|
}
|
|
3848
3903
|
/**
|
|
@@ -3861,7 +3916,7 @@ var _Recipe = class _Recipe {
|
|
|
3861
3916
|
function scaleAlternativesBy(alternatives, factor2) {
|
|
3862
3917
|
for (const alternative of alternatives) {
|
|
3863
3918
|
if (alternative.quantity) {
|
|
3864
|
-
const scaleFactor = alternative.scalable ? (0,
|
|
3919
|
+
const scaleFactor = alternative.scalable ? (0, import_big5.default)(factor2) : 1;
|
|
3865
3920
|
if (alternative.quantity.type !== "fixed" || alternative.quantity.value.type !== "text") {
|
|
3866
3921
|
alternative.quantity = multiplyQuantityValue(
|
|
3867
3922
|
alternative.quantity,
|
|
@@ -3934,10 +3989,16 @@ var _Recipe = class _Recipe {
|
|
|
3934
3989
|
arbitrary.unit = optimized.unit;
|
|
3935
3990
|
}
|
|
3936
3991
|
newRecipe._populateIngredientQuantities();
|
|
3937
|
-
newRecipe.servings = (0,
|
|
3938
|
-
for (const metaVar of ["servings", "
|
|
3939
|
-
if (newRecipe.metadata[metaVar]
|
|
3940
|
-
|
|
3992
|
+
newRecipe.servings = (0, import_big5.default)(originalServings).times(factor).toNumber();
|
|
3993
|
+
for (const metaVar of ["servings", "serves"]) {
|
|
3994
|
+
if (typeof newRecipe.metadata[metaVar] === "number") {
|
|
3995
|
+
newRecipe.metadata[metaVar] = (0, import_big5.default)(newRecipe.metadata[metaVar]).times(factor).toNumber();
|
|
3996
|
+
}
|
|
3997
|
+
}
|
|
3998
|
+
if (newRecipe.metadata.yield && this.metadata.yield) {
|
|
3999
|
+
const original = this.metadata.yield;
|
|
4000
|
+
if (original.quantity.type === "fixed" && original.quantity.value.type === "text") {
|
|
4001
|
+
} else {
|
|
3941
4002
|
const scaledQuantity = multiplyQuantityValue(
|
|
3942
4003
|
original.quantity,
|
|
3943
4004
|
factor
|
|
@@ -3952,8 +4013,7 @@ var _Recipe = class _Recipe {
|
|
|
3952
4013
|
if (optimized.unit) scaled.unit = optimized.unit;
|
|
3953
4014
|
if (original.textBefore) scaled.textBefore = original.textBefore;
|
|
3954
4015
|
if (original.textAfter) scaled.textAfter = original.textAfter;
|
|
3955
|
-
|
|
3956
|
-
newRecipe.metadata[metaVar] = scaled;
|
|
4016
|
+
newRecipe.metadata.yield = scaled;
|
|
3957
4017
|
}
|
|
3958
4018
|
}
|
|
3959
4019
|
return newRecipe;
|
|
@@ -4170,7 +4230,7 @@ __publicField(_Recipe, "subgroupIndices", /* @__PURE__ */ new WeakMap());
|
|
|
4170
4230
|
var Recipe = _Recipe;
|
|
4171
4231
|
|
|
4172
4232
|
// src/classes/shopping_list.ts
|
|
4173
|
-
var ShoppingList = class
|
|
4233
|
+
var ShoppingList = class {
|
|
4174
4234
|
/**
|
|
4175
4235
|
* Creates a new ShoppingList instance
|
|
4176
4236
|
* @param categoryConfigStr - The category configuration to parse.
|
|
@@ -4265,7 +4325,7 @@ var ShoppingList = class _ShoppingList {
|
|
|
4265
4325
|
}
|
|
4266
4326
|
}
|
|
4267
4327
|
if (numericEntries.length > 1) {
|
|
4268
|
-
const ratioMap =
|
|
4328
|
+
const ratioMap = buildEquivalenceRatioMap(
|
|
4269
4329
|
getEquivalentUnitsLists(...numericEntries)
|
|
4270
4330
|
);
|
|
4271
4331
|
if (Object.keys(ratioMap).length > 0) {
|
|
@@ -4322,10 +4382,12 @@ var ShoppingList = class _ShoppingList {
|
|
|
4322
4382
|
const pantryHasUnit = pantryExtended.unit !== void 0 && pantryExtended.unit.name !== "";
|
|
4323
4383
|
const ratioMap = this.equivalenceRatios.get(ingredient.name);
|
|
4324
4384
|
const unitMismatch = leafHasUnit !== pantryHasUnit && ratioMap !== void 0;
|
|
4385
|
+
const leafDef = normalizeUnit(leaf.unit);
|
|
4386
|
+
const pantryDef = normalizeUnit(pantryExtended.unit?.name);
|
|
4325
4387
|
if (unitMismatch) {
|
|
4326
4388
|
const leafUnit = leaf.unit ?? NO_UNIT;
|
|
4327
4389
|
const pantryUnit = pantryExtended.unit?.name ?? NO_UNIT;
|
|
4328
|
-
const ratioFromPantry = ratioMap[leafUnit]?.[pantryUnit];
|
|
4390
|
+
const ratioFromPantry = ratioMap[normalizeUnit(leafUnit)?.name ?? leafUnit]?.[normalizeUnit(pantryUnit)?.name ?? pantryUnit];
|
|
4329
4391
|
if (ratioFromPantry !== void 0) {
|
|
4330
4392
|
const pantryValue = getAverageValue(pantryExtended.quantity);
|
|
4331
4393
|
const leafValue = getAverageValue(ingredientExtended.quantity);
|
|
@@ -4340,7 +4402,7 @@ var ShoppingList = class _ShoppingList {
|
|
|
4340
4402
|
type: "fixed",
|
|
4341
4403
|
value: { type: "decimal", decimal: remainingLeafValue }
|
|
4342
4404
|
};
|
|
4343
|
-
const consumedInPantryUnits =
|
|
4405
|
+
const consumedInPantryUnits = subtracted / ratioFromPantry;
|
|
4344
4406
|
const remainingPantryValue = Math.max(
|
|
4345
4407
|
pantryValue - consumedInPantryUnits,
|
|
4346
4408
|
0
|
|
@@ -4357,9 +4419,10 @@ var ShoppingList = class _ShoppingList {
|
|
|
4357
4419
|
};
|
|
4358
4420
|
continue;
|
|
4359
4421
|
}
|
|
4422
|
+
} else {
|
|
4423
|
+
continue;
|
|
4360
4424
|
}
|
|
4361
|
-
}
|
|
4362
|
-
try {
|
|
4425
|
+
} else if (leafDef && pantryDef && areUnitsConvertible(leafDef, pantryDef) || (leaf.unit ?? "").toLowerCase() === (pantryExtended.unit?.name ?? "").toLowerCase()) {
|
|
4363
4426
|
const remaining = subtractQuantities(
|
|
4364
4427
|
ingredientExtended,
|
|
4365
4428
|
pantryExtended,
|
|
@@ -4374,7 +4437,47 @@ var ShoppingList = class _ShoppingList {
|
|
|
4374
4437
|
const updated = toPlainUnit(remaining);
|
|
4375
4438
|
leaf.quantity = updated.quantity;
|
|
4376
4439
|
leaf.unit = updated.unit;
|
|
4377
|
-
}
|
|
4440
|
+
} else if (ratioMap) {
|
|
4441
|
+
const canonicalLeaf = normalizeUnit(leaf.unit)?.name ?? leaf.unit;
|
|
4442
|
+
const leafValue = getAverageValue(ingredientExtended.quantity);
|
|
4443
|
+
const pantryValue = getAverageValue(pantryExtended.quantity);
|
|
4444
|
+
if (typeof leafValue === "number" && typeof pantryValue === "number" && pantryDef) {
|
|
4445
|
+
for (const [equivUnit, ratios] of Object.entries(ratioMap)) {
|
|
4446
|
+
const ratio = ratios[canonicalLeaf];
|
|
4447
|
+
if (ratio === void 0) continue;
|
|
4448
|
+
const equivDef = normalizeUnit(equivUnit);
|
|
4449
|
+
if (!equivDef || !areUnitsConvertible(equivDef, pantryDef))
|
|
4450
|
+
continue;
|
|
4451
|
+
const pantryInEquiv = pantryValue * getToBase(pantryDef) / getToBase(equivDef);
|
|
4452
|
+
const pantryInLeafUnits = pantryInEquiv / ratio;
|
|
4453
|
+
const subtracted = Math.min(pantryInLeafUnits, leafValue);
|
|
4454
|
+
const remainingLeafValue = Math.max(
|
|
4455
|
+
leafValue - pantryInLeafUnits,
|
|
4456
|
+
0
|
|
4457
|
+
);
|
|
4458
|
+
leaf.quantity = {
|
|
4459
|
+
type: "fixed",
|
|
4460
|
+
value: { type: "decimal", decimal: remainingLeafValue }
|
|
4461
|
+
};
|
|
4462
|
+
const consumedInEquiv = subtracted * ratio;
|
|
4463
|
+
const consumedInPantryUnits = consumedInEquiv * getToBase(equivDef) / getToBase(pantryDef);
|
|
4464
|
+
const remainingPantryValue = Math.max(
|
|
4465
|
+
pantryValue - consumedInPantryUnits,
|
|
4466
|
+
0
|
|
4467
|
+
);
|
|
4468
|
+
pantryExtended = {
|
|
4469
|
+
quantity: {
|
|
4470
|
+
type: "fixed",
|
|
4471
|
+
value: {
|
|
4472
|
+
type: "decimal",
|
|
4473
|
+
decimal: remainingPantryValue
|
|
4474
|
+
}
|
|
4475
|
+
},
|
|
4476
|
+
...pantryExtended.unit && { unit: pantryExtended.unit }
|
|
4477
|
+
};
|
|
4478
|
+
break;
|
|
4479
|
+
}
|
|
4480
|
+
}
|
|
4378
4481
|
}
|
|
4379
4482
|
}
|
|
4380
4483
|
if ("and" in entry) {
|
|
@@ -4385,8 +4488,8 @@ var ShoppingList = class _ShoppingList {
|
|
|
4385
4488
|
entry.and.push(...nonZero);
|
|
4386
4489
|
const ratioMap = this.equivalenceRatios.get(ingredient.name);
|
|
4387
4490
|
if (entry.equivalents && ratioMap) {
|
|
4388
|
-
const equivUnits = entry.equivalents.map((e2) => e2.unit
|
|
4389
|
-
entry.equivalents =
|
|
4491
|
+
const equivUnits = entry.equivalents.map((e2) => e2.unit);
|
|
4492
|
+
entry.equivalents = recomputeEquivalents(
|
|
4390
4493
|
entry.and,
|
|
4391
4494
|
ratioMap,
|
|
4392
4495
|
equivUnits
|
|
@@ -4397,17 +4500,17 @@ var ShoppingList = class _ShoppingList {
|
|
|
4397
4500
|
ingredient.quantities[i2] = {
|
|
4398
4501
|
quantity: single.quantity,
|
|
4399
4502
|
...single.unit && { unit: single.unit },
|
|
4400
|
-
...entry.equivalents && { equivalents: entry.equivalents }
|
|
4401
|
-
...entry.alternatives && { alternatives: entry.alternatives }
|
|
4503
|
+
...entry.equivalents && { equivalents: entry.equivalents }
|
|
4402
4504
|
};
|
|
4403
4505
|
}
|
|
4404
4506
|
} else if ("equivalents" in entry && entry.equivalents) {
|
|
4405
4507
|
const ratioMap = this.equivalenceRatios.get(ingredient.name);
|
|
4406
4508
|
if (ratioMap) {
|
|
4407
4509
|
const equivUnits = entry.equivalents.map(
|
|
4408
|
-
(e2) => e2.unit
|
|
4510
|
+
(e2) => e2.unit
|
|
4511
|
+
// equivalents always have units
|
|
4409
4512
|
);
|
|
4410
|
-
const recomputed =
|
|
4513
|
+
const recomputed = recomputeEquivalents(
|
|
4411
4514
|
[entry],
|
|
4412
4515
|
ratioMap,
|
|
4413
4516
|
equivUnits
|
|
@@ -4430,57 +4533,6 @@ var ShoppingList = class _ShoppingList {
|
|
|
4430
4533
|
}
|
|
4431
4534
|
this.resultingPantry = clonedPantry;
|
|
4432
4535
|
}
|
|
4433
|
-
/**
|
|
4434
|
-
* Builds a ratio map from equivalence lists.
|
|
4435
|
-
* For each equivalence list, stores ratio = equiv_value / primary_value
|
|
4436
|
-
* for every pair of units, so equivalents can be recomputed after
|
|
4437
|
-
* pantry subtraction modifies primary quantities.
|
|
4438
|
-
*/
|
|
4439
|
-
static buildEquivalenceRatioMap(unitsLists) {
|
|
4440
|
-
const ratioMap = {};
|
|
4441
|
-
for (const list of unitsLists) {
|
|
4442
|
-
for (const equiv of list) {
|
|
4443
|
-
const equivValue = getAverageValue(equiv.quantity);
|
|
4444
|
-
for (const primary of list) {
|
|
4445
|
-
if (primary === equiv) continue;
|
|
4446
|
-
const primaryValue = getAverageValue(primary.quantity);
|
|
4447
|
-
const equivUnit = equiv.unit.name;
|
|
4448
|
-
const primaryUnit = primary.unit.name;
|
|
4449
|
-
ratioMap[equivUnit] ?? (ratioMap[equivUnit] = {});
|
|
4450
|
-
ratioMap[equivUnit][primaryUnit] = equivValue / primaryValue;
|
|
4451
|
-
}
|
|
4452
|
-
}
|
|
4453
|
-
}
|
|
4454
|
-
return ratioMap;
|
|
4455
|
-
}
|
|
4456
|
-
/**
|
|
4457
|
-
* Recomputes equivalent quantities from current primary values and stored ratios.
|
|
4458
|
-
* For each equivalent unit in equivUnits, new_value = Σ (primary_value × ratio[equivUnit][primaryUnit]).
|
|
4459
|
-
* Returns undefined if all equivalents compute to zero.
|
|
4460
|
-
*/
|
|
4461
|
-
static recomputeEquivalents(primaries, ratioMap, equivUnits) {
|
|
4462
|
-
const equivalents = [];
|
|
4463
|
-
for (const equivUnit of equivUnits) {
|
|
4464
|
-
const ratios = ratioMap[equivUnit];
|
|
4465
|
-
let total = 0;
|
|
4466
|
-
for (const primary of primaries) {
|
|
4467
|
-
const pUnit = primary.unit ?? NO_UNIT;
|
|
4468
|
-
const ratio = ratios[pUnit];
|
|
4469
|
-
const pValue = getAverageValue(primary.quantity);
|
|
4470
|
-
total += pValue * ratio;
|
|
4471
|
-
}
|
|
4472
|
-
if (total > 0) {
|
|
4473
|
-
equivalents.push({
|
|
4474
|
-
quantity: {
|
|
4475
|
-
type: "fixed",
|
|
4476
|
-
value: { type: "decimal", decimal: total }
|
|
4477
|
-
},
|
|
4478
|
-
...equivUnit !== "" && { unit: equivUnit }
|
|
4479
|
-
});
|
|
4480
|
-
}
|
|
4481
|
-
}
|
|
4482
|
-
return equivalents.length > 0 ? equivalents : void 0;
|
|
4483
|
-
}
|
|
4484
4536
|
/**
|
|
4485
4537
|
* Adds a recipe to the shopping list, then automatically
|
|
4486
4538
|
* recalculates the quantities and recategorize the ingredients.
|
|
@@ -5095,6 +5147,9 @@ function getEffectiveChoices(recipe, variant) {
|
|
|
5095
5147
|
// v8 ignore if -- @preserve: defensive type guard
|
|
5096
5148
|
/* v8 ignore if -- @preserve */
|
|
5097
5149
|
// v8 ignore next -- @preserve
|
|
5150
|
+
// v8 ignore else -- @preserve: text quantities never reach the equivalence path
|
|
5098
5151
|
// v8 ignore else --@preserve: defensive type guard
|
|
5099
5152
|
// v8 ignore else -- @preserve: detection if
|
|
5153
|
+
/* v8 ignore else -- @preserve: only act when there are matches */
|
|
5154
|
+
/* v8 ignore else -- @preserve: initialization pattern */
|
|
5100
5155
|
//# sourceMappingURL=index.cjs.map
|