@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.js
CHANGED
|
@@ -265,6 +265,12 @@ var i = (() => {
|
|
|
265
265
|
})();
|
|
266
266
|
|
|
267
267
|
// src/regex.ts
|
|
268
|
+
var metadataKeyRegex = /^([^:\n]+?):/gm;
|
|
269
|
+
var numericValueRegex = /^-?\d+(\.\d+)?$/;
|
|
270
|
+
var nestedMetaVarRegex = (varName) => new RegExp(
|
|
271
|
+
`^${varName}:\\s*\\r?\\n((?:[ ]+.+(?:\\r?\\n|$))+)`,
|
|
272
|
+
"m"
|
|
273
|
+
);
|
|
268
274
|
var metadataRegex = d().literal("---").newline().startCaptureGroup().anyCharacter().zeroOrMore().optional().endGroup().newline().literal("---").dotAll().toRegExp();
|
|
269
275
|
var scalingMetaValueRegex = (varName) => d().startAnchor().literal(varName).literal(":").anyOf("\\t ").zeroOrMore().startCaptureGroup().startCaptureGroup().notAnyOf(",\\n").oneOrMore().endGroup().startGroup().literal(",").whitespace().zeroOrMore().startCaptureGroup().anyCharacter().oneOrMore().endGroup().endGroup().optional().endGroup().endAnchor().multiline().toRegExp();
|
|
270
276
|
var nonWordChar = "\\s@#~\\[\\]{(,;:!?";
|
|
@@ -293,6 +299,37 @@ var shoppingListRegex = d().literal("[").startNamedGroup("name").anyCharacter().
|
|
|
293
299
|
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();
|
|
294
300
|
var numberLikeRegex = d().startAnchor().digit().oneOrMore().startGroup().anyOf(".,/").exactly(1).digit().oneOrMore().endGroup().optional().endAnchor().toRegExp();
|
|
295
301
|
var floatRegex = d().startAnchor().digit().oneOrMore().startGroup().anyOf(".").exactly(1).digit().oneOrMore().endGroup().optional().endAnchor().toRegExp();
|
|
302
|
+
var mdEscaped = d().literal("\\").startCaptureGroup().anyOf("*_`").endGroup();
|
|
303
|
+
var mdInlineCode = d().literal("`").startCaptureGroup().notAnyOf("`").oneOrMore().lazy().endGroup().literal("`");
|
|
304
|
+
var mdLink = d().literal("[").startCaptureGroup().notAnyOf("\\]").oneOrMore().lazy().endGroup().literal("](").startCaptureGroup().notAnyOf(")").oneOrMore().lazy().endGroup().literal(")");
|
|
305
|
+
var mdTripleAsterisk = d().literal("***").startCaptureGroup().anyCharacter().oneOrMore().lazy().endGroup().literal("***");
|
|
306
|
+
var mdTripleUnderscore = d().literal("___").startCaptureGroup().anyCharacter().oneOrMore().lazy().endGroup().literal("___");
|
|
307
|
+
var mdBoldAstItalicUnd = d().literal("**_").startCaptureGroup().anyCharacter().oneOrMore().lazy().endGroup().literal("_**");
|
|
308
|
+
var mdBoldUndItalicAst = d().literal("__*").startCaptureGroup().anyCharacter().oneOrMore().lazy().endGroup().literal("*__");
|
|
309
|
+
var mdItalicAstBoldUnd = d().literal("*__").startCaptureGroup().anyCharacter().oneOrMore().lazy().endGroup().literal("__*");
|
|
310
|
+
var mdItalicUndBoldAst = d().literal("_**").startCaptureGroup().anyCharacter().oneOrMore().lazy().endGroup().literal("**_");
|
|
311
|
+
var mdBoldAsterisk = d().literal("**").startCaptureGroup().anyCharacter().oneOrMore().lazy().endGroup().literal("**");
|
|
312
|
+
var mdBoldUnderscore = d().wordBoundary().literal("__").startCaptureGroup().anyCharacter().oneOrMore().lazy().endGroup().literal("__").wordBoundary();
|
|
313
|
+
var mdItalicAsterisk = d().literal("*").startCaptureGroup().anyCharacter().oneOrMore().lazy().endGroup().literal("*");
|
|
314
|
+
var mdItalicUnderscore = d().wordBoundary().literal("_").startCaptureGroup().anyCharacter().oneOrMore().lazy().endGroup().literal("_").wordBoundary();
|
|
315
|
+
var markdownRegex = new RegExp(
|
|
316
|
+
[
|
|
317
|
+
mdEscaped,
|
|
318
|
+
mdInlineCode,
|
|
319
|
+
mdLink,
|
|
320
|
+
mdTripleAsterisk,
|
|
321
|
+
mdTripleUnderscore,
|
|
322
|
+
mdBoldAstItalicUnd,
|
|
323
|
+
mdBoldUndItalicAst,
|
|
324
|
+
mdItalicAstBoldUnd,
|
|
325
|
+
mdItalicUndBoldAst,
|
|
326
|
+
mdBoldAsterisk,
|
|
327
|
+
mdBoldUnderscore,
|
|
328
|
+
mdItalicAsterisk,
|
|
329
|
+
mdItalicUnderscore
|
|
330
|
+
].map((r2) => r2.toRegExp().source).join("|"),
|
|
331
|
+
"g"
|
|
332
|
+
);
|
|
296
333
|
|
|
297
334
|
// src/units/definitions.ts
|
|
298
335
|
var units = [
|
|
@@ -876,6 +913,20 @@ var InvalidQuantityFormat = class extends Error {
|
|
|
876
913
|
this.name = "InvalidQuantityFormat";
|
|
877
914
|
}
|
|
878
915
|
};
|
|
916
|
+
var NoTabAsIndentError = class extends Error {
|
|
917
|
+
constructor() {
|
|
918
|
+
super(
|
|
919
|
+
`Tabs are not allowed for indentation in metadata blocks. Please use spaces only.`
|
|
920
|
+
);
|
|
921
|
+
this.name = "NoTabAsIndentError";
|
|
922
|
+
}
|
|
923
|
+
};
|
|
924
|
+
var BadIndentationError = class extends Error {
|
|
925
|
+
constructor() {
|
|
926
|
+
super(`Bad identation of a nested block. Please use spaces only.`);
|
|
927
|
+
this.name = "BadIndentationError";
|
|
928
|
+
}
|
|
929
|
+
};
|
|
879
930
|
|
|
880
931
|
// src/utils/type_guards.ts
|
|
881
932
|
function isGroup(x) {
|
|
@@ -1405,12 +1456,106 @@ function parseQuantityInput(input_str) {
|
|
|
1405
1456
|
}
|
|
1406
1457
|
return { type: "fixed", value: parseFixedValue(clean_str) };
|
|
1407
1458
|
}
|
|
1459
|
+
function parseMarkdownSegments(text) {
|
|
1460
|
+
const items = [];
|
|
1461
|
+
let cursor = 0;
|
|
1462
|
+
for (const match of text.matchAll(markdownRegex)) {
|
|
1463
|
+
const idx = match.index;
|
|
1464
|
+
if (idx > cursor) {
|
|
1465
|
+
items.push({ type: "text", value: text.slice(cursor, idx) });
|
|
1466
|
+
}
|
|
1467
|
+
const [
|
|
1468
|
+
,
|
|
1469
|
+
escaped,
|
|
1470
|
+
// group 1: escaped character
|
|
1471
|
+
code,
|
|
1472
|
+
// group 2: inline code
|
|
1473
|
+
linkText,
|
|
1474
|
+
// group 3: link text
|
|
1475
|
+
linkUrl,
|
|
1476
|
+
// group 4: link url
|
|
1477
|
+
tripleAst,
|
|
1478
|
+
// group 5: ***bold+italic***
|
|
1479
|
+
tripleUnd,
|
|
1480
|
+
// group 6: ___bold+italic___
|
|
1481
|
+
astUnd,
|
|
1482
|
+
// group 7: **_bold+italic_**
|
|
1483
|
+
undAst,
|
|
1484
|
+
// group 8: __*bold+italic*__
|
|
1485
|
+
astUndUnd,
|
|
1486
|
+
// group 9: *__bold+italic__*
|
|
1487
|
+
undAstAst,
|
|
1488
|
+
// group 10: _**bold+italic**_
|
|
1489
|
+
boldAst,
|
|
1490
|
+
// group 11: **bold**
|
|
1491
|
+
boldUnd,
|
|
1492
|
+
// group 12: __bold__
|
|
1493
|
+
italicAst,
|
|
1494
|
+
// group 13: *italic*
|
|
1495
|
+
italicUnd
|
|
1496
|
+
// group 14: _italic_
|
|
1497
|
+
] = match;
|
|
1498
|
+
let value;
|
|
1499
|
+
let attribute;
|
|
1500
|
+
let href;
|
|
1501
|
+
if (escaped !== void 0) {
|
|
1502
|
+
items.push({ type: "text", value: escaped });
|
|
1503
|
+
cursor = idx + match[0].length;
|
|
1504
|
+
continue;
|
|
1505
|
+
} else if (code !== void 0) {
|
|
1506
|
+
value = code;
|
|
1507
|
+
attribute = "code";
|
|
1508
|
+
} else if (linkText !== void 0) {
|
|
1509
|
+
value = linkText;
|
|
1510
|
+
attribute = "link";
|
|
1511
|
+
href = linkUrl;
|
|
1512
|
+
} else if (tripleAst !== void 0 || tripleUnd !== void 0 || astUnd !== void 0 || undAst !== void 0 || astUndUnd !== void 0 || undAstAst !== void 0) {
|
|
1513
|
+
value = tripleAst ?? tripleUnd ?? astUnd ?? undAst ?? astUndUnd ?? undAstAst;
|
|
1514
|
+
attribute = "bold+italic";
|
|
1515
|
+
} else if (boldAst !== void 0 || boldUnd !== void 0) {
|
|
1516
|
+
value = boldAst ?? boldUnd;
|
|
1517
|
+
attribute = "bold";
|
|
1518
|
+
} else {
|
|
1519
|
+
value = italicAst ?? italicUnd;
|
|
1520
|
+
attribute = "italic";
|
|
1521
|
+
}
|
|
1522
|
+
const item = { type: "text", value };
|
|
1523
|
+
if (attribute) item.attribute = attribute;
|
|
1524
|
+
if (href) item.href = href;
|
|
1525
|
+
items.push(item);
|
|
1526
|
+
cursor = idx + match[0].length;
|
|
1527
|
+
}
|
|
1528
|
+
if (cursor < text.length) {
|
|
1529
|
+
items.push({ type: "text", value: text.slice(cursor) });
|
|
1530
|
+
}
|
|
1531
|
+
return items;
|
|
1532
|
+
}
|
|
1408
1533
|
function parseSimpleMetaVar(content, varName) {
|
|
1409
1534
|
const varMatch = content.match(
|
|
1410
1535
|
new RegExp(`^${varName}:\\s*(.*(?:\\r?\\n\\s+.*)*)+`, "m")
|
|
1411
1536
|
);
|
|
1412
1537
|
return varMatch ? varMatch[1]?.trim().replace(/\s*\r?\n\s+/g, " ") : void 0;
|
|
1413
1538
|
}
|
|
1539
|
+
function parseBlockScalarMetaVar(content, varName) {
|
|
1540
|
+
const match = content.match(
|
|
1541
|
+
new RegExp(
|
|
1542
|
+
`^${varName}:\\s*([|>])\\s*\\r?\\n((?:(?:[ ]+.*|\\s*)(?:\\r?\\n|$))+)`,
|
|
1543
|
+
"m"
|
|
1544
|
+
)
|
|
1545
|
+
);
|
|
1546
|
+
if (!match) return void 0;
|
|
1547
|
+
const style = match[1];
|
|
1548
|
+
const rawBlock = match[2];
|
|
1549
|
+
const lines = rawBlock.split(/\r?\n/);
|
|
1550
|
+
const firstNonEmpty = lines.find((l) => l.trim() !== "");
|
|
1551
|
+
if (!firstNonEmpty) return void 0;
|
|
1552
|
+
const baseIndent = firstNonEmpty.match(/^([ ]*)/)[1].length;
|
|
1553
|
+
const stripped = lines.map((line) => line.trim() === "" ? "" : line.slice(baseIndent)).join("\n").replace(/\n+$/, "");
|
|
1554
|
+
if (style === "|") {
|
|
1555
|
+
return stripped;
|
|
1556
|
+
}
|
|
1557
|
+
return stripped.replace(/\n\n/g, "\0").replace(/\n/g, " ").replace(/\0/g, "\n");
|
|
1558
|
+
}
|
|
1414
1559
|
function parseScalingMetaVar(content, varName) {
|
|
1415
1560
|
const varMatch = content.match(scalingMetaValueRegex(varName));
|
|
1416
1561
|
if (!varMatch) return void 0;
|
|
@@ -1433,6 +1578,108 @@ function parseListMetaVar(content, varName) {
|
|
|
1433
1578
|
return listMatch[2].split("\n").filter((line) => line.trim() !== "").map((line) => line.replace(/^\s*-\s*/, "").trim());
|
|
1434
1579
|
}
|
|
1435
1580
|
}
|
|
1581
|
+
function extractAllMetadataKeys(content) {
|
|
1582
|
+
const keys = [];
|
|
1583
|
+
for (const match of content.matchAll(metadataKeyRegex)) {
|
|
1584
|
+
keys.push(match[1].trim());
|
|
1585
|
+
}
|
|
1586
|
+
return [...new Set(keys)];
|
|
1587
|
+
}
|
|
1588
|
+
function parseNestedMetaVar(content, varName) {
|
|
1589
|
+
const match = content.match(nestedMetaVarRegex(varName));
|
|
1590
|
+
if (!match) return void 0;
|
|
1591
|
+
const nestedContent = match[1];
|
|
1592
|
+
return parseNestedBlock(nestedContent);
|
|
1593
|
+
}
|
|
1594
|
+
function parseNestedBlock(content) {
|
|
1595
|
+
const lines = content.split(/\r?\n/).filter((line) => line.trim() !== "");
|
|
1596
|
+
if (lines.length === 0) return void 0;
|
|
1597
|
+
const baseIndentMatch = lines[0].match(/^(\s*)/);
|
|
1598
|
+
if (baseIndentMatch?.[0]?.includes(" ")) {
|
|
1599
|
+
throw new NoTabAsIndentError();
|
|
1600
|
+
}
|
|
1601
|
+
const baseIndent = baseIndentMatch?.[1]?.length;
|
|
1602
|
+
if (lines[0].trim().startsWith("- ")) return void 0;
|
|
1603
|
+
const result = {};
|
|
1604
|
+
let i2 = 0;
|
|
1605
|
+
while (i2 < lines.length) {
|
|
1606
|
+
const line = lines[i2];
|
|
1607
|
+
const leadingWhitespace = line.match(/^(\s*)/)?.[1];
|
|
1608
|
+
if (leadingWhitespace && leadingWhitespace.includes(" ")) {
|
|
1609
|
+
throw new NoTabAsIndentError();
|
|
1610
|
+
}
|
|
1611
|
+
const currentIndent = leadingWhitespace.length;
|
|
1612
|
+
if (currentIndent < baseIndent) {
|
|
1613
|
+
break;
|
|
1614
|
+
}
|
|
1615
|
+
if (currentIndent !== baseIndent) {
|
|
1616
|
+
throw new BadIndentationError();
|
|
1617
|
+
}
|
|
1618
|
+
const keyValueMatch = line.match(/^[ ]*([^:\n]+?):\s*(.*)$/);
|
|
1619
|
+
if (!keyValueMatch) {
|
|
1620
|
+
i2++;
|
|
1621
|
+
continue;
|
|
1622
|
+
}
|
|
1623
|
+
const key = keyValueMatch[1].trim();
|
|
1624
|
+
const rawValue = keyValueMatch[2].trim();
|
|
1625
|
+
if (rawValue === "") {
|
|
1626
|
+
const childLines = [];
|
|
1627
|
+
let j = i2 + 1;
|
|
1628
|
+
while (j < lines.length) {
|
|
1629
|
+
const childLine = lines[j];
|
|
1630
|
+
const childIndent = childLine.match(/^([ ]*)/)?.[1]?.length;
|
|
1631
|
+
if (childIndent && childIndent > baseIndent) {
|
|
1632
|
+
childLines.push(childLine);
|
|
1633
|
+
j++;
|
|
1634
|
+
} else {
|
|
1635
|
+
break;
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
if (childLines.length > 0) {
|
|
1639
|
+
const firstChildTrimmed = childLines[0].trim();
|
|
1640
|
+
if (firstChildTrimmed.startsWith("- ")) {
|
|
1641
|
+
const reconstructedContent = `${key}:
|
|
1642
|
+
${childLines.join("\n")}`;
|
|
1643
|
+
const listResult = parseListMetaVar(reconstructedContent, key);
|
|
1644
|
+
if (listResult) {
|
|
1645
|
+
result[key] = listResult.map(
|
|
1646
|
+
(item) => parseMetadataValue(item)
|
|
1647
|
+
);
|
|
1648
|
+
}
|
|
1649
|
+
} else {
|
|
1650
|
+
const childContent = childLines.join("\n");
|
|
1651
|
+
const nested = parseNestedBlock(childContent);
|
|
1652
|
+
if (nested) {
|
|
1653
|
+
result[key] = nested;
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
i2 = j;
|
|
1658
|
+
} else {
|
|
1659
|
+
result[key] = parseMetadataValue(rawValue);
|
|
1660
|
+
i2++;
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
return result;
|
|
1664
|
+
}
|
|
1665
|
+
function parseMetadataValue(rawValue) {
|
|
1666
|
+
if (rawValue.startsWith("[") && rawValue.endsWith("]")) {
|
|
1667
|
+
return rawValue.slice(1, -1).split(",").map((item) => item.trim());
|
|
1668
|
+
}
|
|
1669
|
+
if (numericValueRegex.test(rawValue)) {
|
|
1670
|
+
return Number(rawValue);
|
|
1671
|
+
}
|
|
1672
|
+
return rawValue;
|
|
1673
|
+
}
|
|
1674
|
+
function parseAnyMetaVar(content, varName) {
|
|
1675
|
+
const nested = parseNestedMetaVar(content, varName);
|
|
1676
|
+
if (nested) return nested;
|
|
1677
|
+
const list = parseListMetaVar(content, varName);
|
|
1678
|
+
if (list) return list;
|
|
1679
|
+
const simple = parseSimpleMetaVar(content, varName);
|
|
1680
|
+
if (simple) return parseMetadataValue(simple);
|
|
1681
|
+
return void 0;
|
|
1682
|
+
}
|
|
1436
1683
|
function extractMetadata(content) {
|
|
1437
1684
|
const metadata = {};
|
|
1438
1685
|
let servings = void 0;
|
|
@@ -1440,13 +1687,24 @@ function extractMetadata(content) {
|
|
|
1440
1687
|
if (!metadataContent) {
|
|
1441
1688
|
return { metadata };
|
|
1442
1689
|
}
|
|
1443
|
-
|
|
1690
|
+
const handledKeys = /* @__PURE__ */ new Set([
|
|
1691
|
+
// Simple string fields
|
|
1444
1692
|
"title",
|
|
1693
|
+
"author",
|
|
1694
|
+
"locale",
|
|
1695
|
+
"introduction",
|
|
1696
|
+
"description",
|
|
1697
|
+
"course",
|
|
1698
|
+
"category",
|
|
1699
|
+
"diet",
|
|
1700
|
+
"cuisine",
|
|
1701
|
+
"difficulty",
|
|
1702
|
+
// Source fields
|
|
1445
1703
|
"source",
|
|
1446
1704
|
"source.name",
|
|
1447
1705
|
"source.url",
|
|
1448
|
-
"author",
|
|
1449
1706
|
"source.author",
|
|
1707
|
+
// Time fields
|
|
1450
1708
|
"prep time",
|
|
1451
1709
|
"time.prep",
|
|
1452
1710
|
"cook time",
|
|
@@ -1454,6 +1712,23 @@ function extractMetadata(content) {
|
|
|
1454
1712
|
"time required",
|
|
1455
1713
|
"time",
|
|
1456
1714
|
"duration",
|
|
1715
|
+
// Image fields
|
|
1716
|
+
"image",
|
|
1717
|
+
"picture",
|
|
1718
|
+
"images",
|
|
1719
|
+
"pictures",
|
|
1720
|
+
// Unit system
|
|
1721
|
+
"unit system",
|
|
1722
|
+
// Scaling fields
|
|
1723
|
+
"servings",
|
|
1724
|
+
"yield",
|
|
1725
|
+
"serves",
|
|
1726
|
+
// List fields
|
|
1727
|
+
"tags"
|
|
1728
|
+
]);
|
|
1729
|
+
for (const metaVar of [
|
|
1730
|
+
"title",
|
|
1731
|
+
"author",
|
|
1457
1732
|
"locale",
|
|
1458
1733
|
"introduction",
|
|
1459
1734
|
"description",
|
|
@@ -1461,17 +1736,64 @@ function extractMetadata(content) {
|
|
|
1461
1736
|
"category",
|
|
1462
1737
|
"diet",
|
|
1463
1738
|
"cuisine",
|
|
1464
|
-
"difficulty"
|
|
1465
|
-
"image",
|
|
1466
|
-
"picture"
|
|
1739
|
+
"difficulty"
|
|
1467
1740
|
]) {
|
|
1741
|
+
if (metaVar === "description" || metaVar === "introduction") {
|
|
1742
|
+
const blockValue = parseBlockScalarMetaVar(metadataContent, metaVar);
|
|
1743
|
+
if (blockValue) {
|
|
1744
|
+
metadata[metaVar] = blockValue;
|
|
1745
|
+
continue;
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1468
1748
|
const stringMetaValue = parseSimpleMetaVar(metadataContent, metaVar);
|
|
1469
1749
|
if (stringMetaValue) metadata[metaVar] = stringMetaValue;
|
|
1470
1750
|
}
|
|
1751
|
+
const sourceNested = parseNestedMetaVar(metadataContent, "source");
|
|
1752
|
+
const sourceTxt = parseSimpleMetaVar(metadataContent, "source");
|
|
1753
|
+
const sourceName = parseSimpleMetaVar(metadataContent, "source.name");
|
|
1754
|
+
const sourceUrl = parseSimpleMetaVar(metadataContent, "source.url");
|
|
1755
|
+
const sourceAuthor = parseSimpleMetaVar(metadataContent, "source.author");
|
|
1756
|
+
if (sourceNested) {
|
|
1757
|
+
const source = {};
|
|
1758
|
+
if (typeof sourceNested.name === "string") source.name = sourceNested.name;
|
|
1759
|
+
if (typeof sourceNested.url === "string") source.url = sourceNested.url;
|
|
1760
|
+
if (typeof sourceNested.author === "string")
|
|
1761
|
+
source.author = sourceNested.author;
|
|
1762
|
+
if (Object.keys(source).length > 0) metadata.source = source;
|
|
1763
|
+
} else if (sourceName || sourceAuthor || sourceUrl) {
|
|
1764
|
+
const source = {};
|
|
1765
|
+
if (sourceName) source.name = sourceName;
|
|
1766
|
+
if (sourceUrl) source.url = sourceUrl;
|
|
1767
|
+
if (sourceAuthor) source.author = sourceAuthor;
|
|
1768
|
+
metadata.source = source;
|
|
1769
|
+
} else if (sourceTxt) {
|
|
1770
|
+
metadata.source = sourceTxt;
|
|
1771
|
+
}
|
|
1772
|
+
const timeNested = parseNestedMetaVar(metadataContent, "time");
|
|
1773
|
+
const prepTime = parseSimpleMetaVar(metadataContent, "prep time") ?? parseSimpleMetaVar(metadataContent, "time.prep");
|
|
1774
|
+
const cookTime = parseSimpleMetaVar(metadataContent, "cook time") ?? parseSimpleMetaVar(metadataContent, "time.cook");
|
|
1775
|
+
const totalTime = parseSimpleMetaVar(metadataContent, "time required") ?? parseSimpleMetaVar(metadataContent, "time") ?? parseSimpleMetaVar(metadataContent, "duration");
|
|
1776
|
+
if (timeNested) {
|
|
1777
|
+
const time = {};
|
|
1778
|
+
if (typeof timeNested.prep === "string") time.prep = timeNested.prep;
|
|
1779
|
+
if (typeof timeNested.cook === "string") time.cook = timeNested.cook;
|
|
1780
|
+
if (typeof timeNested.total === "string") time.total = timeNested.total;
|
|
1781
|
+
if (Object.keys(time).length > 0) metadata.time = time;
|
|
1782
|
+
} else if (prepTime || cookTime || totalTime) {
|
|
1783
|
+
const time = {};
|
|
1784
|
+
if (prepTime) time.prep = prepTime;
|
|
1785
|
+
if (cookTime) time.cook = cookTime;
|
|
1786
|
+
if (totalTime) time.total = totalTime;
|
|
1787
|
+
metadata.time = time;
|
|
1788
|
+
}
|
|
1789
|
+
const image = parseSimpleMetaVar(metadataContent, "image") ?? parseSimpleMetaVar(metadataContent, "picture");
|
|
1790
|
+
if (image) metadata.image = image;
|
|
1791
|
+
const images = parseListMetaVar(metadataContent, "images") ?? parseListMetaVar(metadataContent, "pictures");
|
|
1792
|
+
if (images) metadata.images = images;
|
|
1471
1793
|
let unitSystem;
|
|
1472
1794
|
const unitSystemRaw = parseSimpleMetaVar(metadataContent, "unit system");
|
|
1473
1795
|
if (unitSystemRaw) {
|
|
1474
|
-
metadata
|
|
1796
|
+
metadata.unitSystem = unitSystemRaw;
|
|
1475
1797
|
const unitSystemMap = {
|
|
1476
1798
|
metric: "metric",
|
|
1477
1799
|
us: "US",
|
|
@@ -1480,16 +1802,22 @@ function extractMetadata(content) {
|
|
|
1480
1802
|
};
|
|
1481
1803
|
unitSystem = unitSystemMap[unitSystemRaw.toLowerCase()];
|
|
1482
1804
|
}
|
|
1483
|
-
for (const metaVar of ["
|
|
1805
|
+
for (const metaVar of ["servings", "yield", "serves"]) {
|
|
1484
1806
|
const scalingMetaValue = parseScalingMetaVar(metadataContent, metaVar);
|
|
1485
1807
|
if (scalingMetaValue && scalingMetaValue[1]) {
|
|
1486
1808
|
metadata[metaVar] = scalingMetaValue[1];
|
|
1487
1809
|
servings = scalingMetaValue[0];
|
|
1488
1810
|
}
|
|
1489
1811
|
}
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1812
|
+
const tags = parseListMetaVar(metadataContent, "tags");
|
|
1813
|
+
if (tags) metadata.tags = tags;
|
|
1814
|
+
const allKeys = extractAllMetadataKeys(metadataContent);
|
|
1815
|
+
for (const key of allKeys) {
|
|
1816
|
+
if (handledKeys.has(key)) continue;
|
|
1817
|
+
const value = parseAnyMetaVar(metadataContent, key);
|
|
1818
|
+
if (value !== void 0) {
|
|
1819
|
+
metadata[key] = value;
|
|
1820
|
+
}
|
|
1493
1821
|
}
|
|
1494
1822
|
return { metadata, servings, unitSystem };
|
|
1495
1823
|
}
|
|
@@ -2120,13 +2448,13 @@ var _Recipe = class _Recipe {
|
|
|
2120
2448
|
for (const match of text.matchAll(globalRegex)) {
|
|
2121
2449
|
const idx = match.index;
|
|
2122
2450
|
if (idx > cursor) {
|
|
2123
|
-
noteItems.push(
|
|
2451
|
+
noteItems.push(...parseMarkdownSegments(text.slice(cursor, idx)));
|
|
2124
2452
|
}
|
|
2125
2453
|
this._parseArbitraryScalable(match.groups, noteItems);
|
|
2126
2454
|
cursor = idx + match[0].length;
|
|
2127
2455
|
}
|
|
2128
2456
|
if (cursor < text.length) {
|
|
2129
|
-
noteItems.push(
|
|
2457
|
+
noteItems.push(...parseMarkdownSegments(text.slice(cursor)));
|
|
2130
2458
|
}
|
|
2131
2459
|
return noteItems;
|
|
2132
2460
|
}
|
|
@@ -2390,7 +2718,7 @@ var _Recipe = class _Recipe {
|
|
|
2390
2718
|
* Quantities are grouped by their alternative signature and summed using addEquivalentsAndSimplify.
|
|
2391
2719
|
* @internal
|
|
2392
2720
|
*/
|
|
2393
|
-
|
|
2721
|
+
_populateIngredientQuantities() {
|
|
2394
2722
|
for (const ing of this.ingredients) {
|
|
2395
2723
|
delete ing.quantities;
|
|
2396
2724
|
delete ing.usedAsPrimary;
|
|
@@ -2680,7 +3008,7 @@ var _Recipe = class _Recipe {
|
|
|
2680
3008
|
for (const match of line.matchAll(tokensRegex)) {
|
|
2681
3009
|
const idx = match.index;
|
|
2682
3010
|
if (idx > cursor) {
|
|
2683
|
-
items.push(
|
|
3011
|
+
items.push(...parseMarkdownSegments(line.slice(cursor, idx)));
|
|
2684
3012
|
}
|
|
2685
3013
|
const groups = match.groups;
|
|
2686
3014
|
if (groups.mIngredientName || groups.sIngredientName) {
|
|
@@ -2742,7 +3070,7 @@ var _Recipe = class _Recipe {
|
|
|
2742
3070
|
cursor = idx + match[0].length;
|
|
2743
3071
|
}
|
|
2744
3072
|
if (cursor < line.length) {
|
|
2745
|
-
items.push(
|
|
3073
|
+
items.push(...parseMarkdownSegments(line.slice(cursor)));
|
|
2746
3074
|
}
|
|
2747
3075
|
blankLineBefore = false;
|
|
2748
3076
|
}
|
|
@@ -2751,7 +3079,7 @@ var _Recipe = class _Recipe {
|
|
|
2751
3079
|
if (!section.isBlank()) {
|
|
2752
3080
|
this.sections.push(section);
|
|
2753
3081
|
}
|
|
2754
|
-
this.
|
|
3082
|
+
this._populateIngredientQuantities();
|
|
2755
3083
|
}
|
|
2756
3084
|
/**
|
|
2757
3085
|
* Scales the recipe to a new number of servings. In practice, it calls
|
|
@@ -2849,7 +3177,7 @@ var _Recipe = class _Recipe {
|
|
|
2849
3177
|
factor
|
|
2850
3178
|
);
|
|
2851
3179
|
}
|
|
2852
|
-
newRecipe.
|
|
3180
|
+
newRecipe._populateIngredientQuantities();
|
|
2853
3181
|
newRecipe.servings = Big4(originalServings).times(factor).toNumber();
|
|
2854
3182
|
if (newRecipe.metadata.servings && this.metadata.servings) {
|
|
2855
3183
|
if (floatRegex.test(String(this.metadata.servings).replace(",", ".").trim())) {
|
|
@@ -2914,9 +3242,9 @@ var _Recipe = class _Recipe {
|
|
|
2914
3242
|
if (method === "remove") {
|
|
2915
3243
|
return newPrimary;
|
|
2916
3244
|
} else if (method === "replace") {
|
|
3245
|
+
if (source === "converted") remainingEquivalents.push(oldPrimary);
|
|
2917
3246
|
if (remainingEquivalents.length > 0) {
|
|
2918
3247
|
newPrimary.equivalents = remainingEquivalents;
|
|
2919
|
-
if (source === "converted") newPrimary.equivalents.push(oldPrimary);
|
|
2920
3248
|
}
|
|
2921
3249
|
} else {
|
|
2922
3250
|
newPrimary.equivalents = [oldPrimary, ...remainingEquivalents];
|
|
@@ -3034,7 +3362,7 @@ var _Recipe = class _Recipe {
|
|
|
3034
3362
|
for (const alternatives of newRecipe.choices.ingredientItems.values()) {
|
|
3035
3363
|
convertAlternatives(alternatives);
|
|
3036
3364
|
}
|
|
3037
|
-
newRecipe.
|
|
3365
|
+
newRecipe._populateIngredientQuantities();
|
|
3038
3366
|
if (method !== "keep") _Recipe.unitSystems.set(newRecipe, system);
|
|
3039
3367
|
return newRecipe;
|
|
3040
3368
|
}
|
|
@@ -3087,9 +3415,9 @@ var Recipe = _Recipe;
|
|
|
3087
3415
|
var ShoppingList = class {
|
|
3088
3416
|
/**
|
|
3089
3417
|
* Creates a new ShoppingList instance
|
|
3090
|
-
* @param
|
|
3418
|
+
* @param categoryConfigStr - The category configuration to parse.
|
|
3091
3419
|
*/
|
|
3092
|
-
constructor(
|
|
3420
|
+
constructor(categoryConfigStr) {
|
|
3093
3421
|
// TODO: backport type change
|
|
3094
3422
|
/**
|
|
3095
3423
|
* The ingredients in the shopping list.
|
|
@@ -3102,16 +3430,16 @@ var ShoppingList = class {
|
|
|
3102
3430
|
/**
|
|
3103
3431
|
* The category configuration for the shopping list.
|
|
3104
3432
|
*/
|
|
3105
|
-
__publicField(this, "
|
|
3433
|
+
__publicField(this, "categoryConfig");
|
|
3106
3434
|
/**
|
|
3107
3435
|
* The categorized ingredients in the shopping list.
|
|
3108
3436
|
*/
|
|
3109
3437
|
__publicField(this, "categories");
|
|
3110
|
-
if (
|
|
3111
|
-
this.
|
|
3438
|
+
if (categoryConfigStr) {
|
|
3439
|
+
this.setCategoryConfig(categoryConfigStr);
|
|
3112
3440
|
}
|
|
3113
3441
|
}
|
|
3114
|
-
|
|
3442
|
+
calculateIngredients() {
|
|
3115
3443
|
this.ingredients = [];
|
|
3116
3444
|
const addIngredientQuantity = (name, quantityTotal) => {
|
|
3117
3445
|
const quantityTotalExtended = extendAllUnits(quantityTotal);
|
|
@@ -3198,7 +3526,7 @@ var ShoppingList = class {
|
|
|
3198
3526
|
* @param options - Options for adding the recipe.
|
|
3199
3527
|
* @throws Error if the recipe has alternatives without corresponding choices.
|
|
3200
3528
|
*/
|
|
3201
|
-
|
|
3529
|
+
addRecipe(recipe, options = {}) {
|
|
3202
3530
|
const errorMessage = this.getUnresolvedAlternativesError(
|
|
3203
3531
|
recipe,
|
|
3204
3532
|
options.choices
|
|
@@ -3227,7 +3555,7 @@ var ShoppingList = class {
|
|
|
3227
3555
|
});
|
|
3228
3556
|
}
|
|
3229
3557
|
}
|
|
3230
|
-
this.
|
|
3558
|
+
this.calculateIngredients();
|
|
3231
3559
|
this.categorize();
|
|
3232
3560
|
}
|
|
3233
3561
|
/**
|
|
@@ -3267,15 +3595,15 @@ var ShoppingList = class {
|
|
|
3267
3595
|
}
|
|
3268
3596
|
/**
|
|
3269
3597
|
* Removes a recipe from the shopping list, then automatically
|
|
3270
|
-
* recalculates the quantities and recategorize the ingredients.
|
|
3598
|
+
* recalculates the quantities and recategorize the ingredients.
|
|
3271
3599
|
* @param index - The index of the recipe to remove.
|
|
3272
3600
|
*/
|
|
3273
|
-
|
|
3601
|
+
removeRecipe(index) {
|
|
3274
3602
|
if (index < 0 || index >= this.recipes.length) {
|
|
3275
3603
|
throw new Error("Index out of bounds");
|
|
3276
3604
|
}
|
|
3277
3605
|
this.recipes.splice(index, 1);
|
|
3278
|
-
this.
|
|
3606
|
+
this.calculateIngredients();
|
|
3279
3607
|
this.categorize();
|
|
3280
3608
|
}
|
|
3281
3609
|
/**
|
|
@@ -3283,10 +3611,10 @@ var ShoppingList = class {
|
|
|
3283
3611
|
* and automatically categorize current ingredients from the list.
|
|
3284
3612
|
* @param config - The category configuration to parse.
|
|
3285
3613
|
*/
|
|
3286
|
-
|
|
3614
|
+
setCategoryConfig(config) {
|
|
3287
3615
|
if (typeof config === "string")
|
|
3288
|
-
this.
|
|
3289
|
-
else if (config instanceof CategoryConfig) this.
|
|
3616
|
+
this.categoryConfig = new CategoryConfig(config);
|
|
3617
|
+
else if (config instanceof CategoryConfig) this.categoryConfig = config;
|
|
3290
3618
|
else throw new Error("Invalid category configuration");
|
|
3291
3619
|
this.categorize();
|
|
3292
3620
|
}
|
|
@@ -3295,17 +3623,17 @@ var ShoppingList = class {
|
|
|
3295
3623
|
* Will use the category config if any, otherwise all ingredients will be placed in the "other" category
|
|
3296
3624
|
*/
|
|
3297
3625
|
categorize() {
|
|
3298
|
-
if (!this.
|
|
3626
|
+
if (!this.categoryConfig) {
|
|
3299
3627
|
this.categories = { other: this.ingredients };
|
|
3300
3628
|
return;
|
|
3301
3629
|
}
|
|
3302
3630
|
const categories = { other: [] };
|
|
3303
|
-
for (const category of this.
|
|
3631
|
+
for (const category of this.categoryConfig.categories) {
|
|
3304
3632
|
categories[category.name] = [];
|
|
3305
3633
|
}
|
|
3306
3634
|
for (const ingredient of this.ingredients) {
|
|
3307
3635
|
let found = false;
|
|
3308
|
-
for (const category of this.
|
|
3636
|
+
for (const category of this.categoryConfig.categories) {
|
|
3309
3637
|
for (const categoryIngredient of category.ingredients) {
|
|
3310
3638
|
if (categoryIngredient.aliases.includes(ingredient.name)) {
|
|
3311
3639
|
categories[category.name].push(ingredient);
|
|
@@ -3369,7 +3697,6 @@ var ShoppingCart = class {
|
|
|
3369
3697
|
setProductCatalog(catalog) {
|
|
3370
3698
|
this.productCatalog = catalog;
|
|
3371
3699
|
}
|
|
3372
|
-
// TODO: harmonize recipe name to use underscores
|
|
3373
3700
|
/**
|
|
3374
3701
|
* Sets the shopping list to build the cart from.
|
|
3375
3702
|
* To use if a shopping list was not provided at the creation of the instance
|
|
@@ -3672,9 +3999,11 @@ function isAlternativeSelected(recipe, choices, item, alternativeIndex) {
|
|
|
3672
3999
|
return alternativeIndex === selectedIndex;
|
|
3673
4000
|
}
|
|
3674
4001
|
export {
|
|
4002
|
+
BadIndentationError,
|
|
3675
4003
|
CategoryConfig,
|
|
3676
4004
|
NoProductCatalogForCartError,
|
|
3677
4005
|
NoShoppingListForCartError,
|
|
4006
|
+
NoTabAsIndentError,
|
|
3678
4007
|
ProductCatalog,
|
|
3679
4008
|
Recipe,
|
|
3680
4009
|
Section,
|