@tmlmt/cooklang-parser 1.0.8 → 1.2.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 +19 -8
- package/dist/index.cjs +436 -73
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +82 -9
- package/dist/index.d.ts +82 -9
- package/dist/index.js +435 -73
- package/dist/index.js.map +1 -1
- package/package.json +28 -9
package/dist/index.js
CHANGED
|
@@ -65,23 +65,234 @@ var AisleConfig = class {
|
|
|
65
65
|
|
|
66
66
|
// src/classes/section.ts
|
|
67
67
|
var Section = class {
|
|
68
|
+
/**
|
|
69
|
+
* Creates an instance of Section.
|
|
70
|
+
* @param name - The name of the section. Defaults to an empty string.
|
|
71
|
+
*/
|
|
68
72
|
constructor(name = "") {
|
|
73
|
+
/** The name of the section. Can be an empty string for the default (first) section. */
|
|
69
74
|
__publicField(this, "name");
|
|
75
|
+
/** An array of steps and notes that make up the content of the section. */
|
|
70
76
|
__publicField(this, "content", []);
|
|
71
77
|
this.name = name;
|
|
72
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Checks if the section is blank (has no name and no content).
|
|
81
|
+
* Used during recipe parsing
|
|
82
|
+
* @returns `true` if the section is blank, otherwise `false`.
|
|
83
|
+
*/
|
|
73
84
|
isBlank() {
|
|
74
85
|
return this.name === "" && this.content.length === 0;
|
|
75
86
|
}
|
|
76
87
|
};
|
|
77
88
|
|
|
89
|
+
// node_modules/.pnpm/human-regex@2.1.5_patch_hash=6d6bd9e233f99785a7c2187fd464edc114b76d47001dbb4eb6b5d72168de7460/node_modules/human-regex/dist/human-regex.esm.js
|
|
90
|
+
var t = /* @__PURE__ */ new Map();
|
|
91
|
+
var r = { GLOBAL: "g", NON_SENSITIVE: "i", MULTILINE: "m", DOT_ALL: "s", UNICODE: "u", STICKY: "y" };
|
|
92
|
+
var e = Object.freeze({ digit: "0-9", lowercaseLetter: "a-z", uppercaseLetter: "A-Z", letter: "a-zA-Z", alphanumeric: "a-zA-Z0-9", anyCharacter: "." });
|
|
93
|
+
var n = Object.freeze({ zeroOrMore: "*", oneOrMore: "+", optional: "?" });
|
|
94
|
+
var a = class {
|
|
95
|
+
constructor() {
|
|
96
|
+
this.parts = [], this.flags = /* @__PURE__ */ new Set();
|
|
97
|
+
}
|
|
98
|
+
digit() {
|
|
99
|
+
return this.add("\\d");
|
|
100
|
+
}
|
|
101
|
+
special() {
|
|
102
|
+
return this.add("(?=.*[!@#$%^&*])");
|
|
103
|
+
}
|
|
104
|
+
word() {
|
|
105
|
+
return this.add("\\w");
|
|
106
|
+
}
|
|
107
|
+
whitespace() {
|
|
108
|
+
return this.add("\\s");
|
|
109
|
+
}
|
|
110
|
+
nonWhitespace() {
|
|
111
|
+
return this.add("\\S");
|
|
112
|
+
}
|
|
113
|
+
literal(r2) {
|
|
114
|
+
return this.add((function(r3) {
|
|
115
|
+
t.has(r3) || t.set(r3, r3.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
116
|
+
return t.get(r3);
|
|
117
|
+
})(r2));
|
|
118
|
+
}
|
|
119
|
+
or() {
|
|
120
|
+
return this.add("|");
|
|
121
|
+
}
|
|
122
|
+
range(t2) {
|
|
123
|
+
const r2 = e[t2];
|
|
124
|
+
if (!r2) throw new Error(`Unknown range: ${t2}`);
|
|
125
|
+
return this.add(`[${r2}]`);
|
|
126
|
+
}
|
|
127
|
+
notRange(t2) {
|
|
128
|
+
const r2 = e[t2];
|
|
129
|
+
if (!r2) throw new Error(`Unknown range: ${t2}`);
|
|
130
|
+
return this.add(`[^${r2}]`);
|
|
131
|
+
}
|
|
132
|
+
anyOf(t2) {
|
|
133
|
+
return this.add(`[${t2}]`);
|
|
134
|
+
}
|
|
135
|
+
notAnyOf(t2) {
|
|
136
|
+
return this.add(`[^${t2}]`);
|
|
137
|
+
}
|
|
138
|
+
lazy() {
|
|
139
|
+
const t2 = this.parts.pop();
|
|
140
|
+
if (!t2) throw new Error("No quantifier to make lazy");
|
|
141
|
+
return this.add(`${t2}?`);
|
|
142
|
+
}
|
|
143
|
+
letter() {
|
|
144
|
+
return this.add("[a-zA-Z]");
|
|
145
|
+
}
|
|
146
|
+
anyCharacter() {
|
|
147
|
+
return this.add(".");
|
|
148
|
+
}
|
|
149
|
+
newline() {
|
|
150
|
+
return this.add("(?:\\r\\n|\\r|\\n)");
|
|
151
|
+
}
|
|
152
|
+
negativeLookahead(t2) {
|
|
153
|
+
return this.add(`(?!${t2})`);
|
|
154
|
+
}
|
|
155
|
+
positiveLookahead(t2) {
|
|
156
|
+
return this.add(`(?=${t2})`);
|
|
157
|
+
}
|
|
158
|
+
positiveLookbehind(t2) {
|
|
159
|
+
return this.add(`(?<=${t2})`);
|
|
160
|
+
}
|
|
161
|
+
negativeLookbehind(t2) {
|
|
162
|
+
return this.add(`(?<!${t2})`);
|
|
163
|
+
}
|
|
164
|
+
hasSpecialCharacter() {
|
|
165
|
+
return this.add("(?=.*[!@#$%^&*])");
|
|
166
|
+
}
|
|
167
|
+
hasDigit() {
|
|
168
|
+
return this.add("(?=.*\\d)");
|
|
169
|
+
}
|
|
170
|
+
hasLetter() {
|
|
171
|
+
return this.add("(?=.*[a-zA-Z])");
|
|
172
|
+
}
|
|
173
|
+
optional() {
|
|
174
|
+
return this.add(n.optional);
|
|
175
|
+
}
|
|
176
|
+
exactly(t2) {
|
|
177
|
+
return this.add(`{${t2}}`);
|
|
178
|
+
}
|
|
179
|
+
atLeast(t2) {
|
|
180
|
+
return this.add(`{${t2},}`);
|
|
181
|
+
}
|
|
182
|
+
atMost(t2) {
|
|
183
|
+
return this.add(`{0,${t2}}`);
|
|
184
|
+
}
|
|
185
|
+
between(t2, r2) {
|
|
186
|
+
return this.add(`{${t2},${r2}}`);
|
|
187
|
+
}
|
|
188
|
+
oneOrMore() {
|
|
189
|
+
return this.add(n.oneOrMore);
|
|
190
|
+
}
|
|
191
|
+
zeroOrMore() {
|
|
192
|
+
return this.add(n.zeroOrMore);
|
|
193
|
+
}
|
|
194
|
+
startNamedGroup(t2) {
|
|
195
|
+
return this.add(`(?<${t2}>`);
|
|
196
|
+
}
|
|
197
|
+
startGroup() {
|
|
198
|
+
return this.add("(?:");
|
|
199
|
+
}
|
|
200
|
+
startCaptureGroup() {
|
|
201
|
+
return this.add("(");
|
|
202
|
+
}
|
|
203
|
+
wordBoundary() {
|
|
204
|
+
return this.add("\\b");
|
|
205
|
+
}
|
|
206
|
+
nonWordBoundary() {
|
|
207
|
+
return this.add("\\B");
|
|
208
|
+
}
|
|
209
|
+
endGroup() {
|
|
210
|
+
return this.add(")");
|
|
211
|
+
}
|
|
212
|
+
startAnchor() {
|
|
213
|
+
return this.add("^");
|
|
214
|
+
}
|
|
215
|
+
endAnchor() {
|
|
216
|
+
return this.add("$");
|
|
217
|
+
}
|
|
218
|
+
global() {
|
|
219
|
+
return this.flags.add(r.GLOBAL), this;
|
|
220
|
+
}
|
|
221
|
+
nonSensitive() {
|
|
222
|
+
return this.flags.add(r.NON_SENSITIVE), this;
|
|
223
|
+
}
|
|
224
|
+
multiline() {
|
|
225
|
+
return this.flags.add(r.MULTILINE), this;
|
|
226
|
+
}
|
|
227
|
+
dotAll() {
|
|
228
|
+
return this.flags.add(r.DOT_ALL), this;
|
|
229
|
+
}
|
|
230
|
+
sticky() {
|
|
231
|
+
return this.flags.add(r.STICKY), this;
|
|
232
|
+
}
|
|
233
|
+
unicodeChar(t2) {
|
|
234
|
+
this.flags.add(r.UNICODE);
|
|
235
|
+
const e2 = /* @__PURE__ */ new Set(["u", "l", "t", "m", "o"]);
|
|
236
|
+
if (void 0 !== t2 && !e2.has(t2)) throw new Error(`Invalid Unicode letter variant: ${t2}`);
|
|
237
|
+
return this.add(`\\p{L${null != t2 ? t2 : ""}}`);
|
|
238
|
+
}
|
|
239
|
+
unicodeDigit() {
|
|
240
|
+
return this.flags.add(r.UNICODE), this.add("\\p{N}");
|
|
241
|
+
}
|
|
242
|
+
unicodePunctuation() {
|
|
243
|
+
return this.flags.add(r.UNICODE), this.add("\\p{P}");
|
|
244
|
+
}
|
|
245
|
+
unicodeSymbol() {
|
|
246
|
+
return this.flags.add(r.UNICODE), this.add("\\p{S}");
|
|
247
|
+
}
|
|
248
|
+
repeat(t2) {
|
|
249
|
+
if (0 === this.parts.length) throw new Error("No pattern to repeat");
|
|
250
|
+
const r2 = this.parts.pop();
|
|
251
|
+
return this.parts.push(`(${r2}){${t2}}`), this;
|
|
252
|
+
}
|
|
253
|
+
ipv4Octet() {
|
|
254
|
+
return this.add("(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)");
|
|
255
|
+
}
|
|
256
|
+
protocol() {
|
|
257
|
+
return this.add("https?://");
|
|
258
|
+
}
|
|
259
|
+
www() {
|
|
260
|
+
return this.add("(www\\.)?");
|
|
261
|
+
}
|
|
262
|
+
tld() {
|
|
263
|
+
return this.add("(com|org|net)");
|
|
264
|
+
}
|
|
265
|
+
path() {
|
|
266
|
+
return this.add("(/\\w+)*");
|
|
267
|
+
}
|
|
268
|
+
add(t2) {
|
|
269
|
+
return this.parts.push(t2), this;
|
|
270
|
+
}
|
|
271
|
+
toString() {
|
|
272
|
+
return this.parts.join("");
|
|
273
|
+
}
|
|
274
|
+
toRegExp() {
|
|
275
|
+
return new RegExp(this.toString(), [...this.flags].join(""));
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
var d = () => new a();
|
|
279
|
+
var i = (() => {
|
|
280
|
+
const t2 = (t3) => {
|
|
281
|
+
const r2 = t3().toRegExp();
|
|
282
|
+
return () => new RegExp(r2.source, r2.flags);
|
|
283
|
+
};
|
|
284
|
+
return { email: t2((() => d().startAnchor().word().oneOrMore().literal("@").word().oneOrMore().startGroup().literal(".").word().oneOrMore().endGroup().zeroOrMore().literal(".").letter().atLeast(2).endAnchor())), url: t2((() => d().startAnchor().protocol().www().word().oneOrMore().literal(".").tld().path().endAnchor())), phoneInternational: t2((() => d().startAnchor().literal("+").digit().between(1, 3).literal("-").digit().between(3, 14).endAnchor())) };
|
|
285
|
+
})();
|
|
286
|
+
|
|
78
287
|
// src/regex.ts
|
|
79
|
-
var metadataRegex =
|
|
80
|
-
var
|
|
81
|
-
var
|
|
82
|
-
var
|
|
83
|
-
var
|
|
84
|
-
var
|
|
288
|
+
var metadataRegex = d().literal("---").newline().startCaptureGroup().anyCharacter().zeroOrMore().optional().endGroup().newline().literal("---").dotAll().toRegExp();
|
|
289
|
+
var nonWordChar = "\\s@#~\\[\\]{(.,;:!?";
|
|
290
|
+
var multiwordIngredient = d().literal("@").startNamedGroup("mIngredientModifier").anyOf("@\\-&?").endGroup().optional().startNamedGroup("mIngredientName").notAnyOf(nonWordChar).oneOrMore().startGroup().whitespace().oneOrMore().notAnyOf(nonWordChar).oneOrMore().endGroup().oneOrMore().endGroup().positiveLookahead("\\s*(?:\\{[^\\}]*\\}|\\([^)]*\\))").startGroup().literal("{").startNamedGroup("mIngredientQuantity").notAnyOf("}%").oneOrMore().endGroup().optional().startGroup().literal("%").startNamedGroup("mIngredientUnits").notAnyOf("}").oneOrMore().lazy().endGroup().endGroup().optional().literal("}").endGroup().optional().startGroup().literal("(").startNamedGroup("mIngredientPreparation").notAnyOf(")").oneOrMore().lazy().endGroup().literal(")").endGroup().optional().toRegExp();
|
|
291
|
+
var singleWordIngredient = d().literal("@").startNamedGroup("sIngredientModifier").anyOf("@\\-&?").endGroup().optional().startNamedGroup("sIngredientName").notAnyOf(nonWordChar).oneOrMore().endGroup().startGroup().literal("{").startNamedGroup("sIngredientQuantity").notAnyOf("}%").oneOrMore().endGroup().optional().startGroup().literal("%").startNamedGroup("sIngredientUnits").notAnyOf("}").oneOrMore().lazy().endGroup().endGroup().optional().literal("}").endGroup().optional().startGroup().literal("(").startNamedGroup("sIngredientPreparation").notAnyOf(")").oneOrMore().lazy().endGroup().literal(")").endGroup().optional().toRegExp();
|
|
292
|
+
var ingredientAliasRegex = d().startAnchor().startNamedGroup("ingredientListName").notAnyOf("|").oneOrMore().endGroup().literal("|").startNamedGroup("ingredientDisplayName").notAnyOf("|").oneOrMore().endGroup().endAnchor().toRegExp();
|
|
293
|
+
var multiwordCookware = d().literal("#").startNamedGroup("mCookwareModifier").anyOf("\\-&?").endGroup().optional().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();
|
|
294
|
+
var singleWordCookware = d().literal("#").startNamedGroup("sCookwareModifier").anyOf("\\-&?").endGroup().optional().startNamedGroup("sCookwareName").notAnyOf(nonWordChar).oneOrMore().endGroup().startGroup().literal("{").startNamedGroup("sCookwareQuantity").anyCharacter().zeroOrMore().lazy().endGroup().literal("}").endGroup().optional().toRegExp();
|
|
295
|
+
var timer = d().literal("~").startNamedGroup("timerName").anyCharacter().zeroOrMore().lazy().endGroup().literal("{").startNamedGroup("timerQuantity").anyCharacter().oneOrMore().lazy().endGroup().startGroup().literal("%").startNamedGroup("timerUnits").anyCharacter().oneOrMore().lazy().endGroup().endGroup().optional().literal("}").toRegExp();
|
|
85
296
|
var tokensRegex = new RegExp(
|
|
86
297
|
[
|
|
87
298
|
multiwordIngredient,
|
|
@@ -89,11 +300,14 @@ var tokensRegex = new RegExp(
|
|
|
89
300
|
multiwordCookware,
|
|
90
301
|
singleWordCookware,
|
|
91
302
|
timer
|
|
92
|
-
].map((
|
|
303
|
+
].map((r2) => r2.source).join("|"),
|
|
93
304
|
"gu"
|
|
94
305
|
);
|
|
95
|
-
var commentRegex =
|
|
96
|
-
var blockCommentRegex =
|
|
306
|
+
var commentRegex = d().literal("--").anyCharacter().zeroOrMore().global().toRegExp();
|
|
307
|
+
var blockCommentRegex = d().whitespace().zeroOrMore().literal("[-").anyCharacter().zeroOrMore().lazy().literal("-]").whitespace().zeroOrMore().global().toRegExp();
|
|
308
|
+
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();
|
|
310
|
+
var numberLikeRegex = d().startAnchor().digit().oneOrMore().startGroup().anyOf(".,/").exactly(1).digit().oneOrMore().endGroup().optional().endAnchor().toRegExp();
|
|
97
311
|
|
|
98
312
|
// src/units.ts
|
|
99
313
|
var units = [
|
|
@@ -211,46 +425,144 @@ for (const unit of units) {
|
|
|
211
425
|
function normalizeUnit(unit) {
|
|
212
426
|
return unitMap.get(unit.toLowerCase().trim());
|
|
213
427
|
}
|
|
428
|
+
var CannotAddTextValueError = class extends Error {
|
|
429
|
+
constructor() {
|
|
430
|
+
super("Cannot add a quantity with a text value.");
|
|
431
|
+
this.name = "CannotAddTextValueError";
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
var IncompatibleUnitsError = class extends Error {
|
|
435
|
+
constructor(unit1, unit2) {
|
|
436
|
+
super(
|
|
437
|
+
`Cannot add quantities with incompatible or unknown units: ${unit1} and ${unit2}`
|
|
438
|
+
);
|
|
439
|
+
this.name = "IncompatibleUnitsError";
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
function gcd(a2, b) {
|
|
443
|
+
return b === 0 ? a2 : gcd(b, a2 % b);
|
|
444
|
+
}
|
|
445
|
+
function simplifyFraction(num, den) {
|
|
446
|
+
if (den === 0) {
|
|
447
|
+
throw new Error("Denominator cannot be zero.");
|
|
448
|
+
}
|
|
449
|
+
const commonDivisor = gcd(Math.abs(num), Math.abs(den));
|
|
450
|
+
let simplifiedNum = num / commonDivisor;
|
|
451
|
+
let simplifiedDen = den / commonDivisor;
|
|
452
|
+
if (simplifiedDen < 0) {
|
|
453
|
+
simplifiedNum = -simplifiedNum;
|
|
454
|
+
simplifiedDen = -simplifiedDen;
|
|
455
|
+
}
|
|
456
|
+
if (simplifiedDen === 1) {
|
|
457
|
+
return { type: "decimal", value: simplifiedNum };
|
|
458
|
+
} else {
|
|
459
|
+
return { type: "fraction", num: simplifiedNum, den: simplifiedDen };
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
function multiplyNumericValue(v, factor) {
|
|
463
|
+
if (v.type === "decimal") {
|
|
464
|
+
return { type: "decimal", value: v.value * factor };
|
|
465
|
+
}
|
|
466
|
+
return simplifyFraction(v.num * factor, v.den);
|
|
467
|
+
}
|
|
468
|
+
function addNumericValues(val1, val2) {
|
|
469
|
+
let num1;
|
|
470
|
+
let den1;
|
|
471
|
+
let num2;
|
|
472
|
+
let den2;
|
|
473
|
+
if (val1.type === "decimal") {
|
|
474
|
+
num1 = val1.value;
|
|
475
|
+
den1 = 1;
|
|
476
|
+
} else {
|
|
477
|
+
num1 = val1.num;
|
|
478
|
+
den1 = val1.den;
|
|
479
|
+
}
|
|
480
|
+
if (val2.type === "decimal") {
|
|
481
|
+
num2 = val2.value;
|
|
482
|
+
den2 = 1;
|
|
483
|
+
} else {
|
|
484
|
+
num2 = val2.num;
|
|
485
|
+
den2 = val2.den;
|
|
486
|
+
}
|
|
487
|
+
if (val1.type === "fraction" && val2.type === "fraction") {
|
|
488
|
+
const commonDen = den1 * den2;
|
|
489
|
+
const sumNum = num1 * den2 + num2 * den1;
|
|
490
|
+
return simplifyFraction(sumNum, commonDen);
|
|
491
|
+
} else {
|
|
492
|
+
return { type: "decimal", value: num1 / den1 + num2 / den2 };
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
var toRoundedDecimal = (v) => {
|
|
496
|
+
const value = v.type === "decimal" ? v.value : v.num / v.den;
|
|
497
|
+
return { type: "decimal", value: Math.floor(value * 100) / 100 };
|
|
498
|
+
};
|
|
499
|
+
function multiplyQuantityValue(value, factor) {
|
|
500
|
+
if (value.type === "fixed") {
|
|
501
|
+
return {
|
|
502
|
+
type: "fixed",
|
|
503
|
+
value: toRoundedDecimal(
|
|
504
|
+
multiplyNumericValue(
|
|
505
|
+
value.value,
|
|
506
|
+
factor
|
|
507
|
+
)
|
|
508
|
+
)
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
return {
|
|
512
|
+
type: "range",
|
|
513
|
+
min: toRoundedDecimal(multiplyNumericValue(value.min, factor)),
|
|
514
|
+
max: toRoundedDecimal(multiplyNumericValue(value.max, factor))
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
var convertQuantityValue = (value, def, targetDef) => {
|
|
518
|
+
if (def.name === targetDef.name) return value;
|
|
519
|
+
const factor = def.toBase / targetDef.toBase;
|
|
520
|
+
return multiplyQuantityValue(value, factor);
|
|
521
|
+
};
|
|
214
522
|
function addQuantities(q1, q2) {
|
|
523
|
+
const v1 = q1.value;
|
|
524
|
+
const v2 = q2.value;
|
|
525
|
+
if (v1.type === "fixed" && v1.value.type === "text" || v2.type === "fixed" && v2.value.type === "text") {
|
|
526
|
+
throw new CannotAddTextValueError();
|
|
527
|
+
}
|
|
215
528
|
const unit1Def = normalizeUnit(q1.unit);
|
|
216
529
|
const unit2Def = normalizeUnit(q2.unit);
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
530
|
+
const addQuantityValuesAndSetUnit = (val1, val2, unit) => {
|
|
531
|
+
if (val1.type === "fixed" && val2.type === "fixed") {
|
|
532
|
+
const res = addNumericValues(
|
|
533
|
+
val1.value,
|
|
534
|
+
val2.value
|
|
535
|
+
);
|
|
536
|
+
return { value: { type: "fixed", value: res }, unit };
|
|
537
|
+
}
|
|
538
|
+
const r1 = val1.type === "range" ? val1 : { type: "range", min: val1.value, max: val1.value };
|
|
539
|
+
const r2 = val2.type === "range" ? val2 : { type: "range", min: val2.value, max: val2.value };
|
|
540
|
+
const newMin = addNumericValues(
|
|
541
|
+
r1.min,
|
|
542
|
+
r2.min
|
|
220
543
|
);
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
`Cannot add quantity to string-quantified value: ${q2.value}`
|
|
544
|
+
const newMax = addNumericValues(
|
|
545
|
+
r1.max,
|
|
546
|
+
r2.max
|
|
225
547
|
);
|
|
226
|
-
|
|
548
|
+
return { value: { type: "range", min: newMin, max: newMax }, unit };
|
|
549
|
+
};
|
|
227
550
|
if (q1.unit === "" && unit2Def) {
|
|
228
|
-
return
|
|
229
|
-
value: Math.round((q1.value + q2.value) * 100) / 100,
|
|
230
|
-
unit: q2.unit
|
|
231
|
-
};
|
|
551
|
+
return addQuantityValuesAndSetUnit(v1, v2, q2.unit);
|
|
232
552
|
}
|
|
233
553
|
if (q2.unit === "" && unit1Def) {
|
|
234
|
-
return
|
|
235
|
-
value: Math.round((q1.value + q2.value) * 100) / 100,
|
|
236
|
-
unit: q1.unit
|
|
237
|
-
};
|
|
554
|
+
return addQuantityValuesAndSetUnit(v1, v2, q1.unit);
|
|
238
555
|
}
|
|
239
556
|
if (q1.unit.toLowerCase() === q2.unit.toLowerCase()) {
|
|
240
|
-
return
|
|
241
|
-
value: Math.round((q1.value + q2.value) * 100) / 100,
|
|
242
|
-
unit: q1.unit
|
|
243
|
-
};
|
|
557
|
+
return addQuantityValuesAndSetUnit(v1, v2, q1.unit);
|
|
244
558
|
}
|
|
245
559
|
if (unit1Def && unit2Def) {
|
|
246
560
|
if (unit1Def.type !== unit2Def.type) {
|
|
247
|
-
throw new
|
|
248
|
-
|
|
561
|
+
throw new IncompatibleUnitsError(
|
|
562
|
+
`${unit1Def.type} (${q1.unit})`,
|
|
563
|
+
`${unit2Def.type} (${q2.unit})`
|
|
249
564
|
);
|
|
250
565
|
}
|
|
251
|
-
const baseValue1 = q1.value * unit1Def.toBase;
|
|
252
|
-
const baseValue2 = q2.value * unit2Def.toBase;
|
|
253
|
-
const totalBaseValue = baseValue1 + baseValue2;
|
|
254
566
|
let targetUnitDef;
|
|
255
567
|
if (unit1Def.system !== unit2Def.system) {
|
|
256
568
|
const metricUnitDef = unit1Def.system === "metric" ? unit1Def : unit2Def;
|
|
@@ -260,15 +572,15 @@ function addQuantities(q1, q2) {
|
|
|
260
572
|
} else {
|
|
261
573
|
targetUnitDef = unit1Def.toBase >= unit2Def.toBase ? unit1Def : unit2Def;
|
|
262
574
|
}
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
575
|
+
const convertedV1 = convertQuantityValue(v1, unit1Def, targetUnitDef);
|
|
576
|
+
const convertedV2 = convertQuantityValue(v2, unit2Def, targetUnitDef);
|
|
577
|
+
return addQuantityValuesAndSetUnit(
|
|
578
|
+
convertedV1,
|
|
579
|
+
convertedV2,
|
|
580
|
+
targetUnitDef.name
|
|
581
|
+
);
|
|
268
582
|
}
|
|
269
|
-
throw new
|
|
270
|
-
`Cannot add quantities with incompatible or unknown units: ${q1.unit} and ${q2.unit}`
|
|
271
|
-
);
|
|
583
|
+
throw new IncompatibleUnitsError(q1.unit, q2.unit);
|
|
272
584
|
}
|
|
273
585
|
|
|
274
586
|
// src/parser_helpers.ts
|
|
@@ -281,14 +593,14 @@ function findOrPush(list, finder, creator) {
|
|
|
281
593
|
}
|
|
282
594
|
function flushPendingNote(section, note) {
|
|
283
595
|
if (note.length > 0) {
|
|
284
|
-
section.content.push({ note });
|
|
596
|
+
section.content.push({ type: "note", note });
|
|
285
597
|
return "";
|
|
286
598
|
}
|
|
287
599
|
return note;
|
|
288
600
|
}
|
|
289
601
|
function flushPendingItems(section, items) {
|
|
290
602
|
if (items.length > 0) {
|
|
291
|
-
section.content.push({ items: [...items] });
|
|
603
|
+
section.content.push({ type: "step", items: [...items] });
|
|
292
604
|
items.length = 0;
|
|
293
605
|
return true;
|
|
294
606
|
}
|
|
@@ -298,7 +610,7 @@ function findAndUpsertIngredient(ingredients, newIngredient, isReference) {
|
|
|
298
610
|
const { name, quantity, unit } = newIngredient;
|
|
299
611
|
if (isReference) {
|
|
300
612
|
const index = ingredients.findIndex(
|
|
301
|
-
(
|
|
613
|
+
(i2) => i2.name.toLowerCase() === name.toLowerCase()
|
|
302
614
|
);
|
|
303
615
|
if (index === -1) {
|
|
304
616
|
throw new Error(
|
|
@@ -308,13 +620,22 @@ function findAndUpsertIngredient(ingredients, newIngredient, isReference) {
|
|
|
308
620
|
const existingIngredient = ingredients[index];
|
|
309
621
|
if (quantity !== void 0) {
|
|
310
622
|
const currentQuantity = {
|
|
311
|
-
value: existingIngredient.quantity ??
|
|
623
|
+
value: existingIngredient.quantity ?? {
|
|
624
|
+
type: "fixed",
|
|
625
|
+
value: { type: "decimal", value: 0 }
|
|
626
|
+
},
|
|
312
627
|
unit: existingIngredient.unit ?? ""
|
|
313
628
|
};
|
|
314
629
|
const newQuantity = { value: quantity, unit: unit ?? "" };
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
630
|
+
try {
|
|
631
|
+
const total = addQuantities(currentQuantity, newQuantity);
|
|
632
|
+
existingIngredient.quantity = total.value;
|
|
633
|
+
existingIngredient.unit = total.unit || void 0;
|
|
634
|
+
} catch (e2) {
|
|
635
|
+
if (e2 instanceof IncompatibleUnitsError || e2 instanceof CannotAddTextValueError) {
|
|
636
|
+
return ingredients.push(newIngredient) - 1;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
318
639
|
}
|
|
319
640
|
return index;
|
|
320
641
|
}
|
|
@@ -324,7 +645,7 @@ function findAndUpsertCookware(cookware, newCookware, isReference) {
|
|
|
324
645
|
const { name } = newCookware;
|
|
325
646
|
if (isReference) {
|
|
326
647
|
const index = cookware.findIndex(
|
|
327
|
-
(
|
|
648
|
+
(i2) => i2.name.toLowerCase() === name.toLowerCase()
|
|
328
649
|
);
|
|
329
650
|
if (index === -1) {
|
|
330
651
|
throw new Error(
|
|
@@ -335,13 +656,28 @@ function findAndUpsertCookware(cookware, newCookware, isReference) {
|
|
|
335
656
|
}
|
|
336
657
|
return cookware.push(newCookware) - 1;
|
|
337
658
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
659
|
+
var parseFixedValue = (input_str) => {
|
|
660
|
+
if (!numberLikeRegex.test(input_str)) {
|
|
661
|
+
return { type: "text", value: input_str };
|
|
662
|
+
}
|
|
663
|
+
const s = input_str.trim().replace(",", ".");
|
|
664
|
+
if (s.includes("/")) {
|
|
665
|
+
const parts = s.split("/");
|
|
666
|
+
const num = Number(parts[0]);
|
|
667
|
+
const den = Number(parts[1]);
|
|
668
|
+
return { type: "fraction", num, den };
|
|
669
|
+
}
|
|
670
|
+
return { type: "decimal", value: Number(s) };
|
|
671
|
+
};
|
|
672
|
+
function parseQuantityInput(input_str) {
|
|
673
|
+
const clean_str = String(input_str).trim();
|
|
674
|
+
if (rangeRegex.test(clean_str)) {
|
|
675
|
+
const range_parts = clean_str.split("-");
|
|
676
|
+
const min = parseFixedValue(range_parts[0].trim());
|
|
677
|
+
const max = parseFixedValue(range_parts[1].trim());
|
|
678
|
+
return { type: "range", min, max };
|
|
343
679
|
}
|
|
344
|
-
return
|
|
680
|
+
return { type: "fixed", value: parseFixedValue(clean_str) };
|
|
345
681
|
}
|
|
346
682
|
function parseSimpleMetaVar(content, varName) {
|
|
347
683
|
const varMatch = content.match(
|
|
@@ -534,11 +870,20 @@ var Recipe = class _Recipe {
|
|
|
534
870
|
const hidden = modifier === "-";
|
|
535
871
|
const reference = modifier === "&";
|
|
536
872
|
const isRecipe = modifier === "@";
|
|
537
|
-
const quantity = quantityRaw ?
|
|
873
|
+
const quantity = quantityRaw ? parseQuantityInput(quantityRaw) : void 0;
|
|
874
|
+
const aliasMatch = name.match(ingredientAliasRegex);
|
|
875
|
+
let listName, displayName;
|
|
876
|
+
if (aliasMatch && aliasMatch.groups.ingredientListName.trim().length > 0 && aliasMatch.groups.ingredientDisplayName.trim().length > 0) {
|
|
877
|
+
listName = aliasMatch.groups.ingredientListName.trim();
|
|
878
|
+
displayName = aliasMatch.groups.ingredientDisplayName.trim();
|
|
879
|
+
} else {
|
|
880
|
+
listName = name;
|
|
881
|
+
displayName = name;
|
|
882
|
+
}
|
|
538
883
|
const idxInList = findAndUpsertIngredient(
|
|
539
884
|
this.ingredients,
|
|
540
885
|
{
|
|
541
|
-
name,
|
|
886
|
+
name: listName,
|
|
542
887
|
quantity,
|
|
543
888
|
unit: units2,
|
|
544
889
|
optional,
|
|
@@ -550,26 +895,30 @@ var Recipe = class _Recipe {
|
|
|
550
895
|
);
|
|
551
896
|
const newItem = {
|
|
552
897
|
type: "ingredient",
|
|
553
|
-
value: idxInList
|
|
898
|
+
value: idxInList,
|
|
899
|
+
itemQuantity: quantity,
|
|
900
|
+
itemUnit: units2,
|
|
901
|
+
displayName
|
|
554
902
|
};
|
|
555
|
-
if (reference) {
|
|
556
|
-
newItem.partialQuantity = quantity;
|
|
557
|
-
newItem.partialUnit = units2;
|
|
558
|
-
newItem.partialPreparation = preparation;
|
|
559
|
-
}
|
|
560
903
|
items.push(newItem);
|
|
561
904
|
} else if (groups.mCookwareName || groups.sCookwareName) {
|
|
562
905
|
const name = groups.mCookwareName || groups.sCookwareName;
|
|
563
906
|
const modifier = groups.mCookwareModifier || groups.sCookwareModifier;
|
|
907
|
+
const quantityRaw = groups.mCookwareQuantity || groups.sCookwareQuantity;
|
|
564
908
|
const optional = modifier === "?";
|
|
565
909
|
const hidden = modifier === "-";
|
|
566
910
|
const reference = modifier === "&";
|
|
911
|
+
const quantity = quantityRaw ? parseQuantityInput(quantityRaw) : void 0;
|
|
567
912
|
const idxInList = findAndUpsertCookware(
|
|
568
913
|
this.cookware,
|
|
569
|
-
{ name, optional, hidden },
|
|
914
|
+
{ name, quantity, optional, hidden },
|
|
570
915
|
reference
|
|
571
916
|
);
|
|
572
|
-
items.push({
|
|
917
|
+
items.push({
|
|
918
|
+
type: "cookware",
|
|
919
|
+
value: idxInList,
|
|
920
|
+
itemQuantity: quantity
|
|
921
|
+
});
|
|
573
922
|
} else if (groups.timerQuantity !== void 0) {
|
|
574
923
|
const durationStr = groups.timerQuantity.trim();
|
|
575
924
|
const unit = (groups.timerUnits || "").trim();
|
|
@@ -577,7 +926,7 @@ var Recipe = class _Recipe {
|
|
|
577
926
|
throw new Error("Timer missing units");
|
|
578
927
|
}
|
|
579
928
|
const name = groups.timerName || void 0;
|
|
580
|
-
const duration =
|
|
929
|
+
const duration = parseQuantityInput(durationStr);
|
|
581
930
|
const timerObj = {
|
|
582
931
|
name,
|
|
583
932
|
duration,
|
|
@@ -585,7 +934,7 @@ var Recipe = class _Recipe {
|
|
|
585
934
|
};
|
|
586
935
|
const idxInList = findOrPush(
|
|
587
936
|
this.timers,
|
|
588
|
-
(
|
|
937
|
+
(t2) => t2.name === timerObj.name && t2.duration === timerObj.duration && t2.unit === timerObj.unit,
|
|
589
938
|
() => timerObj
|
|
590
939
|
);
|
|
591
940
|
items.push({ type: "timer", value: idxInList });
|
|
@@ -628,8 +977,11 @@ var Recipe = class _Recipe {
|
|
|
628
977
|
throw new Error("Error scaling recipe: no initial servings value set");
|
|
629
978
|
}
|
|
630
979
|
newRecipe.ingredients = newRecipe.ingredients.map((ingredient) => {
|
|
631
|
-
if (ingredient.quantity && !
|
|
632
|
-
ingredient.quantity
|
|
980
|
+
if (ingredient.quantity && !(ingredient.quantity.type === "fixed" && ingredient.quantity.value.type === "text")) {
|
|
981
|
+
ingredient.quantity = multiplyQuantityValue(
|
|
982
|
+
ingredient.quantity,
|
|
983
|
+
factor
|
|
984
|
+
);
|
|
633
985
|
}
|
|
634
986
|
return ingredient;
|
|
635
987
|
}).filter((ingredient) => ingredient.quantity !== null);
|
|
@@ -654,6 +1006,11 @@ var Recipe = class _Recipe {
|
|
|
654
1006
|
}
|
|
655
1007
|
return newRecipe;
|
|
656
1008
|
}
|
|
1009
|
+
/**
|
|
1010
|
+
* Gets the number of servings for the recipe.
|
|
1011
|
+
* @private
|
|
1012
|
+
* @returns The number of servings, or undefined if not set.
|
|
1013
|
+
*/
|
|
657
1014
|
getServings() {
|
|
658
1015
|
if (this.servings) {
|
|
659
1016
|
return this.servings;
|
|
@@ -667,9 +1024,13 @@ var Recipe = class _Recipe {
|
|
|
667
1024
|
clone() {
|
|
668
1025
|
const newRecipe = new _Recipe();
|
|
669
1026
|
newRecipe.metadata = JSON.parse(JSON.stringify(this.metadata));
|
|
670
|
-
newRecipe.ingredients = JSON.parse(
|
|
1027
|
+
newRecipe.ingredients = JSON.parse(
|
|
1028
|
+
JSON.stringify(this.ingredients)
|
|
1029
|
+
);
|
|
671
1030
|
newRecipe.sections = JSON.parse(JSON.stringify(this.sections));
|
|
672
|
-
newRecipe.cookware = JSON.parse(
|
|
1031
|
+
newRecipe.cookware = JSON.parse(
|
|
1032
|
+
JSON.stringify(this.cookware)
|
|
1033
|
+
);
|
|
673
1034
|
newRecipe.timers = JSON.parse(JSON.stringify(this.timers));
|
|
674
1035
|
newRecipe.servings = this.servings;
|
|
675
1036
|
return newRecipe;
|
|
@@ -716,7 +1077,7 @@ var ShoppingList = class {
|
|
|
716
1077
|
continue;
|
|
717
1078
|
}
|
|
718
1079
|
const existingIngredient = this.ingredients.find(
|
|
719
|
-
(
|
|
1080
|
+
(i2) => i2.name === ingredient.name
|
|
720
1081
|
);
|
|
721
1082
|
let addSeparate = false;
|
|
722
1083
|
try {
|
|
@@ -826,6 +1187,7 @@ var ShoppingList = class {
|
|
|
826
1187
|
export {
|
|
827
1188
|
AisleConfig,
|
|
828
1189
|
Recipe,
|
|
1190
|
+
Section,
|
|
829
1191
|
ShoppingList
|
|
830
1192
|
};
|
|
831
1193
|
//# sourceMappingURL=index.js.map
|