@tmlmt/cooklang-parser 3.0.0-alpha.11 → 3.0.0-alpha.13
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 +294 -81
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +83 -71
- package/dist/index.d.ts +83 -71
- package/dist/index.js +294 -81
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -320,6 +320,12 @@ var i = (() => {
|
|
|
320
320
|
})();
|
|
321
321
|
|
|
322
322
|
// src/regex.ts
|
|
323
|
+
var metadataKeyRegex = /^([^:\n]+?):/gm;
|
|
324
|
+
var numericValueRegex = /^-?\d+(\.\d+)?$/;
|
|
325
|
+
var nestedMetaVarRegex = (varName) => new RegExp(
|
|
326
|
+
`^${varName}:\\s*\\r?\\n((?:[ ]+.+(?:\\r?\\n|$))+)`,
|
|
327
|
+
"m"
|
|
328
|
+
);
|
|
323
329
|
var metadataRegex = d().literal("---").newline().startCaptureGroup().anyCharacter().zeroOrMore().optional().endGroup().newline().literal("---").dotAll().toRegExp();
|
|
324
330
|
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();
|
|
325
331
|
var nonWordChar = "\\s@#~\\[\\]{(,;:!?";
|
|
@@ -931,6 +937,20 @@ var InvalidQuantityFormat = class extends Error {
|
|
|
931
937
|
this.name = "InvalidQuantityFormat";
|
|
932
938
|
}
|
|
933
939
|
};
|
|
940
|
+
var NoTabAsIndentError = class extends Error {
|
|
941
|
+
constructor() {
|
|
942
|
+
super(
|
|
943
|
+
`Tabs are not allowed for indentation in metadata blocks. Please use spaces only.`
|
|
944
|
+
);
|
|
945
|
+
this.name = "NoTabAsIndentError";
|
|
946
|
+
}
|
|
947
|
+
};
|
|
948
|
+
var BadIndentationError = class extends Error {
|
|
949
|
+
constructor() {
|
|
950
|
+
super(`Bad identation of a nested block. Please use spaces only.`);
|
|
951
|
+
this.name = "BadIndentationError";
|
|
952
|
+
}
|
|
953
|
+
};
|
|
934
954
|
|
|
935
955
|
// src/utils/type_guards.ts
|
|
936
956
|
function isGroup(x) {
|
|
@@ -1488,6 +1508,108 @@ function parseListMetaVar(content, varName) {
|
|
|
1488
1508
|
return listMatch[2].split("\n").filter((line) => line.trim() !== "").map((line) => line.replace(/^\s*-\s*/, "").trim());
|
|
1489
1509
|
}
|
|
1490
1510
|
}
|
|
1511
|
+
function extractAllMetadataKeys(content) {
|
|
1512
|
+
const keys = [];
|
|
1513
|
+
for (const match of content.matchAll(metadataKeyRegex)) {
|
|
1514
|
+
keys.push(match[1].trim());
|
|
1515
|
+
}
|
|
1516
|
+
return [...new Set(keys)];
|
|
1517
|
+
}
|
|
1518
|
+
function parseNestedMetaVar(content, varName) {
|
|
1519
|
+
const match = content.match(nestedMetaVarRegex(varName));
|
|
1520
|
+
if (!match) return void 0;
|
|
1521
|
+
const nestedContent = match[1];
|
|
1522
|
+
return parseNestedBlock(nestedContent);
|
|
1523
|
+
}
|
|
1524
|
+
function parseNestedBlock(content) {
|
|
1525
|
+
const lines = content.split(/\r?\n/).filter((line) => line.trim() !== "");
|
|
1526
|
+
if (lines.length === 0) return void 0;
|
|
1527
|
+
const baseIndentMatch = lines[0].match(/^(\s*)/);
|
|
1528
|
+
if (baseIndentMatch?.[0]?.includes(" ")) {
|
|
1529
|
+
throw new NoTabAsIndentError();
|
|
1530
|
+
}
|
|
1531
|
+
const baseIndent = baseIndentMatch?.[1]?.length;
|
|
1532
|
+
if (lines[0].trim().startsWith("- ")) return void 0;
|
|
1533
|
+
const result = {};
|
|
1534
|
+
let i2 = 0;
|
|
1535
|
+
while (i2 < lines.length) {
|
|
1536
|
+
const line = lines[i2];
|
|
1537
|
+
const leadingWhitespace = line.match(/^(\s*)/)?.[1];
|
|
1538
|
+
if (leadingWhitespace && leadingWhitespace.includes(" ")) {
|
|
1539
|
+
throw new NoTabAsIndentError();
|
|
1540
|
+
}
|
|
1541
|
+
const currentIndent = leadingWhitespace.length;
|
|
1542
|
+
if (currentIndent < baseIndent) {
|
|
1543
|
+
break;
|
|
1544
|
+
}
|
|
1545
|
+
if (currentIndent !== baseIndent) {
|
|
1546
|
+
throw new BadIndentationError();
|
|
1547
|
+
}
|
|
1548
|
+
const keyValueMatch = line.match(/^[ ]*([^:\n]+?):\s*(.*)$/);
|
|
1549
|
+
if (!keyValueMatch) {
|
|
1550
|
+
i2++;
|
|
1551
|
+
continue;
|
|
1552
|
+
}
|
|
1553
|
+
const key = keyValueMatch[1].trim();
|
|
1554
|
+
const rawValue = keyValueMatch[2].trim();
|
|
1555
|
+
if (rawValue === "") {
|
|
1556
|
+
const childLines = [];
|
|
1557
|
+
let j = i2 + 1;
|
|
1558
|
+
while (j < lines.length) {
|
|
1559
|
+
const childLine = lines[j];
|
|
1560
|
+
const childIndent = childLine.match(/^([ ]*)/)?.[1]?.length;
|
|
1561
|
+
if (childIndent && childIndent > baseIndent) {
|
|
1562
|
+
childLines.push(childLine);
|
|
1563
|
+
j++;
|
|
1564
|
+
} else {
|
|
1565
|
+
break;
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
if (childLines.length > 0) {
|
|
1569
|
+
const firstChildTrimmed = childLines[0].trim();
|
|
1570
|
+
if (firstChildTrimmed.startsWith("- ")) {
|
|
1571
|
+
const reconstructedContent = `${key}:
|
|
1572
|
+
${childLines.join("\n")}`;
|
|
1573
|
+
const listResult = parseListMetaVar(reconstructedContent, key);
|
|
1574
|
+
if (listResult) {
|
|
1575
|
+
result[key] = listResult.map(
|
|
1576
|
+
(item) => parseMetadataValue(item)
|
|
1577
|
+
);
|
|
1578
|
+
}
|
|
1579
|
+
} else {
|
|
1580
|
+
const childContent = childLines.join("\n");
|
|
1581
|
+
const nested = parseNestedBlock(childContent);
|
|
1582
|
+
if (nested) {
|
|
1583
|
+
result[key] = nested;
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
i2 = j;
|
|
1588
|
+
} else {
|
|
1589
|
+
result[key] = parseMetadataValue(rawValue);
|
|
1590
|
+
i2++;
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
return result;
|
|
1594
|
+
}
|
|
1595
|
+
function parseMetadataValue(rawValue) {
|
|
1596
|
+
if (rawValue.startsWith("[") && rawValue.endsWith("]")) {
|
|
1597
|
+
return rawValue.slice(1, -1).split(",").map((item) => item.trim());
|
|
1598
|
+
}
|
|
1599
|
+
if (numericValueRegex.test(rawValue)) {
|
|
1600
|
+
return Number(rawValue);
|
|
1601
|
+
}
|
|
1602
|
+
return rawValue;
|
|
1603
|
+
}
|
|
1604
|
+
function parseAnyMetaVar(content, varName) {
|
|
1605
|
+
const nested = parseNestedMetaVar(content, varName);
|
|
1606
|
+
if (nested) return nested;
|
|
1607
|
+
const list = parseListMetaVar(content, varName);
|
|
1608
|
+
if (list) return list;
|
|
1609
|
+
const simple = parseSimpleMetaVar(content, varName);
|
|
1610
|
+
if (simple) return parseMetadataValue(simple);
|
|
1611
|
+
return void 0;
|
|
1612
|
+
}
|
|
1491
1613
|
function extractMetadata(content) {
|
|
1492
1614
|
const metadata = {};
|
|
1493
1615
|
let servings = void 0;
|
|
@@ -1495,13 +1617,24 @@ function extractMetadata(content) {
|
|
|
1495
1617
|
if (!metadataContent) {
|
|
1496
1618
|
return { metadata };
|
|
1497
1619
|
}
|
|
1498
|
-
|
|
1620
|
+
const handledKeys = /* @__PURE__ */ new Set([
|
|
1621
|
+
// Simple string fields
|
|
1499
1622
|
"title",
|
|
1623
|
+
"author",
|
|
1624
|
+
"locale",
|
|
1625
|
+
"introduction",
|
|
1626
|
+
"description",
|
|
1627
|
+
"course",
|
|
1628
|
+
"category",
|
|
1629
|
+
"diet",
|
|
1630
|
+
"cuisine",
|
|
1631
|
+
"difficulty",
|
|
1632
|
+
// Source fields
|
|
1500
1633
|
"source",
|
|
1501
1634
|
"source.name",
|
|
1502
1635
|
"source.url",
|
|
1503
|
-
"author",
|
|
1504
1636
|
"source.author",
|
|
1637
|
+
// Time fields
|
|
1505
1638
|
"prep time",
|
|
1506
1639
|
"time.prep",
|
|
1507
1640
|
"cook time",
|
|
@@ -1509,6 +1642,23 @@ function extractMetadata(content) {
|
|
|
1509
1642
|
"time required",
|
|
1510
1643
|
"time",
|
|
1511
1644
|
"duration",
|
|
1645
|
+
// Image fields
|
|
1646
|
+
"image",
|
|
1647
|
+
"picture",
|
|
1648
|
+
"images",
|
|
1649
|
+
"pictures",
|
|
1650
|
+
// Unit system
|
|
1651
|
+
"unit system",
|
|
1652
|
+
// Scaling fields
|
|
1653
|
+
"servings",
|
|
1654
|
+
"yield",
|
|
1655
|
+
"serves",
|
|
1656
|
+
// List fields
|
|
1657
|
+
"tags"
|
|
1658
|
+
]);
|
|
1659
|
+
for (const metaVar of [
|
|
1660
|
+
"title",
|
|
1661
|
+
"author",
|
|
1512
1662
|
"locale",
|
|
1513
1663
|
"introduction",
|
|
1514
1664
|
"description",
|
|
@@ -1516,17 +1666,57 @@ function extractMetadata(content) {
|
|
|
1516
1666
|
"category",
|
|
1517
1667
|
"diet",
|
|
1518
1668
|
"cuisine",
|
|
1519
|
-
"difficulty"
|
|
1520
|
-
"image",
|
|
1521
|
-
"picture"
|
|
1669
|
+
"difficulty"
|
|
1522
1670
|
]) {
|
|
1523
1671
|
const stringMetaValue = parseSimpleMetaVar(metadataContent, metaVar);
|
|
1524
1672
|
if (stringMetaValue) metadata[metaVar] = stringMetaValue;
|
|
1525
1673
|
}
|
|
1674
|
+
const sourceNested = parseNestedMetaVar(metadataContent, "source");
|
|
1675
|
+
const sourceTxt = parseSimpleMetaVar(metadataContent, "source");
|
|
1676
|
+
const sourceName = parseSimpleMetaVar(metadataContent, "source.name");
|
|
1677
|
+
const sourceUrl = parseSimpleMetaVar(metadataContent, "source.url");
|
|
1678
|
+
const sourceAuthor = parseSimpleMetaVar(metadataContent, "source.author");
|
|
1679
|
+
if (sourceNested) {
|
|
1680
|
+
const source = {};
|
|
1681
|
+
if (typeof sourceNested.name === "string") source.name = sourceNested.name;
|
|
1682
|
+
if (typeof sourceNested.url === "string") source.url = sourceNested.url;
|
|
1683
|
+
if (typeof sourceNested.author === "string")
|
|
1684
|
+
source.author = sourceNested.author;
|
|
1685
|
+
if (Object.keys(source).length > 0) metadata.source = source;
|
|
1686
|
+
} else if (sourceName || sourceAuthor || sourceUrl) {
|
|
1687
|
+
const source = {};
|
|
1688
|
+
if (sourceName) source.name = sourceName;
|
|
1689
|
+
if (sourceUrl) source.url = sourceUrl;
|
|
1690
|
+
if (sourceAuthor) source.author = sourceAuthor;
|
|
1691
|
+
metadata.source = source;
|
|
1692
|
+
} else if (sourceTxt) {
|
|
1693
|
+
metadata.source = sourceTxt;
|
|
1694
|
+
}
|
|
1695
|
+
const timeNested = parseNestedMetaVar(metadataContent, "time");
|
|
1696
|
+
const prepTime = parseSimpleMetaVar(metadataContent, "prep time") ?? parseSimpleMetaVar(metadataContent, "time.prep");
|
|
1697
|
+
const cookTime = parseSimpleMetaVar(metadataContent, "cook time") ?? parseSimpleMetaVar(metadataContent, "time.cook");
|
|
1698
|
+
const totalTime = parseSimpleMetaVar(metadataContent, "time required") ?? parseSimpleMetaVar(metadataContent, "time") ?? parseSimpleMetaVar(metadataContent, "duration");
|
|
1699
|
+
if (timeNested) {
|
|
1700
|
+
const time = {};
|
|
1701
|
+
if (typeof timeNested.prep === "string") time.prep = timeNested.prep;
|
|
1702
|
+
if (typeof timeNested.cook === "string") time.cook = timeNested.cook;
|
|
1703
|
+
if (typeof timeNested.total === "string") time.total = timeNested.total;
|
|
1704
|
+
if (Object.keys(time).length > 0) metadata.time = time;
|
|
1705
|
+
} else if (prepTime || cookTime || totalTime) {
|
|
1706
|
+
const time = {};
|
|
1707
|
+
if (prepTime) time.prep = prepTime;
|
|
1708
|
+
if (cookTime) time.cook = cookTime;
|
|
1709
|
+
if (totalTime) time.total = totalTime;
|
|
1710
|
+
metadata.time = time;
|
|
1711
|
+
}
|
|
1712
|
+
const image = parseSimpleMetaVar(metadataContent, "image") ?? parseSimpleMetaVar(metadataContent, "picture");
|
|
1713
|
+
if (image) metadata.image = image;
|
|
1714
|
+
const images = parseListMetaVar(metadataContent, "images") ?? parseListMetaVar(metadataContent, "pictures");
|
|
1715
|
+
if (images) metadata.images = images;
|
|
1526
1716
|
let unitSystem;
|
|
1527
1717
|
const unitSystemRaw = parseSimpleMetaVar(metadataContent, "unit system");
|
|
1528
1718
|
if (unitSystemRaw) {
|
|
1529
|
-
metadata
|
|
1719
|
+
metadata.unitSystem = unitSystemRaw;
|
|
1530
1720
|
const unitSystemMap = {
|
|
1531
1721
|
metric: "metric",
|
|
1532
1722
|
us: "US",
|
|
@@ -1535,16 +1725,22 @@ function extractMetadata(content) {
|
|
|
1535
1725
|
};
|
|
1536
1726
|
unitSystem = unitSystemMap[unitSystemRaw.toLowerCase()];
|
|
1537
1727
|
}
|
|
1538
|
-
for (const metaVar of ["
|
|
1728
|
+
for (const metaVar of ["servings", "yield", "serves"]) {
|
|
1539
1729
|
const scalingMetaValue = parseScalingMetaVar(metadataContent, metaVar);
|
|
1540
1730
|
if (scalingMetaValue && scalingMetaValue[1]) {
|
|
1541
1731
|
metadata[metaVar] = scalingMetaValue[1];
|
|
1542
1732
|
servings = scalingMetaValue[0];
|
|
1543
1733
|
}
|
|
1544
1734
|
}
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1735
|
+
const tags = parseListMetaVar(metadataContent, "tags");
|
|
1736
|
+
if (tags) metadata.tags = tags;
|
|
1737
|
+
const allKeys = extractAllMetadataKeys(metadataContent);
|
|
1738
|
+
for (const key of allKeys) {
|
|
1739
|
+
if (handledKeys.has(key)) continue;
|
|
1740
|
+
const value = parseAnyMetaVar(metadataContent, key);
|
|
1741
|
+
if (value !== void 0) {
|
|
1742
|
+
metadata[key] = value;
|
|
1743
|
+
}
|
|
1548
1744
|
}
|
|
1549
1745
|
return { metadata, servings, unitSystem };
|
|
1550
1746
|
}
|
|
@@ -2290,7 +2486,7 @@ var _Recipe = class _Recipe {
|
|
|
2290
2486
|
alternative.note = note;
|
|
2291
2487
|
}
|
|
2292
2488
|
if (itemQuantity) {
|
|
2293
|
-
alternative
|
|
2489
|
+
Object.assign(alternative, itemQuantity);
|
|
2294
2490
|
}
|
|
2295
2491
|
alternatives.push(alternative);
|
|
2296
2492
|
testString = groups.ingredientAlternative || "";
|
|
@@ -2398,7 +2594,7 @@ var _Recipe = class _Recipe {
|
|
|
2398
2594
|
displayName
|
|
2399
2595
|
};
|
|
2400
2596
|
if (itemQuantity) {
|
|
2401
|
-
alternative
|
|
2597
|
+
Object.assign(alternative, itemQuantity);
|
|
2402
2598
|
}
|
|
2403
2599
|
const existingAlternatives = this.choices.ingredientGroups.get(groupKey);
|
|
2404
2600
|
function upsertAlternativeToIngredient(ingredients, ingredientIdx, newAlternativeIdx) {
|
|
@@ -2445,7 +2641,7 @@ var _Recipe = class _Recipe {
|
|
|
2445
2641
|
* Quantities are grouped by their alternative signature and summed using addEquivalentsAndSimplify.
|
|
2446
2642
|
* @internal
|
|
2447
2643
|
*/
|
|
2448
|
-
|
|
2644
|
+
_populateIngredientQuantities() {
|
|
2449
2645
|
for (const ing of this.ingredients) {
|
|
2450
2646
|
delete ing.quantities;
|
|
2451
2647
|
delete ing.usedAsPrimary;
|
|
@@ -2534,28 +2730,28 @@ var _Recipe = class _Recipe {
|
|
|
2534
2730
|
for (const alt of allAlts) {
|
|
2535
2731
|
referencedIndices.add(alt.index);
|
|
2536
2732
|
}
|
|
2537
|
-
if (!alternative.
|
|
2733
|
+
if (!alternative.quantity) continue;
|
|
2538
2734
|
const baseQty = {
|
|
2539
|
-
quantity: alternative.
|
|
2540
|
-
...alternative.
|
|
2541
|
-
unit: alternative.
|
|
2735
|
+
quantity: alternative.quantity,
|
|
2736
|
+
...alternative.unit && {
|
|
2737
|
+
unit: alternative.unit
|
|
2542
2738
|
}
|
|
2543
2739
|
};
|
|
2544
|
-
const quantityEntry = alternative.
|
|
2740
|
+
const quantityEntry = alternative.equivalents?.length ? { or: [baseQty, ...alternative.equivalents] } : baseQty;
|
|
2545
2741
|
let alternativeRefs;
|
|
2546
2742
|
if (!hasExplicitChoice && allAlts.length > 1) {
|
|
2547
2743
|
alternativeRefs = allAlts.filter(
|
|
2548
2744
|
(alt) => isGrouped ? alt.itemId !== item.id : alt.index !== alternative.index
|
|
2549
2745
|
).map((otherAlt) => {
|
|
2550
2746
|
const ref = { index: otherAlt.index };
|
|
2551
|
-
if (otherAlt.
|
|
2747
|
+
if (otherAlt.quantity) {
|
|
2552
2748
|
const altQty = {
|
|
2553
|
-
quantity: otherAlt.
|
|
2554
|
-
...otherAlt.
|
|
2555
|
-
unit: otherAlt.
|
|
2749
|
+
quantity: otherAlt.quantity,
|
|
2750
|
+
...otherAlt.unit && {
|
|
2751
|
+
unit: otherAlt.unit.name
|
|
2556
2752
|
},
|
|
2557
|
-
...otherAlt.
|
|
2558
|
-
equivalents: otherAlt.
|
|
2753
|
+
...otherAlt.equivalents && {
|
|
2754
|
+
equivalents: otherAlt.equivalents.map(
|
|
2559
2755
|
(eq) => toPlainUnit(eq)
|
|
2560
2756
|
)
|
|
2561
2757
|
}
|
|
@@ -2568,14 +2764,10 @@ var _Recipe = class _Recipe {
|
|
|
2568
2764
|
const altIndices = getAlternativeSignature(alternativeRefs) ?? "";
|
|
2569
2765
|
let signature;
|
|
2570
2766
|
if (isGrouped) {
|
|
2571
|
-
const resolvedUnit = resolveUnit(
|
|
2572
|
-
alternative.itemQuantity.unit?.name
|
|
2573
|
-
);
|
|
2767
|
+
const resolvedUnit = resolveUnit(alternative.unit?.name);
|
|
2574
2768
|
signature = `group:${item.group}|${altIndices}|${resolvedUnit.type}`;
|
|
2575
2769
|
} else if (altIndices) {
|
|
2576
|
-
const resolvedUnit = resolveUnit(
|
|
2577
|
-
alternative.itemQuantity.unit?.name
|
|
2578
|
-
);
|
|
2770
|
+
const resolvedUnit = resolveUnit(alternative.unit?.name);
|
|
2579
2771
|
signature = `${altIndices}|${resolvedUnit.type}}`;
|
|
2580
2772
|
} else {
|
|
2581
2773
|
signature = null;
|
|
@@ -2810,7 +3002,7 @@ var _Recipe = class _Recipe {
|
|
|
2810
3002
|
if (!section.isBlank()) {
|
|
2811
3003
|
this.sections.push(section);
|
|
2812
3004
|
}
|
|
2813
|
-
this.
|
|
3005
|
+
this._populateIngredientQuantities();
|
|
2814
3006
|
}
|
|
2815
3007
|
/**
|
|
2816
3008
|
* Scales the recipe to a new number of servings. In practice, it calls
|
|
@@ -2843,16 +3035,16 @@ var _Recipe = class _Recipe {
|
|
|
2843
3035
|
const unitSystem = this.unitSystem;
|
|
2844
3036
|
function scaleAlternativesBy(alternatives, factor2) {
|
|
2845
3037
|
for (const alternative of alternatives) {
|
|
2846
|
-
if (alternative.
|
|
2847
|
-
const scaleFactor = alternative.
|
|
2848
|
-
if (alternative.
|
|
2849
|
-
alternative.
|
|
2850
|
-
alternative.
|
|
3038
|
+
if (alternative.quantity) {
|
|
3039
|
+
const scaleFactor = alternative.scalable ? (0, import_big4.default)(factor2) : 1;
|
|
3040
|
+
if (alternative.quantity.type !== "fixed" || alternative.quantity.value.type !== "text") {
|
|
3041
|
+
alternative.quantity = multiplyQuantityValue(
|
|
3042
|
+
alternative.quantity,
|
|
2851
3043
|
scaleFactor
|
|
2852
3044
|
);
|
|
2853
3045
|
}
|
|
2854
|
-
if (alternative.
|
|
2855
|
-
alternative.
|
|
3046
|
+
if (alternative.equivalents) {
|
|
3047
|
+
alternative.equivalents = alternative.equivalents.map(
|
|
2856
3048
|
(altQuantity) => {
|
|
2857
3049
|
if (altQuantity.quantity.type === "fixed" && altQuantity.quantity.value.type === "text") {
|
|
2858
3050
|
return altQuantity;
|
|
@@ -2870,15 +3062,15 @@ var _Recipe = class _Recipe {
|
|
|
2870
3062
|
}
|
|
2871
3063
|
const optimizedPrimary = applyBestUnit(
|
|
2872
3064
|
{
|
|
2873
|
-
quantity: alternative.
|
|
2874
|
-
unit: alternative.
|
|
3065
|
+
quantity: alternative.quantity,
|
|
3066
|
+
unit: alternative.unit
|
|
2875
3067
|
},
|
|
2876
3068
|
unitSystem
|
|
2877
3069
|
);
|
|
2878
|
-
alternative.
|
|
2879
|
-
alternative.
|
|
2880
|
-
if (alternative.
|
|
2881
|
-
alternative.
|
|
3070
|
+
alternative.quantity = optimizedPrimary.quantity;
|
|
3071
|
+
alternative.unit = optimizedPrimary.unit;
|
|
3072
|
+
if (alternative.equivalents) {
|
|
3073
|
+
alternative.equivalents = alternative.equivalents.map(
|
|
2882
3074
|
(eq) => applyBestUnit(eq, unitSystem)
|
|
2883
3075
|
);
|
|
2884
3076
|
}
|
|
@@ -2908,7 +3100,7 @@ var _Recipe = class _Recipe {
|
|
|
2908
3100
|
factor
|
|
2909
3101
|
);
|
|
2910
3102
|
}
|
|
2911
|
-
newRecipe.
|
|
3103
|
+
newRecipe._populateIngredientQuantities();
|
|
2912
3104
|
newRecipe.servings = (0, import_big4.default)(originalServings).times(factor).toNumber();
|
|
2913
3105
|
if (newRecipe.metadata.servings && this.metadata.servings) {
|
|
2914
3106
|
if (floatRegex.test(String(this.metadata.servings).replace(",", ".").trim())) {
|
|
@@ -2973,27 +3165,36 @@ var _Recipe = class _Recipe {
|
|
|
2973
3165
|
if (method === "remove") {
|
|
2974
3166
|
return newPrimary;
|
|
2975
3167
|
} else if (method === "replace") {
|
|
3168
|
+
if (source === "converted") remainingEquivalents.push(oldPrimary);
|
|
2976
3169
|
if (remainingEquivalents.length > 0) {
|
|
2977
3170
|
newPrimary.equivalents = remainingEquivalents;
|
|
2978
|
-
if (source === "converted") newPrimary.equivalents.push(oldPrimary);
|
|
2979
3171
|
}
|
|
2980
3172
|
} else {
|
|
2981
3173
|
newPrimary.equivalents = [oldPrimary, ...remainingEquivalents];
|
|
2982
3174
|
}
|
|
2983
3175
|
return newPrimary;
|
|
2984
3176
|
}
|
|
2985
|
-
function
|
|
2986
|
-
const primaryUnit = resolveUnit(
|
|
2987
|
-
const equivalents =
|
|
3177
|
+
function convertAlternativeQuantity(alternative) {
|
|
3178
|
+
const primaryUnit = resolveUnit(alternative.unit?.name);
|
|
3179
|
+
const equivalents = alternative.equivalents ?? [];
|
|
2988
3180
|
const oldPrimary = {
|
|
2989
|
-
quantity:
|
|
2990
|
-
unit:
|
|
3181
|
+
quantity: alternative.quantity,
|
|
3182
|
+
unit: alternative.unit
|
|
2991
3183
|
};
|
|
2992
3184
|
if (primaryUnit.type !== "other" && isUnitCompatibleWithSystem(primaryUnit, system)) {
|
|
2993
3185
|
if (method === "remove") {
|
|
2994
|
-
return {
|
|
3186
|
+
return {
|
|
3187
|
+
quantity: alternative.quantity,
|
|
3188
|
+
unit: alternative.unit,
|
|
3189
|
+
scalable: alternative.scalable
|
|
3190
|
+
};
|
|
2995
3191
|
}
|
|
2996
|
-
return
|
|
3192
|
+
return {
|
|
3193
|
+
quantity: alternative.quantity,
|
|
3194
|
+
unit: alternative.unit,
|
|
3195
|
+
scalable: alternative.scalable,
|
|
3196
|
+
equivalents
|
|
3197
|
+
};
|
|
2997
3198
|
}
|
|
2998
3199
|
const targetEquivIndex = equivalents.findIndex((eq) => {
|
|
2999
3200
|
const eqUnit = resolveUnit(eq.unit?.name);
|
|
@@ -3008,7 +3209,7 @@ var _Recipe = class _Recipe {
|
|
|
3008
3209
|
targetEquiv,
|
|
3009
3210
|
oldPrimary,
|
|
3010
3211
|
remainingEquivalents,
|
|
3011
|
-
|
|
3212
|
+
alternative.scalable,
|
|
3012
3213
|
targetEquiv.unit?.integerProtected,
|
|
3013
3214
|
"swapped"
|
|
3014
3215
|
);
|
|
@@ -3019,8 +3220,8 @@ var _Recipe = class _Recipe {
|
|
|
3019
3220
|
converted,
|
|
3020
3221
|
oldPrimary,
|
|
3021
3222
|
equivalents,
|
|
3022
|
-
|
|
3023
|
-
|
|
3223
|
+
alternative.scalable,
|
|
3224
|
+
alternative.unit?.integerProtected,
|
|
3024
3225
|
"swapped"
|
|
3025
3226
|
);
|
|
3026
3227
|
}
|
|
@@ -3033,24 +3234,37 @@ var _Recipe = class _Recipe {
|
|
|
3033
3234
|
convertedEquiv,
|
|
3034
3235
|
oldPrimary,
|
|
3035
3236
|
remainingEquivalents,
|
|
3036
|
-
|
|
3237
|
+
alternative.scalable,
|
|
3037
3238
|
equiv.unit?.integerProtected,
|
|
3038
3239
|
"converted"
|
|
3039
3240
|
);
|
|
3040
3241
|
}
|
|
3041
3242
|
}
|
|
3042
3243
|
if (method === "remove") {
|
|
3043
|
-
return {
|
|
3244
|
+
return {
|
|
3245
|
+
quantity: alternative.quantity,
|
|
3246
|
+
unit: alternative.unit,
|
|
3247
|
+
scalable: alternative.scalable
|
|
3248
|
+
};
|
|
3044
3249
|
} else {
|
|
3045
|
-
return
|
|
3250
|
+
return {
|
|
3251
|
+
quantity: alternative.quantity,
|
|
3252
|
+
unit: alternative.unit,
|
|
3253
|
+
scalable: alternative.scalable,
|
|
3254
|
+
equivalents
|
|
3255
|
+
};
|
|
3046
3256
|
}
|
|
3047
3257
|
}
|
|
3048
3258
|
function convertAlternatives(alternatives) {
|
|
3049
3259
|
for (const alternative of alternatives) {
|
|
3050
|
-
if (alternative.
|
|
3051
|
-
|
|
3052
|
-
alternative
|
|
3260
|
+
if (alternative.quantity) {
|
|
3261
|
+
const converted = convertAlternativeQuantity(
|
|
3262
|
+
alternative
|
|
3053
3263
|
);
|
|
3264
|
+
alternative.quantity = converted.quantity;
|
|
3265
|
+
alternative.unit = converted.unit;
|
|
3266
|
+
alternative.scalable = converted.scalable;
|
|
3267
|
+
alternative.equivalents = converted.equivalents;
|
|
3054
3268
|
}
|
|
3055
3269
|
}
|
|
3056
3270
|
}
|
|
@@ -3071,7 +3285,7 @@ var _Recipe = class _Recipe {
|
|
|
3071
3285
|
for (const alternatives of newRecipe.choices.ingredientItems.values()) {
|
|
3072
3286
|
convertAlternatives(alternatives);
|
|
3073
3287
|
}
|
|
3074
|
-
newRecipe.
|
|
3288
|
+
newRecipe._populateIngredientQuantities();
|
|
3075
3289
|
if (method !== "keep") _Recipe.unitSystems.set(newRecipe, system);
|
|
3076
3290
|
return newRecipe;
|
|
3077
3291
|
}
|
|
@@ -3124,9 +3338,9 @@ var Recipe = _Recipe;
|
|
|
3124
3338
|
var ShoppingList = class {
|
|
3125
3339
|
/**
|
|
3126
3340
|
* Creates a new ShoppingList instance
|
|
3127
|
-
* @param
|
|
3341
|
+
* @param categoryConfigStr - The category configuration to parse.
|
|
3128
3342
|
*/
|
|
3129
|
-
constructor(
|
|
3343
|
+
constructor(categoryConfigStr) {
|
|
3130
3344
|
// TODO: backport type change
|
|
3131
3345
|
/**
|
|
3132
3346
|
* The ingredients in the shopping list.
|
|
@@ -3139,16 +3353,16 @@ var ShoppingList = class {
|
|
|
3139
3353
|
/**
|
|
3140
3354
|
* The category configuration for the shopping list.
|
|
3141
3355
|
*/
|
|
3142
|
-
__publicField(this, "
|
|
3356
|
+
__publicField(this, "categoryConfig");
|
|
3143
3357
|
/**
|
|
3144
3358
|
* The categorized ingredients in the shopping list.
|
|
3145
3359
|
*/
|
|
3146
3360
|
__publicField(this, "categories");
|
|
3147
|
-
if (
|
|
3148
|
-
this.
|
|
3361
|
+
if (categoryConfigStr) {
|
|
3362
|
+
this.setCategoryConfig(categoryConfigStr);
|
|
3149
3363
|
}
|
|
3150
3364
|
}
|
|
3151
|
-
|
|
3365
|
+
calculateIngredients() {
|
|
3152
3366
|
this.ingredients = [];
|
|
3153
3367
|
const addIngredientQuantity = (name, quantityTotal) => {
|
|
3154
3368
|
const quantityTotalExtended = extendAllUnits(quantityTotal);
|
|
@@ -3235,7 +3449,7 @@ var ShoppingList = class {
|
|
|
3235
3449
|
* @param options - Options for adding the recipe.
|
|
3236
3450
|
* @throws Error if the recipe has alternatives without corresponding choices.
|
|
3237
3451
|
*/
|
|
3238
|
-
|
|
3452
|
+
addRecipe(recipe, options = {}) {
|
|
3239
3453
|
const errorMessage = this.getUnresolvedAlternativesError(
|
|
3240
3454
|
recipe,
|
|
3241
3455
|
options.choices
|
|
@@ -3264,7 +3478,7 @@ var ShoppingList = class {
|
|
|
3264
3478
|
});
|
|
3265
3479
|
}
|
|
3266
3480
|
}
|
|
3267
|
-
this.
|
|
3481
|
+
this.calculateIngredients();
|
|
3268
3482
|
this.categorize();
|
|
3269
3483
|
}
|
|
3270
3484
|
/**
|
|
@@ -3304,15 +3518,15 @@ var ShoppingList = class {
|
|
|
3304
3518
|
}
|
|
3305
3519
|
/**
|
|
3306
3520
|
* Removes a recipe from the shopping list, then automatically
|
|
3307
|
-
* recalculates the quantities and recategorize the ingredients.
|
|
3521
|
+
* recalculates the quantities and recategorize the ingredients.
|
|
3308
3522
|
* @param index - The index of the recipe to remove.
|
|
3309
3523
|
*/
|
|
3310
|
-
|
|
3524
|
+
removeRecipe(index) {
|
|
3311
3525
|
if (index < 0 || index >= this.recipes.length) {
|
|
3312
3526
|
throw new Error("Index out of bounds");
|
|
3313
3527
|
}
|
|
3314
3528
|
this.recipes.splice(index, 1);
|
|
3315
|
-
this.
|
|
3529
|
+
this.calculateIngredients();
|
|
3316
3530
|
this.categorize();
|
|
3317
3531
|
}
|
|
3318
3532
|
/**
|
|
@@ -3320,10 +3534,10 @@ var ShoppingList = class {
|
|
|
3320
3534
|
* and automatically categorize current ingredients from the list.
|
|
3321
3535
|
* @param config - The category configuration to parse.
|
|
3322
3536
|
*/
|
|
3323
|
-
|
|
3537
|
+
setCategoryConfig(config) {
|
|
3324
3538
|
if (typeof config === "string")
|
|
3325
|
-
this.
|
|
3326
|
-
else if (config instanceof CategoryConfig) this.
|
|
3539
|
+
this.categoryConfig = new CategoryConfig(config);
|
|
3540
|
+
else if (config instanceof CategoryConfig) this.categoryConfig = config;
|
|
3327
3541
|
else throw new Error("Invalid category configuration");
|
|
3328
3542
|
this.categorize();
|
|
3329
3543
|
}
|
|
@@ -3332,17 +3546,17 @@ var ShoppingList = class {
|
|
|
3332
3546
|
* Will use the category config if any, otherwise all ingredients will be placed in the "other" category
|
|
3333
3547
|
*/
|
|
3334
3548
|
categorize() {
|
|
3335
|
-
if (!this.
|
|
3549
|
+
if (!this.categoryConfig) {
|
|
3336
3550
|
this.categories = { other: this.ingredients };
|
|
3337
3551
|
return;
|
|
3338
3552
|
}
|
|
3339
3553
|
const categories = { other: [] };
|
|
3340
|
-
for (const category of this.
|
|
3554
|
+
for (const category of this.categoryConfig.categories) {
|
|
3341
3555
|
categories[category.name] = [];
|
|
3342
3556
|
}
|
|
3343
3557
|
for (const ingredient of this.ingredients) {
|
|
3344
3558
|
let found = false;
|
|
3345
|
-
for (const category of this.
|
|
3559
|
+
for (const category of this.categoryConfig.categories) {
|
|
3346
3560
|
for (const categoryIngredient of category.ingredients) {
|
|
3347
3561
|
if (categoryIngredient.aliases.includes(ingredient.name)) {
|
|
3348
3562
|
categories[category.name].push(ingredient);
|
|
@@ -3406,7 +3620,6 @@ var ShoppingCart = class {
|
|
|
3406
3620
|
setProductCatalog(catalog) {
|
|
3407
3621
|
this.productCatalog = catalog;
|
|
3408
3622
|
}
|
|
3409
|
-
// TODO: harmonize recipe name to use underscores
|
|
3410
3623
|
/**
|
|
3411
3624
|
* Sets the shopping list to build the cart from.
|
|
3412
3625
|
* To use if a shopping list was not provided at the creation of the instance
|