@tmlmt/cooklang-parser 3.0.0-alpha.17 → 3.0.0-alpha.19
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/dist/index.cjs +280 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +145 -13
- package/dist/index.d.ts +145 -13
- package/dist/index.js +277 -28
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -277,7 +277,7 @@ var nonWordCharStrict = "\\s@#~\\[\\]{(,;:!?|";
|
|
|
277
277
|
var ingredientWithAlternativeRegex = d().literal("@").startNamedGroup("ingredientModifiers").anyOf("@\\-&?").zeroOrMore().endGroup().optional().startNamedGroup("ingredientRecipeAnchor").literal("./").endGroup().optional().startGroup().startGroup().startNamedGroup("mIngredientName").notAnyOf(nonWordChar).oneOrMore().startGroup().whitespace().oneOrMore().notAnyOf(nonWordChar).oneOrMore().endGroup().oneOrMore().endGroup().positiveLookahead("\\s*(?:\\{[^\\}]*\\}|\\([^)]*\\))").endGroup().or().startNamedGroup("sIngredientName").notAnyOf(nonWordChar).zeroOrMore().notAnyOf("\\." + nonWordChar).endGroup().endGroup().startGroup().literal("{").startNamedGroup("ingredientQuantityModifier").literal("=").exactly(1).endGroup().optional().startNamedGroup("ingredientQuantity").startGroup().notAnyOf("}|%").oneOrMore().endGroup().optional().startGroup().literal("%").notAnyOf("|}").oneOrMore().lazy().endGroup().optional().startGroup().literal("|").notAnyOf("}").oneOrMore().lazy().endGroup().zeroOrMore().endGroup().literal("}").endGroup().optional().startGroup().literal("(").startNamedGroup("ingredientPreparation").notAnyOf(")").oneOrMore().lazy().endGroup().literal(")").endGroup().optional().startGroup().literal("[").startNamedGroup("ingredientNote").notAnyOf("\\]").oneOrMore().lazy().endGroup().literal("]").endGroup().optional().startNamedGroup("ingredientAlternative").startGroup().literal("|").startGroup().anyOf("@\\-&?").zeroOrMore().endGroup().optional().startGroup().literal("./").endGroup().optional().startGroup().startGroup().startGroup().notAnyOf(nonWordChar).oneOrMore().startGroup().whitespace().oneOrMore().notAnyOf(nonWordChar).oneOrMore().endGroup().oneOrMore().endGroup().positiveLookahead("\\s*(?:\\{[^\\}]*\\}|\\([^)]*\\))").endGroup().or().startGroup().notAnyOf(nonWordChar).oneOrMore().endGroup().endGroup().startGroup().literal("{").startGroup().literal("=").exactly(1).endGroup().optional().startGroup().notAnyOf("}%").oneOrMore().endGroup().optional().startGroup().literal("%").startGroup().notAnyOf("}").oneOrMore().lazy().endGroup().endGroup().optional().literal("}").endGroup().optional().startGroup().literal("(").startGroup().notAnyOf(")").oneOrMore().lazy().endGroup().literal(")").endGroup().optional().startGroup().literal("[").startGroup().notAnyOf("\\]").oneOrMore().lazy().endGroup().literal("]").endGroup().optional().endGroup().zeroOrMore().endGroup().toRegExp();
|
|
278
278
|
var inlineIngredientAlternativesRegex = new RegExp("\\|" + ingredientWithAlternativeRegex.source.slice(1));
|
|
279
279
|
var quantityAlternativeRegex = d().startNamedGroup("quantity").notAnyOf("}|%").oneOrMore().endGroup().optional().startGroup().literal("%").startNamedGroup("unit").notAnyOf("|}").oneOrMore().endGroup().endGroup().optional().startGroup().literal("|").startNamedGroup("alternative").startGroup().notAnyOf("}").oneOrMore().endGroup().zeroOrMore().endGroup().endGroup().optional().toRegExp();
|
|
280
|
-
var ingredientWithGroupKeyRegex = d().literal("@|").startNamedGroup("gIngredientGroupKey").notAnyOf(nonWordCharStrict + "/").oneOrMore().endGroup().startGroup().literal("/").startNamedGroup("gIngredientSubgroupKey").notAnyOf(nonWordCharStrict).oneOrMore().endGroup().endGroup().optional().literal("|").startNamedGroup("gIngredientModifiers").anyOf("@\\-&?").zeroOrMore().endGroup().optional().startNamedGroup("gIngredientRecipeAnchor").literal("./").endGroup().optional().startGroup().startGroup().startNamedGroup("gmIngredientName").notAnyOf(nonWordChar).oneOrMore().startGroup().whitespace().oneOrMore().notAnyOf(nonWordChar).oneOrMore().endGroup().oneOrMore().endGroup().positiveLookahead("\\s*(?:\\{[^\\}]*\\}|\\([^)]*\\))").endGroup().or().startNamedGroup("gsIngredientName").notAnyOf(nonWordChar).zeroOrMore().notAnyOf("\\." + nonWordChar).endGroup().endGroup().startGroup().literal("{").startNamedGroup("gIngredientQuantityModifier").literal("=").exactly(1).endGroup().optional().startNamedGroup("gIngredientQuantity").startGroup().notAnyOf("}|%").oneOrMore().endGroup().optional().startGroup().literal("%").notAnyOf("|}").oneOrMore().lazy().endGroup().optional().startGroup().literal("|").notAnyOf("}").oneOrMore().lazy().endGroup().zeroOrMore().endGroup().literal("}").endGroup().optional().startGroup().literal("(").startNamedGroup("gIngredientPreparation").notAnyOf(")").oneOrMore().lazy().endGroup().literal(")").endGroup().optional().toRegExp();
|
|
280
|
+
var ingredientWithGroupKeyRegex = d().literal("@|").startNamedGroup("gIngredientGroupKey").notAnyOf(nonWordCharStrict + "/").oneOrMore().endGroup().startGroup().literal("/").startNamedGroup("gIngredientSubgroupKey").notAnyOf(nonWordCharStrict).oneOrMore().endGroup().endGroup().optional().literal("|").startNamedGroup("gIngredientModifiers").anyOf("@\\-&?").zeroOrMore().endGroup().optional().startNamedGroup("gIngredientRecipeAnchor").literal("./").endGroup().optional().startGroup().startGroup().startNamedGroup("gmIngredientName").notAnyOf(nonWordChar).oneOrMore().startGroup().whitespace().oneOrMore().notAnyOf(nonWordChar).oneOrMore().endGroup().oneOrMore().endGroup().positiveLookahead("\\s*(?:\\{[^\\}]*\\}|\\([^)]*\\))").endGroup().or().startNamedGroup("gsIngredientName").notAnyOf(nonWordChar).zeroOrMore().notAnyOf("\\." + nonWordChar).endGroup().endGroup().startGroup().literal("{").startNamedGroup("gIngredientQuantityModifier").literal("=").exactly(1).endGroup().optional().startNamedGroup("gIngredientQuantity").startGroup().notAnyOf("}|%").oneOrMore().endGroup().optional().startGroup().literal("%").notAnyOf("|}").oneOrMore().lazy().endGroup().optional().startGroup().literal("|").notAnyOf("}").oneOrMore().lazy().endGroup().zeroOrMore().endGroup().literal("}").endGroup().optional().startGroup().literal("(").startNamedGroup("gIngredientPreparation").notAnyOf(")").oneOrMore().lazy().endGroup().literal(")").endGroup().optional().startGroup().literal("[").startNamedGroup("gIngredientNote").notAnyOf("\\]").oneOrMore().lazy().endGroup().literal("]").endGroup().optional().toRegExp();
|
|
281
281
|
var ingredientAliasRegex = d().startAnchor().startNamedGroup("ingredientListName").notAnyOf("|").oneOrMore().endGroup().literal("|").startNamedGroup("ingredientDisplayName").notAnyOf("|").oneOrMore().endGroup().endAnchor().toRegExp();
|
|
282
282
|
var cookwareRegex = d().literal("#").startNamedGroup("cookwareModifiers").anyOf("\\-&?").zeroOrMore().endGroup().startGroup().startGroup().startNamedGroup("mCookwareName").notAnyOf(nonWordChar).oneOrMore().startGroup().whitespace().oneOrMore().notAnyOf(nonWordChar).oneOrMore().endGroup().oneOrMore().endGroup().positiveLookahead("\\s*(?:\\{[^\\}]*\\})").endGroup().or().startNamedGroup("sCookwareName").notAnyOf(nonWordChar).zeroOrMore().notAnyOf("\\." + nonWordChar).endGroup().endGroup().startGroup().literal("{").startNamedGroup("cookwareQuantity").anyCharacter().zeroOrMore().lazy().endGroup().literal("}").endGroup().optional().toRegExp();
|
|
283
283
|
var timerRegex = d().literal("~").startNamedGroup("timerName").anyCharacter().zeroOrMore().lazy().endGroup().literal("{").startNamedGroup("timerQuantity").anyCharacter().oneOrMore().lazy().endGroup().startGroup().literal("%").startNamedGroup("timerUnit").anyCharacter().oneOrMore().lazy().endGroup().endGroup().optional().literal("}").toRegExp();
|
|
@@ -305,6 +305,7 @@ var shoppingListRegex = d().literal("[").startNamedGroup("name").anyCharacter().
|
|
|
305
305
|
var rangeRegex = d().startAnchor().digit().oneOrMore().startGroup().anyOf(".,/").exactly(1).digit().oneOrMore().endGroup().optional().literal("-").digit().oneOrMore().startGroup().anyOf(".,/").exactly(1).digit().oneOrMore().endGroup().optional().endAnchor().toRegExp();
|
|
306
306
|
var numberLikeRegex = d().startAnchor().digit().oneOrMore().startGroup().anyOf(".,/").exactly(1).digit().oneOrMore().endGroup().optional().endAnchor().toRegExp();
|
|
307
307
|
var floatRegex = d().startAnchor().digit().oneOrMore().startGroup().anyOf(".").exactly(1).digit().oneOrMore().endGroup().optional().endAnchor().toRegExp();
|
|
308
|
+
var variantTagRegex = d().startAnchor().literal("[").startNamedGroup("variantOptionalPrefix").literal("?").endGroup().optional().startNamedGroup("variantNames").notAnyOf("\\]").zeroOrMore().endGroup().literal("]").whitespace().zeroOrMore().toRegExp();
|
|
308
309
|
var mdEscaped = d().literal("\\").startCaptureGroup().anyOf("*_`").endGroup();
|
|
309
310
|
var mdInlineCode = d().literal("`").startCaptureGroup().notAnyOf("`").oneOrMore().lazy().endGroup().literal("`");
|
|
310
311
|
var mdLink = d().literal("[").startCaptureGroup().notAnyOf("\\]").oneOrMore().lazy().endGroup().literal("](").startCaptureGroup().notAnyOf(")").oneOrMore().lazy().endGroup().literal(")");
|
|
@@ -1359,9 +1360,12 @@ function flushPendingNote(section, noteItems) {
|
|
|
1359
1360
|
}
|
|
1360
1361
|
return noteItems;
|
|
1361
1362
|
}
|
|
1362
|
-
function flushPendingItems(section, items) {
|
|
1363
|
+
function flushPendingItems(section, items, stepVariants, stepOptional) {
|
|
1363
1364
|
if (items.length > 0) {
|
|
1364
|
-
|
|
1365
|
+
const step = { type: "step", items: [...items] };
|
|
1366
|
+
if (stepVariants) step.variants = stepVariants;
|
|
1367
|
+
if (stepOptional) step.optional = true;
|
|
1368
|
+
section.content.push(step);
|
|
1365
1369
|
items.length = 0;
|
|
1366
1370
|
return true;
|
|
1367
1371
|
}
|
|
@@ -1905,7 +1909,8 @@ function extractMetadata(content) {
|
|
|
1905
1909
|
"yield",
|
|
1906
1910
|
"serves",
|
|
1907
1911
|
// List fields
|
|
1908
|
-
"tags"
|
|
1912
|
+
"tags",
|
|
1913
|
+
"variants"
|
|
1909
1914
|
]);
|
|
1910
1915
|
for (const metaVar of [
|
|
1911
1916
|
"title",
|
|
@@ -1992,6 +1997,8 @@ function extractMetadata(content) {
|
|
|
1992
1997
|
}
|
|
1993
1998
|
const tags = parseListMetaVar(metadataContent, "tags");
|
|
1994
1999
|
if (tags) metadata.tags = tags;
|
|
2000
|
+
const variants = parseListMetaVar(metadataContent, "variants");
|
|
2001
|
+
if (variants) metadata.variants = variants;
|
|
1995
2002
|
const allKeys = extractAllMetadataKeys(metadataContent);
|
|
1996
2003
|
for (const key of allKeys) {
|
|
1997
2004
|
if (handledKeys.has(key)) continue;
|
|
@@ -2379,8 +2386,10 @@ var Section = class {
|
|
|
2379
2386
|
/**
|
|
2380
2387
|
* Creates an instance of Section.
|
|
2381
2388
|
* @param name - The name of the section. Defaults to an empty string.
|
|
2389
|
+
* @param variants - Optional variant names for this section.
|
|
2390
|
+
* @param optional - Whether the section is optional.
|
|
2382
2391
|
*/
|
|
2383
|
-
constructor(name = "") {
|
|
2392
|
+
constructor(name = "", variants, optional) {
|
|
2384
2393
|
/**
|
|
2385
2394
|
* The name of the section. Can be an empty string for the default (first) section.
|
|
2386
2395
|
* @defaultValue `""`
|
|
@@ -2388,7 +2397,13 @@ var Section = class {
|
|
|
2388
2397
|
__publicField(this, "name");
|
|
2389
2398
|
/** An array of steps and notes that make up the content of the section. */
|
|
2390
2399
|
__publicField(this, "content", []);
|
|
2400
|
+
/** Optional list of variant names this section belongs to. */
|
|
2401
|
+
__publicField(this, "variants");
|
|
2402
|
+
/** Whether the section has been marked as optional ([?]) */
|
|
2403
|
+
__publicField(this, "optional");
|
|
2391
2404
|
this.name = name;
|
|
2405
|
+
if (variants) this.variants = variants;
|
|
2406
|
+
if (optional) this.optional = true;
|
|
2392
2407
|
}
|
|
2393
2408
|
/**
|
|
2394
2409
|
* Checks if the section is blank (has no name and no content).
|
|
@@ -2748,7 +2763,8 @@ var _Recipe = class _Recipe {
|
|
|
2748
2763
|
*/
|
|
2749
2764
|
__publicField(this, "choices", {
|
|
2750
2765
|
ingredientItems: /* @__PURE__ */ new Map(),
|
|
2751
|
-
ingredientGroups: /* @__PURE__ */ new Map()
|
|
2766
|
+
ingredientGroups: /* @__PURE__ */ new Map(),
|
|
2767
|
+
variants: []
|
|
2752
2768
|
});
|
|
2753
2769
|
/**
|
|
2754
2770
|
* The parsed recipe ingredients.
|
|
@@ -3065,6 +3081,10 @@ var _Recipe = class _Recipe {
|
|
|
3065
3081
|
if (itemQuantity) {
|
|
3066
3082
|
Object.assign(alternative, itemQuantity);
|
|
3067
3083
|
}
|
|
3084
|
+
const note = groups.gIngredientNote?.trim();
|
|
3085
|
+
if (note) {
|
|
3086
|
+
alternative.note = note;
|
|
3087
|
+
}
|
|
3068
3088
|
const existingSubgroups = this.choices.ingredientGroups.get(groupKey);
|
|
3069
3089
|
const existingAlternativesFlat = existingSubgroups?.flat();
|
|
3070
3090
|
function upsertAlternativeToIngredient(ingredients, ingredientIdx, newAlternativeIdx) {
|
|
@@ -3158,6 +3178,8 @@ var _Recipe = class _Recipe {
|
|
|
3158
3178
|
/** @internal */
|
|
3159
3179
|
collectQuantityGroups(options) {
|
|
3160
3180
|
const { section, step, choices } = options || {};
|
|
3181
|
+
const activeVariant = choices?.variant;
|
|
3182
|
+
const isDefaultVariant = activeVariant === void 0 || activeVariant === "*";
|
|
3161
3183
|
const sectionsToProcess = section !== void 0 ? (() => {
|
|
3162
3184
|
const idx = typeof section === "number" ? section : this.sections.indexOf(section);
|
|
3163
3185
|
return idx >= 0 && idx < this.sections.length ? [this.sections[idx]] : [];
|
|
@@ -3165,12 +3187,29 @@ var _Recipe = class _Recipe {
|
|
|
3165
3187
|
const ingredientGroups = /* @__PURE__ */ new Map();
|
|
3166
3188
|
const selectedIndices = /* @__PURE__ */ new Set();
|
|
3167
3189
|
const referencedIndices = /* @__PURE__ */ new Set();
|
|
3190
|
+
const dynamicOptionalIndices = /* @__PURE__ */ new Set();
|
|
3168
3191
|
for (const currentSection of sectionsToProcess) {
|
|
3192
|
+
if (currentSection.variants) {
|
|
3193
|
+
if (isDefaultVariant) {
|
|
3194
|
+
if (!currentSection.variants.includes("*")) continue;
|
|
3195
|
+
} else {
|
|
3196
|
+
if (!currentSection.variants.includes(activeVariant)) continue;
|
|
3197
|
+
}
|
|
3198
|
+
}
|
|
3169
3199
|
const allSteps = currentSection.content.filter(
|
|
3170
3200
|
(item) => item.type === "step"
|
|
3171
3201
|
);
|
|
3202
|
+
const isOptionalSection = currentSection.optional === true;
|
|
3172
3203
|
const stepsToProcess = step === void 0 ? allSteps : typeof step === "number" ? step >= 0 && step < allSteps.length ? [allSteps[step]] : [] : allSteps.includes(step) ? [step] : [];
|
|
3173
3204
|
for (const currentStep of stepsToProcess) {
|
|
3205
|
+
if (currentStep.variants) {
|
|
3206
|
+
if (isDefaultVariant) {
|
|
3207
|
+
if (!currentStep.variants.includes("*")) continue;
|
|
3208
|
+
} else {
|
|
3209
|
+
if (!currentStep.variants.includes(activeVariant)) continue;
|
|
3210
|
+
}
|
|
3211
|
+
}
|
|
3212
|
+
const isOptionalStep = currentStep.optional === true || isOptionalSection;
|
|
3174
3213
|
for (const item of currentStep.items.filter(
|
|
3175
3214
|
(item2) => item2.type === "ingredient"
|
|
3176
3215
|
)) {
|
|
@@ -3182,18 +3221,53 @@ var _Recipe = class _Recipe {
|
|
|
3182
3221
|
if (isGrouped) {
|
|
3183
3222
|
const groupChoice = choices?.ingredientGroups?.get(item.group);
|
|
3184
3223
|
hasExplicitChoice = groupChoice !== void 0;
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3224
|
+
if (!hasExplicitChoice && !isDefaultVariant) {
|
|
3225
|
+
const matchingSubgroupIdx = groupSubgroups?.findIndex(
|
|
3226
|
+
(sg) => sg.some(
|
|
3227
|
+
(alt) => alt.note && alt.note.toLowerCase().includes(activeVariant.toLowerCase())
|
|
3228
|
+
)
|
|
3229
|
+
);
|
|
3230
|
+
if (matchingSubgroupIdx !== void 0 && matchingSubgroupIdx >= 0) {
|
|
3231
|
+
const matchedSubgroup = groupSubgroups[matchingSubgroupIdx];
|
|
3232
|
+
isSelected = matchedSubgroup.some(
|
|
3233
|
+
(alt) => alt.itemId === item.id
|
|
3234
|
+
);
|
|
3235
|
+
hasExplicitChoice = true;
|
|
3236
|
+
selectedAltIndex = 0;
|
|
3237
|
+
} else {
|
|
3238
|
+
const targetSubgroupIndex = 0;
|
|
3239
|
+
const selectedSubgroup = groupSubgroups?.[targetSubgroupIndex];
|
|
3240
|
+
isSelected = selectedSubgroup?.some((alt) => alt.itemId === item.id) ?? false;
|
|
3241
|
+
}
|
|
3242
|
+
} else {
|
|
3243
|
+
const targetSubgroupIndex = groupChoice ?? 0;
|
|
3244
|
+
const selectedSubgroup = groupSubgroups?.[targetSubgroupIndex];
|
|
3245
|
+
isSelected = selectedSubgroup?.some((alt) => alt.itemId === item.id) ?? false;
|
|
3246
|
+
}
|
|
3188
3247
|
} else {
|
|
3189
3248
|
const itemChoice = choices?.ingredientItems?.get(item.id);
|
|
3190
3249
|
hasExplicitChoice = itemChoice !== void 0;
|
|
3191
|
-
|
|
3250
|
+
if (!hasExplicitChoice && !isDefaultVariant) {
|
|
3251
|
+
const matchingIndices = item.alternatives.map((alt, idx) => ({ alt, idx })).filter(
|
|
3252
|
+
({ alt }) => alt.note && alt.note.toLowerCase().includes(activeVariant.toLowerCase())
|
|
3253
|
+
).map(({ idx }) => idx);
|
|
3254
|
+
if (matchingIndices.length > 0) {
|
|
3255
|
+
selectedAltIndex = matchingIndices[0];
|
|
3256
|
+
hasExplicitChoice = true;
|
|
3257
|
+
} else {
|
|
3258
|
+
selectedAltIndex = itemChoice ?? 0;
|
|
3259
|
+
}
|
|
3260
|
+
} else {
|
|
3261
|
+
selectedAltIndex = itemChoice ?? 0;
|
|
3262
|
+
}
|
|
3192
3263
|
isSelected = true;
|
|
3193
3264
|
}
|
|
3194
3265
|
const alternative = item.alternatives[selectedAltIndex];
|
|
3195
3266
|
if (!alternative || !isSelected) continue;
|
|
3196
3267
|
selectedIndices.add(alternative.index);
|
|
3268
|
+
if (isOptionalStep) {
|
|
3269
|
+
dynamicOptionalIndices.add(alternative.index);
|
|
3270
|
+
}
|
|
3197
3271
|
const allAltsFlat = isGrouped ? groupSubgroups.flat() : item.alternatives;
|
|
3198
3272
|
for (const alt of allAltsFlat) {
|
|
3199
3273
|
referencedIndices.add(alt.index);
|
|
@@ -3299,7 +3373,12 @@ var _Recipe = class _Recipe {
|
|
|
3299
3373
|
}
|
|
3300
3374
|
}
|
|
3301
3375
|
}
|
|
3302
|
-
return {
|
|
3376
|
+
return {
|
|
3377
|
+
ingredientGroups,
|
|
3378
|
+
selectedIndices,
|
|
3379
|
+
referencedIndices,
|
|
3380
|
+
dynamicOptionalIndices
|
|
3381
|
+
};
|
|
3303
3382
|
}
|
|
3304
3383
|
/**
|
|
3305
3384
|
* Gets the raw (unprocessed) quantity groups for each ingredient, before
|
|
@@ -3318,12 +3397,21 @@ var _Recipe = class _Recipe {
|
|
|
3318
3397
|
* ```
|
|
3319
3398
|
*/
|
|
3320
3399
|
getRawQuantityGroups(options) {
|
|
3321
|
-
const {
|
|
3400
|
+
const {
|
|
3401
|
+
ingredientGroups,
|
|
3402
|
+
selectedIndices,
|
|
3403
|
+
referencedIndices,
|
|
3404
|
+
dynamicOptionalIndices
|
|
3405
|
+
} = this.collectQuantityGroups(options);
|
|
3322
3406
|
const result = [];
|
|
3323
3407
|
for (let index = 0; index < this.ingredients.length; index++) {
|
|
3324
3408
|
if (!referencedIndices.has(index)) continue;
|
|
3325
3409
|
const orig = this.ingredients[index];
|
|
3326
3410
|
const usedAsPrimary = selectedIndices.has(index);
|
|
3411
|
+
let flags = orig.flags;
|
|
3412
|
+
if (dynamicOptionalIndices.has(index) && !flags?.includes("optional")) {
|
|
3413
|
+
flags = [...flags ?? [], "optional"];
|
|
3414
|
+
}
|
|
3327
3415
|
const quantities = [];
|
|
3328
3416
|
if (usedAsPrimary) {
|
|
3329
3417
|
const groupsForIng = ingredientGroups.get(index);
|
|
@@ -3336,7 +3424,7 @@ var _Recipe = class _Recipe {
|
|
|
3336
3424
|
result.push({
|
|
3337
3425
|
name: orig.name,
|
|
3338
3426
|
...usedAsPrimary && { usedAsPrimary: true },
|
|
3339
|
-
...
|
|
3427
|
+
...flags && { flags },
|
|
3340
3428
|
quantities
|
|
3341
3429
|
});
|
|
3342
3430
|
}
|
|
@@ -3370,15 +3458,24 @@ var _Recipe = class _Recipe {
|
|
|
3370
3458
|
* ```
|
|
3371
3459
|
*/
|
|
3372
3460
|
getIngredientQuantities(options) {
|
|
3373
|
-
const {
|
|
3461
|
+
const {
|
|
3462
|
+
ingredientGroups,
|
|
3463
|
+
selectedIndices,
|
|
3464
|
+
referencedIndices,
|
|
3465
|
+
dynamicOptionalIndices
|
|
3466
|
+
} = this.collectQuantityGroups(options);
|
|
3374
3467
|
const result = [];
|
|
3375
3468
|
for (let index = 0; index < this.ingredients.length; index++) {
|
|
3376
3469
|
if (!referencedIndices.has(index)) continue;
|
|
3377
3470
|
const orig = this.ingredients[index];
|
|
3471
|
+
let flags = orig.flags;
|
|
3472
|
+
if (dynamicOptionalIndices.has(index) && !flags?.includes("optional")) {
|
|
3473
|
+
flags = [...flags ?? [], "optional"];
|
|
3474
|
+
}
|
|
3378
3475
|
const ing = {
|
|
3379
3476
|
name: orig.name,
|
|
3380
3477
|
...orig.preparation && { preparation: orig.preparation },
|
|
3381
|
-
...
|
|
3478
|
+
...flags && { flags },
|
|
3382
3479
|
...orig.extras && { extras: orig.extras }
|
|
3383
3480
|
};
|
|
3384
3481
|
if (selectedIndices.has(index)) {
|
|
@@ -3429,6 +3526,59 @@ var _Recipe = class _Recipe {
|
|
|
3429
3526
|
}
|
|
3430
3527
|
return result;
|
|
3431
3528
|
}
|
|
3529
|
+
/**
|
|
3530
|
+
* Returns the list of cookware items that are used in the active variant.
|
|
3531
|
+
* Cookware in steps/sections not matching the active variant are excluded.
|
|
3532
|
+
* Hidden cookware is always excluded.
|
|
3533
|
+
*
|
|
3534
|
+
* @param options - Options for filtering:
|
|
3535
|
+
* - `choices`: The choices to apply (only `variant` is used)
|
|
3536
|
+
* @returns Array of Cookware objects referenced by active steps
|
|
3537
|
+
*
|
|
3538
|
+
* @example
|
|
3539
|
+
* ```typescript
|
|
3540
|
+
* // Get all cookware for the default variant
|
|
3541
|
+
* const cookware = recipe.getCookwareForVariant();
|
|
3542
|
+
*
|
|
3543
|
+
* // Get cookware for a specific variant
|
|
3544
|
+
* const veganCookware = recipe.getCookwareForVariant({ choices: { variant: 'vegan' } });
|
|
3545
|
+
* ```
|
|
3546
|
+
*/
|
|
3547
|
+
getCookwareForVariant(options) {
|
|
3548
|
+
const { choices } = options || {};
|
|
3549
|
+
const activeVariant = choices?.variant;
|
|
3550
|
+
const isDefaultVariant = activeVariant === void 0 || activeVariant === "*";
|
|
3551
|
+
const cookwareIndices = /* @__PURE__ */ new Set();
|
|
3552
|
+
for (const currentSection of this.sections) {
|
|
3553
|
+
if (currentSection.variants) {
|
|
3554
|
+
if (isDefaultVariant) {
|
|
3555
|
+
if (!currentSection.variants.includes("*")) continue;
|
|
3556
|
+
} else {
|
|
3557
|
+
if (!currentSection.variants.includes(activeVariant)) continue;
|
|
3558
|
+
}
|
|
3559
|
+
}
|
|
3560
|
+
const allSteps = currentSection.content.filter(
|
|
3561
|
+
(item) => item.type === "step"
|
|
3562
|
+
);
|
|
3563
|
+
for (const currentStep of allSteps) {
|
|
3564
|
+
if (currentStep.variants) {
|
|
3565
|
+
if (isDefaultVariant) {
|
|
3566
|
+
if (!currentStep.variants.includes("*")) continue;
|
|
3567
|
+
} else {
|
|
3568
|
+
if (!currentStep.variants.includes(activeVariant)) continue;
|
|
3569
|
+
}
|
|
3570
|
+
}
|
|
3571
|
+
for (const item of currentStep.items) {
|
|
3572
|
+
if (item.type === "cookware") {
|
|
3573
|
+
cookwareIndices.add(item.index);
|
|
3574
|
+
}
|
|
3575
|
+
}
|
|
3576
|
+
}
|
|
3577
|
+
}
|
|
3578
|
+
return this.cookware.filter(
|
|
3579
|
+
(cw, idx) => cookwareIndices.has(idx) && !cw.flags?.includes("hidden")
|
|
3580
|
+
);
|
|
3581
|
+
}
|
|
3432
3582
|
/**
|
|
3433
3583
|
* Parses a recipe from a string.
|
|
3434
3584
|
* @param content - The recipe content to parse.
|
|
@@ -3444,9 +3594,14 @@ var _Recipe = class _Recipe {
|
|
|
3444
3594
|
const items = [];
|
|
3445
3595
|
let noteText = "";
|
|
3446
3596
|
let inNote = false;
|
|
3597
|
+
let stepVariants;
|
|
3598
|
+
let stepOptional;
|
|
3599
|
+
const discoveredVariants = /* @__PURE__ */ new Set();
|
|
3447
3600
|
for (const line of cleanContent) {
|
|
3448
3601
|
if (line.trim().length === 0) {
|
|
3449
|
-
flushPendingItems(section, items);
|
|
3602
|
+
flushPendingItems(section, items, stepVariants, stepOptional);
|
|
3603
|
+
stepVariants = void 0;
|
|
3604
|
+
stepOptional = void 0;
|
|
3450
3605
|
flushPendingNote(
|
|
3451
3606
|
section,
|
|
3452
3607
|
noteText ? this._parseNoteText(noteText) : []
|
|
@@ -3457,26 +3612,48 @@ var _Recipe = class _Recipe {
|
|
|
3457
3612
|
continue;
|
|
3458
3613
|
}
|
|
3459
3614
|
if (line.startsWith("=")) {
|
|
3460
|
-
flushPendingItems(section, items);
|
|
3615
|
+
flushPendingItems(section, items, stepVariants, stepOptional);
|
|
3616
|
+
stepVariants = void 0;
|
|
3617
|
+
stepOptional = void 0;
|
|
3461
3618
|
flushPendingNote(
|
|
3462
3619
|
section,
|
|
3463
3620
|
noteText ? this._parseNoteText(noteText) : []
|
|
3464
3621
|
);
|
|
3465
3622
|
noteText = "";
|
|
3623
|
+
let sectionName = line.replace(/^=+|=+$/g, "").trim();
|
|
3624
|
+
let sectionVariants;
|
|
3625
|
+
let sectionOptional;
|
|
3626
|
+
const sectionVarMatch = sectionName.match(variantTagRegex);
|
|
3627
|
+
if (sectionVarMatch?.groups) {
|
|
3628
|
+
const isOptionalPrefix = sectionVarMatch.groups.variantOptionalPrefix === "?";
|
|
3629
|
+
const names = (sectionVarMatch.groups.variantNames ?? "").split(",").map((n2) => n2.trim()).filter((n2) => n2.length > 0);
|
|
3630
|
+
if (names.length > 0) {
|
|
3631
|
+
sectionVariants = names;
|
|
3632
|
+
for (const v of names) discoveredVariants.add(v);
|
|
3633
|
+
}
|
|
3634
|
+
if (isOptionalPrefix) {
|
|
3635
|
+
sectionOptional = true;
|
|
3636
|
+
}
|
|
3637
|
+
sectionName = sectionName.slice(sectionVarMatch[0].length).trim();
|
|
3638
|
+
}
|
|
3466
3639
|
if (this.sections.length === 0 && section.isBlank()) {
|
|
3467
|
-
section.name =
|
|
3640
|
+
section.name = sectionName;
|
|
3641
|
+
if (sectionVariants) section.variants = sectionVariants;
|
|
3642
|
+
if (sectionOptional) section.optional = true;
|
|
3468
3643
|
} else {
|
|
3469
3644
|
if (!section.isBlank()) {
|
|
3470
3645
|
this.sections.push(section);
|
|
3471
3646
|
}
|
|
3472
|
-
section = new Section(
|
|
3647
|
+
section = new Section(sectionName, sectionVariants, sectionOptional);
|
|
3473
3648
|
}
|
|
3474
3649
|
blankLineBefore = true;
|
|
3475
3650
|
inNote = false;
|
|
3476
3651
|
continue;
|
|
3477
3652
|
}
|
|
3478
3653
|
if (blankLineBefore && line.startsWith(">")) {
|
|
3479
|
-
flushPendingItems(section, items);
|
|
3654
|
+
flushPendingItems(section, items, stepVariants, stepOptional);
|
|
3655
|
+
stepVariants = void 0;
|
|
3656
|
+
stepOptional = void 0;
|
|
3480
3657
|
noteText = line.substring(1).trim();
|
|
3481
3658
|
inNote = true;
|
|
3482
3659
|
blankLineBefore = false;
|
|
@@ -3491,11 +3668,31 @@ var _Recipe = class _Recipe {
|
|
|
3491
3668
|
blankLineBefore = false;
|
|
3492
3669
|
continue;
|
|
3493
3670
|
}
|
|
3671
|
+
let currentLine = line;
|
|
3672
|
+
if (items.length === 0) {
|
|
3673
|
+
const varMatch = currentLine.match(variantTagRegex);
|
|
3674
|
+
if (varMatch?.groups) {
|
|
3675
|
+
const isOptionalPrefix = varMatch.groups.variantOptionalPrefix === "?";
|
|
3676
|
+
const names = (varMatch.groups.variantNames ?? "").split(",").map((n2) => n2.trim()).filter((n2) => n2.length > 0);
|
|
3677
|
+
if (names.length > 0) {
|
|
3678
|
+
stepVariants = names;
|
|
3679
|
+
for (const v of names) discoveredVariants.add(v);
|
|
3680
|
+
}
|
|
3681
|
+
if (isOptionalPrefix) {
|
|
3682
|
+
stepOptional = true;
|
|
3683
|
+
}
|
|
3684
|
+
currentLine = currentLine.slice(varMatch[0].length);
|
|
3685
|
+
if (currentLine.trim().length === 0) {
|
|
3686
|
+
blankLineBefore = false;
|
|
3687
|
+
continue;
|
|
3688
|
+
}
|
|
3689
|
+
}
|
|
3690
|
+
}
|
|
3494
3691
|
let cursor = 0;
|
|
3495
|
-
for (const match of
|
|
3692
|
+
for (const match of currentLine.matchAll(tokensRegex)) {
|
|
3496
3693
|
const idx = match.index;
|
|
3497
3694
|
if (idx > cursor) {
|
|
3498
|
-
items.push(...parseMarkdownSegments(
|
|
3695
|
+
items.push(...parseMarkdownSegments(currentLine.slice(cursor, idx)));
|
|
3499
3696
|
}
|
|
3500
3697
|
const groups = match.groups;
|
|
3501
3698
|
if (groups.mIngredientName || groups.sIngredientName) {
|
|
@@ -3556,16 +3753,21 @@ var _Recipe = class _Recipe {
|
|
|
3556
3753
|
}
|
|
3557
3754
|
cursor = idx + match[0].length;
|
|
3558
3755
|
}
|
|
3559
|
-
if (cursor <
|
|
3560
|
-
items.push(...parseMarkdownSegments(
|
|
3756
|
+
if (cursor < currentLine.length) {
|
|
3757
|
+
items.push(...parseMarkdownSegments(currentLine.slice(cursor)));
|
|
3561
3758
|
}
|
|
3562
3759
|
blankLineBefore = false;
|
|
3563
3760
|
}
|
|
3564
|
-
flushPendingItems(section, items);
|
|
3761
|
+
flushPendingItems(section, items, stepVariants, stepOptional);
|
|
3565
3762
|
flushPendingNote(section, noteText ? this._parseNoteText(noteText) : []);
|
|
3566
3763
|
if (!section.isBlank()) {
|
|
3567
3764
|
this.sections.push(section);
|
|
3568
3765
|
}
|
|
3766
|
+
const metaVariants = this.metadata.variants ?? [];
|
|
3767
|
+
const allVariants = /* @__PURE__ */ new Set([...metaVariants, ...discoveredVariants]);
|
|
3768
|
+
if (allVariants.size > 0) {
|
|
3769
|
+
this.choices.variants = [...allVariants];
|
|
3770
|
+
}
|
|
3569
3771
|
this._populateIngredientQuantities();
|
|
3570
3772
|
}
|
|
3571
3773
|
/**
|
|
@@ -3876,7 +4078,11 @@ var _Recipe = class _Recipe {
|
|
|
3876
4078
|
newRecipe.metadata = deepClone(this.metadata);
|
|
3877
4079
|
newRecipe.ingredients = deepClone(this.ingredients);
|
|
3878
4080
|
newRecipe.sections = this.sections.map((section) => {
|
|
3879
|
-
const newSection = new Section(
|
|
4081
|
+
const newSection = new Section(
|
|
4082
|
+
section.name,
|
|
4083
|
+
section.variants,
|
|
4084
|
+
section.optional
|
|
4085
|
+
);
|
|
3880
4086
|
newSection.content = deepClone(section.content);
|
|
3881
4087
|
return newSection;
|
|
3882
4088
|
});
|
|
@@ -4749,12 +4955,52 @@ function isAlternativeSelected(recipe, choices, item, alternativeIndex) {
|
|
|
4749
4955
|
const selectedIndex = choices?.ingredientItems?.get(item.id);
|
|
4750
4956
|
return alternativeIndex === selectedIndex;
|
|
4751
4957
|
}
|
|
4958
|
+
function isSectionActive(section, variant) {
|
|
4959
|
+
if (!section.variants) return true;
|
|
4960
|
+
const isDefault = variant === void 0 || variant === "*";
|
|
4961
|
+
if (isDefault) {
|
|
4962
|
+
return section.variants.includes("*");
|
|
4963
|
+
}
|
|
4964
|
+
return section.variants.includes(variant);
|
|
4965
|
+
}
|
|
4966
|
+
function isStepActive(step, variant) {
|
|
4967
|
+
if (!step.variants) return true;
|
|
4968
|
+
const isDefault = variant === void 0 || variant === "*";
|
|
4969
|
+
if (isDefault) {
|
|
4970
|
+
return step.variants.includes("*");
|
|
4971
|
+
}
|
|
4972
|
+
return step.variants.includes(variant);
|
|
4973
|
+
}
|
|
4974
|
+
function getEffectiveChoices(recipe, variant) {
|
|
4975
|
+
const choices = { variant };
|
|
4976
|
+
if (variant === void 0 || variant === "*") return choices;
|
|
4977
|
+
const variantLower = variant.toLowerCase();
|
|
4978
|
+
for (const [itemId, alternatives] of recipe.choices.ingredientItems) {
|
|
4979
|
+
const matchIdx = alternatives.findIndex(
|
|
4980
|
+
(alt) => alt.note && alt.note.toLowerCase().includes(variantLower)
|
|
4981
|
+
);
|
|
4982
|
+
if (matchIdx >= 0) {
|
|
4983
|
+
if (!choices.ingredientItems) choices.ingredientItems = /* @__PURE__ */ new Map();
|
|
4984
|
+
choices.ingredientItems.set(itemId, matchIdx);
|
|
4985
|
+
}
|
|
4986
|
+
}
|
|
4987
|
+
for (const [groupId, subgroups] of recipe.choices.ingredientGroups) {
|
|
4988
|
+
const matchIdx = subgroups.findIndex(
|
|
4989
|
+
(sg) => sg.some(
|
|
4990
|
+
(alt) => alt.note && alt.note.toLowerCase().includes(variantLower)
|
|
4991
|
+
)
|
|
4992
|
+
);
|
|
4993
|
+
if (matchIdx >= 0) {
|
|
4994
|
+
if (!choices.ingredientGroups) choices.ingredientGroups = /* @__PURE__ */ new Map();
|
|
4995
|
+
choices.ingredientGroups.set(groupId, matchIdx);
|
|
4996
|
+
}
|
|
4997
|
+
}
|
|
4998
|
+
return choices;
|
|
4999
|
+
}
|
|
4752
5000
|
export {
|
|
4753
|
-
BadIndentationError,
|
|
4754
5001
|
CategoryConfig,
|
|
4755
5002
|
NoProductCatalogForCartError,
|
|
4756
5003
|
NoShoppingListForCartError,
|
|
4757
|
-
NoTabAsIndentError,
|
|
4758
5004
|
Pantry,
|
|
4759
5005
|
ProductCatalog,
|
|
4760
5006
|
Recipe,
|
|
@@ -4769,11 +5015,14 @@ export {
|
|
|
4769
5015
|
formatQuantityWithUnit,
|
|
4770
5016
|
formatSingleValue,
|
|
4771
5017
|
formatUnit,
|
|
5018
|
+
getEffectiveChoices,
|
|
4772
5019
|
hasAlternatives,
|
|
4773
5020
|
isAlternativeSelected,
|
|
4774
5021
|
isAndGroup,
|
|
4775
5022
|
isGroupedItem,
|
|
5023
|
+
isSectionActive,
|
|
4776
5024
|
isSimpleGroup,
|
|
5025
|
+
isStepActive,
|
|
4777
5026
|
renderFractionAsVulgar
|
|
4778
5027
|
};
|
|
4779
5028
|
/* v8 ignore else -- @preserve */
|