@tmlmt/cooklang-parser 1.0.7 → 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 +422 -62
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +78 -3
- package/dist/index.d.ts +78 -3
- package/dist/index.js +421 -62
- 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
|
-
|
|
342
|
-
|
|
662
|
+
var parseFixedValue = (input_str) => {
|
|
663
|
+
if (!numberLikeRegex.test(input_str)) {
|
|
664
|
+
return { type: "text", value: input_str };
|
|
665
|
+
}
|
|
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 };
|
|
343
672
|
}
|
|
344
|
-
return Number(
|
|
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
|
{
|
|
@@ -548,16 +887,27 @@ var Recipe = class _Recipe {
|
|
|
548
887
|
},
|
|
549
888
|
reference
|
|
550
889
|
);
|
|
551
|
-
|
|
890
|
+
const newItem = {
|
|
891
|
+
type: "ingredient",
|
|
892
|
+
value: idxInList
|
|
893
|
+
};
|
|
894
|
+
if (reference) {
|
|
895
|
+
newItem.partialQuantity = quantity;
|
|
896
|
+
newItem.partialUnit = units2;
|
|
897
|
+
newItem.partialPreparation = preparation;
|
|
898
|
+
}
|
|
899
|
+
items.push(newItem);
|
|
552
900
|
} else if (groups.mCookwareName || groups.sCookwareName) {
|
|
553
901
|
const name = groups.mCookwareName || groups.sCookwareName;
|
|
554
902
|
const modifier = groups.mCookwareModifier || groups.sCookwareModifier;
|
|
903
|
+
const quantityRaw = groups.mCookwareQuantity || groups.sCookwareQuantity;
|
|
555
904
|
const optional = modifier === "?";
|
|
556
905
|
const hidden = modifier === "-";
|
|
557
906
|
const reference = modifier === "&";
|
|
907
|
+
const quantity = quantityRaw ? parseQuantityInput(quantityRaw) : void 0;
|
|
558
908
|
const idxInList = findAndUpsertCookware(
|
|
559
909
|
this.cookware,
|
|
560
|
-
{ name, optional, hidden },
|
|
910
|
+
{ name, quantity, optional, hidden },
|
|
561
911
|
reference
|
|
562
912
|
);
|
|
563
913
|
items.push({ type: "cookware", value: idxInList });
|
|
@@ -568,7 +918,7 @@ var Recipe = class _Recipe {
|
|
|
568
918
|
throw new Error("Timer missing units");
|
|
569
919
|
}
|
|
570
920
|
const name = groups.timerName || void 0;
|
|
571
|
-
const duration =
|
|
921
|
+
const duration = parseQuantityInput(durationStr);
|
|
572
922
|
const timerObj = {
|
|
573
923
|
name,
|
|
574
924
|
duration,
|
|
@@ -576,7 +926,7 @@ var Recipe = class _Recipe {
|
|
|
576
926
|
};
|
|
577
927
|
const idxInList = findOrPush(
|
|
578
928
|
this.timers,
|
|
579
|
-
(
|
|
929
|
+
(t2) => t2.name === timerObj.name && t2.duration === timerObj.duration && t2.unit === timerObj.unit,
|
|
580
930
|
() => timerObj
|
|
581
931
|
);
|
|
582
932
|
items.push({ type: "timer", value: idxInList });
|
|
@@ -619,8 +969,11 @@ var Recipe = class _Recipe {
|
|
|
619
969
|
throw new Error("Error scaling recipe: no initial servings value set");
|
|
620
970
|
}
|
|
621
971
|
newRecipe.ingredients = newRecipe.ingredients.map((ingredient) => {
|
|
622
|
-
if (ingredient.quantity && !
|
|
623
|
-
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
|
+
);
|
|
624
977
|
}
|
|
625
978
|
return ingredient;
|
|
626
979
|
}).filter((ingredient) => ingredient.quantity !== null);
|
|
@@ -645,6 +998,11 @@ var Recipe = class _Recipe {
|
|
|
645
998
|
}
|
|
646
999
|
return newRecipe;
|
|
647
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
|
+
*/
|
|
648
1006
|
getServings() {
|
|
649
1007
|
if (this.servings) {
|
|
650
1008
|
return this.servings;
|
|
@@ -707,7 +1065,7 @@ var ShoppingList = class {
|
|
|
707
1065
|
continue;
|
|
708
1066
|
}
|
|
709
1067
|
const existingIngredient = this.ingredients.find(
|
|
710
|
-
(
|
|
1068
|
+
(i2) => i2.name === ingredient.name
|
|
711
1069
|
);
|
|
712
1070
|
let addSeparate = false;
|
|
713
1071
|
try {
|
|
@@ -817,6 +1175,7 @@ var ShoppingList = class {
|
|
|
817
1175
|
export {
|
|
818
1176
|
AisleConfig,
|
|
819
1177
|
Recipe,
|
|
1178
|
+
Section,
|
|
820
1179
|
ShoppingList
|
|
821
1180
|
};
|
|
822
1181
|
//# sourceMappingURL=index.js.map
|