@tmlmt/cooklang-parser 1.0.8 → 1.1.0
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 +2 -2
- package/dist/index.cjs +412 -61
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +73 -4
- package/dist/index.d.ts +73 -4
- package/dist/index.js +411 -61
- package/dist/index.js.map +1 -1
- package/package.json +8 -7
package/dist/index.js
CHANGED
|
@@ -65,23 +65,233 @@ 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 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();
|
|
293
|
+
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();
|
|
294
|
+
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
295
|
var tokensRegex = new RegExp(
|
|
86
296
|
[
|
|
87
297
|
multiwordIngredient,
|
|
@@ -89,11 +299,14 @@ var tokensRegex = new RegExp(
|
|
|
89
299
|
multiwordCookware,
|
|
90
300
|
singleWordCookware,
|
|
91
301
|
timer
|
|
92
|
-
].map((
|
|
302
|
+
].map((r2) => r2.source).join("|"),
|
|
93
303
|
"gu"
|
|
94
304
|
);
|
|
95
|
-
var commentRegex =
|
|
96
|
-
var blockCommentRegex =
|
|
305
|
+
var commentRegex = d().literal("--").anyCharacter().zeroOrMore().global().toRegExp();
|
|
306
|
+
var blockCommentRegex = d().whitespace().zeroOrMore().literal("[-").anyCharacter().zeroOrMore().lazy().literal("-]").whitespace().zeroOrMore().global().toRegExp();
|
|
307
|
+
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();
|
|
308
|
+
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();
|
|
309
|
+
var numberLikeRegex = d().startAnchor().digit().oneOrMore().startGroup().anyOf(".,/").exactly(1).digit().oneOrMore().endGroup().optional().endAnchor().toRegExp();
|
|
97
310
|
|
|
98
311
|
// src/units.ts
|
|
99
312
|
var units = [
|
|
@@ -211,46 +424,148 @@ for (const unit of units) {
|
|
|
211
424
|
function normalizeUnit(unit) {
|
|
212
425
|
return unitMap.get(unit.toLowerCase().trim());
|
|
213
426
|
}
|
|
427
|
+
var CannotAddTextValueError = class extends Error {
|
|
428
|
+
constructor() {
|
|
429
|
+
super("Cannot add a quantity with a text value.");
|
|
430
|
+
this.name = "CannotAddTextValueError";
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
var IncompatibleUnitsError = class extends Error {
|
|
434
|
+
constructor(unit1, unit2) {
|
|
435
|
+
super(
|
|
436
|
+
`Cannot add quantities with incompatible or unknown units: ${unit1} and ${unit2}`
|
|
437
|
+
);
|
|
438
|
+
this.name = "IncompatibleUnitsError";
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
function gcd(a2, b) {
|
|
442
|
+
return b === 0 ? a2 : gcd(b, a2 % b);
|
|
443
|
+
}
|
|
444
|
+
function simplifyFraction(num, den) {
|
|
445
|
+
if (den === 0) {
|
|
446
|
+
throw new Error("Denominator cannot be zero.");
|
|
447
|
+
}
|
|
448
|
+
const commonDivisor = gcd(Math.abs(num), Math.abs(den));
|
|
449
|
+
let simplifiedNum = num / commonDivisor;
|
|
450
|
+
let simplifiedDen = den / commonDivisor;
|
|
451
|
+
if (simplifiedDen < 0) {
|
|
452
|
+
simplifiedNum = -simplifiedNum;
|
|
453
|
+
simplifiedDen = -simplifiedDen;
|
|
454
|
+
}
|
|
455
|
+
if (simplifiedDen === 1) {
|
|
456
|
+
return { type: "decimal", value: simplifiedNum };
|
|
457
|
+
} else {
|
|
458
|
+
return { type: "fraction", num: simplifiedNum, den: simplifiedDen };
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
function multiplyNumericValue(v, factor) {
|
|
462
|
+
if (v.type === "decimal") {
|
|
463
|
+
return { type: "decimal", value: v.value * factor };
|
|
464
|
+
}
|
|
465
|
+
return simplifyFraction(v.num * factor, v.den);
|
|
466
|
+
}
|
|
467
|
+
function addNumericValues(val1, val2) {
|
|
468
|
+
let num1;
|
|
469
|
+
let den1;
|
|
470
|
+
let num2;
|
|
471
|
+
let den2;
|
|
472
|
+
if (val1.type === "decimal") {
|
|
473
|
+
num1 = val1.value;
|
|
474
|
+
den1 = 1;
|
|
475
|
+
} else {
|
|
476
|
+
num1 = val1.num;
|
|
477
|
+
den1 = val1.den;
|
|
478
|
+
}
|
|
479
|
+
if (val2.type === "decimal") {
|
|
480
|
+
num2 = val2.value;
|
|
481
|
+
den2 = 1;
|
|
482
|
+
} else {
|
|
483
|
+
num2 = val2.num;
|
|
484
|
+
den2 = val2.den;
|
|
485
|
+
}
|
|
486
|
+
if (val1.type === "fraction" && val2.type === "fraction") {
|
|
487
|
+
const commonDen = den1 * den2;
|
|
488
|
+
const sumNum = num1 * den2 + num2 * den1;
|
|
489
|
+
return simplifyFraction(sumNum, commonDen);
|
|
490
|
+
} else {
|
|
491
|
+
return { type: "decimal", value: num1 / den1 + num2 / den2 };
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
var toRoundedDecimal = (v) => {
|
|
495
|
+
const value = v.type === "decimal" ? v.value : v.num / v.den;
|
|
496
|
+
return { type: "decimal", value: Math.floor(value * 100) / 100 };
|
|
497
|
+
};
|
|
498
|
+
function multiplyQuantityValue(value, factor) {
|
|
499
|
+
if (value.type === "fixed") {
|
|
500
|
+
return {
|
|
501
|
+
type: "fixed",
|
|
502
|
+
value: toRoundedDecimal(
|
|
503
|
+
multiplyNumericValue(
|
|
504
|
+
value.value,
|
|
505
|
+
factor
|
|
506
|
+
)
|
|
507
|
+
)
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
return {
|
|
511
|
+
type: "range",
|
|
512
|
+
min: toRoundedDecimal(
|
|
513
|
+
multiplyNumericValue(value.min, factor)
|
|
514
|
+
),
|
|
515
|
+
max: toRoundedDecimal(
|
|
516
|
+
multiplyNumericValue(value.max, factor)
|
|
517
|
+
)
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
var convertQuantityValue = (value, def, targetDef) => {
|
|
521
|
+
if (def.name === targetDef.name) return value;
|
|
522
|
+
const factor = def.toBase / targetDef.toBase;
|
|
523
|
+
return multiplyQuantityValue(value, factor);
|
|
524
|
+
};
|
|
214
525
|
function addQuantities(q1, q2) {
|
|
526
|
+
const v1 = q1.value;
|
|
527
|
+
const v2 = q2.value;
|
|
528
|
+
if (v1.type === "fixed" && v1.value.type === "text" || v2.type === "fixed" && v2.value.type === "text") {
|
|
529
|
+
throw new CannotAddTextValueError();
|
|
530
|
+
}
|
|
215
531
|
const unit1Def = normalizeUnit(q1.unit);
|
|
216
532
|
const unit2Def = normalizeUnit(q2.unit);
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
533
|
+
const addQuantityValuesAndSetUnit = (val1, val2, unit) => {
|
|
534
|
+
if (val1.type === "fixed" && val2.type === "fixed") {
|
|
535
|
+
const res = addNumericValues(
|
|
536
|
+
val1.value,
|
|
537
|
+
val2.value
|
|
538
|
+
);
|
|
539
|
+
return { value: { type: "fixed", value: res }, unit };
|
|
540
|
+
}
|
|
541
|
+
const r1 = val1.type === "range" ? val1 : { type: "range", min: val1.value, max: val1.value };
|
|
542
|
+
const r2 = val2.type === "range" ? val2 : { type: "range", min: val2.value, max: val2.value };
|
|
543
|
+
const newMin = addNumericValues(
|
|
544
|
+
r1.min,
|
|
545
|
+
r2.min
|
|
220
546
|
);
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
`Cannot add quantity to string-quantified value: ${q2.value}`
|
|
547
|
+
const newMax = addNumericValues(
|
|
548
|
+
r1.max,
|
|
549
|
+
r2.max
|
|
225
550
|
);
|
|
226
|
-
|
|
551
|
+
return { value: { type: "range", min: newMin, max: newMax }, unit };
|
|
552
|
+
};
|
|
227
553
|
if (q1.unit === "" && unit2Def) {
|
|
228
|
-
return
|
|
229
|
-
value: Math.round((q1.value + q2.value) * 100) / 100,
|
|
230
|
-
unit: q2.unit
|
|
231
|
-
};
|
|
554
|
+
return addQuantityValuesAndSetUnit(v1, v2, q2.unit);
|
|
232
555
|
}
|
|
233
556
|
if (q2.unit === "" && unit1Def) {
|
|
234
|
-
return
|
|
235
|
-
value: Math.round((q1.value + q2.value) * 100) / 100,
|
|
236
|
-
unit: q1.unit
|
|
237
|
-
};
|
|
557
|
+
return addQuantityValuesAndSetUnit(v1, v2, q1.unit);
|
|
238
558
|
}
|
|
239
559
|
if (q1.unit.toLowerCase() === q2.unit.toLowerCase()) {
|
|
240
|
-
return
|
|
241
|
-
value: Math.round((q1.value + q2.value) * 100) / 100,
|
|
242
|
-
unit: q1.unit
|
|
243
|
-
};
|
|
560
|
+
return addQuantityValuesAndSetUnit(v1, v2, q1.unit);
|
|
244
561
|
}
|
|
245
562
|
if (unit1Def && unit2Def) {
|
|
246
563
|
if (unit1Def.type !== unit2Def.type) {
|
|
247
|
-
throw new
|
|
248
|
-
|
|
564
|
+
throw new IncompatibleUnitsError(
|
|
565
|
+
`${unit1Def.type} (${q1.unit})`,
|
|
566
|
+
`${unit2Def.type} (${q2.unit})`
|
|
249
567
|
);
|
|
250
568
|
}
|
|
251
|
-
const baseValue1 = q1.value * unit1Def.toBase;
|
|
252
|
-
const baseValue2 = q2.value * unit2Def.toBase;
|
|
253
|
-
const totalBaseValue = baseValue1 + baseValue2;
|
|
254
569
|
let targetUnitDef;
|
|
255
570
|
if (unit1Def.system !== unit2Def.system) {
|
|
256
571
|
const metricUnitDef = unit1Def.system === "metric" ? unit1Def : unit2Def;
|
|
@@ -260,15 +575,15 @@ function addQuantities(q1, q2) {
|
|
|
260
575
|
} else {
|
|
261
576
|
targetUnitDef = unit1Def.toBase >= unit2Def.toBase ? unit1Def : unit2Def;
|
|
262
577
|
}
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
578
|
+
const convertedV1 = convertQuantityValue(v1, unit1Def, targetUnitDef);
|
|
579
|
+
const convertedV2 = convertQuantityValue(v2, unit2Def, targetUnitDef);
|
|
580
|
+
return addQuantityValuesAndSetUnit(
|
|
581
|
+
convertedV1,
|
|
582
|
+
convertedV2,
|
|
583
|
+
targetUnitDef.name
|
|
584
|
+
);
|
|
268
585
|
}
|
|
269
|
-
throw new
|
|
270
|
-
`Cannot add quantities with incompatible or unknown units: ${q1.unit} and ${q2.unit}`
|
|
271
|
-
);
|
|
586
|
+
throw new IncompatibleUnitsError(q1.unit, q2.unit);
|
|
272
587
|
}
|
|
273
588
|
|
|
274
589
|
// src/parser_helpers.ts
|
|
@@ -298,7 +613,7 @@ function findAndUpsertIngredient(ingredients, newIngredient, isReference) {
|
|
|
298
613
|
const { name, quantity, unit } = newIngredient;
|
|
299
614
|
if (isReference) {
|
|
300
615
|
const index = ingredients.findIndex(
|
|
301
|
-
(
|
|
616
|
+
(i2) => i2.name.toLowerCase() === name.toLowerCase()
|
|
302
617
|
);
|
|
303
618
|
if (index === -1) {
|
|
304
619
|
throw new Error(
|
|
@@ -308,13 +623,22 @@ function findAndUpsertIngredient(ingredients, newIngredient, isReference) {
|
|
|
308
623
|
const existingIngredient = ingredients[index];
|
|
309
624
|
if (quantity !== void 0) {
|
|
310
625
|
const currentQuantity = {
|
|
311
|
-
value: existingIngredient.quantity ??
|
|
626
|
+
value: existingIngredient.quantity ?? {
|
|
627
|
+
type: "fixed",
|
|
628
|
+
value: { type: "decimal", value: 0 }
|
|
629
|
+
},
|
|
312
630
|
unit: existingIngredient.unit ?? ""
|
|
313
631
|
};
|
|
314
632
|
const newQuantity = { value: quantity, unit: unit ?? "" };
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
633
|
+
try {
|
|
634
|
+
const total = addQuantities(currentQuantity, newQuantity);
|
|
635
|
+
existingIngredient.quantity = total.value;
|
|
636
|
+
existingIngredient.unit = total.unit || void 0;
|
|
637
|
+
} catch (e2) {
|
|
638
|
+
if (e2 instanceof IncompatibleUnitsError || e2 instanceof CannotAddTextValueError) {
|
|
639
|
+
return ingredients.push(newIngredient) - 1;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
318
642
|
}
|
|
319
643
|
return index;
|
|
320
644
|
}
|
|
@@ -324,7 +648,7 @@ function findAndUpsertCookware(cookware, newCookware, isReference) {
|
|
|
324
648
|
const { name } = newCookware;
|
|
325
649
|
if (isReference) {
|
|
326
650
|
const index = cookware.findIndex(
|
|
327
|
-
(
|
|
651
|
+
(i2) => i2.name.toLowerCase() === name.toLowerCase()
|
|
328
652
|
);
|
|
329
653
|
if (index === -1) {
|
|
330
654
|
throw new Error(
|
|
@@ -335,13 +659,28 @@ function findAndUpsertCookware(cookware, newCookware, isReference) {
|
|
|
335
659
|
}
|
|
336
660
|
return cookware.push(newCookware) - 1;
|
|
337
661
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
const [num, den] = clean_str.split("/").map(Number);
|
|
342
|
-
return num / den;
|
|
662
|
+
var parseFixedValue = (input_str) => {
|
|
663
|
+
if (!numberLikeRegex.test(input_str)) {
|
|
664
|
+
return { type: "text", value: input_str };
|
|
343
665
|
}
|
|
344
|
-
|
|
666
|
+
const s = input_str.trim().replace(",", ".");
|
|
667
|
+
if (s.includes("/")) {
|
|
668
|
+
const parts = s.split("/");
|
|
669
|
+
const num = Number(parts[0]);
|
|
670
|
+
const den = Number(parts[1]);
|
|
671
|
+
return { type: "fraction", num, den };
|
|
672
|
+
}
|
|
673
|
+
return { type: "decimal", value: Number(s) };
|
|
674
|
+
};
|
|
675
|
+
function parseQuantityInput(input_str) {
|
|
676
|
+
const clean_str = String(input_str).trim();
|
|
677
|
+
if (rangeRegex.test(clean_str)) {
|
|
678
|
+
const range_parts = clean_str.split("-");
|
|
679
|
+
const min = parseFixedValue(range_parts[0].trim());
|
|
680
|
+
const max = parseFixedValue(range_parts[1].trim());
|
|
681
|
+
return { type: "range", min, max };
|
|
682
|
+
}
|
|
683
|
+
return { type: "fixed", value: parseFixedValue(clean_str) };
|
|
345
684
|
}
|
|
346
685
|
function parseSimpleMetaVar(content, varName) {
|
|
347
686
|
const varMatch = content.match(
|
|
@@ -534,7 +873,7 @@ var Recipe = class _Recipe {
|
|
|
534
873
|
const hidden = modifier === "-";
|
|
535
874
|
const reference = modifier === "&";
|
|
536
875
|
const isRecipe = modifier === "@";
|
|
537
|
-
const quantity = quantityRaw ?
|
|
876
|
+
const quantity = quantityRaw ? parseQuantityInput(quantityRaw) : void 0;
|
|
538
877
|
const idxInList = findAndUpsertIngredient(
|
|
539
878
|
this.ingredients,
|
|
540
879
|
{
|
|
@@ -561,12 +900,14 @@ var Recipe = class _Recipe {
|
|
|
561
900
|
} else if (groups.mCookwareName || groups.sCookwareName) {
|
|
562
901
|
const name = groups.mCookwareName || groups.sCookwareName;
|
|
563
902
|
const modifier = groups.mCookwareModifier || groups.sCookwareModifier;
|
|
903
|
+
const quantityRaw = groups.mCookwareQuantity || groups.sCookwareQuantity;
|
|
564
904
|
const optional = modifier === "?";
|
|
565
905
|
const hidden = modifier === "-";
|
|
566
906
|
const reference = modifier === "&";
|
|
907
|
+
const quantity = quantityRaw ? parseQuantityInput(quantityRaw) : void 0;
|
|
567
908
|
const idxInList = findAndUpsertCookware(
|
|
568
909
|
this.cookware,
|
|
569
|
-
{ name, optional, hidden },
|
|
910
|
+
{ name, quantity, optional, hidden },
|
|
570
911
|
reference
|
|
571
912
|
);
|
|
572
913
|
items.push({ type: "cookware", value: idxInList });
|
|
@@ -577,7 +918,7 @@ var Recipe = class _Recipe {
|
|
|
577
918
|
throw new Error("Timer missing units");
|
|
578
919
|
}
|
|
579
920
|
const name = groups.timerName || void 0;
|
|
580
|
-
const duration =
|
|
921
|
+
const duration = parseQuantityInput(durationStr);
|
|
581
922
|
const timerObj = {
|
|
582
923
|
name,
|
|
583
924
|
duration,
|
|
@@ -585,7 +926,7 @@ var Recipe = class _Recipe {
|
|
|
585
926
|
};
|
|
586
927
|
const idxInList = findOrPush(
|
|
587
928
|
this.timers,
|
|
588
|
-
(
|
|
929
|
+
(t2) => t2.name === timerObj.name && t2.duration === timerObj.duration && t2.unit === timerObj.unit,
|
|
589
930
|
() => timerObj
|
|
590
931
|
);
|
|
591
932
|
items.push({ type: "timer", value: idxInList });
|
|
@@ -628,8 +969,11 @@ var Recipe = class _Recipe {
|
|
|
628
969
|
throw new Error("Error scaling recipe: no initial servings value set");
|
|
629
970
|
}
|
|
630
971
|
newRecipe.ingredients = newRecipe.ingredients.map((ingredient) => {
|
|
631
|
-
if (ingredient.quantity && !
|
|
632
|
-
ingredient.quantity
|
|
972
|
+
if (ingredient.quantity && !(ingredient.quantity.type === "fixed" && ingredient.quantity.value.type === "text")) {
|
|
973
|
+
ingredient.quantity = multiplyQuantityValue(
|
|
974
|
+
ingredient.quantity,
|
|
975
|
+
factor
|
|
976
|
+
);
|
|
633
977
|
}
|
|
634
978
|
return ingredient;
|
|
635
979
|
}).filter((ingredient) => ingredient.quantity !== null);
|
|
@@ -654,6 +998,11 @@ var Recipe = class _Recipe {
|
|
|
654
998
|
}
|
|
655
999
|
return newRecipe;
|
|
656
1000
|
}
|
|
1001
|
+
/**
|
|
1002
|
+
* Gets the number of servings for the recipe.
|
|
1003
|
+
* @private
|
|
1004
|
+
* @returns The number of servings, or undefined if not set.
|
|
1005
|
+
*/
|
|
657
1006
|
getServings() {
|
|
658
1007
|
if (this.servings) {
|
|
659
1008
|
return this.servings;
|
|
@@ -716,7 +1065,7 @@ var ShoppingList = class {
|
|
|
716
1065
|
continue;
|
|
717
1066
|
}
|
|
718
1067
|
const existingIngredient = this.ingredients.find(
|
|
719
|
-
(
|
|
1068
|
+
(i2) => i2.name === ingredient.name
|
|
720
1069
|
);
|
|
721
1070
|
let addSeparate = false;
|
|
722
1071
|
try {
|
|
@@ -826,6 +1175,7 @@ var ShoppingList = class {
|
|
|
826
1175
|
export {
|
|
827
1176
|
AisleConfig,
|
|
828
1177
|
Recipe,
|
|
1178
|
+
Section,
|
|
829
1179
|
ShoppingList
|
|
830
1180
|
};
|
|
831
1181
|
//# sourceMappingURL=index.js.map
|