@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.cjs
CHANGED
|
@@ -24,6 +24,7 @@ var index_exports = {};
|
|
|
24
24
|
__export(index_exports, {
|
|
25
25
|
AisleConfig: () => AisleConfig,
|
|
26
26
|
Recipe: () => Recipe,
|
|
27
|
+
Section: () => Section,
|
|
27
28
|
ShoppingList: () => ShoppingList
|
|
28
29
|
});
|
|
29
30
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -91,23 +92,234 @@ var AisleConfig = class {
|
|
|
91
92
|
|
|
92
93
|
// src/classes/section.ts
|
|
93
94
|
var Section = class {
|
|
95
|
+
/**
|
|
96
|
+
* Creates an instance of Section.
|
|
97
|
+
* @param name - The name of the section. Defaults to an empty string.
|
|
98
|
+
*/
|
|
94
99
|
constructor(name = "") {
|
|
100
|
+
/** The name of the section. Can be an empty string for the default (first) section. */
|
|
95
101
|
__publicField(this, "name");
|
|
102
|
+
/** An array of steps and notes that make up the content of the section. */
|
|
96
103
|
__publicField(this, "content", []);
|
|
97
104
|
this.name = name;
|
|
98
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Checks if the section is blank (has no name and no content).
|
|
108
|
+
* Used during recipe parsing
|
|
109
|
+
* @returns `true` if the section is blank, otherwise `false`.
|
|
110
|
+
*/
|
|
99
111
|
isBlank() {
|
|
100
112
|
return this.name === "" && this.content.length === 0;
|
|
101
113
|
}
|
|
102
114
|
};
|
|
103
115
|
|
|
116
|
+
// node_modules/.pnpm/human-regex@2.1.5_patch_hash=6d6bd9e233f99785a7c2187fd464edc114b76d47001dbb4eb6b5d72168de7460/node_modules/human-regex/dist/human-regex.esm.js
|
|
117
|
+
var t = /* @__PURE__ */ new Map();
|
|
118
|
+
var r = { GLOBAL: "g", NON_SENSITIVE: "i", MULTILINE: "m", DOT_ALL: "s", UNICODE: "u", STICKY: "y" };
|
|
119
|
+
var e = Object.freeze({ digit: "0-9", lowercaseLetter: "a-z", uppercaseLetter: "A-Z", letter: "a-zA-Z", alphanumeric: "a-zA-Z0-9", anyCharacter: "." });
|
|
120
|
+
var n = Object.freeze({ zeroOrMore: "*", oneOrMore: "+", optional: "?" });
|
|
121
|
+
var a = class {
|
|
122
|
+
constructor() {
|
|
123
|
+
this.parts = [], this.flags = /* @__PURE__ */ new Set();
|
|
124
|
+
}
|
|
125
|
+
digit() {
|
|
126
|
+
return this.add("\\d");
|
|
127
|
+
}
|
|
128
|
+
special() {
|
|
129
|
+
return this.add("(?=.*[!@#$%^&*])");
|
|
130
|
+
}
|
|
131
|
+
word() {
|
|
132
|
+
return this.add("\\w");
|
|
133
|
+
}
|
|
134
|
+
whitespace() {
|
|
135
|
+
return this.add("\\s");
|
|
136
|
+
}
|
|
137
|
+
nonWhitespace() {
|
|
138
|
+
return this.add("\\S");
|
|
139
|
+
}
|
|
140
|
+
literal(r2) {
|
|
141
|
+
return this.add((function(r3) {
|
|
142
|
+
t.has(r3) || t.set(r3, r3.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
143
|
+
return t.get(r3);
|
|
144
|
+
})(r2));
|
|
145
|
+
}
|
|
146
|
+
or() {
|
|
147
|
+
return this.add("|");
|
|
148
|
+
}
|
|
149
|
+
range(t2) {
|
|
150
|
+
const r2 = e[t2];
|
|
151
|
+
if (!r2) throw new Error(`Unknown range: ${t2}`);
|
|
152
|
+
return this.add(`[${r2}]`);
|
|
153
|
+
}
|
|
154
|
+
notRange(t2) {
|
|
155
|
+
const r2 = e[t2];
|
|
156
|
+
if (!r2) throw new Error(`Unknown range: ${t2}`);
|
|
157
|
+
return this.add(`[^${r2}]`);
|
|
158
|
+
}
|
|
159
|
+
anyOf(t2) {
|
|
160
|
+
return this.add(`[${t2}]`);
|
|
161
|
+
}
|
|
162
|
+
notAnyOf(t2) {
|
|
163
|
+
return this.add(`[^${t2}]`);
|
|
164
|
+
}
|
|
165
|
+
lazy() {
|
|
166
|
+
const t2 = this.parts.pop();
|
|
167
|
+
if (!t2) throw new Error("No quantifier to make lazy");
|
|
168
|
+
return this.add(`${t2}?`);
|
|
169
|
+
}
|
|
170
|
+
letter() {
|
|
171
|
+
return this.add("[a-zA-Z]");
|
|
172
|
+
}
|
|
173
|
+
anyCharacter() {
|
|
174
|
+
return this.add(".");
|
|
175
|
+
}
|
|
176
|
+
newline() {
|
|
177
|
+
return this.add("(?:\\r\\n|\\r|\\n)");
|
|
178
|
+
}
|
|
179
|
+
negativeLookahead(t2) {
|
|
180
|
+
return this.add(`(?!${t2})`);
|
|
181
|
+
}
|
|
182
|
+
positiveLookahead(t2) {
|
|
183
|
+
return this.add(`(?=${t2})`);
|
|
184
|
+
}
|
|
185
|
+
positiveLookbehind(t2) {
|
|
186
|
+
return this.add(`(?<=${t2})`);
|
|
187
|
+
}
|
|
188
|
+
negativeLookbehind(t2) {
|
|
189
|
+
return this.add(`(?<!${t2})`);
|
|
190
|
+
}
|
|
191
|
+
hasSpecialCharacter() {
|
|
192
|
+
return this.add("(?=.*[!@#$%^&*])");
|
|
193
|
+
}
|
|
194
|
+
hasDigit() {
|
|
195
|
+
return this.add("(?=.*\\d)");
|
|
196
|
+
}
|
|
197
|
+
hasLetter() {
|
|
198
|
+
return this.add("(?=.*[a-zA-Z])");
|
|
199
|
+
}
|
|
200
|
+
optional() {
|
|
201
|
+
return this.add(n.optional);
|
|
202
|
+
}
|
|
203
|
+
exactly(t2) {
|
|
204
|
+
return this.add(`{${t2}}`);
|
|
205
|
+
}
|
|
206
|
+
atLeast(t2) {
|
|
207
|
+
return this.add(`{${t2},}`);
|
|
208
|
+
}
|
|
209
|
+
atMost(t2) {
|
|
210
|
+
return this.add(`{0,${t2}}`);
|
|
211
|
+
}
|
|
212
|
+
between(t2, r2) {
|
|
213
|
+
return this.add(`{${t2},${r2}}`);
|
|
214
|
+
}
|
|
215
|
+
oneOrMore() {
|
|
216
|
+
return this.add(n.oneOrMore);
|
|
217
|
+
}
|
|
218
|
+
zeroOrMore() {
|
|
219
|
+
return this.add(n.zeroOrMore);
|
|
220
|
+
}
|
|
221
|
+
startNamedGroup(t2) {
|
|
222
|
+
return this.add(`(?<${t2}>`);
|
|
223
|
+
}
|
|
224
|
+
startGroup() {
|
|
225
|
+
return this.add("(?:");
|
|
226
|
+
}
|
|
227
|
+
startCaptureGroup() {
|
|
228
|
+
return this.add("(");
|
|
229
|
+
}
|
|
230
|
+
wordBoundary() {
|
|
231
|
+
return this.add("\\b");
|
|
232
|
+
}
|
|
233
|
+
nonWordBoundary() {
|
|
234
|
+
return this.add("\\B");
|
|
235
|
+
}
|
|
236
|
+
endGroup() {
|
|
237
|
+
return this.add(")");
|
|
238
|
+
}
|
|
239
|
+
startAnchor() {
|
|
240
|
+
return this.add("^");
|
|
241
|
+
}
|
|
242
|
+
endAnchor() {
|
|
243
|
+
return this.add("$");
|
|
244
|
+
}
|
|
245
|
+
global() {
|
|
246
|
+
return this.flags.add(r.GLOBAL), this;
|
|
247
|
+
}
|
|
248
|
+
nonSensitive() {
|
|
249
|
+
return this.flags.add(r.NON_SENSITIVE), this;
|
|
250
|
+
}
|
|
251
|
+
multiline() {
|
|
252
|
+
return this.flags.add(r.MULTILINE), this;
|
|
253
|
+
}
|
|
254
|
+
dotAll() {
|
|
255
|
+
return this.flags.add(r.DOT_ALL), this;
|
|
256
|
+
}
|
|
257
|
+
sticky() {
|
|
258
|
+
return this.flags.add(r.STICKY), this;
|
|
259
|
+
}
|
|
260
|
+
unicodeChar(t2) {
|
|
261
|
+
this.flags.add(r.UNICODE);
|
|
262
|
+
const e2 = /* @__PURE__ */ new Set(["u", "l", "t", "m", "o"]);
|
|
263
|
+
if (void 0 !== t2 && !e2.has(t2)) throw new Error(`Invalid Unicode letter variant: ${t2}`);
|
|
264
|
+
return this.add(`\\p{L${null != t2 ? t2 : ""}}`);
|
|
265
|
+
}
|
|
266
|
+
unicodeDigit() {
|
|
267
|
+
return this.flags.add(r.UNICODE), this.add("\\p{N}");
|
|
268
|
+
}
|
|
269
|
+
unicodePunctuation() {
|
|
270
|
+
return this.flags.add(r.UNICODE), this.add("\\p{P}");
|
|
271
|
+
}
|
|
272
|
+
unicodeSymbol() {
|
|
273
|
+
return this.flags.add(r.UNICODE), this.add("\\p{S}");
|
|
274
|
+
}
|
|
275
|
+
repeat(t2) {
|
|
276
|
+
if (0 === this.parts.length) throw new Error("No pattern to repeat");
|
|
277
|
+
const r2 = this.parts.pop();
|
|
278
|
+
return this.parts.push(`(${r2}){${t2}}`), this;
|
|
279
|
+
}
|
|
280
|
+
ipv4Octet() {
|
|
281
|
+
return this.add("(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)");
|
|
282
|
+
}
|
|
283
|
+
protocol() {
|
|
284
|
+
return this.add("https?://");
|
|
285
|
+
}
|
|
286
|
+
www() {
|
|
287
|
+
return this.add("(www\\.)?");
|
|
288
|
+
}
|
|
289
|
+
tld() {
|
|
290
|
+
return this.add("(com|org|net)");
|
|
291
|
+
}
|
|
292
|
+
path() {
|
|
293
|
+
return this.add("(/\\w+)*");
|
|
294
|
+
}
|
|
295
|
+
add(t2) {
|
|
296
|
+
return this.parts.push(t2), this;
|
|
297
|
+
}
|
|
298
|
+
toString() {
|
|
299
|
+
return this.parts.join("");
|
|
300
|
+
}
|
|
301
|
+
toRegExp() {
|
|
302
|
+
return new RegExp(this.toString(), [...this.flags].join(""));
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
var d = () => new a();
|
|
306
|
+
var i = (() => {
|
|
307
|
+
const t2 = (t3) => {
|
|
308
|
+
const r2 = t3().toRegExp();
|
|
309
|
+
return () => new RegExp(r2.source, r2.flags);
|
|
310
|
+
};
|
|
311
|
+
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())) };
|
|
312
|
+
})();
|
|
313
|
+
|
|
104
314
|
// src/regex.ts
|
|
105
|
-
var metadataRegex =
|
|
106
|
-
var
|
|
107
|
-
var
|
|
108
|
-
var
|
|
109
|
-
var
|
|
110
|
-
var
|
|
315
|
+
var metadataRegex = d().literal("---").newline().startCaptureGroup().anyCharacter().zeroOrMore().optional().endGroup().newline().literal("---").dotAll().toRegExp();
|
|
316
|
+
var nonWordChar = "\\s@#~\\[\\]{(.,;:!?";
|
|
317
|
+
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();
|
|
318
|
+
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();
|
|
319
|
+
var ingredientAliasRegex = d().startAnchor().startNamedGroup("ingredientListName").notAnyOf("|").oneOrMore().endGroup().literal("|").startNamedGroup("ingredientDisplayName").notAnyOf("|").oneOrMore().endGroup().endAnchor().toRegExp();
|
|
320
|
+
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();
|
|
321
|
+
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();
|
|
322
|
+
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();
|
|
111
323
|
var tokensRegex = new RegExp(
|
|
112
324
|
[
|
|
113
325
|
multiwordIngredient,
|
|
@@ -115,11 +327,14 @@ var tokensRegex = new RegExp(
|
|
|
115
327
|
multiwordCookware,
|
|
116
328
|
singleWordCookware,
|
|
117
329
|
timer
|
|
118
|
-
].map((
|
|
330
|
+
].map((r2) => r2.source).join("|"),
|
|
119
331
|
"gu"
|
|
120
332
|
);
|
|
121
|
-
var commentRegex =
|
|
122
|
-
var blockCommentRegex =
|
|
333
|
+
var commentRegex = d().literal("--").anyCharacter().zeroOrMore().global().toRegExp();
|
|
334
|
+
var blockCommentRegex = d().whitespace().zeroOrMore().literal("[-").anyCharacter().zeroOrMore().lazy().literal("-]").whitespace().zeroOrMore().global().toRegExp();
|
|
335
|
+
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();
|
|
337
|
+
var numberLikeRegex = d().startAnchor().digit().oneOrMore().startGroup().anyOf(".,/").exactly(1).digit().oneOrMore().endGroup().optional().endAnchor().toRegExp();
|
|
123
338
|
|
|
124
339
|
// src/units.ts
|
|
125
340
|
var units = [
|
|
@@ -237,46 +452,144 @@ for (const unit of units) {
|
|
|
237
452
|
function normalizeUnit(unit) {
|
|
238
453
|
return unitMap.get(unit.toLowerCase().trim());
|
|
239
454
|
}
|
|
455
|
+
var CannotAddTextValueError = class extends Error {
|
|
456
|
+
constructor() {
|
|
457
|
+
super("Cannot add a quantity with a text value.");
|
|
458
|
+
this.name = "CannotAddTextValueError";
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
var IncompatibleUnitsError = class extends Error {
|
|
462
|
+
constructor(unit1, unit2) {
|
|
463
|
+
super(
|
|
464
|
+
`Cannot add quantities with incompatible or unknown units: ${unit1} and ${unit2}`
|
|
465
|
+
);
|
|
466
|
+
this.name = "IncompatibleUnitsError";
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
function gcd(a2, b) {
|
|
470
|
+
return b === 0 ? a2 : gcd(b, a2 % b);
|
|
471
|
+
}
|
|
472
|
+
function simplifyFraction(num, den) {
|
|
473
|
+
if (den === 0) {
|
|
474
|
+
throw new Error("Denominator cannot be zero.");
|
|
475
|
+
}
|
|
476
|
+
const commonDivisor = gcd(Math.abs(num), Math.abs(den));
|
|
477
|
+
let simplifiedNum = num / commonDivisor;
|
|
478
|
+
let simplifiedDen = den / commonDivisor;
|
|
479
|
+
if (simplifiedDen < 0) {
|
|
480
|
+
simplifiedNum = -simplifiedNum;
|
|
481
|
+
simplifiedDen = -simplifiedDen;
|
|
482
|
+
}
|
|
483
|
+
if (simplifiedDen === 1) {
|
|
484
|
+
return { type: "decimal", value: simplifiedNum };
|
|
485
|
+
} else {
|
|
486
|
+
return { type: "fraction", num: simplifiedNum, den: simplifiedDen };
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
function multiplyNumericValue(v, factor) {
|
|
490
|
+
if (v.type === "decimal") {
|
|
491
|
+
return { type: "decimal", value: v.value * factor };
|
|
492
|
+
}
|
|
493
|
+
return simplifyFraction(v.num * factor, v.den);
|
|
494
|
+
}
|
|
495
|
+
function addNumericValues(val1, val2) {
|
|
496
|
+
let num1;
|
|
497
|
+
let den1;
|
|
498
|
+
let num2;
|
|
499
|
+
let den2;
|
|
500
|
+
if (val1.type === "decimal") {
|
|
501
|
+
num1 = val1.value;
|
|
502
|
+
den1 = 1;
|
|
503
|
+
} else {
|
|
504
|
+
num1 = val1.num;
|
|
505
|
+
den1 = val1.den;
|
|
506
|
+
}
|
|
507
|
+
if (val2.type === "decimal") {
|
|
508
|
+
num2 = val2.value;
|
|
509
|
+
den2 = 1;
|
|
510
|
+
} else {
|
|
511
|
+
num2 = val2.num;
|
|
512
|
+
den2 = val2.den;
|
|
513
|
+
}
|
|
514
|
+
if (val1.type === "fraction" && val2.type === "fraction") {
|
|
515
|
+
const commonDen = den1 * den2;
|
|
516
|
+
const sumNum = num1 * den2 + num2 * den1;
|
|
517
|
+
return simplifyFraction(sumNum, commonDen);
|
|
518
|
+
} else {
|
|
519
|
+
return { type: "decimal", value: num1 / den1 + num2 / den2 };
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
var toRoundedDecimal = (v) => {
|
|
523
|
+
const value = v.type === "decimal" ? v.value : v.num / v.den;
|
|
524
|
+
return { type: "decimal", value: Math.floor(value * 100) / 100 };
|
|
525
|
+
};
|
|
526
|
+
function multiplyQuantityValue(value, factor) {
|
|
527
|
+
if (value.type === "fixed") {
|
|
528
|
+
return {
|
|
529
|
+
type: "fixed",
|
|
530
|
+
value: toRoundedDecimal(
|
|
531
|
+
multiplyNumericValue(
|
|
532
|
+
value.value,
|
|
533
|
+
factor
|
|
534
|
+
)
|
|
535
|
+
)
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
return {
|
|
539
|
+
type: "range",
|
|
540
|
+
min: toRoundedDecimal(multiplyNumericValue(value.min, factor)),
|
|
541
|
+
max: toRoundedDecimal(multiplyNumericValue(value.max, factor))
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
var convertQuantityValue = (value, def, targetDef) => {
|
|
545
|
+
if (def.name === targetDef.name) return value;
|
|
546
|
+
const factor = def.toBase / targetDef.toBase;
|
|
547
|
+
return multiplyQuantityValue(value, factor);
|
|
548
|
+
};
|
|
240
549
|
function addQuantities(q1, q2) {
|
|
550
|
+
const v1 = q1.value;
|
|
551
|
+
const v2 = q2.value;
|
|
552
|
+
if (v1.type === "fixed" && v1.value.type === "text" || v2.type === "fixed" && v2.value.type === "text") {
|
|
553
|
+
throw new CannotAddTextValueError();
|
|
554
|
+
}
|
|
241
555
|
const unit1Def = normalizeUnit(q1.unit);
|
|
242
556
|
const unit2Def = normalizeUnit(q2.unit);
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
557
|
+
const addQuantityValuesAndSetUnit = (val1, val2, unit) => {
|
|
558
|
+
if (val1.type === "fixed" && val2.type === "fixed") {
|
|
559
|
+
const res = addNumericValues(
|
|
560
|
+
val1.value,
|
|
561
|
+
val2.value
|
|
562
|
+
);
|
|
563
|
+
return { value: { type: "fixed", value: res }, unit };
|
|
564
|
+
}
|
|
565
|
+
const r1 = val1.type === "range" ? val1 : { type: "range", min: val1.value, max: val1.value };
|
|
566
|
+
const r2 = val2.type === "range" ? val2 : { type: "range", min: val2.value, max: val2.value };
|
|
567
|
+
const newMin = addNumericValues(
|
|
568
|
+
r1.min,
|
|
569
|
+
r2.min
|
|
246
570
|
);
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
`Cannot add quantity to string-quantified value: ${q2.value}`
|
|
571
|
+
const newMax = addNumericValues(
|
|
572
|
+
r1.max,
|
|
573
|
+
r2.max
|
|
251
574
|
);
|
|
252
|
-
|
|
575
|
+
return { value: { type: "range", min: newMin, max: newMax }, unit };
|
|
576
|
+
};
|
|
253
577
|
if (q1.unit === "" && unit2Def) {
|
|
254
|
-
return
|
|
255
|
-
value: Math.round((q1.value + q2.value) * 100) / 100,
|
|
256
|
-
unit: q2.unit
|
|
257
|
-
};
|
|
578
|
+
return addQuantityValuesAndSetUnit(v1, v2, q2.unit);
|
|
258
579
|
}
|
|
259
580
|
if (q2.unit === "" && unit1Def) {
|
|
260
|
-
return
|
|
261
|
-
value: Math.round((q1.value + q2.value) * 100) / 100,
|
|
262
|
-
unit: q1.unit
|
|
263
|
-
};
|
|
581
|
+
return addQuantityValuesAndSetUnit(v1, v2, q1.unit);
|
|
264
582
|
}
|
|
265
583
|
if (q1.unit.toLowerCase() === q2.unit.toLowerCase()) {
|
|
266
|
-
return
|
|
267
|
-
value: Math.round((q1.value + q2.value) * 100) / 100,
|
|
268
|
-
unit: q1.unit
|
|
269
|
-
};
|
|
584
|
+
return addQuantityValuesAndSetUnit(v1, v2, q1.unit);
|
|
270
585
|
}
|
|
271
586
|
if (unit1Def && unit2Def) {
|
|
272
587
|
if (unit1Def.type !== unit2Def.type) {
|
|
273
|
-
throw new
|
|
274
|
-
|
|
588
|
+
throw new IncompatibleUnitsError(
|
|
589
|
+
`${unit1Def.type} (${q1.unit})`,
|
|
590
|
+
`${unit2Def.type} (${q2.unit})`
|
|
275
591
|
);
|
|
276
592
|
}
|
|
277
|
-
const baseValue1 = q1.value * unit1Def.toBase;
|
|
278
|
-
const baseValue2 = q2.value * unit2Def.toBase;
|
|
279
|
-
const totalBaseValue = baseValue1 + baseValue2;
|
|
280
593
|
let targetUnitDef;
|
|
281
594
|
if (unit1Def.system !== unit2Def.system) {
|
|
282
595
|
const metricUnitDef = unit1Def.system === "metric" ? unit1Def : unit2Def;
|
|
@@ -286,15 +599,15 @@ function addQuantities(q1, q2) {
|
|
|
286
599
|
} else {
|
|
287
600
|
targetUnitDef = unit1Def.toBase >= unit2Def.toBase ? unit1Def : unit2Def;
|
|
288
601
|
}
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
602
|
+
const convertedV1 = convertQuantityValue(v1, unit1Def, targetUnitDef);
|
|
603
|
+
const convertedV2 = convertQuantityValue(v2, unit2Def, targetUnitDef);
|
|
604
|
+
return addQuantityValuesAndSetUnit(
|
|
605
|
+
convertedV1,
|
|
606
|
+
convertedV2,
|
|
607
|
+
targetUnitDef.name
|
|
608
|
+
);
|
|
294
609
|
}
|
|
295
|
-
throw new
|
|
296
|
-
`Cannot add quantities with incompatible or unknown units: ${q1.unit} and ${q2.unit}`
|
|
297
|
-
);
|
|
610
|
+
throw new IncompatibleUnitsError(q1.unit, q2.unit);
|
|
298
611
|
}
|
|
299
612
|
|
|
300
613
|
// src/parser_helpers.ts
|
|
@@ -307,14 +620,14 @@ function findOrPush(list, finder, creator) {
|
|
|
307
620
|
}
|
|
308
621
|
function flushPendingNote(section, note) {
|
|
309
622
|
if (note.length > 0) {
|
|
310
|
-
section.content.push({ note });
|
|
623
|
+
section.content.push({ type: "note", note });
|
|
311
624
|
return "";
|
|
312
625
|
}
|
|
313
626
|
return note;
|
|
314
627
|
}
|
|
315
628
|
function flushPendingItems(section, items) {
|
|
316
629
|
if (items.length > 0) {
|
|
317
|
-
section.content.push({ items: [...items] });
|
|
630
|
+
section.content.push({ type: "step", items: [...items] });
|
|
318
631
|
items.length = 0;
|
|
319
632
|
return true;
|
|
320
633
|
}
|
|
@@ -324,7 +637,7 @@ function findAndUpsertIngredient(ingredients, newIngredient, isReference) {
|
|
|
324
637
|
const { name, quantity, unit } = newIngredient;
|
|
325
638
|
if (isReference) {
|
|
326
639
|
const index = ingredients.findIndex(
|
|
327
|
-
(
|
|
640
|
+
(i2) => i2.name.toLowerCase() === name.toLowerCase()
|
|
328
641
|
);
|
|
329
642
|
if (index === -1) {
|
|
330
643
|
throw new Error(
|
|
@@ -334,13 +647,22 @@ function findAndUpsertIngredient(ingredients, newIngredient, isReference) {
|
|
|
334
647
|
const existingIngredient = ingredients[index];
|
|
335
648
|
if (quantity !== void 0) {
|
|
336
649
|
const currentQuantity = {
|
|
337
|
-
value: existingIngredient.quantity ??
|
|
650
|
+
value: existingIngredient.quantity ?? {
|
|
651
|
+
type: "fixed",
|
|
652
|
+
value: { type: "decimal", value: 0 }
|
|
653
|
+
},
|
|
338
654
|
unit: existingIngredient.unit ?? ""
|
|
339
655
|
};
|
|
340
656
|
const newQuantity = { value: quantity, unit: unit ?? "" };
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
657
|
+
try {
|
|
658
|
+
const total = addQuantities(currentQuantity, newQuantity);
|
|
659
|
+
existingIngredient.quantity = total.value;
|
|
660
|
+
existingIngredient.unit = total.unit || void 0;
|
|
661
|
+
} catch (e2) {
|
|
662
|
+
if (e2 instanceof IncompatibleUnitsError || e2 instanceof CannotAddTextValueError) {
|
|
663
|
+
return ingredients.push(newIngredient) - 1;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
344
666
|
}
|
|
345
667
|
return index;
|
|
346
668
|
}
|
|
@@ -350,7 +672,7 @@ function findAndUpsertCookware(cookware, newCookware, isReference) {
|
|
|
350
672
|
const { name } = newCookware;
|
|
351
673
|
if (isReference) {
|
|
352
674
|
const index = cookware.findIndex(
|
|
353
|
-
(
|
|
675
|
+
(i2) => i2.name.toLowerCase() === name.toLowerCase()
|
|
354
676
|
);
|
|
355
677
|
if (index === -1) {
|
|
356
678
|
throw new Error(
|
|
@@ -361,13 +683,28 @@ function findAndUpsertCookware(cookware, newCookware, isReference) {
|
|
|
361
683
|
}
|
|
362
684
|
return cookware.push(newCookware) - 1;
|
|
363
685
|
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
686
|
+
var parseFixedValue = (input_str) => {
|
|
687
|
+
if (!numberLikeRegex.test(input_str)) {
|
|
688
|
+
return { type: "text", value: input_str };
|
|
689
|
+
}
|
|
690
|
+
const s = input_str.trim().replace(",", ".");
|
|
691
|
+
if (s.includes("/")) {
|
|
692
|
+
const parts = s.split("/");
|
|
693
|
+
const num = Number(parts[0]);
|
|
694
|
+
const den = Number(parts[1]);
|
|
695
|
+
return { type: "fraction", num, den };
|
|
696
|
+
}
|
|
697
|
+
return { type: "decimal", value: Number(s) };
|
|
698
|
+
};
|
|
699
|
+
function parseQuantityInput(input_str) {
|
|
700
|
+
const clean_str = String(input_str).trim();
|
|
701
|
+
if (rangeRegex.test(clean_str)) {
|
|
702
|
+
const range_parts = clean_str.split("-");
|
|
703
|
+
const min = parseFixedValue(range_parts[0].trim());
|
|
704
|
+
const max = parseFixedValue(range_parts[1].trim());
|
|
705
|
+
return { type: "range", min, max };
|
|
369
706
|
}
|
|
370
|
-
return
|
|
707
|
+
return { type: "fixed", value: parseFixedValue(clean_str) };
|
|
371
708
|
}
|
|
372
709
|
function parseSimpleMetaVar(content, varName) {
|
|
373
710
|
const varMatch = content.match(
|
|
@@ -560,11 +897,20 @@ var Recipe = class _Recipe {
|
|
|
560
897
|
const hidden = modifier === "-";
|
|
561
898
|
const reference = modifier === "&";
|
|
562
899
|
const isRecipe = modifier === "@";
|
|
563
|
-
const quantity = quantityRaw ?
|
|
900
|
+
const quantity = quantityRaw ? parseQuantityInput(quantityRaw) : void 0;
|
|
901
|
+
const aliasMatch = name.match(ingredientAliasRegex);
|
|
902
|
+
let listName, displayName;
|
|
903
|
+
if (aliasMatch && aliasMatch.groups.ingredientListName.trim().length > 0 && aliasMatch.groups.ingredientDisplayName.trim().length > 0) {
|
|
904
|
+
listName = aliasMatch.groups.ingredientListName.trim();
|
|
905
|
+
displayName = aliasMatch.groups.ingredientDisplayName.trim();
|
|
906
|
+
} else {
|
|
907
|
+
listName = name;
|
|
908
|
+
displayName = name;
|
|
909
|
+
}
|
|
564
910
|
const idxInList = findAndUpsertIngredient(
|
|
565
911
|
this.ingredients,
|
|
566
912
|
{
|
|
567
|
-
name,
|
|
913
|
+
name: listName,
|
|
568
914
|
quantity,
|
|
569
915
|
unit: units2,
|
|
570
916
|
optional,
|
|
@@ -576,26 +922,30 @@ var Recipe = class _Recipe {
|
|
|
576
922
|
);
|
|
577
923
|
const newItem = {
|
|
578
924
|
type: "ingredient",
|
|
579
|
-
value: idxInList
|
|
925
|
+
value: idxInList,
|
|
926
|
+
itemQuantity: quantity,
|
|
927
|
+
itemUnit: units2,
|
|
928
|
+
displayName
|
|
580
929
|
};
|
|
581
|
-
if (reference) {
|
|
582
|
-
newItem.partialQuantity = quantity;
|
|
583
|
-
newItem.partialUnit = units2;
|
|
584
|
-
newItem.partialPreparation = preparation;
|
|
585
|
-
}
|
|
586
930
|
items.push(newItem);
|
|
587
931
|
} else if (groups.mCookwareName || groups.sCookwareName) {
|
|
588
932
|
const name = groups.mCookwareName || groups.sCookwareName;
|
|
589
933
|
const modifier = groups.mCookwareModifier || groups.sCookwareModifier;
|
|
934
|
+
const quantityRaw = groups.mCookwareQuantity || groups.sCookwareQuantity;
|
|
590
935
|
const optional = modifier === "?";
|
|
591
936
|
const hidden = modifier === "-";
|
|
592
937
|
const reference = modifier === "&";
|
|
938
|
+
const quantity = quantityRaw ? parseQuantityInput(quantityRaw) : void 0;
|
|
593
939
|
const idxInList = findAndUpsertCookware(
|
|
594
940
|
this.cookware,
|
|
595
|
-
{ name, optional, hidden },
|
|
941
|
+
{ name, quantity, optional, hidden },
|
|
596
942
|
reference
|
|
597
943
|
);
|
|
598
|
-
items.push({
|
|
944
|
+
items.push({
|
|
945
|
+
type: "cookware",
|
|
946
|
+
value: idxInList,
|
|
947
|
+
itemQuantity: quantity
|
|
948
|
+
});
|
|
599
949
|
} else if (groups.timerQuantity !== void 0) {
|
|
600
950
|
const durationStr = groups.timerQuantity.trim();
|
|
601
951
|
const unit = (groups.timerUnits || "").trim();
|
|
@@ -603,7 +953,7 @@ var Recipe = class _Recipe {
|
|
|
603
953
|
throw new Error("Timer missing units");
|
|
604
954
|
}
|
|
605
955
|
const name = groups.timerName || void 0;
|
|
606
|
-
const duration =
|
|
956
|
+
const duration = parseQuantityInput(durationStr);
|
|
607
957
|
const timerObj = {
|
|
608
958
|
name,
|
|
609
959
|
duration,
|
|
@@ -611,7 +961,7 @@ var Recipe = class _Recipe {
|
|
|
611
961
|
};
|
|
612
962
|
const idxInList = findOrPush(
|
|
613
963
|
this.timers,
|
|
614
|
-
(
|
|
964
|
+
(t2) => t2.name === timerObj.name && t2.duration === timerObj.duration && t2.unit === timerObj.unit,
|
|
615
965
|
() => timerObj
|
|
616
966
|
);
|
|
617
967
|
items.push({ type: "timer", value: idxInList });
|
|
@@ -654,8 +1004,11 @@ var Recipe = class _Recipe {
|
|
|
654
1004
|
throw new Error("Error scaling recipe: no initial servings value set");
|
|
655
1005
|
}
|
|
656
1006
|
newRecipe.ingredients = newRecipe.ingredients.map((ingredient) => {
|
|
657
|
-
if (ingredient.quantity && !
|
|
658
|
-
ingredient.quantity
|
|
1007
|
+
if (ingredient.quantity && !(ingredient.quantity.type === "fixed" && ingredient.quantity.value.type === "text")) {
|
|
1008
|
+
ingredient.quantity = multiplyQuantityValue(
|
|
1009
|
+
ingredient.quantity,
|
|
1010
|
+
factor
|
|
1011
|
+
);
|
|
659
1012
|
}
|
|
660
1013
|
return ingredient;
|
|
661
1014
|
}).filter((ingredient) => ingredient.quantity !== null);
|
|
@@ -680,6 +1033,11 @@ var Recipe = class _Recipe {
|
|
|
680
1033
|
}
|
|
681
1034
|
return newRecipe;
|
|
682
1035
|
}
|
|
1036
|
+
/**
|
|
1037
|
+
* Gets the number of servings for the recipe.
|
|
1038
|
+
* @private
|
|
1039
|
+
* @returns The number of servings, or undefined if not set.
|
|
1040
|
+
*/
|
|
683
1041
|
getServings() {
|
|
684
1042
|
if (this.servings) {
|
|
685
1043
|
return this.servings;
|
|
@@ -693,9 +1051,13 @@ var Recipe = class _Recipe {
|
|
|
693
1051
|
clone() {
|
|
694
1052
|
const newRecipe = new _Recipe();
|
|
695
1053
|
newRecipe.metadata = JSON.parse(JSON.stringify(this.metadata));
|
|
696
|
-
newRecipe.ingredients = JSON.parse(
|
|
1054
|
+
newRecipe.ingredients = JSON.parse(
|
|
1055
|
+
JSON.stringify(this.ingredients)
|
|
1056
|
+
);
|
|
697
1057
|
newRecipe.sections = JSON.parse(JSON.stringify(this.sections));
|
|
698
|
-
newRecipe.cookware = JSON.parse(
|
|
1058
|
+
newRecipe.cookware = JSON.parse(
|
|
1059
|
+
JSON.stringify(this.cookware)
|
|
1060
|
+
);
|
|
699
1061
|
newRecipe.timers = JSON.parse(JSON.stringify(this.timers));
|
|
700
1062
|
newRecipe.servings = this.servings;
|
|
701
1063
|
return newRecipe;
|
|
@@ -742,7 +1104,7 @@ var ShoppingList = class {
|
|
|
742
1104
|
continue;
|
|
743
1105
|
}
|
|
744
1106
|
const existingIngredient = this.ingredients.find(
|
|
745
|
-
(
|
|
1107
|
+
(i2) => i2.name === ingredient.name
|
|
746
1108
|
);
|
|
747
1109
|
let addSeparate = false;
|
|
748
1110
|
try {
|
|
@@ -853,6 +1215,7 @@ var ShoppingList = class {
|
|
|
853
1215
|
0 && (module.exports = {
|
|
854
1216
|
AisleConfig,
|
|
855
1217
|
Recipe,
|
|
1218
|
+
Section,
|
|
856
1219
|
ShoppingList
|
|
857
1220
|
});
|
|
858
1221
|
//# sourceMappingURL=index.cjs.map
|