@tmlmt/cooklang-parser 1.0.3

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 [fullname]
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,94 @@
1
+ # Cooklang Parser
2
+
3
+ A typescript library to parse and manipulate [cooklang](https://cooklang.org/) recipes.
4
+
5
+ ## Introduction
6
+
7
+ This library provides a set of tools to work with recipes written in the Cooklang format. It allows you to parse recipes, extract ingredients, cookware, and timers, scale recipes, and generate shopping lists.
8
+
9
+ ## Features
10
+
11
+ - **Cooklang Compliant:** Fully compliant with the Cooklang specifications.
12
+ - **Useful modifiers:** in line with the same in the canonical cooklang parser in Rust ([cooklang-rs](https://github.com/cooklang/cooklang-rs/blob/main/extensions.md))
13
+ - `@`: referenced recipe
14
+ - `&`: referenced ingredient (quantities will be added)
15
+ - `-`: hidden ingredient
16
+ - `?`: optional ingredient
17
+ - **Recipe Parsing:** Parse Cooklang recipes to extract metadata, ingredients, cookware, timers, and steps.
18
+ - **Recipe Scaling:** Scale recipes by a given factor.
19
+ - **Shopping Lists:** Generate shopping lists from one or more recipes.
20
+ - **Aisle Configuration:** Categorize shopping list ingredients based on a custom aisle configuration.
21
+ - **Typescript:** Written in Typescript, providing type safety for all the data structures.
22
+
23
+ ## Quick start
24
+
25
+ Install the package with your favorite package manager e.g. `npm install @tmlmt/cooklang-parser`
26
+
27
+ To get started, you can use the `Recipe` class to parse a cooklang recipe:
28
+
29
+ ```typescript
30
+ import { Recipe } from "@tmlmt/cooklang-parser";
31
+
32
+ const recipeString = `
33
+ ---
34
+ title: Pancakes
35
+ tags: breakfast, easy
36
+ ---
37
+
38
+ Crack the @eggs{3} into a bowl, and add @coarse salt{}.
39
+ Melt the @butter{50%g} in a #pan on medium heat.
40
+ Cook for ~{15%minutes}.
41
+ Serve hot.
42
+ `;
43
+
44
+ const recipe = new Recipe(recipeString);
45
+
46
+ console.log(recipe.metadata.title); // "Pancakes"
47
+ console.log(recipe.ingredients);
48
+ console.log(recipe.cookware);
49
+ console.log(recipe.timers);
50
+ ```
51
+
52
+ You can also create a shopping list from multiple recipes:
53
+
54
+ ```typescript
55
+ import { ShoppingList, Recipe } from "@tmlmt/cooklang-parser";
56
+
57
+ const recipe1 = new Recipe(/* ... */);
58
+ const recipe2 = new Recipe(/* ... */);
59
+
60
+ const shoppingList = new ShoppingList();
61
+ shoppingList.add_recipe(recipe1);
62
+ shoppingList.add_recipe(recipe2);
63
+
64
+ console.log(shoppingList.ingredients);
65
+ ```
66
+
67
+ And you can categorize those ingredients according to an aisle configuration defined in the cooklang format:
68
+
69
+ ```typescript
70
+ const shoppingList = `
71
+ [Dairy]
72
+ milk
73
+ butter
74
+
75
+ [Bakery]
76
+ flour
77
+ sugar
78
+ `;
79
+
80
+ shoppingList.set_aisle_config(aisleConfig);
81
+ shoppingList.categorize();
82
+
83
+ console.log(shoppingList.categories);
84
+ ```
85
+
86
+ ## Test coverage
87
+
88
+ This project maintains a high level of test coverage to ensure reliability. You can run the tests yourself by cloning the repository and running `pnpm test`.
89
+
90
+ To see the coverage report, run `pnpm run test:coverage`.
91
+
92
+ ## License
93
+
94
+ MIT
@@ -0,0 +1,369 @@
1
+ declare class Section {
2
+ name: string;
3
+ content: (Step | Note)[];
4
+ constructor(name?: string);
5
+ isBlank(): boolean;
6
+ }
7
+
8
+ /**
9
+ * Represents a recipe.
10
+ * @category Classes
11
+ */
12
+ declare class Recipe {
13
+ /**
14
+ * The recipe's metadata.
15
+ * @see {@link Metadata}
16
+ */
17
+ metadata: Metadata;
18
+ /**
19
+ * The recipe's ingredients.
20
+ * @see {@link Ingredient}
21
+ */
22
+ ingredients: Ingredient[];
23
+ /**
24
+ * The recipe's sections.
25
+ * @see {@link Section}
26
+ */
27
+ sections: Section[];
28
+ /**
29
+ * The recipe's cookware.
30
+ * @see {@link Cookware}
31
+ */
32
+ cookware: Cookware[];
33
+ /**
34
+ * The recipe's timers.
35
+ * @see {@link Timer}
36
+ */
37
+ timers: Timer[];
38
+ /**
39
+ * The recipe's servings. Used for scaling
40
+ */
41
+ servings?: number;
42
+ /**
43
+ * Creates a new Recipe instance.
44
+ * @param content - The recipe content to parse.
45
+ */
46
+ constructor(content?: string);
47
+ /**
48
+ * Parses a recipe from a string.
49
+ * @param content - The recipe content to parse.
50
+ */
51
+ parse(content: string): void;
52
+ /**
53
+ * Scales the recipe to a new number of servings.
54
+ * @param newServings - The new number of servings.
55
+ * @returns A new Recipe instance with the scaled ingredients.
56
+ */
57
+ scaleTo(newServings: number): Recipe;
58
+ /**
59
+ * Scales the recipe by a factor.
60
+ * @param factor - The factor to scale the recipe by.
61
+ * @returns A new Recipe instance with the scaled ingredients.
62
+ */
63
+ scaleBy(factor: number): Recipe;
64
+ private getServings;
65
+ /**
66
+ * Clones the recipe.
67
+ * @returns A new Recipe instance with the same properties.
68
+ */
69
+ clone(): Recipe;
70
+ }
71
+
72
+ /**
73
+ * Represents the metadata of a recipe.
74
+ * @category Types
75
+ */
76
+ interface Metadata {
77
+ /** The title of the recipe. */
78
+ title?: string;
79
+ /** The tags of the recipe. */
80
+ tags?: string[];
81
+ /** The source of the recipe. */
82
+ source?: string;
83
+ /** The author of the recipe. */
84
+ author?: string;
85
+ /** The number of servings the recipe makes.
86
+ * Complex info can be given, as long as the first part before a comma has a numerical value, which will be used for scaling
87
+ * Interchangeable with `yield` or `serves`. If multiple ones are defined, the latest one will be used for scaling */
88
+ servings?: string;
89
+ /** The yield of the recipe.
90
+ * Complex info can be given, as long as the first part before a comma has a numerical value, which will be used for scaling
91
+ * Interchangeable with `servings` or `serves`. If multiple ones are defined, the latest one will be used for scaling
92
+ */
93
+ yield?: string;
94
+ /** The number of people the recipe serves.
95
+ * Complex info can be given, as long as the first part before a comma has a numerical value, which will be used for scaling
96
+ * Interchangeable with `servings` or `yield`. If multiple ones are defined, the latest one will be used for scaling
97
+ */
98
+ serves?: string;
99
+ /** The course of the recipe. */
100
+ course?: string;
101
+ /** The category of the recipe. */
102
+ category?: string;
103
+ /**
104
+ * The preparation time of the recipe.
105
+ * Will not be further parsed into any DateTime format nor normalize
106
+ */
107
+ "prep time"?: string;
108
+ /**
109
+ * Alias of `prep time`
110
+ */
111
+ "time.prep"?: string;
112
+ /**
113
+ * The cooking time of the recipe.
114
+ * Will not be further parsed into any DateTime format nor normalize
115
+ */
116
+ "cook time"?: string;
117
+ /**
118
+ * Alias of `cook time`
119
+ */
120
+ "time.cook"?: string;
121
+ /**
122
+ * The total time of the recipe.
123
+ * Will not be further parsed into any DateTime format nor normalize
124
+ */
125
+ "time required"?: string;
126
+ time?: string;
127
+ duration?: string;
128
+ /** The difficulty of the recipe. */
129
+ difficulty?: string;
130
+ /** The cuisine of the recipe. */
131
+ cuisine?: string;
132
+ /** The diet of the recipe. */
133
+ diet?: string;
134
+ /** The description of the recipe. */
135
+ description?: string;
136
+ /** The images of the recipe. */
137
+ images?: string[];
138
+ }
139
+ /**
140
+ * Represents the extracted metadata from a recipe.
141
+ * @category Types
142
+ */
143
+ interface MetadataExtract {
144
+ /** The metadata of the recipe. */
145
+ metadata: Metadata;
146
+ /** The number of servings the recipe makes. Used for scaling */
147
+ servings?: number;
148
+ }
149
+ /**
150
+ * Represents an ingredient in a recipe.
151
+ * @category Types
152
+ */
153
+ interface Ingredient {
154
+ /** The name of the ingredient. */
155
+ name: string;
156
+ /** The quantity of the ingredient. */
157
+ quantity?: number | string;
158
+ /** The unit of the ingredient. */
159
+ unit?: string;
160
+ /** The preparation of the ingredient. */
161
+ preparation?: string;
162
+ /** Whether the ingredient is optional. */
163
+ optional?: boolean;
164
+ /** Whether the ingredient is hidden. */
165
+ hidden?: boolean;
166
+ /** Whether the ingredient is a recipe. */
167
+ isRecipe?: boolean;
168
+ }
169
+ /**
170
+ * Represents a timer in a recipe.
171
+ * @category Types
172
+ */
173
+ interface Timer {
174
+ /** The name of the timer. */
175
+ name?: string;
176
+ /** The duration of the timer. */
177
+ duration: number;
178
+ /** The unit of the timer. */
179
+ unit: string;
180
+ }
181
+ /**
182
+ * Represents a text item in a recipe step.
183
+ * @category Types
184
+ */
185
+ interface TextItem {
186
+ /** The type of the item. */
187
+ type: "text";
188
+ /** The value of the item. */
189
+ value: string;
190
+ }
191
+ /**
192
+ * Represents an ingredient item in a recipe step.
193
+ * @category Types
194
+ */
195
+ interface IngredientItem {
196
+ /** The type of the item. */
197
+ type: "ingredient";
198
+ /** The value of the item. */
199
+ value: number;
200
+ }
201
+ /**
202
+ * Represents a cookware item in a recipe step.
203
+ * @category Types
204
+ */
205
+ interface CookwareItem {
206
+ /** The type of the item. */
207
+ type: "cookware";
208
+ /** The value of the item. */
209
+ value: number;
210
+ }
211
+ /**
212
+ * Represents a timer item in a recipe step.
213
+ * @category Types
214
+ */
215
+ interface TimerItem {
216
+ /** The type of the item. */
217
+ type: "timer";
218
+ /** The value of the item. */
219
+ value: number;
220
+ }
221
+ /**
222
+ * Represents an item in a recipe step.
223
+ * @category Types
224
+ */
225
+ type Item = TextItem | IngredientItem | CookwareItem | TimerItem;
226
+ /**
227
+ * Represents a step in a recipe.
228
+ * @category Types
229
+ */
230
+ interface Step {
231
+ /** The items in the step. */
232
+ items: Item[];
233
+ }
234
+ /**
235
+ * Represents a note in a recipe.
236
+ * @category Types
237
+ */
238
+ interface Note {
239
+ /** The content of the note. */
240
+ note: string;
241
+ }
242
+ /**
243
+ * Represents a piece of cookware in a recipe.
244
+ * @category Types
245
+ */
246
+ interface Cookware {
247
+ /** The name of the cookware. */
248
+ name: string;
249
+ /** Whether the cookware is optional. */
250
+ optional?: boolean;
251
+ /** Whether the cookware is hidden. */
252
+ hidden?: boolean;
253
+ }
254
+ /**
255
+ * Represents categorized ingredients.
256
+ * @category Types
257
+ */
258
+ interface CategorizedIngredients {
259
+ /** The category of the ingredients. */
260
+ [category: string]: Ingredient[];
261
+ }
262
+ /**
263
+ * Represents a recipe that has been added to a shopping list.
264
+ * @category Types
265
+ */
266
+ interface AddedRecipe {
267
+ /** The recipe that was added. */
268
+ recipe: Recipe;
269
+ /** The factor the recipe was scaled by. */
270
+ factor: number;
271
+ }
272
+ /**
273
+ * Represents an ingredient in an aisle.
274
+ * @category Types
275
+ */
276
+ interface AisleIngredient {
277
+ /** The name of the ingredient. */
278
+ name: string;
279
+ /** The aliases of the ingredient. */
280
+ aliases: string[];
281
+ }
282
+ /**
283
+ * Represents a category of aisles.
284
+ * @category Types
285
+ */
286
+ interface AisleCategory {
287
+ /** The name of the category. */
288
+ name: string;
289
+ /** The ingredients in the category. */
290
+ ingredients: AisleIngredient[];
291
+ }
292
+
293
+ /**
294
+ * Represents the aisle configuration for a shopping list.
295
+ * @category Classes
296
+ */
297
+ declare class AisleConfig {
298
+ /**
299
+ * The categories of aisles.
300
+ * @see {@link AisleCategory}
301
+ */
302
+ categories: AisleCategory[];
303
+ /**
304
+ * Creates a new AisleConfig instance.
305
+ * @param config - The aisle configuration to parse.
306
+ */
307
+ constructor(config?: string);
308
+ /**
309
+ * Parses an aisle configuration from a string.
310
+ * @param config - The aisle configuration to parse.
311
+ */
312
+ parse(config: string): void;
313
+ }
314
+
315
+ /**
316
+ * Represents a shopping list.
317
+ * @category Classes
318
+ */
319
+ declare class ShoppingList {
320
+ /**
321
+ * The ingredients in the shopping list.
322
+ * @see {@link Ingredient}
323
+ */
324
+ ingredients: Ingredient[];
325
+ /**
326
+ * The recipes in the shopping list.
327
+ * @see {@link AddedRecipe}
328
+ */
329
+ recipes: AddedRecipe[];
330
+ /**
331
+ * The aisle configuration for the shopping list.
332
+ * @see {@link AisleConfig}
333
+ */
334
+ aisle_config?: AisleConfig;
335
+ /**
336
+ * The categorized ingredients in the shopping list.
337
+ * @see {@link CategorizedIngredients}
338
+ */
339
+ categories?: CategorizedIngredients;
340
+ /**
341
+ * Creates a new ShoppingList instance.
342
+ * @param aisle_config_str - The aisle configuration to parse.
343
+ */
344
+ constructor(aisle_config_str?: string);
345
+ private calculate_ingredients;
346
+ /**
347
+ * Adds a recipe to the shopping list.
348
+ * @param recipe - The recipe to add.
349
+ * @param factor - The factor to scale the recipe by.
350
+ */
351
+ add_recipe(recipe: Recipe, factor?: number): void;
352
+ /**
353
+ * Removes a recipe from the shopping list.
354
+ * @param index - The index of the recipe to remove.
355
+ */
356
+ remove_recipe(index: number): void;
357
+ /**
358
+ * Sets the aisle configuration for the shopping list.
359
+ * @param config - The aisle configuration to parse.
360
+ */
361
+ set_aisle_config(config: string): void;
362
+ /**
363
+ * Categorizes the ingredients in the shopping list
364
+ * Will use the aisle config if any, otherwise all ingredients will be placed in the "other" category
365
+ */
366
+ categorize(): void;
367
+ }
368
+
369
+ export { type AddedRecipe, type AisleCategory, AisleConfig, type AisleIngredient, type CategorizedIngredients, type Cookware, type CookwareItem, type Ingredient, type IngredientItem, type Item, type Metadata, type MetadataExtract, type Note, Recipe, ShoppingList, type Step, type TextItem, type Timer, type TimerItem };