@tmlmt/cooklang-parser 3.0.0-alpha.12 → 3.0.0-alpha.14
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 +368 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +83 -53
- package/dist/index.d.ts +83 -53
- package/dist/index.js +366 -37
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -32,9 +32,11 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
32
32
|
// src/index.ts
|
|
33
33
|
var index_exports = {};
|
|
34
34
|
__export(index_exports, {
|
|
35
|
+
BadIndentationError: () => BadIndentationError,
|
|
35
36
|
CategoryConfig: () => CategoryConfig,
|
|
36
37
|
NoProductCatalogForCartError: () => NoProductCatalogForCartError,
|
|
37
38
|
NoShoppingListForCartError: () => NoShoppingListForCartError,
|
|
39
|
+
NoTabAsIndentError: () => NoTabAsIndentError,
|
|
38
40
|
ProductCatalog: () => ProductCatalog,
|
|
39
41
|
Recipe: () => Recipe,
|
|
40
42
|
Section: () => Section,
|
|
@@ -320,6 +322,12 @@ var i = (() => {
|
|
|
320
322
|
})();
|
|
321
323
|
|
|
322
324
|
// src/regex.ts
|
|
325
|
+
var metadataKeyRegex = /^([^:\n]+?):/gm;
|
|
326
|
+
var numericValueRegex = /^-?\d+(\.\d+)?$/;
|
|
327
|
+
var nestedMetaVarRegex = (varName) => new RegExp(
|
|
328
|
+
`^${varName}:\\s*\\r?\\n((?:[ ]+.+(?:\\r?\\n|$))+)`,
|
|
329
|
+
"m"
|
|
330
|
+
);
|
|
323
331
|
var metadataRegex = d().literal("---").newline().startCaptureGroup().anyCharacter().zeroOrMore().optional().endGroup().newline().literal("---").dotAll().toRegExp();
|
|
324
332
|
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
333
|
var nonWordChar = "\\s@#~\\[\\]{(,;:!?";
|
|
@@ -348,6 +356,37 @@ var shoppingListRegex = d().literal("[").startNamedGroup("name").anyCharacter().
|
|
|
348
356
|
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();
|
|
349
357
|
var numberLikeRegex = d().startAnchor().digit().oneOrMore().startGroup().anyOf(".,/").exactly(1).digit().oneOrMore().endGroup().optional().endAnchor().toRegExp();
|
|
350
358
|
var floatRegex = d().startAnchor().digit().oneOrMore().startGroup().anyOf(".").exactly(1).digit().oneOrMore().endGroup().optional().endAnchor().toRegExp();
|
|
359
|
+
var mdEscaped = d().literal("\\").startCaptureGroup().anyOf("*_`").endGroup();
|
|
360
|
+
var mdInlineCode = d().literal("`").startCaptureGroup().notAnyOf("`").oneOrMore().lazy().endGroup().literal("`");
|
|
361
|
+
var mdLink = d().literal("[").startCaptureGroup().notAnyOf("\\]").oneOrMore().lazy().endGroup().literal("](").startCaptureGroup().notAnyOf(")").oneOrMore().lazy().endGroup().literal(")");
|
|
362
|
+
var mdTripleAsterisk = d().literal("***").startCaptureGroup().anyCharacter().oneOrMore().lazy().endGroup().literal("***");
|
|
363
|
+
var mdTripleUnderscore = d().literal("___").startCaptureGroup().anyCharacter().oneOrMore().lazy().endGroup().literal("___");
|
|
364
|
+
var mdBoldAstItalicUnd = d().literal("**_").startCaptureGroup().anyCharacter().oneOrMore().lazy().endGroup().literal("_**");
|
|
365
|
+
var mdBoldUndItalicAst = d().literal("__*").startCaptureGroup().anyCharacter().oneOrMore().lazy().endGroup().literal("*__");
|
|
366
|
+
var mdItalicAstBoldUnd = d().literal("*__").startCaptureGroup().anyCharacter().oneOrMore().lazy().endGroup().literal("__*");
|
|
367
|
+
var mdItalicUndBoldAst = d().literal("_**").startCaptureGroup().anyCharacter().oneOrMore().lazy().endGroup().literal("**_");
|
|
368
|
+
var mdBoldAsterisk = d().literal("**").startCaptureGroup().anyCharacter().oneOrMore().lazy().endGroup().literal("**");
|
|
369
|
+
var mdBoldUnderscore = d().wordBoundary().literal("__").startCaptureGroup().anyCharacter().oneOrMore().lazy().endGroup().literal("__").wordBoundary();
|
|
370
|
+
var mdItalicAsterisk = d().literal("*").startCaptureGroup().anyCharacter().oneOrMore().lazy().endGroup().literal("*");
|
|
371
|
+
var mdItalicUnderscore = d().wordBoundary().literal("_").startCaptureGroup().anyCharacter().oneOrMore().lazy().endGroup().literal("_").wordBoundary();
|
|
372
|
+
var markdownRegex = new RegExp(
|
|
373
|
+
[
|
|
374
|
+
mdEscaped,
|
|
375
|
+
mdInlineCode,
|
|
376
|
+
mdLink,
|
|
377
|
+
mdTripleAsterisk,
|
|
378
|
+
mdTripleUnderscore,
|
|
379
|
+
mdBoldAstItalicUnd,
|
|
380
|
+
mdBoldUndItalicAst,
|
|
381
|
+
mdItalicAstBoldUnd,
|
|
382
|
+
mdItalicUndBoldAst,
|
|
383
|
+
mdBoldAsterisk,
|
|
384
|
+
mdBoldUnderscore,
|
|
385
|
+
mdItalicAsterisk,
|
|
386
|
+
mdItalicUnderscore
|
|
387
|
+
].map((r2) => r2.toRegExp().source).join("|"),
|
|
388
|
+
"g"
|
|
389
|
+
);
|
|
351
390
|
|
|
352
391
|
// src/units/definitions.ts
|
|
353
392
|
var units = [
|
|
@@ -931,6 +970,20 @@ var InvalidQuantityFormat = class extends Error {
|
|
|
931
970
|
this.name = "InvalidQuantityFormat";
|
|
932
971
|
}
|
|
933
972
|
};
|
|
973
|
+
var NoTabAsIndentError = class extends Error {
|
|
974
|
+
constructor() {
|
|
975
|
+
super(
|
|
976
|
+
`Tabs are not allowed for indentation in metadata blocks. Please use spaces only.`
|
|
977
|
+
);
|
|
978
|
+
this.name = "NoTabAsIndentError";
|
|
979
|
+
}
|
|
980
|
+
};
|
|
981
|
+
var BadIndentationError = class extends Error {
|
|
982
|
+
constructor() {
|
|
983
|
+
super(`Bad identation of a nested block. Please use spaces only.`);
|
|
984
|
+
this.name = "BadIndentationError";
|
|
985
|
+
}
|
|
986
|
+
};
|
|
934
987
|
|
|
935
988
|
// src/utils/type_guards.ts
|
|
936
989
|
function isGroup(x) {
|
|
@@ -1460,12 +1513,106 @@ function parseQuantityInput(input_str) {
|
|
|
1460
1513
|
}
|
|
1461
1514
|
return { type: "fixed", value: parseFixedValue(clean_str) };
|
|
1462
1515
|
}
|
|
1516
|
+
function parseMarkdownSegments(text) {
|
|
1517
|
+
const items = [];
|
|
1518
|
+
let cursor = 0;
|
|
1519
|
+
for (const match of text.matchAll(markdownRegex)) {
|
|
1520
|
+
const idx = match.index;
|
|
1521
|
+
if (idx > cursor) {
|
|
1522
|
+
items.push({ type: "text", value: text.slice(cursor, idx) });
|
|
1523
|
+
}
|
|
1524
|
+
const [
|
|
1525
|
+
,
|
|
1526
|
+
escaped,
|
|
1527
|
+
// group 1: escaped character
|
|
1528
|
+
code,
|
|
1529
|
+
// group 2: inline code
|
|
1530
|
+
linkText,
|
|
1531
|
+
// group 3: link text
|
|
1532
|
+
linkUrl,
|
|
1533
|
+
// group 4: link url
|
|
1534
|
+
tripleAst,
|
|
1535
|
+
// group 5: ***bold+italic***
|
|
1536
|
+
tripleUnd,
|
|
1537
|
+
// group 6: ___bold+italic___
|
|
1538
|
+
astUnd,
|
|
1539
|
+
// group 7: **_bold+italic_**
|
|
1540
|
+
undAst,
|
|
1541
|
+
// group 8: __*bold+italic*__
|
|
1542
|
+
astUndUnd,
|
|
1543
|
+
// group 9: *__bold+italic__*
|
|
1544
|
+
undAstAst,
|
|
1545
|
+
// group 10: _**bold+italic**_
|
|
1546
|
+
boldAst,
|
|
1547
|
+
// group 11: **bold**
|
|
1548
|
+
boldUnd,
|
|
1549
|
+
// group 12: __bold__
|
|
1550
|
+
italicAst,
|
|
1551
|
+
// group 13: *italic*
|
|
1552
|
+
italicUnd
|
|
1553
|
+
// group 14: _italic_
|
|
1554
|
+
] = match;
|
|
1555
|
+
let value;
|
|
1556
|
+
let attribute;
|
|
1557
|
+
let href;
|
|
1558
|
+
if (escaped !== void 0) {
|
|
1559
|
+
items.push({ type: "text", value: escaped });
|
|
1560
|
+
cursor = idx + match[0].length;
|
|
1561
|
+
continue;
|
|
1562
|
+
} else if (code !== void 0) {
|
|
1563
|
+
value = code;
|
|
1564
|
+
attribute = "code";
|
|
1565
|
+
} else if (linkText !== void 0) {
|
|
1566
|
+
value = linkText;
|
|
1567
|
+
attribute = "link";
|
|
1568
|
+
href = linkUrl;
|
|
1569
|
+
} else if (tripleAst !== void 0 || tripleUnd !== void 0 || astUnd !== void 0 || undAst !== void 0 || astUndUnd !== void 0 || undAstAst !== void 0) {
|
|
1570
|
+
value = tripleAst ?? tripleUnd ?? astUnd ?? undAst ?? astUndUnd ?? undAstAst;
|
|
1571
|
+
attribute = "bold+italic";
|
|
1572
|
+
} else if (boldAst !== void 0 || boldUnd !== void 0) {
|
|
1573
|
+
value = boldAst ?? boldUnd;
|
|
1574
|
+
attribute = "bold";
|
|
1575
|
+
} else {
|
|
1576
|
+
value = italicAst ?? italicUnd;
|
|
1577
|
+
attribute = "italic";
|
|
1578
|
+
}
|
|
1579
|
+
const item = { type: "text", value };
|
|
1580
|
+
if (attribute) item.attribute = attribute;
|
|
1581
|
+
if (href) item.href = href;
|
|
1582
|
+
items.push(item);
|
|
1583
|
+
cursor = idx + match[0].length;
|
|
1584
|
+
}
|
|
1585
|
+
if (cursor < text.length) {
|
|
1586
|
+
items.push({ type: "text", value: text.slice(cursor) });
|
|
1587
|
+
}
|
|
1588
|
+
return items;
|
|
1589
|
+
}
|
|
1463
1590
|
function parseSimpleMetaVar(content, varName) {
|
|
1464
1591
|
const varMatch = content.match(
|
|
1465
1592
|
new RegExp(`^${varName}:\\s*(.*(?:\\r?\\n\\s+.*)*)+`, "m")
|
|
1466
1593
|
);
|
|
1467
1594
|
return varMatch ? varMatch[1]?.trim().replace(/\s*\r?\n\s+/g, " ") : void 0;
|
|
1468
1595
|
}
|
|
1596
|
+
function parseBlockScalarMetaVar(content, varName) {
|
|
1597
|
+
const match = content.match(
|
|
1598
|
+
new RegExp(
|
|
1599
|
+
`^${varName}:\\s*([|>])\\s*\\r?\\n((?:(?:[ ]+.*|\\s*)(?:\\r?\\n|$))+)`,
|
|
1600
|
+
"m"
|
|
1601
|
+
)
|
|
1602
|
+
);
|
|
1603
|
+
if (!match) return void 0;
|
|
1604
|
+
const style = match[1];
|
|
1605
|
+
const rawBlock = match[2];
|
|
1606
|
+
const lines = rawBlock.split(/\r?\n/);
|
|
1607
|
+
const firstNonEmpty = lines.find((l) => l.trim() !== "");
|
|
1608
|
+
if (!firstNonEmpty) return void 0;
|
|
1609
|
+
const baseIndent = firstNonEmpty.match(/^([ ]*)/)[1].length;
|
|
1610
|
+
const stripped = lines.map((line) => line.trim() === "" ? "" : line.slice(baseIndent)).join("\n").replace(/\n+$/, "");
|
|
1611
|
+
if (style === "|") {
|
|
1612
|
+
return stripped;
|
|
1613
|
+
}
|
|
1614
|
+
return stripped.replace(/\n\n/g, "\0").replace(/\n/g, " ").replace(/\0/g, "\n");
|
|
1615
|
+
}
|
|
1469
1616
|
function parseScalingMetaVar(content, varName) {
|
|
1470
1617
|
const varMatch = content.match(scalingMetaValueRegex(varName));
|
|
1471
1618
|
if (!varMatch) return void 0;
|
|
@@ -1488,6 +1635,108 @@ function parseListMetaVar(content, varName) {
|
|
|
1488
1635
|
return listMatch[2].split("\n").filter((line) => line.trim() !== "").map((line) => line.replace(/^\s*-\s*/, "").trim());
|
|
1489
1636
|
}
|
|
1490
1637
|
}
|
|
1638
|
+
function extractAllMetadataKeys(content) {
|
|
1639
|
+
const keys = [];
|
|
1640
|
+
for (const match of content.matchAll(metadataKeyRegex)) {
|
|
1641
|
+
keys.push(match[1].trim());
|
|
1642
|
+
}
|
|
1643
|
+
return [...new Set(keys)];
|
|
1644
|
+
}
|
|
1645
|
+
function parseNestedMetaVar(content, varName) {
|
|
1646
|
+
const match = content.match(nestedMetaVarRegex(varName));
|
|
1647
|
+
if (!match) return void 0;
|
|
1648
|
+
const nestedContent = match[1];
|
|
1649
|
+
return parseNestedBlock(nestedContent);
|
|
1650
|
+
}
|
|
1651
|
+
function parseNestedBlock(content) {
|
|
1652
|
+
const lines = content.split(/\r?\n/).filter((line) => line.trim() !== "");
|
|
1653
|
+
if (lines.length === 0) return void 0;
|
|
1654
|
+
const baseIndentMatch = lines[0].match(/^(\s*)/);
|
|
1655
|
+
if (baseIndentMatch?.[0]?.includes(" ")) {
|
|
1656
|
+
throw new NoTabAsIndentError();
|
|
1657
|
+
}
|
|
1658
|
+
const baseIndent = baseIndentMatch?.[1]?.length;
|
|
1659
|
+
if (lines[0].trim().startsWith("- ")) return void 0;
|
|
1660
|
+
const result = {};
|
|
1661
|
+
let i2 = 0;
|
|
1662
|
+
while (i2 < lines.length) {
|
|
1663
|
+
const line = lines[i2];
|
|
1664
|
+
const leadingWhitespace = line.match(/^(\s*)/)?.[1];
|
|
1665
|
+
if (leadingWhitespace && leadingWhitespace.includes(" ")) {
|
|
1666
|
+
throw new NoTabAsIndentError();
|
|
1667
|
+
}
|
|
1668
|
+
const currentIndent = leadingWhitespace.length;
|
|
1669
|
+
if (currentIndent < baseIndent) {
|
|
1670
|
+
break;
|
|
1671
|
+
}
|
|
1672
|
+
if (currentIndent !== baseIndent) {
|
|
1673
|
+
throw new BadIndentationError();
|
|
1674
|
+
}
|
|
1675
|
+
const keyValueMatch = line.match(/^[ ]*([^:\n]+?):\s*(.*)$/);
|
|
1676
|
+
if (!keyValueMatch) {
|
|
1677
|
+
i2++;
|
|
1678
|
+
continue;
|
|
1679
|
+
}
|
|
1680
|
+
const key = keyValueMatch[1].trim();
|
|
1681
|
+
const rawValue = keyValueMatch[2].trim();
|
|
1682
|
+
if (rawValue === "") {
|
|
1683
|
+
const childLines = [];
|
|
1684
|
+
let j = i2 + 1;
|
|
1685
|
+
while (j < lines.length) {
|
|
1686
|
+
const childLine = lines[j];
|
|
1687
|
+
const childIndent = childLine.match(/^([ ]*)/)?.[1]?.length;
|
|
1688
|
+
if (childIndent && childIndent > baseIndent) {
|
|
1689
|
+
childLines.push(childLine);
|
|
1690
|
+
j++;
|
|
1691
|
+
} else {
|
|
1692
|
+
break;
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
if (childLines.length > 0) {
|
|
1696
|
+
const firstChildTrimmed = childLines[0].trim();
|
|
1697
|
+
if (firstChildTrimmed.startsWith("- ")) {
|
|
1698
|
+
const reconstructedContent = `${key}:
|
|
1699
|
+
${childLines.join("\n")}`;
|
|
1700
|
+
const listResult = parseListMetaVar(reconstructedContent, key);
|
|
1701
|
+
if (listResult) {
|
|
1702
|
+
result[key] = listResult.map(
|
|
1703
|
+
(item) => parseMetadataValue(item)
|
|
1704
|
+
);
|
|
1705
|
+
}
|
|
1706
|
+
} else {
|
|
1707
|
+
const childContent = childLines.join("\n");
|
|
1708
|
+
const nested = parseNestedBlock(childContent);
|
|
1709
|
+
if (nested) {
|
|
1710
|
+
result[key] = nested;
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
i2 = j;
|
|
1715
|
+
} else {
|
|
1716
|
+
result[key] = parseMetadataValue(rawValue);
|
|
1717
|
+
i2++;
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
return result;
|
|
1721
|
+
}
|
|
1722
|
+
function parseMetadataValue(rawValue) {
|
|
1723
|
+
if (rawValue.startsWith("[") && rawValue.endsWith("]")) {
|
|
1724
|
+
return rawValue.slice(1, -1).split(",").map((item) => item.trim());
|
|
1725
|
+
}
|
|
1726
|
+
if (numericValueRegex.test(rawValue)) {
|
|
1727
|
+
return Number(rawValue);
|
|
1728
|
+
}
|
|
1729
|
+
return rawValue;
|
|
1730
|
+
}
|
|
1731
|
+
function parseAnyMetaVar(content, varName) {
|
|
1732
|
+
const nested = parseNestedMetaVar(content, varName);
|
|
1733
|
+
if (nested) return nested;
|
|
1734
|
+
const list = parseListMetaVar(content, varName);
|
|
1735
|
+
if (list) return list;
|
|
1736
|
+
const simple = parseSimpleMetaVar(content, varName);
|
|
1737
|
+
if (simple) return parseMetadataValue(simple);
|
|
1738
|
+
return void 0;
|
|
1739
|
+
}
|
|
1491
1740
|
function extractMetadata(content) {
|
|
1492
1741
|
const metadata = {};
|
|
1493
1742
|
let servings = void 0;
|
|
@@ -1495,13 +1744,24 @@ function extractMetadata(content) {
|
|
|
1495
1744
|
if (!metadataContent) {
|
|
1496
1745
|
return { metadata };
|
|
1497
1746
|
}
|
|
1498
|
-
|
|
1747
|
+
const handledKeys = /* @__PURE__ */ new Set([
|
|
1748
|
+
// Simple string fields
|
|
1499
1749
|
"title",
|
|
1750
|
+
"author",
|
|
1751
|
+
"locale",
|
|
1752
|
+
"introduction",
|
|
1753
|
+
"description",
|
|
1754
|
+
"course",
|
|
1755
|
+
"category",
|
|
1756
|
+
"diet",
|
|
1757
|
+
"cuisine",
|
|
1758
|
+
"difficulty",
|
|
1759
|
+
// Source fields
|
|
1500
1760
|
"source",
|
|
1501
1761
|
"source.name",
|
|
1502
1762
|
"source.url",
|
|
1503
|
-
"author",
|
|
1504
1763
|
"source.author",
|
|
1764
|
+
// Time fields
|
|
1505
1765
|
"prep time",
|
|
1506
1766
|
"time.prep",
|
|
1507
1767
|
"cook time",
|
|
@@ -1509,6 +1769,23 @@ function extractMetadata(content) {
|
|
|
1509
1769
|
"time required",
|
|
1510
1770
|
"time",
|
|
1511
1771
|
"duration",
|
|
1772
|
+
// Image fields
|
|
1773
|
+
"image",
|
|
1774
|
+
"picture",
|
|
1775
|
+
"images",
|
|
1776
|
+
"pictures",
|
|
1777
|
+
// Unit system
|
|
1778
|
+
"unit system",
|
|
1779
|
+
// Scaling fields
|
|
1780
|
+
"servings",
|
|
1781
|
+
"yield",
|
|
1782
|
+
"serves",
|
|
1783
|
+
// List fields
|
|
1784
|
+
"tags"
|
|
1785
|
+
]);
|
|
1786
|
+
for (const metaVar of [
|
|
1787
|
+
"title",
|
|
1788
|
+
"author",
|
|
1512
1789
|
"locale",
|
|
1513
1790
|
"introduction",
|
|
1514
1791
|
"description",
|
|
@@ -1516,17 +1793,64 @@ function extractMetadata(content) {
|
|
|
1516
1793
|
"category",
|
|
1517
1794
|
"diet",
|
|
1518
1795
|
"cuisine",
|
|
1519
|
-
"difficulty"
|
|
1520
|
-
"image",
|
|
1521
|
-
"picture"
|
|
1796
|
+
"difficulty"
|
|
1522
1797
|
]) {
|
|
1798
|
+
if (metaVar === "description" || metaVar === "introduction") {
|
|
1799
|
+
const blockValue = parseBlockScalarMetaVar(metadataContent, metaVar);
|
|
1800
|
+
if (blockValue) {
|
|
1801
|
+
metadata[metaVar] = blockValue;
|
|
1802
|
+
continue;
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1523
1805
|
const stringMetaValue = parseSimpleMetaVar(metadataContent, metaVar);
|
|
1524
1806
|
if (stringMetaValue) metadata[metaVar] = stringMetaValue;
|
|
1525
1807
|
}
|
|
1808
|
+
const sourceNested = parseNestedMetaVar(metadataContent, "source");
|
|
1809
|
+
const sourceTxt = parseSimpleMetaVar(metadataContent, "source");
|
|
1810
|
+
const sourceName = parseSimpleMetaVar(metadataContent, "source.name");
|
|
1811
|
+
const sourceUrl = parseSimpleMetaVar(metadataContent, "source.url");
|
|
1812
|
+
const sourceAuthor = parseSimpleMetaVar(metadataContent, "source.author");
|
|
1813
|
+
if (sourceNested) {
|
|
1814
|
+
const source = {};
|
|
1815
|
+
if (typeof sourceNested.name === "string") source.name = sourceNested.name;
|
|
1816
|
+
if (typeof sourceNested.url === "string") source.url = sourceNested.url;
|
|
1817
|
+
if (typeof sourceNested.author === "string")
|
|
1818
|
+
source.author = sourceNested.author;
|
|
1819
|
+
if (Object.keys(source).length > 0) metadata.source = source;
|
|
1820
|
+
} else if (sourceName || sourceAuthor || sourceUrl) {
|
|
1821
|
+
const source = {};
|
|
1822
|
+
if (sourceName) source.name = sourceName;
|
|
1823
|
+
if (sourceUrl) source.url = sourceUrl;
|
|
1824
|
+
if (sourceAuthor) source.author = sourceAuthor;
|
|
1825
|
+
metadata.source = source;
|
|
1826
|
+
} else if (sourceTxt) {
|
|
1827
|
+
metadata.source = sourceTxt;
|
|
1828
|
+
}
|
|
1829
|
+
const timeNested = parseNestedMetaVar(metadataContent, "time");
|
|
1830
|
+
const prepTime = parseSimpleMetaVar(metadataContent, "prep time") ?? parseSimpleMetaVar(metadataContent, "time.prep");
|
|
1831
|
+
const cookTime = parseSimpleMetaVar(metadataContent, "cook time") ?? parseSimpleMetaVar(metadataContent, "time.cook");
|
|
1832
|
+
const totalTime = parseSimpleMetaVar(metadataContent, "time required") ?? parseSimpleMetaVar(metadataContent, "time") ?? parseSimpleMetaVar(metadataContent, "duration");
|
|
1833
|
+
if (timeNested) {
|
|
1834
|
+
const time = {};
|
|
1835
|
+
if (typeof timeNested.prep === "string") time.prep = timeNested.prep;
|
|
1836
|
+
if (typeof timeNested.cook === "string") time.cook = timeNested.cook;
|
|
1837
|
+
if (typeof timeNested.total === "string") time.total = timeNested.total;
|
|
1838
|
+
if (Object.keys(time).length > 0) metadata.time = time;
|
|
1839
|
+
} else if (prepTime || cookTime || totalTime) {
|
|
1840
|
+
const time = {};
|
|
1841
|
+
if (prepTime) time.prep = prepTime;
|
|
1842
|
+
if (cookTime) time.cook = cookTime;
|
|
1843
|
+
if (totalTime) time.total = totalTime;
|
|
1844
|
+
metadata.time = time;
|
|
1845
|
+
}
|
|
1846
|
+
const image = parseSimpleMetaVar(metadataContent, "image") ?? parseSimpleMetaVar(metadataContent, "picture");
|
|
1847
|
+
if (image) metadata.image = image;
|
|
1848
|
+
const images = parseListMetaVar(metadataContent, "images") ?? parseListMetaVar(metadataContent, "pictures");
|
|
1849
|
+
if (images) metadata.images = images;
|
|
1526
1850
|
let unitSystem;
|
|
1527
1851
|
const unitSystemRaw = parseSimpleMetaVar(metadataContent, "unit system");
|
|
1528
1852
|
if (unitSystemRaw) {
|
|
1529
|
-
metadata
|
|
1853
|
+
metadata.unitSystem = unitSystemRaw;
|
|
1530
1854
|
const unitSystemMap = {
|
|
1531
1855
|
metric: "metric",
|
|
1532
1856
|
us: "US",
|
|
@@ -1535,16 +1859,22 @@ function extractMetadata(content) {
|
|
|
1535
1859
|
};
|
|
1536
1860
|
unitSystem = unitSystemMap[unitSystemRaw.toLowerCase()];
|
|
1537
1861
|
}
|
|
1538
|
-
for (const metaVar of ["
|
|
1862
|
+
for (const metaVar of ["servings", "yield", "serves"]) {
|
|
1539
1863
|
const scalingMetaValue = parseScalingMetaVar(metadataContent, metaVar);
|
|
1540
1864
|
if (scalingMetaValue && scalingMetaValue[1]) {
|
|
1541
1865
|
metadata[metaVar] = scalingMetaValue[1];
|
|
1542
1866
|
servings = scalingMetaValue[0];
|
|
1543
1867
|
}
|
|
1544
1868
|
}
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1869
|
+
const tags = parseListMetaVar(metadataContent, "tags");
|
|
1870
|
+
if (tags) metadata.tags = tags;
|
|
1871
|
+
const allKeys = extractAllMetadataKeys(metadataContent);
|
|
1872
|
+
for (const key of allKeys) {
|
|
1873
|
+
if (handledKeys.has(key)) continue;
|
|
1874
|
+
const value = parseAnyMetaVar(metadataContent, key);
|
|
1875
|
+
if (value !== void 0) {
|
|
1876
|
+
metadata[key] = value;
|
|
1877
|
+
}
|
|
1548
1878
|
}
|
|
1549
1879
|
return { metadata, servings, unitSystem };
|
|
1550
1880
|
}
|
|
@@ -2175,13 +2505,13 @@ var _Recipe = class _Recipe {
|
|
|
2175
2505
|
for (const match of text.matchAll(globalRegex)) {
|
|
2176
2506
|
const idx = match.index;
|
|
2177
2507
|
if (idx > cursor) {
|
|
2178
|
-
noteItems.push(
|
|
2508
|
+
noteItems.push(...parseMarkdownSegments(text.slice(cursor, idx)));
|
|
2179
2509
|
}
|
|
2180
2510
|
this._parseArbitraryScalable(match.groups, noteItems);
|
|
2181
2511
|
cursor = idx + match[0].length;
|
|
2182
2512
|
}
|
|
2183
2513
|
if (cursor < text.length) {
|
|
2184
|
-
noteItems.push(
|
|
2514
|
+
noteItems.push(...parseMarkdownSegments(text.slice(cursor)));
|
|
2185
2515
|
}
|
|
2186
2516
|
return noteItems;
|
|
2187
2517
|
}
|
|
@@ -2445,7 +2775,7 @@ var _Recipe = class _Recipe {
|
|
|
2445
2775
|
* Quantities are grouped by their alternative signature and summed using addEquivalentsAndSimplify.
|
|
2446
2776
|
* @internal
|
|
2447
2777
|
*/
|
|
2448
|
-
|
|
2778
|
+
_populateIngredientQuantities() {
|
|
2449
2779
|
for (const ing of this.ingredients) {
|
|
2450
2780
|
delete ing.quantities;
|
|
2451
2781
|
delete ing.usedAsPrimary;
|
|
@@ -2735,7 +3065,7 @@ var _Recipe = class _Recipe {
|
|
|
2735
3065
|
for (const match of line.matchAll(tokensRegex)) {
|
|
2736
3066
|
const idx = match.index;
|
|
2737
3067
|
if (idx > cursor) {
|
|
2738
|
-
items.push(
|
|
3068
|
+
items.push(...parseMarkdownSegments(line.slice(cursor, idx)));
|
|
2739
3069
|
}
|
|
2740
3070
|
const groups = match.groups;
|
|
2741
3071
|
if (groups.mIngredientName || groups.sIngredientName) {
|
|
@@ -2797,7 +3127,7 @@ var _Recipe = class _Recipe {
|
|
|
2797
3127
|
cursor = idx + match[0].length;
|
|
2798
3128
|
}
|
|
2799
3129
|
if (cursor < line.length) {
|
|
2800
|
-
items.push(
|
|
3130
|
+
items.push(...parseMarkdownSegments(line.slice(cursor)));
|
|
2801
3131
|
}
|
|
2802
3132
|
blankLineBefore = false;
|
|
2803
3133
|
}
|
|
@@ -2806,7 +3136,7 @@ var _Recipe = class _Recipe {
|
|
|
2806
3136
|
if (!section.isBlank()) {
|
|
2807
3137
|
this.sections.push(section);
|
|
2808
3138
|
}
|
|
2809
|
-
this.
|
|
3139
|
+
this._populateIngredientQuantities();
|
|
2810
3140
|
}
|
|
2811
3141
|
/**
|
|
2812
3142
|
* Scales the recipe to a new number of servings. In practice, it calls
|
|
@@ -2904,7 +3234,7 @@ var _Recipe = class _Recipe {
|
|
|
2904
3234
|
factor
|
|
2905
3235
|
);
|
|
2906
3236
|
}
|
|
2907
|
-
newRecipe.
|
|
3237
|
+
newRecipe._populateIngredientQuantities();
|
|
2908
3238
|
newRecipe.servings = (0, import_big4.default)(originalServings).times(factor).toNumber();
|
|
2909
3239
|
if (newRecipe.metadata.servings && this.metadata.servings) {
|
|
2910
3240
|
if (floatRegex.test(String(this.metadata.servings).replace(",", ".").trim())) {
|
|
@@ -2969,9 +3299,9 @@ var _Recipe = class _Recipe {
|
|
|
2969
3299
|
if (method === "remove") {
|
|
2970
3300
|
return newPrimary;
|
|
2971
3301
|
} else if (method === "replace") {
|
|
3302
|
+
if (source === "converted") remainingEquivalents.push(oldPrimary);
|
|
2972
3303
|
if (remainingEquivalents.length > 0) {
|
|
2973
3304
|
newPrimary.equivalents = remainingEquivalents;
|
|
2974
|
-
if (source === "converted") newPrimary.equivalents.push(oldPrimary);
|
|
2975
3305
|
}
|
|
2976
3306
|
} else {
|
|
2977
3307
|
newPrimary.equivalents = [oldPrimary, ...remainingEquivalents];
|
|
@@ -3089,7 +3419,7 @@ var _Recipe = class _Recipe {
|
|
|
3089
3419
|
for (const alternatives of newRecipe.choices.ingredientItems.values()) {
|
|
3090
3420
|
convertAlternatives(alternatives);
|
|
3091
3421
|
}
|
|
3092
|
-
newRecipe.
|
|
3422
|
+
newRecipe._populateIngredientQuantities();
|
|
3093
3423
|
if (method !== "keep") _Recipe.unitSystems.set(newRecipe, system);
|
|
3094
3424
|
return newRecipe;
|
|
3095
3425
|
}
|
|
@@ -3142,9 +3472,9 @@ var Recipe = _Recipe;
|
|
|
3142
3472
|
var ShoppingList = class {
|
|
3143
3473
|
/**
|
|
3144
3474
|
* Creates a new ShoppingList instance
|
|
3145
|
-
* @param
|
|
3475
|
+
* @param categoryConfigStr - The category configuration to parse.
|
|
3146
3476
|
*/
|
|
3147
|
-
constructor(
|
|
3477
|
+
constructor(categoryConfigStr) {
|
|
3148
3478
|
// TODO: backport type change
|
|
3149
3479
|
/**
|
|
3150
3480
|
* The ingredients in the shopping list.
|
|
@@ -3157,16 +3487,16 @@ var ShoppingList = class {
|
|
|
3157
3487
|
/**
|
|
3158
3488
|
* The category configuration for the shopping list.
|
|
3159
3489
|
*/
|
|
3160
|
-
__publicField(this, "
|
|
3490
|
+
__publicField(this, "categoryConfig");
|
|
3161
3491
|
/**
|
|
3162
3492
|
* The categorized ingredients in the shopping list.
|
|
3163
3493
|
*/
|
|
3164
3494
|
__publicField(this, "categories");
|
|
3165
|
-
if (
|
|
3166
|
-
this.
|
|
3495
|
+
if (categoryConfigStr) {
|
|
3496
|
+
this.setCategoryConfig(categoryConfigStr);
|
|
3167
3497
|
}
|
|
3168
3498
|
}
|
|
3169
|
-
|
|
3499
|
+
calculateIngredients() {
|
|
3170
3500
|
this.ingredients = [];
|
|
3171
3501
|
const addIngredientQuantity = (name, quantityTotal) => {
|
|
3172
3502
|
const quantityTotalExtended = extendAllUnits(quantityTotal);
|
|
@@ -3253,7 +3583,7 @@ var ShoppingList = class {
|
|
|
3253
3583
|
* @param options - Options for adding the recipe.
|
|
3254
3584
|
* @throws Error if the recipe has alternatives without corresponding choices.
|
|
3255
3585
|
*/
|
|
3256
|
-
|
|
3586
|
+
addRecipe(recipe, options = {}) {
|
|
3257
3587
|
const errorMessage = this.getUnresolvedAlternativesError(
|
|
3258
3588
|
recipe,
|
|
3259
3589
|
options.choices
|
|
@@ -3282,7 +3612,7 @@ var ShoppingList = class {
|
|
|
3282
3612
|
});
|
|
3283
3613
|
}
|
|
3284
3614
|
}
|
|
3285
|
-
this.
|
|
3615
|
+
this.calculateIngredients();
|
|
3286
3616
|
this.categorize();
|
|
3287
3617
|
}
|
|
3288
3618
|
/**
|
|
@@ -3322,15 +3652,15 @@ var ShoppingList = class {
|
|
|
3322
3652
|
}
|
|
3323
3653
|
/**
|
|
3324
3654
|
* Removes a recipe from the shopping list, then automatically
|
|
3325
|
-
* recalculates the quantities and recategorize the ingredients.
|
|
3655
|
+
* recalculates the quantities and recategorize the ingredients.
|
|
3326
3656
|
* @param index - The index of the recipe to remove.
|
|
3327
3657
|
*/
|
|
3328
|
-
|
|
3658
|
+
removeRecipe(index) {
|
|
3329
3659
|
if (index < 0 || index >= this.recipes.length) {
|
|
3330
3660
|
throw new Error("Index out of bounds");
|
|
3331
3661
|
}
|
|
3332
3662
|
this.recipes.splice(index, 1);
|
|
3333
|
-
this.
|
|
3663
|
+
this.calculateIngredients();
|
|
3334
3664
|
this.categorize();
|
|
3335
3665
|
}
|
|
3336
3666
|
/**
|
|
@@ -3338,10 +3668,10 @@ var ShoppingList = class {
|
|
|
3338
3668
|
* and automatically categorize current ingredients from the list.
|
|
3339
3669
|
* @param config - The category configuration to parse.
|
|
3340
3670
|
*/
|
|
3341
|
-
|
|
3671
|
+
setCategoryConfig(config) {
|
|
3342
3672
|
if (typeof config === "string")
|
|
3343
|
-
this.
|
|
3344
|
-
else if (config instanceof CategoryConfig) this.
|
|
3673
|
+
this.categoryConfig = new CategoryConfig(config);
|
|
3674
|
+
else if (config instanceof CategoryConfig) this.categoryConfig = config;
|
|
3345
3675
|
else throw new Error("Invalid category configuration");
|
|
3346
3676
|
this.categorize();
|
|
3347
3677
|
}
|
|
@@ -3350,17 +3680,17 @@ var ShoppingList = class {
|
|
|
3350
3680
|
* Will use the category config if any, otherwise all ingredients will be placed in the "other" category
|
|
3351
3681
|
*/
|
|
3352
3682
|
categorize() {
|
|
3353
|
-
if (!this.
|
|
3683
|
+
if (!this.categoryConfig) {
|
|
3354
3684
|
this.categories = { other: this.ingredients };
|
|
3355
3685
|
return;
|
|
3356
3686
|
}
|
|
3357
3687
|
const categories = { other: [] };
|
|
3358
|
-
for (const category of this.
|
|
3688
|
+
for (const category of this.categoryConfig.categories) {
|
|
3359
3689
|
categories[category.name] = [];
|
|
3360
3690
|
}
|
|
3361
3691
|
for (const ingredient of this.ingredients) {
|
|
3362
3692
|
let found = false;
|
|
3363
|
-
for (const category of this.
|
|
3693
|
+
for (const category of this.categoryConfig.categories) {
|
|
3364
3694
|
for (const categoryIngredient of category.ingredients) {
|
|
3365
3695
|
if (categoryIngredient.aliases.includes(ingredient.name)) {
|
|
3366
3696
|
categories[category.name].push(ingredient);
|
|
@@ -3424,7 +3754,6 @@ var ShoppingCart = class {
|
|
|
3424
3754
|
setProductCatalog(catalog) {
|
|
3425
3755
|
this.productCatalog = catalog;
|
|
3426
3756
|
}
|
|
3427
|
-
// TODO: harmonize recipe name to use underscores
|
|
3428
3757
|
/**
|
|
3429
3758
|
* Sets the shopping list to build the cart from.
|
|
3430
3759
|
* To use if a shopping list was not provided at the creation of the instance
|
|
@@ -3728,9 +4057,11 @@ function isAlternativeSelected(recipe, choices, item, alternativeIndex) {
|
|
|
3728
4057
|
}
|
|
3729
4058
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3730
4059
|
0 && (module.exports = {
|
|
4060
|
+
BadIndentationError,
|
|
3731
4061
|
CategoryConfig,
|
|
3732
4062
|
NoProductCatalogForCartError,
|
|
3733
4063
|
NoShoppingListForCartError,
|
|
4064
|
+
NoTabAsIndentError,
|
|
3734
4065
|
ProductCatalog,
|
|
3735
4066
|
Recipe,
|
|
3736
4067
|
Section,
|