@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 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
- for (const metaVar of [
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["unit system"] = unitSystemRaw;
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 ["serves", "yield", "servings"]) {
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
- for (const metaVar of ["tags", "images", "pictures"]) {
1546
- const listMetaValue = parseListMetaVar(metadataContent, metaVar);
1547
- if (listMetaValue) metadata[metaVar] = listMetaValue;
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({ type: "text", value: text.slice(cursor, idx) });
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({ type: "text", value: text.slice(cursor) });
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
- _populate_ingredient_quantities() {
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({ type: "text", value: line.slice(cursor, idx) });
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({ type: "text", value: line.slice(cursor) });
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._populate_ingredient_quantities();
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._populate_ingredient_quantities();
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._populate_ingredient_quantities();
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 category_config_str - The category configuration to parse.
3475
+ * @param categoryConfigStr - The category configuration to parse.
3146
3476
  */
3147
- constructor(category_config_str) {
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, "category_config");
3490
+ __publicField(this, "categoryConfig");
3161
3491
  /**
3162
3492
  * The categorized ingredients in the shopping list.
3163
3493
  */
3164
3494
  __publicField(this, "categories");
3165
- if (category_config_str) {
3166
- this.set_category_config(category_config_str);
3495
+ if (categoryConfigStr) {
3496
+ this.setCategoryConfig(categoryConfigStr);
3167
3497
  }
3168
3498
  }
3169
- calculate_ingredients() {
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
- add_recipe(recipe, options = {}) {
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.calculate_ingredients();
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.s
3655
+ * recalculates the quantities and recategorize the ingredients.
3326
3656
  * @param index - The index of the recipe to remove.
3327
3657
  */
3328
- remove_recipe(index) {
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.calculate_ingredients();
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
- set_category_config(config) {
3671
+ setCategoryConfig(config) {
3342
3672
  if (typeof config === "string")
3343
- this.category_config = new CategoryConfig(config);
3344
- else if (config instanceof CategoryConfig) this.category_config = config;
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.category_config) {
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.category_config.categories) {
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.category_config.categories) {
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,