@tmlmt/cooklang-parser 2.0.0 → 2.0.1
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/README.md +23 -57
- package/dist/index.cjs +328 -172
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +255 -94
- package/dist/index.d.ts +255 -94
- package/dist/index.js +327 -171
- package/dist/index.js.map +1 -1
- package/package.json +16 -15
package/dist/index.cjs
CHANGED
|
@@ -22,23 +22,22 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
22
22
|
// src/index.ts
|
|
23
23
|
var index_exports = {};
|
|
24
24
|
__export(index_exports, {
|
|
25
|
-
|
|
25
|
+
CategoryConfig: () => CategoryConfig,
|
|
26
26
|
Recipe: () => Recipe,
|
|
27
27
|
Section: () => Section,
|
|
28
28
|
ShoppingList: () => ShoppingList
|
|
29
29
|
});
|
|
30
30
|
module.exports = __toCommonJS(index_exports);
|
|
31
31
|
|
|
32
|
-
// src/classes/
|
|
33
|
-
var
|
|
32
|
+
// src/classes/category_config.ts
|
|
33
|
+
var CategoryConfig = class {
|
|
34
34
|
/**
|
|
35
|
-
* Creates a new
|
|
36
|
-
* @param config - The
|
|
35
|
+
* Creates a new CategoryConfig instance.
|
|
36
|
+
* @param config - The category configuration to parse.
|
|
37
37
|
*/
|
|
38
38
|
constructor(config) {
|
|
39
39
|
/**
|
|
40
|
-
* The categories of
|
|
41
|
-
* @see {@link AisleCategory}
|
|
40
|
+
* The parsed categories of ingredients.
|
|
42
41
|
*/
|
|
43
42
|
__publicField(this, "categories", []);
|
|
44
43
|
if (config) {
|
|
@@ -46,8 +45,9 @@ var AisleConfig = class {
|
|
|
46
45
|
}
|
|
47
46
|
}
|
|
48
47
|
/**
|
|
49
|
-
* Parses
|
|
50
|
-
*
|
|
48
|
+
* Parses a category configuration from a string into property
|
|
49
|
+
* {@link CategoryConfig.categories | categories}
|
|
50
|
+
* @param config - The category configuration to parse.
|
|
51
51
|
*/
|
|
52
52
|
parse(config) {
|
|
53
53
|
let currentCategory = null;
|
|
@@ -97,7 +97,10 @@ var Section = class {
|
|
|
97
97
|
* @param name - The name of the section. Defaults to an empty string.
|
|
98
98
|
*/
|
|
99
99
|
constructor(name = "") {
|
|
100
|
-
/**
|
|
100
|
+
/**
|
|
101
|
+
* The name of the section. Can be an empty string for the default (first) section.
|
|
102
|
+
* @defaultValue `""`
|
|
103
|
+
*/
|
|
101
104
|
__publicField(this, "name");
|
|
102
105
|
/** An array of steps and notes that make up the content of the section. */
|
|
103
106
|
__publicField(this, "content", []);
|
|
@@ -313,13 +316,14 @@ var i = (() => {
|
|
|
313
316
|
|
|
314
317
|
// src/regex.ts
|
|
315
318
|
var metadataRegex = d().literal("---").newline().startCaptureGroup().anyCharacter().zeroOrMore().optional().endGroup().newline().literal("---").dotAll().toRegExp();
|
|
316
|
-
var
|
|
317
|
-
var
|
|
318
|
-
var
|
|
319
|
+
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();
|
|
320
|
+
var nonWordChar = "\\s@#~\\[\\]{(,;:!?";
|
|
321
|
+
var multiwordIngredient = d().literal("@").startNamedGroup("mIngredientModifiers").anyOf("@\\-&?").zeroOrMore().endGroup().optional().startNamedGroup("mIngredientRecipeAnchor").literal("./").endGroup().optional().startNamedGroup("mIngredientName").notAnyOf(nonWordChar).oneOrMore().startGroup().whitespace().oneOrMore().notAnyOf(nonWordChar).oneOrMore().endGroup().oneOrMore().endGroup().positiveLookahead("\\s*(?:\\{[^\\}]*\\}|\\([^)]*\\))").startGroup().literal("{").startNamedGroup("mIngredientQuantityModifier").literal("=").exactly(1).endGroup().optional().startNamedGroup("mIngredientQuantity").notAnyOf("}%").oneOrMore().endGroup().optional().startGroup().literal("%").startNamedGroup("mIngredientUnit").notAnyOf("}").oneOrMore().lazy().endGroup().endGroup().optional().literal("}").endGroup().optional().startGroup().literal("(").startNamedGroup("mIngredientPreparation").notAnyOf(")").oneOrMore().lazy().endGroup().literal(")").endGroup().optional().toRegExp();
|
|
322
|
+
var singleWordIngredient = d().literal("@").startNamedGroup("sIngredientModifiers").anyOf("@\\-&?").zeroOrMore().endGroup().optional().startNamedGroup("sIngredientRecipeAnchor").literal("./").endGroup().optional().startNamedGroup("sIngredientName").notAnyOf(nonWordChar).oneOrMore().endGroup().startGroup().literal("{").startNamedGroup("sIngredientQuantityModifier").literal("=").exactly(1).endGroup().optional().startNamedGroup("sIngredientQuantity").notAnyOf("}%").oneOrMore().endGroup().optional().startGroup().literal("%").startNamedGroup("sIngredientUnit").notAnyOf("}").oneOrMore().lazy().endGroup().endGroup().optional().literal("}").endGroup().optional().startGroup().literal("(").startNamedGroup("sIngredientPreparation").notAnyOf(")").oneOrMore().lazy().endGroup().literal(")").endGroup().optional().toRegExp();
|
|
319
323
|
var ingredientAliasRegex = d().startAnchor().startNamedGroup("ingredientListName").notAnyOf("|").oneOrMore().endGroup().literal("|").startNamedGroup("ingredientDisplayName").notAnyOf("|").oneOrMore().endGroup().endAnchor().toRegExp();
|
|
320
|
-
var multiwordCookware = d().literal("#").startNamedGroup("
|
|
321
|
-
var singleWordCookware = d().literal("#").startNamedGroup("
|
|
322
|
-
var timer = d().literal("~").startNamedGroup("timerName").anyCharacter().zeroOrMore().lazy().endGroup().literal("{").startNamedGroup("timerQuantity").anyCharacter().oneOrMore().lazy().endGroup().startGroup().literal("%").startNamedGroup("
|
|
324
|
+
var multiwordCookware = d().literal("#").startNamedGroup("mCookwareModifiers").anyOf("\\-&?").zeroOrMore().endGroup().startNamedGroup("mCookwareName").notAnyOf(nonWordChar).oneOrMore().startGroup().whitespace().oneOrMore().notAnyOf(nonWordChar).oneOrMore().endGroup().oneOrMore().endGroup().positiveLookahead("\\s*(?:\\{[^\\}]*\\})").literal("{").startNamedGroup("mCookwareQuantity").anyCharacter().zeroOrMore().lazy().endGroup().literal("}").toRegExp();
|
|
325
|
+
var singleWordCookware = d().literal("#").startNamedGroup("sCookwareModifiers").anyOf("\\-&?").zeroOrMore().endGroup().startNamedGroup("sCookwareName").notAnyOf(nonWordChar).oneOrMore().endGroup().startGroup().literal("{").startNamedGroup("sCookwareQuantity").anyCharacter().zeroOrMore().lazy().endGroup().literal("}").endGroup().optional().toRegExp();
|
|
326
|
+
var timer = d().literal("~").startNamedGroup("timerName").anyCharacter().zeroOrMore().lazy().endGroup().literal("{").startNamedGroup("timerQuantity").anyCharacter().oneOrMore().lazy().endGroup().startGroup().literal("%").startNamedGroup("timerUnit").anyCharacter().oneOrMore().lazy().endGroup().endGroup().optional().literal("}").toRegExp();
|
|
323
327
|
var tokensRegex = new RegExp(
|
|
324
328
|
[
|
|
325
329
|
multiwordIngredient,
|
|
@@ -333,8 +337,9 @@ var tokensRegex = new RegExp(
|
|
|
333
337
|
var commentRegex = d().literal("--").anyCharacter().zeroOrMore().global().toRegExp();
|
|
334
338
|
var blockCommentRegex = d().whitespace().zeroOrMore().literal("[-").anyCharacter().zeroOrMore().lazy().literal("-]").whitespace().zeroOrMore().global().toRegExp();
|
|
335
339
|
var shoppingListRegex = d().literal("[").startNamedGroup("name").anyCharacter().oneOrMore().endGroup().literal("]").newline().startNamedGroup("items").anyCharacter().zeroOrMore().lazy().endGroup().startGroup().newline().newline().or().endAnchor().endGroup().global().toRegExp();
|
|
336
|
-
var rangeRegex = d().startAnchor().digit().oneOrMore().startGroup().anyOf(".,/").exactly(1).digit().oneOrMore().endGroup().optional().literal("-").startGroup().anyOf(".,/").exactly(1).digit().oneOrMore().endGroup().optional().endAnchor().toRegExp();
|
|
340
|
+
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();
|
|
337
341
|
var numberLikeRegex = d().startAnchor().digit().oneOrMore().startGroup().anyOf(".,/").exactly(1).digit().oneOrMore().endGroup().optional().endAnchor().toRegExp();
|
|
342
|
+
var floatRegex = d().startAnchor().digit().oneOrMore().startGroup().anyOf(".").exactly(1).digit().oneOrMore().endGroup().optional().endAnchor().toRegExp();
|
|
338
343
|
|
|
339
344
|
// src/units.ts
|
|
340
345
|
var units = [
|
|
@@ -343,14 +348,14 @@ var units = [
|
|
|
343
348
|
name: "g",
|
|
344
349
|
type: "mass",
|
|
345
350
|
system: "metric",
|
|
346
|
-
aliases: ["gram", "grams"],
|
|
351
|
+
aliases: ["gram", "grams", "grammes"],
|
|
347
352
|
toBase: 1
|
|
348
353
|
},
|
|
349
354
|
{
|
|
350
355
|
name: "kg",
|
|
351
356
|
type: "mass",
|
|
352
357
|
system: "metric",
|
|
353
|
-
aliases: ["kilogram", "kilograms"],
|
|
358
|
+
aliases: ["kilogram", "kilograms", "kilogrammes", "kilos", "kilo"],
|
|
354
359
|
toBase: 1e3
|
|
355
360
|
},
|
|
356
361
|
// Mass (Imperial)
|
|
@@ -373,7 +378,7 @@ var units = [
|
|
|
373
378
|
name: "ml",
|
|
374
379
|
type: "volume",
|
|
375
380
|
system: "metric",
|
|
376
|
-
aliases: ["milliliter", "milliliters", "millilitre", "millilitres"],
|
|
381
|
+
aliases: ["milliliter", "milliliters", "millilitre", "millilitres", "cc"],
|
|
377
382
|
toBase: 1
|
|
378
383
|
},
|
|
379
384
|
{
|
|
@@ -438,7 +443,7 @@ var units = [
|
|
|
438
443
|
name: "piece",
|
|
439
444
|
type: "count",
|
|
440
445
|
system: "metric",
|
|
441
|
-
aliases: ["pieces"],
|
|
446
|
+
aliases: ["pieces", "pc"],
|
|
442
447
|
toBase: 1
|
|
443
448
|
}
|
|
444
449
|
];
|
|
@@ -449,7 +454,7 @@ for (const unit of units) {
|
|
|
449
454
|
unitMap.set(alias.toLowerCase(), unit);
|
|
450
455
|
}
|
|
451
456
|
}
|
|
452
|
-
function normalizeUnit(unit) {
|
|
457
|
+
function normalizeUnit(unit = "") {
|
|
453
458
|
return unitMap.get(unit.toLowerCase().trim());
|
|
454
459
|
}
|
|
455
460
|
var CannotAddTextValueError = class extends Error {
|
|
@@ -511,7 +516,10 @@ function addNumericValues(val1, val2) {
|
|
|
511
516
|
num2 = val2.num;
|
|
512
517
|
den2 = val2.den;
|
|
513
518
|
}
|
|
514
|
-
if (
|
|
519
|
+
if (num1 === 0 && num2 === 0) {
|
|
520
|
+
return { type: "decimal", value: 0 };
|
|
521
|
+
}
|
|
522
|
+
if (val1.type === "fraction" && val2.type === "fraction" || val1.type === "fraction" && val2.type === "decimal" && val2.value === 0 || val2.type === "fraction" && val1.type === "decimal" && val1.value === 0) {
|
|
515
523
|
const commonDen = den1 * den2;
|
|
516
524
|
const sumNum = num1 * den2 + num2 * den1;
|
|
517
525
|
return simplifyFraction(sumNum, commonDen);
|
|
@@ -525,24 +533,26 @@ var toRoundedDecimal = (v) => {
|
|
|
525
533
|
};
|
|
526
534
|
function multiplyQuantityValue(value, factor) {
|
|
527
535
|
if (value.type === "fixed") {
|
|
536
|
+
const newValue = multiplyNumericValue(
|
|
537
|
+
value.value,
|
|
538
|
+
factor
|
|
539
|
+
);
|
|
540
|
+
if (factor === parseInt(factor.toString()) || // e.g. 2 === int
|
|
541
|
+
1 / factor === parseInt((1 / factor).toString())) {
|
|
542
|
+
return {
|
|
543
|
+
type: "fixed",
|
|
544
|
+
value: newValue
|
|
545
|
+
};
|
|
546
|
+
}
|
|
528
547
|
return {
|
|
529
548
|
type: "fixed",
|
|
530
|
-
value: toRoundedDecimal(
|
|
531
|
-
multiplyNumericValue(
|
|
532
|
-
value.value,
|
|
533
|
-
factor
|
|
534
|
-
)
|
|
535
|
-
)
|
|
549
|
+
value: toRoundedDecimal(newValue)
|
|
536
550
|
};
|
|
537
551
|
}
|
|
538
552
|
return {
|
|
539
553
|
type: "range",
|
|
540
|
-
min: toRoundedDecimal(
|
|
541
|
-
|
|
542
|
-
),
|
|
543
|
-
max: toRoundedDecimal(
|
|
544
|
-
multiplyNumericValue(value.max, factor)
|
|
545
|
-
)
|
|
554
|
+
min: toRoundedDecimal(multiplyNumericValue(value.min, factor)),
|
|
555
|
+
max: toRoundedDecimal(multiplyNumericValue(value.max, factor))
|
|
546
556
|
};
|
|
547
557
|
}
|
|
548
558
|
var convertQuantityValue = (value, def, targetDef) => {
|
|
@@ -550,6 +560,32 @@ var convertQuantityValue = (value, def, targetDef) => {
|
|
|
550
560
|
const factor = def.toBase / targetDef.toBase;
|
|
551
561
|
return multiplyQuantityValue(value, factor);
|
|
552
562
|
};
|
|
563
|
+
function getDefaultQuantityValue() {
|
|
564
|
+
return { type: "fixed", value: { type: "decimal", value: 0 } };
|
|
565
|
+
}
|
|
566
|
+
function addQuantityValues(v1, v2) {
|
|
567
|
+
if (v1.type === "fixed" && v1.value.type === "text" || v2.type === "fixed" && v2.value.type === "text") {
|
|
568
|
+
throw new CannotAddTextValueError();
|
|
569
|
+
}
|
|
570
|
+
if (v1.type === "fixed" && v2.type === "fixed") {
|
|
571
|
+
const res = addNumericValues(
|
|
572
|
+
v1.value,
|
|
573
|
+
v2.value
|
|
574
|
+
);
|
|
575
|
+
return { type: "fixed", value: res };
|
|
576
|
+
}
|
|
577
|
+
const r1 = v1.type === "range" ? v1 : { type: "range", min: v1.value, max: v1.value };
|
|
578
|
+
const r2 = v2.type === "range" ? v2 : { type: "range", min: v2.value, max: v2.value };
|
|
579
|
+
const newMin = addNumericValues(
|
|
580
|
+
r1.min,
|
|
581
|
+
r2.min
|
|
582
|
+
);
|
|
583
|
+
const newMax = addNumericValues(
|
|
584
|
+
r1.max,
|
|
585
|
+
r2.max
|
|
586
|
+
);
|
|
587
|
+
return { type: "range", min: newMin, max: newMax };
|
|
588
|
+
}
|
|
553
589
|
function addQuantities(q1, q2) {
|
|
554
590
|
const v1 = q1.value;
|
|
555
591
|
const v2 = q2.value;
|
|
@@ -558,33 +594,14 @@ function addQuantities(q1, q2) {
|
|
|
558
594
|
}
|
|
559
595
|
const unit1Def = normalizeUnit(q1.unit);
|
|
560
596
|
const unit2Def = normalizeUnit(q2.unit);
|
|
561
|
-
const addQuantityValuesAndSetUnit = (val1, val2, unit) => {
|
|
562
|
-
|
|
563
|
-
const res = addNumericValues(
|
|
564
|
-
val1.value,
|
|
565
|
-
val2.value
|
|
566
|
-
);
|
|
567
|
-
return { value: { type: "fixed", value: res }, unit };
|
|
568
|
-
}
|
|
569
|
-
const r1 = val1.type === "range" ? val1 : { type: "range", min: val1.value, max: val1.value };
|
|
570
|
-
const r2 = val2.type === "range" ? val2 : { type: "range", min: val2.value, max: val2.value };
|
|
571
|
-
const newMin = addNumericValues(
|
|
572
|
-
r1.min,
|
|
573
|
-
r2.min
|
|
574
|
-
);
|
|
575
|
-
const newMax = addNumericValues(
|
|
576
|
-
r1.max,
|
|
577
|
-
r2.max
|
|
578
|
-
);
|
|
579
|
-
return { value: { type: "range", min: newMin, max: newMax }, unit };
|
|
580
|
-
};
|
|
581
|
-
if (q1.unit === "" && unit2Def) {
|
|
597
|
+
const addQuantityValuesAndSetUnit = (val1, val2, unit) => ({ value: addQuantityValues(val1, val2), unit });
|
|
598
|
+
if ((q1.unit === "" || q1.unit === void 0) && q2.unit !== void 0) {
|
|
582
599
|
return addQuantityValuesAndSetUnit(v1, v2, q2.unit);
|
|
583
600
|
}
|
|
584
|
-
if (q2.unit === "" &&
|
|
601
|
+
if ((q2.unit === "" || q2.unit === void 0) && q1.unit !== void 0) {
|
|
585
602
|
return addQuantityValuesAndSetUnit(v1, v2, q1.unit);
|
|
586
603
|
}
|
|
587
|
-
if (q1.unit.toLowerCase() === q2.unit.toLowerCase()) {
|
|
604
|
+
if (!q1.unit && !q2.unit || q1.unit && q2.unit && q1.unit.toLowerCase() === q2.unit.toLowerCase()) {
|
|
588
605
|
return addQuantityValuesAndSetUnit(v1, v2, q1.unit);
|
|
589
606
|
}
|
|
590
607
|
if (unit1Def && unit2Def) {
|
|
@@ -614,14 +631,18 @@ function addQuantities(q1, q2) {
|
|
|
614
631
|
throw new IncompatibleUnitsError(q1.unit, q2.unit);
|
|
615
632
|
}
|
|
616
633
|
|
|
617
|
-
// src/
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
634
|
+
// src/errors.ts
|
|
635
|
+
var ReferencedItemCannotBeRedefinedError = class extends Error {
|
|
636
|
+
constructor(item_type, item_name, new_modifier) {
|
|
637
|
+
super(
|
|
638
|
+
`The referenced ${item_type} "${item_name}" cannot be redefined as ${new_modifier}.
|
|
639
|
+
You can either remove the reference to create a new ${item_type} defined as ${new_modifier} or add the ${new_modifier} flag to the original definition of the ${item_type}`
|
|
640
|
+
);
|
|
641
|
+
this.name = "ReferencedItemCannotBeRedefinedError";
|
|
622
642
|
}
|
|
623
|
-
|
|
624
|
-
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
// src/parser_helpers.ts
|
|
625
646
|
function flushPendingNote(section, note) {
|
|
626
647
|
if (note.length > 0) {
|
|
627
648
|
section.content.push({ type: "note", note });
|
|
@@ -640,21 +661,28 @@ function flushPendingItems(section, items) {
|
|
|
640
661
|
function findAndUpsertIngredient(ingredients, newIngredient, isReference) {
|
|
641
662
|
const { name, quantity, unit } = newIngredient;
|
|
642
663
|
if (isReference) {
|
|
643
|
-
const
|
|
664
|
+
const indexFind = ingredients.findIndex(
|
|
644
665
|
(i2) => i2.name.toLowerCase() === name.toLowerCase()
|
|
645
666
|
);
|
|
646
|
-
if (
|
|
667
|
+
if (indexFind === -1) {
|
|
647
668
|
throw new Error(
|
|
648
669
|
`Referenced ingredient "${name}" not found. A referenced ingredient must be declared before being referenced with '&'.`
|
|
649
670
|
);
|
|
650
671
|
}
|
|
651
|
-
const existingIngredient = ingredients[
|
|
672
|
+
const existingIngredient = ingredients[indexFind];
|
|
673
|
+
for (const flag of newIngredient.flags) {
|
|
674
|
+
if (!existingIngredient.flags.includes(flag)) {
|
|
675
|
+
throw new ReferencedItemCannotBeRedefinedError(
|
|
676
|
+
"ingredient",
|
|
677
|
+
existingIngredient.name,
|
|
678
|
+
flag
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
let quantityPartIndex = void 0;
|
|
652
683
|
if (quantity !== void 0) {
|
|
653
684
|
const currentQuantity = {
|
|
654
|
-
value: existingIngredient.quantity ??
|
|
655
|
-
type: "fixed",
|
|
656
|
-
value: { type: "decimal", value: 0 }
|
|
657
|
-
},
|
|
685
|
+
value: existingIngredient.quantity ?? getDefaultQuantityValue(),
|
|
658
686
|
unit: existingIngredient.unit ?? ""
|
|
659
687
|
};
|
|
660
688
|
const newQuantity = { value: quantity, unit: unit ?? "" };
|
|
@@ -662,18 +690,35 @@ function findAndUpsertIngredient(ingredients, newIngredient, isReference) {
|
|
|
662
690
|
const total = addQuantities(currentQuantity, newQuantity);
|
|
663
691
|
existingIngredient.quantity = total.value;
|
|
664
692
|
existingIngredient.unit = total.unit || void 0;
|
|
693
|
+
if (existingIngredient.quantityParts) {
|
|
694
|
+
existingIngredient.quantityParts.push(
|
|
695
|
+
...newIngredient.quantityParts
|
|
696
|
+
);
|
|
697
|
+
} else {
|
|
698
|
+
existingIngredient.quantityParts = newIngredient.quantityParts;
|
|
699
|
+
}
|
|
700
|
+
quantityPartIndex = existingIngredient.quantityParts.length - 1;
|
|
665
701
|
} catch (e2) {
|
|
666
702
|
if (e2 instanceof IncompatibleUnitsError || e2 instanceof CannotAddTextValueError) {
|
|
667
|
-
return
|
|
703
|
+
return {
|
|
704
|
+
ingredientIndex: ingredients.push(newIngredient) - 1,
|
|
705
|
+
quantityPartIndex: 0
|
|
706
|
+
};
|
|
668
707
|
}
|
|
669
708
|
}
|
|
670
709
|
}
|
|
671
|
-
return
|
|
710
|
+
return {
|
|
711
|
+
ingredientIndex: indexFind,
|
|
712
|
+
quantityPartIndex
|
|
713
|
+
};
|
|
672
714
|
}
|
|
673
|
-
return
|
|
715
|
+
return {
|
|
716
|
+
ingredientIndex: ingredients.push(newIngredient) - 1,
|
|
717
|
+
quantityPartIndex: 0
|
|
718
|
+
};
|
|
674
719
|
}
|
|
675
720
|
function findAndUpsertCookware(cookware, newCookware, isReference) {
|
|
676
|
-
const { name } = newCookware;
|
|
721
|
+
const { name, quantity } = newCookware;
|
|
677
722
|
if (isReference) {
|
|
678
723
|
const index = cookware.findIndex(
|
|
679
724
|
(i2) => i2.name.toLowerCase() === name.toLowerCase()
|
|
@@ -683,9 +728,55 @@ function findAndUpsertCookware(cookware, newCookware, isReference) {
|
|
|
683
728
|
`Referenced cookware "${name}" not found. A referenced cookware must be declared before being referenced with '&'.`
|
|
684
729
|
);
|
|
685
730
|
}
|
|
686
|
-
|
|
731
|
+
const existingCookware = cookware[index];
|
|
732
|
+
for (const flag of newCookware.flags) {
|
|
733
|
+
if (!existingCookware.flags.includes(flag)) {
|
|
734
|
+
throw new ReferencedItemCannotBeRedefinedError(
|
|
735
|
+
"cookware",
|
|
736
|
+
existingCookware.name,
|
|
737
|
+
flag
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
let quantityPartIndex = void 0;
|
|
742
|
+
if (quantity !== void 0) {
|
|
743
|
+
if (!existingCookware.quantity) {
|
|
744
|
+
existingCookware.quantity = quantity;
|
|
745
|
+
existingCookware.quantityParts = newCookware.quantityParts;
|
|
746
|
+
quantityPartIndex = 0;
|
|
747
|
+
} else {
|
|
748
|
+
try {
|
|
749
|
+
existingCookware.quantity = addQuantityValues(
|
|
750
|
+
existingCookware.quantity,
|
|
751
|
+
quantity
|
|
752
|
+
);
|
|
753
|
+
if (!existingCookware.quantityParts) {
|
|
754
|
+
existingCookware.quantityParts = newCookware.quantityParts;
|
|
755
|
+
quantityPartIndex = 0;
|
|
756
|
+
} else {
|
|
757
|
+
quantityPartIndex = existingCookware.quantityParts.push(
|
|
758
|
+
...newCookware.quantityParts
|
|
759
|
+
) - 1;
|
|
760
|
+
}
|
|
761
|
+
} catch (e2) {
|
|
762
|
+
if (e2 instanceof CannotAddTextValueError) {
|
|
763
|
+
return {
|
|
764
|
+
cookwareIndex: cookware.push(newCookware) - 1,
|
|
765
|
+
quantityPartIndex: 0
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
return {
|
|
772
|
+
cookwareIndex: index,
|
|
773
|
+
quantityPartIndex
|
|
774
|
+
};
|
|
687
775
|
}
|
|
688
|
-
return
|
|
776
|
+
return {
|
|
777
|
+
cookwareIndex: cookware.push(newCookware) - 1,
|
|
778
|
+
quantityPartIndex: quantity ? 0 : void 0
|
|
779
|
+
};
|
|
689
780
|
}
|
|
690
781
|
var parseFixedValue = (input_str) => {
|
|
691
782
|
if (!numberLikeRegex.test(input_str)) {
|
|
@@ -717,14 +808,12 @@ function parseSimpleMetaVar(content, varName) {
|
|
|
717
808
|
return varMatch ? varMatch[1]?.trim().replace(/\s*\r?\n\s+/g, " ") : void 0;
|
|
718
809
|
}
|
|
719
810
|
function parseScalingMetaVar(content, varName) {
|
|
720
|
-
const varMatch = content.match(
|
|
721
|
-
new RegExp(`^${varName}:[\\t ]*(([^,\\n]*),? ?(?:.*)?)`, "m")
|
|
722
|
-
);
|
|
811
|
+
const varMatch = content.match(scalingMetaValueRegex(varName));
|
|
723
812
|
if (!varMatch) return void 0;
|
|
724
813
|
if (isNaN(Number(varMatch[2]?.trim()))) {
|
|
725
814
|
throw new Error("Scaling variables should be numbers");
|
|
726
815
|
}
|
|
727
|
-
return [Number(varMatch[2]?.trim()), varMatch[1]
|
|
816
|
+
return [Number(varMatch[2]?.trim()), varMatch[1].trim()];
|
|
728
817
|
}
|
|
729
818
|
function parseListMetaVar(content, varName) {
|
|
730
819
|
const listMatch = content.match(
|
|
@@ -775,7 +864,7 @@ function extractMetadata(content) {
|
|
|
775
864
|
const stringMetaValue = parseSimpleMetaVar(metadataContent, metaVar);
|
|
776
865
|
if (stringMetaValue) metadata[metaVar] = stringMetaValue;
|
|
777
866
|
}
|
|
778
|
-
for (const metaVar of ["
|
|
867
|
+
for (const metaVar of ["serves", "yield", "servings"]) {
|
|
779
868
|
const scalingMetaValue = parseScalingMetaVar(metadataContent, metaVar);
|
|
780
869
|
if (scalingMetaValue && scalingMetaValue[1]) {
|
|
781
870
|
metadata[metaVar] = scalingMetaValue[1];
|
|
@@ -797,32 +886,31 @@ var Recipe = class _Recipe {
|
|
|
797
886
|
*/
|
|
798
887
|
constructor(content) {
|
|
799
888
|
/**
|
|
800
|
-
* The recipe
|
|
801
|
-
* @see {@link Metadata}
|
|
889
|
+
* The parsed recipe metadata.
|
|
802
890
|
*/
|
|
803
891
|
__publicField(this, "metadata", {});
|
|
804
892
|
/**
|
|
805
|
-
* The recipe
|
|
806
|
-
* @see {@link Ingredient}
|
|
893
|
+
* The parsed recipe ingredients.
|
|
807
894
|
*/
|
|
808
895
|
__publicField(this, "ingredients", []);
|
|
809
896
|
/**
|
|
810
|
-
* The recipe
|
|
811
|
-
* @see {@link Section}
|
|
897
|
+
* The parsed recipe sections.
|
|
812
898
|
*/
|
|
813
899
|
__publicField(this, "sections", []);
|
|
814
900
|
/**
|
|
815
|
-
* The recipe
|
|
816
|
-
* @see {@link Cookware}
|
|
901
|
+
* The parsed recipe cookware.
|
|
817
902
|
*/
|
|
818
903
|
__publicField(this, "cookware", []);
|
|
819
904
|
/**
|
|
820
|
-
* The recipe
|
|
821
|
-
* @see {@link Timer}
|
|
905
|
+
* The parsed recipe timers.
|
|
822
906
|
*/
|
|
823
907
|
__publicField(this, "timers", []);
|
|
824
908
|
/**
|
|
825
|
-
* The recipe
|
|
909
|
+
* The parsed recipe servings. Used for scaling. Parsed from one of
|
|
910
|
+
* {@link Metadata.servings}, {@link Metadata.yield} or {@link Metadata.serves}
|
|
911
|
+
* metadata fields.
|
|
912
|
+
*
|
|
913
|
+
* @see {@link Recipe.scaleBy | scaleBy()} and {@link Recipe.scaleTo | scaleTo()} methods
|
|
826
914
|
*/
|
|
827
915
|
__publicField(this, "servings");
|
|
828
916
|
if (content) {
|
|
@@ -892,15 +980,28 @@ var Recipe = class _Recipe {
|
|
|
892
980
|
}
|
|
893
981
|
const groups = match.groups;
|
|
894
982
|
if (groups.mIngredientName || groups.sIngredientName) {
|
|
895
|
-
|
|
983
|
+
let name = groups.mIngredientName || groups.sIngredientName;
|
|
984
|
+
const scalableQuantity = (groups.mIngredientQuantityModifier || groups.sIngredientQuantityModifier) !== "=";
|
|
896
985
|
const quantityRaw = groups.mIngredientQuantity || groups.sIngredientQuantity;
|
|
897
|
-
const
|
|
986
|
+
const unit = groups.mIngredientUnit || groups.sIngredientUnit;
|
|
898
987
|
const preparation = groups.mIngredientPreparation || groups.sIngredientPreparation;
|
|
899
|
-
const
|
|
900
|
-
const
|
|
901
|
-
const
|
|
902
|
-
|
|
903
|
-
|
|
988
|
+
const modifiers = groups.mIngredientModifiers || groups.sIngredientModifiers;
|
|
989
|
+
const reference = modifiers !== void 0 && modifiers.includes("&");
|
|
990
|
+
const flags = [];
|
|
991
|
+
if (modifiers !== void 0 && modifiers.includes("?")) {
|
|
992
|
+
flags.push("optional");
|
|
993
|
+
}
|
|
994
|
+
if (modifiers !== void 0 && modifiers.includes("-")) {
|
|
995
|
+
flags.push("hidden");
|
|
996
|
+
}
|
|
997
|
+
if (modifiers !== void 0 && modifiers.includes("@") || groups.mIngredientRecipeAnchor || groups.sIngredientRecipeAnchor) {
|
|
998
|
+
flags.push("recipe");
|
|
999
|
+
}
|
|
1000
|
+
let extras = void 0;
|
|
1001
|
+
if (flags.includes("recipe")) {
|
|
1002
|
+
extras = { path: `${name}.cook` };
|
|
1003
|
+
name = name.substring(name.lastIndexOf("/") + 1);
|
|
1004
|
+
}
|
|
904
1005
|
const quantity = quantityRaw ? parseQuantityInput(quantityRaw) : void 0;
|
|
905
1006
|
const aliasMatch = name.match(ingredientAliasRegex);
|
|
906
1007
|
let listName, displayName;
|
|
@@ -911,50 +1012,70 @@ var Recipe = class _Recipe {
|
|
|
911
1012
|
listName = name;
|
|
912
1013
|
displayName = name;
|
|
913
1014
|
}
|
|
914
|
-
const
|
|
1015
|
+
const newIngredient = {
|
|
1016
|
+
name: listName,
|
|
1017
|
+
quantity,
|
|
1018
|
+
quantityParts: quantity ? [
|
|
1019
|
+
{
|
|
1020
|
+
value: quantity,
|
|
1021
|
+
unit,
|
|
1022
|
+
scalable: scalableQuantity
|
|
1023
|
+
}
|
|
1024
|
+
] : void 0,
|
|
1025
|
+
unit,
|
|
1026
|
+
preparation,
|
|
1027
|
+
flags
|
|
1028
|
+
};
|
|
1029
|
+
if (extras) {
|
|
1030
|
+
newIngredient.extras = extras;
|
|
1031
|
+
}
|
|
1032
|
+
const idxsInList = findAndUpsertIngredient(
|
|
915
1033
|
this.ingredients,
|
|
916
|
-
|
|
917
|
-
name: listName,
|
|
918
|
-
quantity,
|
|
919
|
-
unit: units2,
|
|
920
|
-
optional,
|
|
921
|
-
hidden,
|
|
922
|
-
preparation,
|
|
923
|
-
isRecipe
|
|
924
|
-
},
|
|
1034
|
+
newIngredient,
|
|
925
1035
|
reference
|
|
926
1036
|
);
|
|
927
1037
|
const newItem = {
|
|
928
1038
|
type: "ingredient",
|
|
929
|
-
|
|
930
|
-
itemQuantity: quantity,
|
|
931
|
-
itemUnit: units2,
|
|
1039
|
+
index: idxsInList.ingredientIndex,
|
|
932
1040
|
displayName
|
|
933
1041
|
};
|
|
1042
|
+
if (idxsInList.quantityPartIndex !== void 0) {
|
|
1043
|
+
newItem.quantityPartIndex = idxsInList.quantityPartIndex;
|
|
1044
|
+
}
|
|
934
1045
|
items.push(newItem);
|
|
935
1046
|
} else if (groups.mCookwareName || groups.sCookwareName) {
|
|
936
1047
|
const name = groups.mCookwareName || groups.sCookwareName;
|
|
937
|
-
const
|
|
1048
|
+
const modifiers = groups.mCookwareModifiers || groups.sCookwareModifiers;
|
|
938
1049
|
const quantityRaw = groups.mCookwareQuantity || groups.sCookwareQuantity;
|
|
939
|
-
const
|
|
940
|
-
const
|
|
941
|
-
|
|
1050
|
+
const reference = modifiers !== void 0 && modifiers.includes("&");
|
|
1051
|
+
const flags = [];
|
|
1052
|
+
if (modifiers !== void 0 && modifiers.includes("?")) {
|
|
1053
|
+
flags.push("optional");
|
|
1054
|
+
}
|
|
1055
|
+
if (modifiers !== void 0 && modifiers.includes("-")) {
|
|
1056
|
+
flags.push("hidden");
|
|
1057
|
+
}
|
|
942
1058
|
const quantity = quantityRaw ? parseQuantityInput(quantityRaw) : void 0;
|
|
943
|
-
const
|
|
1059
|
+
const idxsInList = findAndUpsertCookware(
|
|
944
1060
|
this.cookware,
|
|
945
|
-
{
|
|
1061
|
+
{
|
|
1062
|
+
name,
|
|
1063
|
+
quantity,
|
|
1064
|
+
quantityParts: quantity ? [quantity] : void 0,
|
|
1065
|
+
flags
|
|
1066
|
+
},
|
|
946
1067
|
reference
|
|
947
1068
|
);
|
|
948
1069
|
items.push({
|
|
949
1070
|
type: "cookware",
|
|
950
|
-
|
|
951
|
-
|
|
1071
|
+
index: idxsInList.cookwareIndex,
|
|
1072
|
+
quantityPartIndex: idxsInList.quantityPartIndex
|
|
952
1073
|
});
|
|
953
|
-
} else
|
|
1074
|
+
} else {
|
|
954
1075
|
const durationStr = groups.timerQuantity.trim();
|
|
955
|
-
const unit = (groups.
|
|
1076
|
+
const unit = (groups.timerUnit || "").trim();
|
|
956
1077
|
if (!unit) {
|
|
957
|
-
throw new Error("Timer missing
|
|
1078
|
+
throw new Error("Timer missing unit");
|
|
958
1079
|
}
|
|
959
1080
|
const name = groups.timerName || void 0;
|
|
960
1081
|
const duration = parseQuantityInput(durationStr);
|
|
@@ -963,12 +1084,7 @@ var Recipe = class _Recipe {
|
|
|
963
1084
|
duration,
|
|
964
1085
|
unit
|
|
965
1086
|
};
|
|
966
|
-
|
|
967
|
-
this.timers,
|
|
968
|
-
(t2) => t2.name === timerObj.name && t2.duration === timerObj.duration && t2.unit === timerObj.unit,
|
|
969
|
-
() => timerObj
|
|
970
|
-
);
|
|
971
|
-
items.push({ type: "timer", value: idxInList });
|
|
1087
|
+
items.push({ type: "timer", index: this.timers.push(timerObj) - 1 });
|
|
972
1088
|
}
|
|
973
1089
|
cursor = idx + match[0].length;
|
|
974
1090
|
}
|
|
@@ -984,9 +1100,12 @@ var Recipe = class _Recipe {
|
|
|
984
1100
|
}
|
|
985
1101
|
}
|
|
986
1102
|
/**
|
|
987
|
-
* Scales the recipe to a new number of servings.
|
|
1103
|
+
* Scales the recipe to a new number of servings. In practice, it calls
|
|
1104
|
+
* {@link Recipe.scaleBy | scaleBy} with a factor corresponding to the ratio between `newServings`
|
|
1105
|
+
* and the recipe's {@link Recipe.servings | servings} value.
|
|
988
1106
|
* @param newServings - The new number of servings.
|
|
989
1107
|
* @returns A new Recipe instance with the scaled ingredients.
|
|
1108
|
+
* @throws `Error` if the recipe does not contains an initial {@link Recipe.servings | servings} value
|
|
990
1109
|
*/
|
|
991
1110
|
scaleTo(newServings) {
|
|
992
1111
|
const originalServings = this.getServings();
|
|
@@ -1008,30 +1127,57 @@ var Recipe = class _Recipe {
|
|
|
1008
1127
|
throw new Error("Error scaling recipe: no initial servings value set");
|
|
1009
1128
|
}
|
|
1010
1129
|
newRecipe.ingredients = newRecipe.ingredients.map((ingredient) => {
|
|
1011
|
-
if (ingredient.
|
|
1012
|
-
ingredient.
|
|
1013
|
-
|
|
1014
|
-
|
|
1130
|
+
if (ingredient.quantityParts) {
|
|
1131
|
+
ingredient.quantityParts = ingredient.quantityParts.map(
|
|
1132
|
+
(quantityPart) => {
|
|
1133
|
+
if (quantityPart.value.type === "fixed" && quantityPart.value.value.type === "text") {
|
|
1134
|
+
return quantityPart;
|
|
1135
|
+
}
|
|
1136
|
+
return {
|
|
1137
|
+
...quantityPart,
|
|
1138
|
+
value: multiplyQuantityValue(
|
|
1139
|
+
quantityPart.value,
|
|
1140
|
+
quantityPart.scalable ? factor : 1
|
|
1141
|
+
)
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1015
1144
|
);
|
|
1145
|
+
if (ingredient.quantityParts.length === 1) {
|
|
1146
|
+
ingredient.quantity = ingredient.quantityParts[0].value;
|
|
1147
|
+
ingredient.unit = ingredient.quantityParts[0].unit;
|
|
1148
|
+
} else {
|
|
1149
|
+
const totalQuantity = ingredient.quantityParts.reduce(
|
|
1150
|
+
(acc, val) => addQuantities(acc, { value: val.value, unit: val.unit }),
|
|
1151
|
+
{ value: getDefaultQuantityValue() }
|
|
1152
|
+
);
|
|
1153
|
+
ingredient.quantity = totalQuantity.value;
|
|
1154
|
+
ingredient.unit = totalQuantity.unit;
|
|
1155
|
+
}
|
|
1016
1156
|
}
|
|
1017
1157
|
return ingredient;
|
|
1018
1158
|
}).filter((ingredient) => ingredient.quantity !== null);
|
|
1019
1159
|
newRecipe.servings = originalServings * factor;
|
|
1020
1160
|
if (newRecipe.metadata.servings && this.metadata.servings) {
|
|
1021
|
-
|
|
1022
|
-
|
|
1161
|
+
if (floatRegex.test(String(this.metadata.servings).replace(",", ".").trim())) {
|
|
1162
|
+
const servingsValue = parseFloat(
|
|
1163
|
+
String(this.metadata.servings).replace(",", ".")
|
|
1164
|
+
);
|
|
1023
1165
|
newRecipe.metadata.servings = String(servingsValue * factor);
|
|
1024
1166
|
}
|
|
1025
1167
|
}
|
|
1026
1168
|
if (newRecipe.metadata.yield && this.metadata.yield) {
|
|
1027
|
-
|
|
1028
|
-
|
|
1169
|
+
if (floatRegex.test(String(this.metadata.yield).replace(",", ".").trim())) {
|
|
1170
|
+
const yieldValue = parseFloat(
|
|
1171
|
+
String(this.metadata.yield).replace(",", ".")
|
|
1172
|
+
);
|
|
1029
1173
|
newRecipe.metadata.yield = String(yieldValue * factor);
|
|
1030
1174
|
}
|
|
1031
1175
|
}
|
|
1032
1176
|
if (newRecipe.metadata.serves && this.metadata.serves) {
|
|
1033
|
-
|
|
1034
|
-
|
|
1177
|
+
if (floatRegex.test(String(this.metadata.serves).replace(",", ".").trim())) {
|
|
1178
|
+
const servesValue = parseFloat(
|
|
1179
|
+
String(this.metadata.serves).replace(",", ".")
|
|
1180
|
+
);
|
|
1035
1181
|
newRecipe.metadata.serves = String(servesValue * factor);
|
|
1036
1182
|
}
|
|
1037
1183
|
}
|
|
@@ -1055,9 +1201,13 @@ var Recipe = class _Recipe {
|
|
|
1055
1201
|
clone() {
|
|
1056
1202
|
const newRecipe = new _Recipe();
|
|
1057
1203
|
newRecipe.metadata = JSON.parse(JSON.stringify(this.metadata));
|
|
1058
|
-
newRecipe.ingredients = JSON.parse(
|
|
1204
|
+
newRecipe.ingredients = JSON.parse(
|
|
1205
|
+
JSON.stringify(this.ingredients)
|
|
1206
|
+
);
|
|
1059
1207
|
newRecipe.sections = JSON.parse(JSON.stringify(this.sections));
|
|
1060
|
-
newRecipe.cookware = JSON.parse(
|
|
1208
|
+
newRecipe.cookware = JSON.parse(
|
|
1209
|
+
JSON.stringify(this.cookware)
|
|
1210
|
+
);
|
|
1061
1211
|
newRecipe.timers = JSON.parse(JSON.stringify(this.timers));
|
|
1062
1212
|
newRecipe.servings = this.servings;
|
|
1063
1213
|
return newRecipe;
|
|
@@ -1067,32 +1217,28 @@ var Recipe = class _Recipe {
|
|
|
1067
1217
|
// src/classes/shopping_list.ts
|
|
1068
1218
|
var ShoppingList = class {
|
|
1069
1219
|
/**
|
|
1070
|
-
* Creates a new ShoppingList instance
|
|
1071
|
-
* @param
|
|
1220
|
+
* Creates a new ShoppingList instance
|
|
1221
|
+
* @param category_config_str - The category configuration to parse.
|
|
1072
1222
|
*/
|
|
1073
|
-
constructor(
|
|
1223
|
+
constructor(category_config_str) {
|
|
1074
1224
|
/**
|
|
1075
1225
|
* The ingredients in the shopping list.
|
|
1076
|
-
* @see {@link Ingredient}
|
|
1077
1226
|
*/
|
|
1078
1227
|
__publicField(this, "ingredients", []);
|
|
1079
1228
|
/**
|
|
1080
1229
|
* The recipes in the shopping list.
|
|
1081
|
-
* @see {@link AddedRecipe}
|
|
1082
1230
|
*/
|
|
1083
1231
|
__publicField(this, "recipes", []);
|
|
1084
1232
|
/**
|
|
1085
|
-
* The
|
|
1086
|
-
* @see {@link AisleConfig}
|
|
1233
|
+
* The category configuration for the shopping list.
|
|
1087
1234
|
*/
|
|
1088
|
-
__publicField(this, "
|
|
1235
|
+
__publicField(this, "category_config");
|
|
1089
1236
|
/**
|
|
1090
1237
|
* The categorized ingredients in the shopping list.
|
|
1091
|
-
* @see {@link CategorizedIngredients}
|
|
1092
1238
|
*/
|
|
1093
1239
|
__publicField(this, "categories");
|
|
1094
|
-
if (
|
|
1095
|
-
this.
|
|
1240
|
+
if (category_config_str) {
|
|
1241
|
+
this.set_category_config(category_config_str);
|
|
1096
1242
|
}
|
|
1097
1243
|
}
|
|
1098
1244
|
calculate_ingredients() {
|
|
@@ -1100,7 +1246,7 @@ var ShoppingList = class {
|
|
|
1100
1246
|
for (const { recipe, factor } of this.recipes) {
|
|
1101
1247
|
const scaledRecipe = factor === 1 ? recipe : recipe.scaleBy(factor);
|
|
1102
1248
|
for (const ingredient of scaledRecipe.ingredients) {
|
|
1103
|
-
if (ingredient.hidden) {
|
|
1249
|
+
if (ingredient.flags && ingredient.flags.includes("hidden")) {
|
|
1104
1250
|
continue;
|
|
1105
1251
|
}
|
|
1106
1252
|
const existingIngredient = this.ingredients.find(
|
|
@@ -1108,8 +1254,8 @@ var ShoppingList = class {
|
|
|
1108
1254
|
);
|
|
1109
1255
|
let addSeparate = false;
|
|
1110
1256
|
try {
|
|
1111
|
-
if (existingIngredient) {
|
|
1112
|
-
if (existingIngredient.quantity
|
|
1257
|
+
if (existingIngredient && ingredient.quantity) {
|
|
1258
|
+
if (existingIngredient.quantity) {
|
|
1113
1259
|
const newQuantity = addQuantities(
|
|
1114
1260
|
{
|
|
1115
1261
|
value: existingIngredient.quantity,
|
|
@@ -1124,7 +1270,7 @@ var ShoppingList = class {
|
|
|
1124
1270
|
if (newQuantity.unit) {
|
|
1125
1271
|
existingIngredient.unit = newQuantity.unit;
|
|
1126
1272
|
}
|
|
1127
|
-
} else
|
|
1273
|
+
} else {
|
|
1128
1274
|
existingIngredient.quantity = ingredient.quantity;
|
|
1129
1275
|
if (ingredient.unit) {
|
|
1130
1276
|
existingIngredient.unit = ingredient.unit;
|
|
@@ -1148,7 +1294,8 @@ var ShoppingList = class {
|
|
|
1148
1294
|
}
|
|
1149
1295
|
}
|
|
1150
1296
|
/**
|
|
1151
|
-
* Adds a recipe to the shopping list
|
|
1297
|
+
* Adds a recipe to the shopping list, then automatically
|
|
1298
|
+
* recalculates the quantities and recategorize the ingredients.
|
|
1152
1299
|
* @param recipe - The recipe to add.
|
|
1153
1300
|
* @param factor - The factor to scale the recipe by.
|
|
1154
1301
|
*/
|
|
@@ -1158,7 +1305,8 @@ var ShoppingList = class {
|
|
|
1158
1305
|
this.categorize();
|
|
1159
1306
|
}
|
|
1160
1307
|
/**
|
|
1161
|
-
* Removes a recipe from the shopping list
|
|
1308
|
+
* Removes a recipe from the shopping list, then automatically
|
|
1309
|
+
* recalculates the quantities and recategorize the ingredients.s
|
|
1162
1310
|
* @param index - The index of the recipe to remove.
|
|
1163
1311
|
*/
|
|
1164
1312
|
remove_recipe(index) {
|
|
@@ -1170,31 +1318,35 @@ var ShoppingList = class {
|
|
|
1170
1318
|
this.categorize();
|
|
1171
1319
|
}
|
|
1172
1320
|
/**
|
|
1173
|
-
* Sets the
|
|
1174
|
-
*
|
|
1321
|
+
* Sets the category configuration for the shopping list
|
|
1322
|
+
* and automatically categorize current ingredients from the list.
|
|
1323
|
+
* @param config - The category configuration to parse.
|
|
1175
1324
|
*/
|
|
1176
|
-
|
|
1177
|
-
|
|
1325
|
+
set_category_config(config) {
|
|
1326
|
+
if (typeof config === "string")
|
|
1327
|
+
this.category_config = new CategoryConfig(config);
|
|
1328
|
+
else if (config instanceof CategoryConfig) this.category_config = config;
|
|
1329
|
+
else throw new Error("Invalid category configuration");
|
|
1178
1330
|
this.categorize();
|
|
1179
1331
|
}
|
|
1180
1332
|
/**
|
|
1181
1333
|
* Categorizes the ingredients in the shopping list
|
|
1182
|
-
* Will use the
|
|
1334
|
+
* Will use the category config if any, otherwise all ingredients will be placed in the "other" category
|
|
1183
1335
|
*/
|
|
1184
1336
|
categorize() {
|
|
1185
|
-
if (!this.
|
|
1337
|
+
if (!this.category_config) {
|
|
1186
1338
|
this.categories = { other: this.ingredients };
|
|
1187
1339
|
return;
|
|
1188
1340
|
}
|
|
1189
1341
|
const categories = { other: [] };
|
|
1190
|
-
for (const category of this.
|
|
1342
|
+
for (const category of this.category_config.categories) {
|
|
1191
1343
|
categories[category.name] = [];
|
|
1192
1344
|
}
|
|
1193
1345
|
for (const ingredient of this.ingredients) {
|
|
1194
1346
|
let found = false;
|
|
1195
|
-
for (const category of this.
|
|
1196
|
-
for (const
|
|
1197
|
-
if (
|
|
1347
|
+
for (const category of this.category_config.categories) {
|
|
1348
|
+
for (const categoryIngredient of category.ingredients) {
|
|
1349
|
+
if (categoryIngredient.aliases.includes(ingredient.name)) {
|
|
1198
1350
|
categories[category.name].push(ingredient);
|
|
1199
1351
|
found = true;
|
|
1200
1352
|
break;
|
|
@@ -1213,9 +1365,13 @@ var ShoppingList = class {
|
|
|
1213
1365
|
};
|
|
1214
1366
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1215
1367
|
0 && (module.exports = {
|
|
1216
|
-
|
|
1368
|
+
CategoryConfig,
|
|
1217
1369
|
Recipe,
|
|
1218
1370
|
Section,
|
|
1219
1371
|
ShoppingList
|
|
1220
1372
|
});
|
|
1373
|
+
/* v8 ignore else -- @preserve */
|
|
1374
|
+
/* v8 ignore else -- expliciting error types -- @preserve */
|
|
1375
|
+
/* v8 ignore else -- expliciting error type -- @preserve */
|
|
1376
|
+
/* v8 ignore else -- only set unit if it is given -- @preserve */
|
|
1221
1377
|
//# sourceMappingURL=index.cjs.map
|