@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.js
CHANGED
|
@@ -2,16 +2,15 @@ var __defProp = Object.defineProperty;
|
|
|
2
2
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
3
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
4
|
|
|
5
|
-
// src/classes/
|
|
6
|
-
var
|
|
5
|
+
// src/classes/category_config.ts
|
|
6
|
+
var CategoryConfig = class {
|
|
7
7
|
/**
|
|
8
|
-
* Creates a new
|
|
9
|
-
* @param config - The
|
|
8
|
+
* Creates a new CategoryConfig instance.
|
|
9
|
+
* @param config - The category configuration to parse.
|
|
10
10
|
*/
|
|
11
11
|
constructor(config) {
|
|
12
12
|
/**
|
|
13
|
-
* The categories of
|
|
14
|
-
* @see {@link AisleCategory}
|
|
13
|
+
* The parsed categories of ingredients.
|
|
15
14
|
*/
|
|
16
15
|
__publicField(this, "categories", []);
|
|
17
16
|
if (config) {
|
|
@@ -19,8 +18,9 @@ var AisleConfig = class {
|
|
|
19
18
|
}
|
|
20
19
|
}
|
|
21
20
|
/**
|
|
22
|
-
* Parses
|
|
23
|
-
*
|
|
21
|
+
* Parses a category configuration from a string into property
|
|
22
|
+
* {@link CategoryConfig.categories | categories}
|
|
23
|
+
* @param config - The category configuration to parse.
|
|
24
24
|
*/
|
|
25
25
|
parse(config) {
|
|
26
26
|
let currentCategory = null;
|
|
@@ -70,7 +70,10 @@ var Section = class {
|
|
|
70
70
|
* @param name - The name of the section. Defaults to an empty string.
|
|
71
71
|
*/
|
|
72
72
|
constructor(name = "") {
|
|
73
|
-
/**
|
|
73
|
+
/**
|
|
74
|
+
* The name of the section. Can be an empty string for the default (first) section.
|
|
75
|
+
* @defaultValue `""`
|
|
76
|
+
*/
|
|
74
77
|
__publicField(this, "name");
|
|
75
78
|
/** An array of steps and notes that make up the content of the section. */
|
|
76
79
|
__publicField(this, "content", []);
|
|
@@ -286,13 +289,14 @@ var i = (() => {
|
|
|
286
289
|
|
|
287
290
|
// src/regex.ts
|
|
288
291
|
var metadataRegex = d().literal("---").newline().startCaptureGroup().anyCharacter().zeroOrMore().optional().endGroup().newline().literal("---").dotAll().toRegExp();
|
|
289
|
-
var
|
|
290
|
-
var
|
|
291
|
-
var
|
|
292
|
+
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();
|
|
293
|
+
var nonWordChar = "\\s@#~\\[\\]{(,;:!?";
|
|
294
|
+
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();
|
|
295
|
+
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();
|
|
292
296
|
var ingredientAliasRegex = d().startAnchor().startNamedGroup("ingredientListName").notAnyOf("|").oneOrMore().endGroup().literal("|").startNamedGroup("ingredientDisplayName").notAnyOf("|").oneOrMore().endGroup().endAnchor().toRegExp();
|
|
293
|
-
var multiwordCookware = d().literal("#").startNamedGroup("
|
|
294
|
-
var singleWordCookware = d().literal("#").startNamedGroup("
|
|
295
|
-
var timer = d().literal("~").startNamedGroup("timerName").anyCharacter().zeroOrMore().lazy().endGroup().literal("{").startNamedGroup("timerQuantity").anyCharacter().oneOrMore().lazy().endGroup().startGroup().literal("%").startNamedGroup("
|
|
297
|
+
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();
|
|
298
|
+
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();
|
|
299
|
+
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();
|
|
296
300
|
var tokensRegex = new RegExp(
|
|
297
301
|
[
|
|
298
302
|
multiwordIngredient,
|
|
@@ -306,8 +310,9 @@ var tokensRegex = new RegExp(
|
|
|
306
310
|
var commentRegex = d().literal("--").anyCharacter().zeroOrMore().global().toRegExp();
|
|
307
311
|
var blockCommentRegex = d().whitespace().zeroOrMore().literal("[-").anyCharacter().zeroOrMore().lazy().literal("-]").whitespace().zeroOrMore().global().toRegExp();
|
|
308
312
|
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();
|
|
309
|
-
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();
|
|
313
|
+
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();
|
|
310
314
|
var numberLikeRegex = d().startAnchor().digit().oneOrMore().startGroup().anyOf(".,/").exactly(1).digit().oneOrMore().endGroup().optional().endAnchor().toRegExp();
|
|
315
|
+
var floatRegex = d().startAnchor().digit().oneOrMore().startGroup().anyOf(".").exactly(1).digit().oneOrMore().endGroup().optional().endAnchor().toRegExp();
|
|
311
316
|
|
|
312
317
|
// src/units.ts
|
|
313
318
|
var units = [
|
|
@@ -316,14 +321,14 @@ var units = [
|
|
|
316
321
|
name: "g",
|
|
317
322
|
type: "mass",
|
|
318
323
|
system: "metric",
|
|
319
|
-
aliases: ["gram", "grams"],
|
|
324
|
+
aliases: ["gram", "grams", "grammes"],
|
|
320
325
|
toBase: 1
|
|
321
326
|
},
|
|
322
327
|
{
|
|
323
328
|
name: "kg",
|
|
324
329
|
type: "mass",
|
|
325
330
|
system: "metric",
|
|
326
|
-
aliases: ["kilogram", "kilograms"],
|
|
331
|
+
aliases: ["kilogram", "kilograms", "kilogrammes", "kilos", "kilo"],
|
|
327
332
|
toBase: 1e3
|
|
328
333
|
},
|
|
329
334
|
// Mass (Imperial)
|
|
@@ -346,7 +351,7 @@ var units = [
|
|
|
346
351
|
name: "ml",
|
|
347
352
|
type: "volume",
|
|
348
353
|
system: "metric",
|
|
349
|
-
aliases: ["milliliter", "milliliters", "millilitre", "millilitres"],
|
|
354
|
+
aliases: ["milliliter", "milliliters", "millilitre", "millilitres", "cc"],
|
|
350
355
|
toBase: 1
|
|
351
356
|
},
|
|
352
357
|
{
|
|
@@ -411,7 +416,7 @@ var units = [
|
|
|
411
416
|
name: "piece",
|
|
412
417
|
type: "count",
|
|
413
418
|
system: "metric",
|
|
414
|
-
aliases: ["pieces"],
|
|
419
|
+
aliases: ["pieces", "pc"],
|
|
415
420
|
toBase: 1
|
|
416
421
|
}
|
|
417
422
|
];
|
|
@@ -422,7 +427,7 @@ for (const unit of units) {
|
|
|
422
427
|
unitMap.set(alias.toLowerCase(), unit);
|
|
423
428
|
}
|
|
424
429
|
}
|
|
425
|
-
function normalizeUnit(unit) {
|
|
430
|
+
function normalizeUnit(unit = "") {
|
|
426
431
|
return unitMap.get(unit.toLowerCase().trim());
|
|
427
432
|
}
|
|
428
433
|
var CannotAddTextValueError = class extends Error {
|
|
@@ -484,7 +489,10 @@ function addNumericValues(val1, val2) {
|
|
|
484
489
|
num2 = val2.num;
|
|
485
490
|
den2 = val2.den;
|
|
486
491
|
}
|
|
487
|
-
if (
|
|
492
|
+
if (num1 === 0 && num2 === 0) {
|
|
493
|
+
return { type: "decimal", value: 0 };
|
|
494
|
+
}
|
|
495
|
+
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) {
|
|
488
496
|
const commonDen = den1 * den2;
|
|
489
497
|
const sumNum = num1 * den2 + num2 * den1;
|
|
490
498
|
return simplifyFraction(sumNum, commonDen);
|
|
@@ -498,24 +506,26 @@ var toRoundedDecimal = (v) => {
|
|
|
498
506
|
};
|
|
499
507
|
function multiplyQuantityValue(value, factor) {
|
|
500
508
|
if (value.type === "fixed") {
|
|
509
|
+
const newValue = multiplyNumericValue(
|
|
510
|
+
value.value,
|
|
511
|
+
factor
|
|
512
|
+
);
|
|
513
|
+
if (factor === parseInt(factor.toString()) || // e.g. 2 === int
|
|
514
|
+
1 / factor === parseInt((1 / factor).toString())) {
|
|
515
|
+
return {
|
|
516
|
+
type: "fixed",
|
|
517
|
+
value: newValue
|
|
518
|
+
};
|
|
519
|
+
}
|
|
501
520
|
return {
|
|
502
521
|
type: "fixed",
|
|
503
|
-
value: toRoundedDecimal(
|
|
504
|
-
multiplyNumericValue(
|
|
505
|
-
value.value,
|
|
506
|
-
factor
|
|
507
|
-
)
|
|
508
|
-
)
|
|
522
|
+
value: toRoundedDecimal(newValue)
|
|
509
523
|
};
|
|
510
524
|
}
|
|
511
525
|
return {
|
|
512
526
|
type: "range",
|
|
513
|
-
min: toRoundedDecimal(
|
|
514
|
-
|
|
515
|
-
),
|
|
516
|
-
max: toRoundedDecimal(
|
|
517
|
-
multiplyNumericValue(value.max, factor)
|
|
518
|
-
)
|
|
527
|
+
min: toRoundedDecimal(multiplyNumericValue(value.min, factor)),
|
|
528
|
+
max: toRoundedDecimal(multiplyNumericValue(value.max, factor))
|
|
519
529
|
};
|
|
520
530
|
}
|
|
521
531
|
var convertQuantityValue = (value, def, targetDef) => {
|
|
@@ -523,6 +533,32 @@ var convertQuantityValue = (value, def, targetDef) => {
|
|
|
523
533
|
const factor = def.toBase / targetDef.toBase;
|
|
524
534
|
return multiplyQuantityValue(value, factor);
|
|
525
535
|
};
|
|
536
|
+
function getDefaultQuantityValue() {
|
|
537
|
+
return { type: "fixed", value: { type: "decimal", value: 0 } };
|
|
538
|
+
}
|
|
539
|
+
function addQuantityValues(v1, v2) {
|
|
540
|
+
if (v1.type === "fixed" && v1.value.type === "text" || v2.type === "fixed" && v2.value.type === "text") {
|
|
541
|
+
throw new CannotAddTextValueError();
|
|
542
|
+
}
|
|
543
|
+
if (v1.type === "fixed" && v2.type === "fixed") {
|
|
544
|
+
const res = addNumericValues(
|
|
545
|
+
v1.value,
|
|
546
|
+
v2.value
|
|
547
|
+
);
|
|
548
|
+
return { type: "fixed", value: res };
|
|
549
|
+
}
|
|
550
|
+
const r1 = v1.type === "range" ? v1 : { type: "range", min: v1.value, max: v1.value };
|
|
551
|
+
const r2 = v2.type === "range" ? v2 : { type: "range", min: v2.value, max: v2.value };
|
|
552
|
+
const newMin = addNumericValues(
|
|
553
|
+
r1.min,
|
|
554
|
+
r2.min
|
|
555
|
+
);
|
|
556
|
+
const newMax = addNumericValues(
|
|
557
|
+
r1.max,
|
|
558
|
+
r2.max
|
|
559
|
+
);
|
|
560
|
+
return { type: "range", min: newMin, max: newMax };
|
|
561
|
+
}
|
|
526
562
|
function addQuantities(q1, q2) {
|
|
527
563
|
const v1 = q1.value;
|
|
528
564
|
const v2 = q2.value;
|
|
@@ -531,33 +567,14 @@ function addQuantities(q1, q2) {
|
|
|
531
567
|
}
|
|
532
568
|
const unit1Def = normalizeUnit(q1.unit);
|
|
533
569
|
const unit2Def = normalizeUnit(q2.unit);
|
|
534
|
-
const addQuantityValuesAndSetUnit = (val1, val2, unit) => {
|
|
535
|
-
|
|
536
|
-
const res = addNumericValues(
|
|
537
|
-
val1.value,
|
|
538
|
-
val2.value
|
|
539
|
-
);
|
|
540
|
-
return { value: { type: "fixed", value: res }, unit };
|
|
541
|
-
}
|
|
542
|
-
const r1 = val1.type === "range" ? val1 : { type: "range", min: val1.value, max: val1.value };
|
|
543
|
-
const r2 = val2.type === "range" ? val2 : { type: "range", min: val2.value, max: val2.value };
|
|
544
|
-
const newMin = addNumericValues(
|
|
545
|
-
r1.min,
|
|
546
|
-
r2.min
|
|
547
|
-
);
|
|
548
|
-
const newMax = addNumericValues(
|
|
549
|
-
r1.max,
|
|
550
|
-
r2.max
|
|
551
|
-
);
|
|
552
|
-
return { value: { type: "range", min: newMin, max: newMax }, unit };
|
|
553
|
-
};
|
|
554
|
-
if (q1.unit === "" && unit2Def) {
|
|
570
|
+
const addQuantityValuesAndSetUnit = (val1, val2, unit) => ({ value: addQuantityValues(val1, val2), unit });
|
|
571
|
+
if ((q1.unit === "" || q1.unit === void 0) && q2.unit !== void 0) {
|
|
555
572
|
return addQuantityValuesAndSetUnit(v1, v2, q2.unit);
|
|
556
573
|
}
|
|
557
|
-
if (q2.unit === "" &&
|
|
574
|
+
if ((q2.unit === "" || q2.unit === void 0) && q1.unit !== void 0) {
|
|
558
575
|
return addQuantityValuesAndSetUnit(v1, v2, q1.unit);
|
|
559
576
|
}
|
|
560
|
-
if (q1.unit.toLowerCase() === q2.unit.toLowerCase()) {
|
|
577
|
+
if (!q1.unit && !q2.unit || q1.unit && q2.unit && q1.unit.toLowerCase() === q2.unit.toLowerCase()) {
|
|
561
578
|
return addQuantityValuesAndSetUnit(v1, v2, q1.unit);
|
|
562
579
|
}
|
|
563
580
|
if (unit1Def && unit2Def) {
|
|
@@ -587,14 +604,18 @@ function addQuantities(q1, q2) {
|
|
|
587
604
|
throw new IncompatibleUnitsError(q1.unit, q2.unit);
|
|
588
605
|
}
|
|
589
606
|
|
|
590
|
-
// src/
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
607
|
+
// src/errors.ts
|
|
608
|
+
var ReferencedItemCannotBeRedefinedError = class extends Error {
|
|
609
|
+
constructor(item_type, item_name, new_modifier) {
|
|
610
|
+
super(
|
|
611
|
+
`The referenced ${item_type} "${item_name}" cannot be redefined as ${new_modifier}.
|
|
612
|
+
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}`
|
|
613
|
+
);
|
|
614
|
+
this.name = "ReferencedItemCannotBeRedefinedError";
|
|
595
615
|
}
|
|
596
|
-
|
|
597
|
-
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
// src/parser_helpers.ts
|
|
598
619
|
function flushPendingNote(section, note) {
|
|
599
620
|
if (note.length > 0) {
|
|
600
621
|
section.content.push({ type: "note", note });
|
|
@@ -613,21 +634,28 @@ function flushPendingItems(section, items) {
|
|
|
613
634
|
function findAndUpsertIngredient(ingredients, newIngredient, isReference) {
|
|
614
635
|
const { name, quantity, unit } = newIngredient;
|
|
615
636
|
if (isReference) {
|
|
616
|
-
const
|
|
637
|
+
const indexFind = ingredients.findIndex(
|
|
617
638
|
(i2) => i2.name.toLowerCase() === name.toLowerCase()
|
|
618
639
|
);
|
|
619
|
-
if (
|
|
640
|
+
if (indexFind === -1) {
|
|
620
641
|
throw new Error(
|
|
621
642
|
`Referenced ingredient "${name}" not found. A referenced ingredient must be declared before being referenced with '&'.`
|
|
622
643
|
);
|
|
623
644
|
}
|
|
624
|
-
const existingIngredient = ingredients[
|
|
645
|
+
const existingIngredient = ingredients[indexFind];
|
|
646
|
+
for (const flag of newIngredient.flags) {
|
|
647
|
+
if (!existingIngredient.flags.includes(flag)) {
|
|
648
|
+
throw new ReferencedItemCannotBeRedefinedError(
|
|
649
|
+
"ingredient",
|
|
650
|
+
existingIngredient.name,
|
|
651
|
+
flag
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
let quantityPartIndex = void 0;
|
|
625
656
|
if (quantity !== void 0) {
|
|
626
657
|
const currentQuantity = {
|
|
627
|
-
value: existingIngredient.quantity ??
|
|
628
|
-
type: "fixed",
|
|
629
|
-
value: { type: "decimal", value: 0 }
|
|
630
|
-
},
|
|
658
|
+
value: existingIngredient.quantity ?? getDefaultQuantityValue(),
|
|
631
659
|
unit: existingIngredient.unit ?? ""
|
|
632
660
|
};
|
|
633
661
|
const newQuantity = { value: quantity, unit: unit ?? "" };
|
|
@@ -635,18 +663,35 @@ function findAndUpsertIngredient(ingredients, newIngredient, isReference) {
|
|
|
635
663
|
const total = addQuantities(currentQuantity, newQuantity);
|
|
636
664
|
existingIngredient.quantity = total.value;
|
|
637
665
|
existingIngredient.unit = total.unit || void 0;
|
|
666
|
+
if (existingIngredient.quantityParts) {
|
|
667
|
+
existingIngredient.quantityParts.push(
|
|
668
|
+
...newIngredient.quantityParts
|
|
669
|
+
);
|
|
670
|
+
} else {
|
|
671
|
+
existingIngredient.quantityParts = newIngredient.quantityParts;
|
|
672
|
+
}
|
|
673
|
+
quantityPartIndex = existingIngredient.quantityParts.length - 1;
|
|
638
674
|
} catch (e2) {
|
|
639
675
|
if (e2 instanceof IncompatibleUnitsError || e2 instanceof CannotAddTextValueError) {
|
|
640
|
-
return
|
|
676
|
+
return {
|
|
677
|
+
ingredientIndex: ingredients.push(newIngredient) - 1,
|
|
678
|
+
quantityPartIndex: 0
|
|
679
|
+
};
|
|
641
680
|
}
|
|
642
681
|
}
|
|
643
682
|
}
|
|
644
|
-
return
|
|
683
|
+
return {
|
|
684
|
+
ingredientIndex: indexFind,
|
|
685
|
+
quantityPartIndex
|
|
686
|
+
};
|
|
645
687
|
}
|
|
646
|
-
return
|
|
688
|
+
return {
|
|
689
|
+
ingredientIndex: ingredients.push(newIngredient) - 1,
|
|
690
|
+
quantityPartIndex: 0
|
|
691
|
+
};
|
|
647
692
|
}
|
|
648
693
|
function findAndUpsertCookware(cookware, newCookware, isReference) {
|
|
649
|
-
const { name } = newCookware;
|
|
694
|
+
const { name, quantity } = newCookware;
|
|
650
695
|
if (isReference) {
|
|
651
696
|
const index = cookware.findIndex(
|
|
652
697
|
(i2) => i2.name.toLowerCase() === name.toLowerCase()
|
|
@@ -656,9 +701,55 @@ function findAndUpsertCookware(cookware, newCookware, isReference) {
|
|
|
656
701
|
`Referenced cookware "${name}" not found. A referenced cookware must be declared before being referenced with '&'.`
|
|
657
702
|
);
|
|
658
703
|
}
|
|
659
|
-
|
|
704
|
+
const existingCookware = cookware[index];
|
|
705
|
+
for (const flag of newCookware.flags) {
|
|
706
|
+
if (!existingCookware.flags.includes(flag)) {
|
|
707
|
+
throw new ReferencedItemCannotBeRedefinedError(
|
|
708
|
+
"cookware",
|
|
709
|
+
existingCookware.name,
|
|
710
|
+
flag
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
let quantityPartIndex = void 0;
|
|
715
|
+
if (quantity !== void 0) {
|
|
716
|
+
if (!existingCookware.quantity) {
|
|
717
|
+
existingCookware.quantity = quantity;
|
|
718
|
+
existingCookware.quantityParts = newCookware.quantityParts;
|
|
719
|
+
quantityPartIndex = 0;
|
|
720
|
+
} else {
|
|
721
|
+
try {
|
|
722
|
+
existingCookware.quantity = addQuantityValues(
|
|
723
|
+
existingCookware.quantity,
|
|
724
|
+
quantity
|
|
725
|
+
);
|
|
726
|
+
if (!existingCookware.quantityParts) {
|
|
727
|
+
existingCookware.quantityParts = newCookware.quantityParts;
|
|
728
|
+
quantityPartIndex = 0;
|
|
729
|
+
} else {
|
|
730
|
+
quantityPartIndex = existingCookware.quantityParts.push(
|
|
731
|
+
...newCookware.quantityParts
|
|
732
|
+
) - 1;
|
|
733
|
+
}
|
|
734
|
+
} catch (e2) {
|
|
735
|
+
if (e2 instanceof CannotAddTextValueError) {
|
|
736
|
+
return {
|
|
737
|
+
cookwareIndex: cookware.push(newCookware) - 1,
|
|
738
|
+
quantityPartIndex: 0
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
return {
|
|
745
|
+
cookwareIndex: index,
|
|
746
|
+
quantityPartIndex
|
|
747
|
+
};
|
|
660
748
|
}
|
|
661
|
-
return
|
|
749
|
+
return {
|
|
750
|
+
cookwareIndex: cookware.push(newCookware) - 1,
|
|
751
|
+
quantityPartIndex: quantity ? 0 : void 0
|
|
752
|
+
};
|
|
662
753
|
}
|
|
663
754
|
var parseFixedValue = (input_str) => {
|
|
664
755
|
if (!numberLikeRegex.test(input_str)) {
|
|
@@ -690,14 +781,12 @@ function parseSimpleMetaVar(content, varName) {
|
|
|
690
781
|
return varMatch ? varMatch[1]?.trim().replace(/\s*\r?\n\s+/g, " ") : void 0;
|
|
691
782
|
}
|
|
692
783
|
function parseScalingMetaVar(content, varName) {
|
|
693
|
-
const varMatch = content.match(
|
|
694
|
-
new RegExp(`^${varName}:[\\t ]*(([^,\\n]*),? ?(?:.*)?)`, "m")
|
|
695
|
-
);
|
|
784
|
+
const varMatch = content.match(scalingMetaValueRegex(varName));
|
|
696
785
|
if (!varMatch) return void 0;
|
|
697
786
|
if (isNaN(Number(varMatch[2]?.trim()))) {
|
|
698
787
|
throw new Error("Scaling variables should be numbers");
|
|
699
788
|
}
|
|
700
|
-
return [Number(varMatch[2]?.trim()), varMatch[1]
|
|
789
|
+
return [Number(varMatch[2]?.trim()), varMatch[1].trim()];
|
|
701
790
|
}
|
|
702
791
|
function parseListMetaVar(content, varName) {
|
|
703
792
|
const listMatch = content.match(
|
|
@@ -748,7 +837,7 @@ function extractMetadata(content) {
|
|
|
748
837
|
const stringMetaValue = parseSimpleMetaVar(metadataContent, metaVar);
|
|
749
838
|
if (stringMetaValue) metadata[metaVar] = stringMetaValue;
|
|
750
839
|
}
|
|
751
|
-
for (const metaVar of ["
|
|
840
|
+
for (const metaVar of ["serves", "yield", "servings"]) {
|
|
752
841
|
const scalingMetaValue = parseScalingMetaVar(metadataContent, metaVar);
|
|
753
842
|
if (scalingMetaValue && scalingMetaValue[1]) {
|
|
754
843
|
metadata[metaVar] = scalingMetaValue[1];
|
|
@@ -770,32 +859,31 @@ var Recipe = class _Recipe {
|
|
|
770
859
|
*/
|
|
771
860
|
constructor(content) {
|
|
772
861
|
/**
|
|
773
|
-
* The recipe
|
|
774
|
-
* @see {@link Metadata}
|
|
862
|
+
* The parsed recipe metadata.
|
|
775
863
|
*/
|
|
776
864
|
__publicField(this, "metadata", {});
|
|
777
865
|
/**
|
|
778
|
-
* The recipe
|
|
779
|
-
* @see {@link Ingredient}
|
|
866
|
+
* The parsed recipe ingredients.
|
|
780
867
|
*/
|
|
781
868
|
__publicField(this, "ingredients", []);
|
|
782
869
|
/**
|
|
783
|
-
* The recipe
|
|
784
|
-
* @see {@link Section}
|
|
870
|
+
* The parsed recipe sections.
|
|
785
871
|
*/
|
|
786
872
|
__publicField(this, "sections", []);
|
|
787
873
|
/**
|
|
788
|
-
* The recipe
|
|
789
|
-
* @see {@link Cookware}
|
|
874
|
+
* The parsed recipe cookware.
|
|
790
875
|
*/
|
|
791
876
|
__publicField(this, "cookware", []);
|
|
792
877
|
/**
|
|
793
|
-
* The recipe
|
|
794
|
-
* @see {@link Timer}
|
|
878
|
+
* The parsed recipe timers.
|
|
795
879
|
*/
|
|
796
880
|
__publicField(this, "timers", []);
|
|
797
881
|
/**
|
|
798
|
-
* The recipe
|
|
882
|
+
* The parsed recipe servings. Used for scaling. Parsed from one of
|
|
883
|
+
* {@link Metadata.servings}, {@link Metadata.yield} or {@link Metadata.serves}
|
|
884
|
+
* metadata fields.
|
|
885
|
+
*
|
|
886
|
+
* @see {@link Recipe.scaleBy | scaleBy()} and {@link Recipe.scaleTo | scaleTo()} methods
|
|
799
887
|
*/
|
|
800
888
|
__publicField(this, "servings");
|
|
801
889
|
if (content) {
|
|
@@ -865,15 +953,28 @@ var Recipe = class _Recipe {
|
|
|
865
953
|
}
|
|
866
954
|
const groups = match.groups;
|
|
867
955
|
if (groups.mIngredientName || groups.sIngredientName) {
|
|
868
|
-
|
|
956
|
+
let name = groups.mIngredientName || groups.sIngredientName;
|
|
957
|
+
const scalableQuantity = (groups.mIngredientQuantityModifier || groups.sIngredientQuantityModifier) !== "=";
|
|
869
958
|
const quantityRaw = groups.mIngredientQuantity || groups.sIngredientQuantity;
|
|
870
|
-
const
|
|
959
|
+
const unit = groups.mIngredientUnit || groups.sIngredientUnit;
|
|
871
960
|
const preparation = groups.mIngredientPreparation || groups.sIngredientPreparation;
|
|
872
|
-
const
|
|
873
|
-
const
|
|
874
|
-
const
|
|
875
|
-
|
|
876
|
-
|
|
961
|
+
const modifiers = groups.mIngredientModifiers || groups.sIngredientModifiers;
|
|
962
|
+
const reference = modifiers !== void 0 && modifiers.includes("&");
|
|
963
|
+
const flags = [];
|
|
964
|
+
if (modifiers !== void 0 && modifiers.includes("?")) {
|
|
965
|
+
flags.push("optional");
|
|
966
|
+
}
|
|
967
|
+
if (modifiers !== void 0 && modifiers.includes("-")) {
|
|
968
|
+
flags.push("hidden");
|
|
969
|
+
}
|
|
970
|
+
if (modifiers !== void 0 && modifiers.includes("@") || groups.mIngredientRecipeAnchor || groups.sIngredientRecipeAnchor) {
|
|
971
|
+
flags.push("recipe");
|
|
972
|
+
}
|
|
973
|
+
let extras = void 0;
|
|
974
|
+
if (flags.includes("recipe")) {
|
|
975
|
+
extras = { path: `${name}.cook` };
|
|
976
|
+
name = name.substring(name.lastIndexOf("/") + 1);
|
|
977
|
+
}
|
|
877
978
|
const quantity = quantityRaw ? parseQuantityInput(quantityRaw) : void 0;
|
|
878
979
|
const aliasMatch = name.match(ingredientAliasRegex);
|
|
879
980
|
let listName, displayName;
|
|
@@ -884,50 +985,70 @@ var Recipe = class _Recipe {
|
|
|
884
985
|
listName = name;
|
|
885
986
|
displayName = name;
|
|
886
987
|
}
|
|
887
|
-
const
|
|
988
|
+
const newIngredient = {
|
|
989
|
+
name: listName,
|
|
990
|
+
quantity,
|
|
991
|
+
quantityParts: quantity ? [
|
|
992
|
+
{
|
|
993
|
+
value: quantity,
|
|
994
|
+
unit,
|
|
995
|
+
scalable: scalableQuantity
|
|
996
|
+
}
|
|
997
|
+
] : void 0,
|
|
998
|
+
unit,
|
|
999
|
+
preparation,
|
|
1000
|
+
flags
|
|
1001
|
+
};
|
|
1002
|
+
if (extras) {
|
|
1003
|
+
newIngredient.extras = extras;
|
|
1004
|
+
}
|
|
1005
|
+
const idxsInList = findAndUpsertIngredient(
|
|
888
1006
|
this.ingredients,
|
|
889
|
-
|
|
890
|
-
name: listName,
|
|
891
|
-
quantity,
|
|
892
|
-
unit: units2,
|
|
893
|
-
optional,
|
|
894
|
-
hidden,
|
|
895
|
-
preparation,
|
|
896
|
-
isRecipe
|
|
897
|
-
},
|
|
1007
|
+
newIngredient,
|
|
898
1008
|
reference
|
|
899
1009
|
);
|
|
900
1010
|
const newItem = {
|
|
901
1011
|
type: "ingredient",
|
|
902
|
-
|
|
903
|
-
itemQuantity: quantity,
|
|
904
|
-
itemUnit: units2,
|
|
1012
|
+
index: idxsInList.ingredientIndex,
|
|
905
1013
|
displayName
|
|
906
1014
|
};
|
|
1015
|
+
if (idxsInList.quantityPartIndex !== void 0) {
|
|
1016
|
+
newItem.quantityPartIndex = idxsInList.quantityPartIndex;
|
|
1017
|
+
}
|
|
907
1018
|
items.push(newItem);
|
|
908
1019
|
} else if (groups.mCookwareName || groups.sCookwareName) {
|
|
909
1020
|
const name = groups.mCookwareName || groups.sCookwareName;
|
|
910
|
-
const
|
|
1021
|
+
const modifiers = groups.mCookwareModifiers || groups.sCookwareModifiers;
|
|
911
1022
|
const quantityRaw = groups.mCookwareQuantity || groups.sCookwareQuantity;
|
|
912
|
-
const
|
|
913
|
-
const
|
|
914
|
-
|
|
1023
|
+
const reference = modifiers !== void 0 && modifiers.includes("&");
|
|
1024
|
+
const flags = [];
|
|
1025
|
+
if (modifiers !== void 0 && modifiers.includes("?")) {
|
|
1026
|
+
flags.push("optional");
|
|
1027
|
+
}
|
|
1028
|
+
if (modifiers !== void 0 && modifiers.includes("-")) {
|
|
1029
|
+
flags.push("hidden");
|
|
1030
|
+
}
|
|
915
1031
|
const quantity = quantityRaw ? parseQuantityInput(quantityRaw) : void 0;
|
|
916
|
-
const
|
|
1032
|
+
const idxsInList = findAndUpsertCookware(
|
|
917
1033
|
this.cookware,
|
|
918
|
-
{
|
|
1034
|
+
{
|
|
1035
|
+
name,
|
|
1036
|
+
quantity,
|
|
1037
|
+
quantityParts: quantity ? [quantity] : void 0,
|
|
1038
|
+
flags
|
|
1039
|
+
},
|
|
919
1040
|
reference
|
|
920
1041
|
);
|
|
921
1042
|
items.push({
|
|
922
1043
|
type: "cookware",
|
|
923
|
-
|
|
924
|
-
|
|
1044
|
+
index: idxsInList.cookwareIndex,
|
|
1045
|
+
quantityPartIndex: idxsInList.quantityPartIndex
|
|
925
1046
|
});
|
|
926
|
-
} else
|
|
1047
|
+
} else {
|
|
927
1048
|
const durationStr = groups.timerQuantity.trim();
|
|
928
|
-
const unit = (groups.
|
|
1049
|
+
const unit = (groups.timerUnit || "").trim();
|
|
929
1050
|
if (!unit) {
|
|
930
|
-
throw new Error("Timer missing
|
|
1051
|
+
throw new Error("Timer missing unit");
|
|
931
1052
|
}
|
|
932
1053
|
const name = groups.timerName || void 0;
|
|
933
1054
|
const duration = parseQuantityInput(durationStr);
|
|
@@ -936,12 +1057,7 @@ var Recipe = class _Recipe {
|
|
|
936
1057
|
duration,
|
|
937
1058
|
unit
|
|
938
1059
|
};
|
|
939
|
-
|
|
940
|
-
this.timers,
|
|
941
|
-
(t2) => t2.name === timerObj.name && t2.duration === timerObj.duration && t2.unit === timerObj.unit,
|
|
942
|
-
() => timerObj
|
|
943
|
-
);
|
|
944
|
-
items.push({ type: "timer", value: idxInList });
|
|
1060
|
+
items.push({ type: "timer", index: this.timers.push(timerObj) - 1 });
|
|
945
1061
|
}
|
|
946
1062
|
cursor = idx + match[0].length;
|
|
947
1063
|
}
|
|
@@ -957,9 +1073,12 @@ var Recipe = class _Recipe {
|
|
|
957
1073
|
}
|
|
958
1074
|
}
|
|
959
1075
|
/**
|
|
960
|
-
* Scales the recipe to a new number of servings.
|
|
1076
|
+
* Scales the recipe to a new number of servings. In practice, it calls
|
|
1077
|
+
* {@link Recipe.scaleBy | scaleBy} with a factor corresponding to the ratio between `newServings`
|
|
1078
|
+
* and the recipe's {@link Recipe.servings | servings} value.
|
|
961
1079
|
* @param newServings - The new number of servings.
|
|
962
1080
|
* @returns A new Recipe instance with the scaled ingredients.
|
|
1081
|
+
* @throws `Error` if the recipe does not contains an initial {@link Recipe.servings | servings} value
|
|
963
1082
|
*/
|
|
964
1083
|
scaleTo(newServings) {
|
|
965
1084
|
const originalServings = this.getServings();
|
|
@@ -981,30 +1100,57 @@ var Recipe = class _Recipe {
|
|
|
981
1100
|
throw new Error("Error scaling recipe: no initial servings value set");
|
|
982
1101
|
}
|
|
983
1102
|
newRecipe.ingredients = newRecipe.ingredients.map((ingredient) => {
|
|
984
|
-
if (ingredient.
|
|
985
|
-
ingredient.
|
|
986
|
-
|
|
987
|
-
|
|
1103
|
+
if (ingredient.quantityParts) {
|
|
1104
|
+
ingredient.quantityParts = ingredient.quantityParts.map(
|
|
1105
|
+
(quantityPart) => {
|
|
1106
|
+
if (quantityPart.value.type === "fixed" && quantityPart.value.value.type === "text") {
|
|
1107
|
+
return quantityPart;
|
|
1108
|
+
}
|
|
1109
|
+
return {
|
|
1110
|
+
...quantityPart,
|
|
1111
|
+
value: multiplyQuantityValue(
|
|
1112
|
+
quantityPart.value,
|
|
1113
|
+
quantityPart.scalable ? factor : 1
|
|
1114
|
+
)
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
988
1117
|
);
|
|
1118
|
+
if (ingredient.quantityParts.length === 1) {
|
|
1119
|
+
ingredient.quantity = ingredient.quantityParts[0].value;
|
|
1120
|
+
ingredient.unit = ingredient.quantityParts[0].unit;
|
|
1121
|
+
} else {
|
|
1122
|
+
const totalQuantity = ingredient.quantityParts.reduce(
|
|
1123
|
+
(acc, val) => addQuantities(acc, { value: val.value, unit: val.unit }),
|
|
1124
|
+
{ value: getDefaultQuantityValue() }
|
|
1125
|
+
);
|
|
1126
|
+
ingredient.quantity = totalQuantity.value;
|
|
1127
|
+
ingredient.unit = totalQuantity.unit;
|
|
1128
|
+
}
|
|
989
1129
|
}
|
|
990
1130
|
return ingredient;
|
|
991
1131
|
}).filter((ingredient) => ingredient.quantity !== null);
|
|
992
1132
|
newRecipe.servings = originalServings * factor;
|
|
993
1133
|
if (newRecipe.metadata.servings && this.metadata.servings) {
|
|
994
|
-
|
|
995
|
-
|
|
1134
|
+
if (floatRegex.test(String(this.metadata.servings).replace(",", ".").trim())) {
|
|
1135
|
+
const servingsValue = parseFloat(
|
|
1136
|
+
String(this.metadata.servings).replace(",", ".")
|
|
1137
|
+
);
|
|
996
1138
|
newRecipe.metadata.servings = String(servingsValue * factor);
|
|
997
1139
|
}
|
|
998
1140
|
}
|
|
999
1141
|
if (newRecipe.metadata.yield && this.metadata.yield) {
|
|
1000
|
-
|
|
1001
|
-
|
|
1142
|
+
if (floatRegex.test(String(this.metadata.yield).replace(",", ".").trim())) {
|
|
1143
|
+
const yieldValue = parseFloat(
|
|
1144
|
+
String(this.metadata.yield).replace(",", ".")
|
|
1145
|
+
);
|
|
1002
1146
|
newRecipe.metadata.yield = String(yieldValue * factor);
|
|
1003
1147
|
}
|
|
1004
1148
|
}
|
|
1005
1149
|
if (newRecipe.metadata.serves && this.metadata.serves) {
|
|
1006
|
-
|
|
1007
|
-
|
|
1150
|
+
if (floatRegex.test(String(this.metadata.serves).replace(",", ".").trim())) {
|
|
1151
|
+
const servesValue = parseFloat(
|
|
1152
|
+
String(this.metadata.serves).replace(",", ".")
|
|
1153
|
+
);
|
|
1008
1154
|
newRecipe.metadata.serves = String(servesValue * factor);
|
|
1009
1155
|
}
|
|
1010
1156
|
}
|
|
@@ -1028,9 +1174,13 @@ var Recipe = class _Recipe {
|
|
|
1028
1174
|
clone() {
|
|
1029
1175
|
const newRecipe = new _Recipe();
|
|
1030
1176
|
newRecipe.metadata = JSON.parse(JSON.stringify(this.metadata));
|
|
1031
|
-
newRecipe.ingredients = JSON.parse(
|
|
1177
|
+
newRecipe.ingredients = JSON.parse(
|
|
1178
|
+
JSON.stringify(this.ingredients)
|
|
1179
|
+
);
|
|
1032
1180
|
newRecipe.sections = JSON.parse(JSON.stringify(this.sections));
|
|
1033
|
-
newRecipe.cookware = JSON.parse(
|
|
1181
|
+
newRecipe.cookware = JSON.parse(
|
|
1182
|
+
JSON.stringify(this.cookware)
|
|
1183
|
+
);
|
|
1034
1184
|
newRecipe.timers = JSON.parse(JSON.stringify(this.timers));
|
|
1035
1185
|
newRecipe.servings = this.servings;
|
|
1036
1186
|
return newRecipe;
|
|
@@ -1040,32 +1190,28 @@ var Recipe = class _Recipe {
|
|
|
1040
1190
|
// src/classes/shopping_list.ts
|
|
1041
1191
|
var ShoppingList = class {
|
|
1042
1192
|
/**
|
|
1043
|
-
* Creates a new ShoppingList instance
|
|
1044
|
-
* @param
|
|
1193
|
+
* Creates a new ShoppingList instance
|
|
1194
|
+
* @param category_config_str - The category configuration to parse.
|
|
1045
1195
|
*/
|
|
1046
|
-
constructor(
|
|
1196
|
+
constructor(category_config_str) {
|
|
1047
1197
|
/**
|
|
1048
1198
|
* The ingredients in the shopping list.
|
|
1049
|
-
* @see {@link Ingredient}
|
|
1050
1199
|
*/
|
|
1051
1200
|
__publicField(this, "ingredients", []);
|
|
1052
1201
|
/**
|
|
1053
1202
|
* The recipes in the shopping list.
|
|
1054
|
-
* @see {@link AddedRecipe}
|
|
1055
1203
|
*/
|
|
1056
1204
|
__publicField(this, "recipes", []);
|
|
1057
1205
|
/**
|
|
1058
|
-
* The
|
|
1059
|
-
* @see {@link AisleConfig}
|
|
1206
|
+
* The category configuration for the shopping list.
|
|
1060
1207
|
*/
|
|
1061
|
-
__publicField(this, "
|
|
1208
|
+
__publicField(this, "category_config");
|
|
1062
1209
|
/**
|
|
1063
1210
|
* The categorized ingredients in the shopping list.
|
|
1064
|
-
* @see {@link CategorizedIngredients}
|
|
1065
1211
|
*/
|
|
1066
1212
|
__publicField(this, "categories");
|
|
1067
|
-
if (
|
|
1068
|
-
this.
|
|
1213
|
+
if (category_config_str) {
|
|
1214
|
+
this.set_category_config(category_config_str);
|
|
1069
1215
|
}
|
|
1070
1216
|
}
|
|
1071
1217
|
calculate_ingredients() {
|
|
@@ -1073,7 +1219,7 @@ var ShoppingList = class {
|
|
|
1073
1219
|
for (const { recipe, factor } of this.recipes) {
|
|
1074
1220
|
const scaledRecipe = factor === 1 ? recipe : recipe.scaleBy(factor);
|
|
1075
1221
|
for (const ingredient of scaledRecipe.ingredients) {
|
|
1076
|
-
if (ingredient.hidden) {
|
|
1222
|
+
if (ingredient.flags && ingredient.flags.includes("hidden")) {
|
|
1077
1223
|
continue;
|
|
1078
1224
|
}
|
|
1079
1225
|
const existingIngredient = this.ingredients.find(
|
|
@@ -1081,8 +1227,8 @@ var ShoppingList = class {
|
|
|
1081
1227
|
);
|
|
1082
1228
|
let addSeparate = false;
|
|
1083
1229
|
try {
|
|
1084
|
-
if (existingIngredient) {
|
|
1085
|
-
if (existingIngredient.quantity
|
|
1230
|
+
if (existingIngredient && ingredient.quantity) {
|
|
1231
|
+
if (existingIngredient.quantity) {
|
|
1086
1232
|
const newQuantity = addQuantities(
|
|
1087
1233
|
{
|
|
1088
1234
|
value: existingIngredient.quantity,
|
|
@@ -1097,7 +1243,7 @@ var ShoppingList = class {
|
|
|
1097
1243
|
if (newQuantity.unit) {
|
|
1098
1244
|
existingIngredient.unit = newQuantity.unit;
|
|
1099
1245
|
}
|
|
1100
|
-
} else
|
|
1246
|
+
} else {
|
|
1101
1247
|
existingIngredient.quantity = ingredient.quantity;
|
|
1102
1248
|
if (ingredient.unit) {
|
|
1103
1249
|
existingIngredient.unit = ingredient.unit;
|
|
@@ -1121,7 +1267,8 @@ var ShoppingList = class {
|
|
|
1121
1267
|
}
|
|
1122
1268
|
}
|
|
1123
1269
|
/**
|
|
1124
|
-
* Adds a recipe to the shopping list
|
|
1270
|
+
* Adds a recipe to the shopping list, then automatically
|
|
1271
|
+
* recalculates the quantities and recategorize the ingredients.
|
|
1125
1272
|
* @param recipe - The recipe to add.
|
|
1126
1273
|
* @param factor - The factor to scale the recipe by.
|
|
1127
1274
|
*/
|
|
@@ -1131,7 +1278,8 @@ var ShoppingList = class {
|
|
|
1131
1278
|
this.categorize();
|
|
1132
1279
|
}
|
|
1133
1280
|
/**
|
|
1134
|
-
* Removes a recipe from the shopping list
|
|
1281
|
+
* Removes a recipe from the shopping list, then automatically
|
|
1282
|
+
* recalculates the quantities and recategorize the ingredients.s
|
|
1135
1283
|
* @param index - The index of the recipe to remove.
|
|
1136
1284
|
*/
|
|
1137
1285
|
remove_recipe(index) {
|
|
@@ -1143,31 +1291,35 @@ var ShoppingList = class {
|
|
|
1143
1291
|
this.categorize();
|
|
1144
1292
|
}
|
|
1145
1293
|
/**
|
|
1146
|
-
* Sets the
|
|
1147
|
-
*
|
|
1294
|
+
* Sets the category configuration for the shopping list
|
|
1295
|
+
* and automatically categorize current ingredients from the list.
|
|
1296
|
+
* @param config - The category configuration to parse.
|
|
1148
1297
|
*/
|
|
1149
|
-
|
|
1150
|
-
|
|
1298
|
+
set_category_config(config) {
|
|
1299
|
+
if (typeof config === "string")
|
|
1300
|
+
this.category_config = new CategoryConfig(config);
|
|
1301
|
+
else if (config instanceof CategoryConfig) this.category_config = config;
|
|
1302
|
+
else throw new Error("Invalid category configuration");
|
|
1151
1303
|
this.categorize();
|
|
1152
1304
|
}
|
|
1153
1305
|
/**
|
|
1154
1306
|
* Categorizes the ingredients in the shopping list
|
|
1155
|
-
* Will use the
|
|
1307
|
+
* Will use the category config if any, otherwise all ingredients will be placed in the "other" category
|
|
1156
1308
|
*/
|
|
1157
1309
|
categorize() {
|
|
1158
|
-
if (!this.
|
|
1310
|
+
if (!this.category_config) {
|
|
1159
1311
|
this.categories = { other: this.ingredients };
|
|
1160
1312
|
return;
|
|
1161
1313
|
}
|
|
1162
1314
|
const categories = { other: [] };
|
|
1163
|
-
for (const category of this.
|
|
1315
|
+
for (const category of this.category_config.categories) {
|
|
1164
1316
|
categories[category.name] = [];
|
|
1165
1317
|
}
|
|
1166
1318
|
for (const ingredient of this.ingredients) {
|
|
1167
1319
|
let found = false;
|
|
1168
|
-
for (const category of this.
|
|
1169
|
-
for (const
|
|
1170
|
-
if (
|
|
1320
|
+
for (const category of this.category_config.categories) {
|
|
1321
|
+
for (const categoryIngredient of category.ingredients) {
|
|
1322
|
+
if (categoryIngredient.aliases.includes(ingredient.name)) {
|
|
1171
1323
|
categories[category.name].push(ingredient);
|
|
1172
1324
|
found = true;
|
|
1173
1325
|
break;
|
|
@@ -1185,9 +1337,13 @@ var ShoppingList = class {
|
|
|
1185
1337
|
}
|
|
1186
1338
|
};
|
|
1187
1339
|
export {
|
|
1188
|
-
|
|
1340
|
+
CategoryConfig,
|
|
1189
1341
|
Recipe,
|
|
1190
1342
|
Section,
|
|
1191
1343
|
ShoppingList
|
|
1192
1344
|
};
|
|
1345
|
+
/* v8 ignore else -- @preserve */
|
|
1346
|
+
/* v8 ignore else -- expliciting error types -- @preserve */
|
|
1347
|
+
/* v8 ignore else -- expliciting error type -- @preserve */
|
|
1348
|
+
/* v8 ignore else -- only set unit if it is given -- @preserve */
|
|
1193
1349
|
//# sourceMappingURL=index.js.map
|