@tmlmt/cooklang-parser 1.2.5 → 1.3.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 CHANGED
@@ -2,12 +2,16 @@
2
2
 
3
3
  A typescript library to parse and manipulate [cooklang](https://cooklang.org/) recipes.
4
4
 
5
+ <picture><img src="https://badges.ws/maintenance/yes/2025" /></picture>
6
+ <picture><img src="https://badges.ws/npm/dt/@tmlmt/cooklang-parser" /></picture>
7
+ <picture><img src="https://badges.ws/npm/l/@tmlmt/cooklang-parser" /></picture>
8
+ <picture><img src="https://badges.ws/github/release/tmlmt/cooklang-parser" /></picture>
9
+ [<img src="https://badges.ws/badge/documentation-5672CD?icon=vitepress" />](https://cooklang-parser.tmlmt.com)
10
+
5
11
  ## Introduction
6
12
 
7
13
  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
14
 
9
- The documentation is available at [https://cooklang-parser.tmlmt.com](https://cooklang-parser.tmlmt.com) (work in progress)
10
-
11
15
  ## Features
12
16
 
13
17
  - **Cooklang Compliant:** Fully compliant with the Cooklang specifications.
@@ -30,7 +34,7 @@ The documentation is available at [https://cooklang-parser.tmlmt.com](https://co
30
34
  - Also work with referencing etc., e.g. `Mix @wheat flour{100%g} with additional @&wheat flour|flour{50%g}` enables to get 150g of wheat flour in the ingredients list, and let you display "Mix wheat flour (100 g) with additional flour (50 g)" in your recipe renderer.
31
35
  - **Recipe Scaling:** Scale recipes by a given factor.
32
36
  - **Shopping Lists:** Generate shopping lists from one or more recipes.
33
- - **Aisle Configuration:** Categorize shopping list ingredients based on a custom aisle configuration.
37
+ - **Category Configuration:** Categorize shopping list ingredients based on a custom category configuration.
34
38
  - **Typescript:** Written in Typescript, providing type safety for all the data structures.
35
39
 
36
40
  ## Quick start
@@ -59,50 +63,14 @@ Serve hot.
59
63
  const recipe = new Recipe(recipeString);
60
64
 
61
65
  console.log(recipe.metadata.title); // "Pancakes"
62
- console.log(recipe.ingredients);
63
- console.log(recipe.cookware);
64
- console.log(recipe.timers);
65
- ```
66
-
67
- You can also create a shopping list from multiple recipes:
68
-
69
- ```typescript
70
- import { ShoppingList, Recipe } from "@tmlmt/cooklang-parser";
71
-
72
- const recipe1 = new Recipe(/* ... */);
73
- const recipe2 = new Recipe(/* ... */);
74
-
75
- const shoppingList = new ShoppingList();
76
- shoppingList.add_recipe(recipe1);
77
- shoppingList.add_recipe(recipe2);
78
-
79
- console.log(shoppingList.ingredients);
80
- ```
81
-
82
- And you can categorize those ingredients according to an aisle configuration defined in the cooklang format:
83
-
84
- ```typescript
85
- const shoppingList = `
86
- [Dairy]
87
- milk
88
- butter
89
-
90
- [Bakery]
91
- flour
92
- sugar
93
- `;
94
-
95
- shoppingList.set_aisle_config(aisleConfig);
96
- shoppingList.categorize();
97
-
98
- console.log(shoppingList.categories);
66
+ console.log(recipe.ingredients); // [{ name: "eggs", ...}, ...]
67
+ console.log(recipe.cookware); // [{ name: "pan", ...}]
68
+ console.log(recipe.timers); // [{ duration: 15, unit: "minutes", name: undefined}]
99
69
  ```
100
70
 
101
71
  ## Future plans
102
72
 
103
- I plan to further develop features depending on the needs I will encounter in using this library in a practical application. The current todo includes:
104
-
105
- - Ingredients aliases. See issue tmlmt/cooklang-parser#5
73
+ I plan to further develop features depending on the needs I will encounter in using this library in a practical application. The main item on the todo list is to improve the documentation with detailed explanation of the extensions, as well as examples.
106
74
 
107
75
  ## Test coverage
108
76
 
@@ -111,7 +79,3 @@ This project includes a test setup aimed at eventually ensuring reliable parsing
111
79
  You can run the tests yourself by cloning the repository and running `pnpm test`. To see the coverage report, run `pnpm test:coverage`.
112
80
 
113
81
  If you find any issue with your own examples of recipes, feel free to open an Issue and if you want to help fix it, to submit a Pull Request.
114
-
115
- ## License
116
-
117
- MIT
package/dist/index.cjs CHANGED
@@ -22,23 +22,22 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
22
22
  // src/index.ts
23
23
  var index_exports = {};
24
24
  __export(index_exports, {
25
- AisleConfig: () => AisleConfig,
25
+ CategoryConfig: () => CategoryConfig,
26
26
  Recipe: () => Recipe,
27
27
  Section: () => Section,
28
28
  ShoppingList: () => ShoppingList
29
29
  });
30
30
  module.exports = __toCommonJS(index_exports);
31
31
 
32
- // src/classes/aisle_config.ts
33
- var AisleConfig = class {
32
+ // src/classes/category_config.ts
33
+ var CategoryConfig = class {
34
34
  /**
35
- * Creates a new AisleConfig instance.
36
- * @param config - The aisle configuration to parse.
35
+ * Creates a new CategoryConfig instance.
36
+ * @param config - The category configuration to parse.
37
37
  */
38
38
  constructor(config) {
39
39
  /**
40
- * The categories of aisles.
41
- * @see {@link AisleCategory}
40
+ * The parsed categories of ingredients.
42
41
  */
43
42
  __publicField(this, "categories", []);
44
43
  if (config) {
@@ -46,8 +45,9 @@ var AisleConfig = class {
46
45
  }
47
46
  }
48
47
  /**
49
- * Parses an aisle configuration from a string.
50
- * @param config - The aisle configuration to parse.
48
+ * Parses a category configuration from a string into property
49
+ * {@link CategoryConfig.categories | categories}
50
+ * @param config - The category configuration to parse.
51
51
  */
52
52
  parse(config) {
53
53
  let currentCategory = null;
@@ -97,7 +97,10 @@ var Section = class {
97
97
  * @param name - The name of the section. Defaults to an empty string.
98
98
  */
99
99
  constructor(name = "") {
100
- /** The name of the section. Can be an empty string for the default (first) section. */
100
+ /**
101
+ * The name of the section. Can be an empty string for the default (first) section.
102
+ * @defaultValue `""`
103
+ */
101
104
  __publicField(this, "name");
102
105
  /** An array of steps and notes that make up the content of the section. */
103
106
  __publicField(this, "content", []);
@@ -793,32 +796,31 @@ var Recipe = class _Recipe {
793
796
  */
794
797
  constructor(content) {
795
798
  /**
796
- * The recipe's metadata.
797
- * @see {@link Metadata}
799
+ * The parsed recipe metadata.
798
800
  */
799
801
  __publicField(this, "metadata", {});
800
802
  /**
801
- * The recipe's ingredients.
802
- * @see {@link Ingredient}
803
+ * The parsed recipe ingredients.
803
804
  */
804
805
  __publicField(this, "ingredients", []);
805
806
  /**
806
- * The recipe's sections.
807
- * @see {@link Section}
807
+ * The parsed recipe sections.
808
808
  */
809
809
  __publicField(this, "sections", []);
810
810
  /**
811
- * The recipe's cookware.
812
- * @see {@link Cookware}
811
+ * The parsed recipe cookware.
813
812
  */
814
813
  __publicField(this, "cookware", []);
815
814
  /**
816
- * The recipe's timers.
817
- * @see {@link Timer}
815
+ * The parsed recipe timers.
818
816
  */
819
817
  __publicField(this, "timers", []);
820
818
  /**
821
- * The recipe's servings. Used for scaling
819
+ * The parsed recipe servings. Used for scaling. Parsed from one of
820
+ * {@link Metadata.servings}, {@link Metadata.yield} or {@link Metadata.serves}
821
+ * metadata fields.
822
+ *
823
+ * @see {@link Recipe.scaleBy | scaleBy()} and {@link Recipe.scaleTo | scaleTo()} methods
822
824
  */
823
825
  __publicField(this, "servings");
824
826
  if (content) {
@@ -980,9 +982,12 @@ var Recipe = class _Recipe {
980
982
  }
981
983
  }
982
984
  /**
983
- * Scales the recipe to a new number of servings.
985
+ * Scales the recipe to a new number of servings. In practice, it calls
986
+ * {@link Recipe.scaleBy | scaleBy} with a factor corresponding to the ratio between `newServings`
987
+ * and the recipe's {@link Recipe.servings | servings} value.
984
988
  * @param newServings - The new number of servings.
985
989
  * @returns A new Recipe instance with the scaled ingredients.
990
+ * @throws `Error` if the recipe does not contains an initial {@link Recipe.servings | servings} value
986
991
  */
987
992
  scaleTo(newServings) {
988
993
  const originalServings = this.getServings();
@@ -1067,32 +1072,28 @@ var Recipe = class _Recipe {
1067
1072
  // src/classes/shopping_list.ts
1068
1073
  var ShoppingList = class {
1069
1074
  /**
1070
- * Creates a new ShoppingList instance.
1071
- * @param aisle_config_str - The aisle configuration to parse.
1075
+ * Creates a new ShoppingList instance
1076
+ * @param category_config_str - The category configuration to parse.
1072
1077
  */
1073
- constructor(aisle_config_str) {
1078
+ constructor(category_config_str) {
1074
1079
  /**
1075
1080
  * The ingredients in the shopping list.
1076
- * @see {@link Ingredient}
1077
1081
  */
1078
1082
  __publicField(this, "ingredients", []);
1079
1083
  /**
1080
1084
  * The recipes in the shopping list.
1081
- * @see {@link AddedRecipe}
1082
1085
  */
1083
1086
  __publicField(this, "recipes", []);
1084
1087
  /**
1085
- * The aisle configuration for the shopping list.
1086
- * @see {@link AisleConfig}
1088
+ * The category configuration for the shopping list.
1087
1089
  */
1088
- __publicField(this, "aisle_config");
1090
+ __publicField(this, "category_config");
1089
1091
  /**
1090
1092
  * The categorized ingredients in the shopping list.
1091
- * @see {@link CategorizedIngredients}
1092
1093
  */
1093
1094
  __publicField(this, "categories");
1094
- if (aisle_config_str) {
1095
- this.set_aisle_config(aisle_config_str);
1095
+ if (category_config_str) {
1096
+ this.set_category_config(category_config_str);
1096
1097
  }
1097
1098
  }
1098
1099
  calculate_ingredients() {
@@ -1170,31 +1171,35 @@ var ShoppingList = class {
1170
1171
  this.categorize();
1171
1172
  }
1172
1173
  /**
1173
- * Sets the aisle configuration for the shopping list.
1174
- * @param config - The aisle configuration to parse.
1174
+ * Sets the category configuration for the shopping list
1175
+ * and automatically categorize current ingredients from the list.
1176
+ * @param config - The category configuration to parse.
1175
1177
  */
1176
- set_aisle_config(config) {
1177
- this.aisle_config = new AisleConfig(config);
1178
+ set_category_config(config) {
1179
+ if (typeof config === "string")
1180
+ this.category_config = new CategoryConfig(config);
1181
+ else if (config instanceof CategoryConfig) this.category_config = config;
1182
+ else throw new Error("Invalid category configuration");
1178
1183
  this.categorize();
1179
1184
  }
1180
1185
  /**
1181
1186
  * Categorizes the ingredients in the shopping list
1182
- * Will use the aisle config if any, otherwise all ingredients will be placed in the "other" category
1187
+ * Will use the category config if any, otherwise all ingredients will be placed in the "other" category
1183
1188
  */
1184
1189
  categorize() {
1185
- if (!this.aisle_config) {
1190
+ if (!this.category_config) {
1186
1191
  this.categories = { other: this.ingredients };
1187
1192
  return;
1188
1193
  }
1189
1194
  const categories = { other: [] };
1190
- for (const category of this.aisle_config.categories) {
1195
+ for (const category of this.category_config.categories) {
1191
1196
  categories[category.name] = [];
1192
1197
  }
1193
1198
  for (const ingredient of this.ingredients) {
1194
1199
  let found = false;
1195
- for (const category of this.aisle_config.categories) {
1196
- for (const aisleIngredient of category.ingredients) {
1197
- if (aisleIngredient.aliases.includes(ingredient.name)) {
1200
+ for (const category of this.category_config.categories) {
1201
+ for (const categoryIngredient of category.ingredients) {
1202
+ if (categoryIngredient.aliases.includes(ingredient.name)) {
1198
1203
  categories[category.name].push(ingredient);
1199
1204
  found = true;
1200
1205
  break;
@@ -1213,7 +1218,7 @@ var ShoppingList = class {
1213
1218
  };
1214
1219
  // Annotate the CommonJS export names for ESM import in node:
1215
1220
  0 && (module.exports = {
1216
- AisleConfig,
1221
+ CategoryConfig,
1217
1222
  Recipe,
1218
1223
  Section,
1219
1224
  ShoppingList