cooklang-parse 1.1.2 → 1.2.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
@@ -11,8 +11,8 @@
11
11
 
12
12
  - Full Cooklang spec support including ingredients, cookware, timers, metadata, sections, notes, and YAML frontmatter
13
13
  - Written in TypeScript with exported type definitions
14
- - Single function API — `parseCooklang(source)` returns a structured recipe
15
- - 188 tests with canonical parity against the [Rust reference implementation](https://github.com/cooklang/cooklang-rs)
14
+ - Single function API with extension presets — `parseCooklang(source, options?)`
15
+ - 235 tests with parity coverage against [cooklang-rs](https://github.com/cooklang/cooklang-rs) canonical and default parser behaviors
16
16
  - Source position tracking and parse error reporting
17
17
 
18
18
  ## Installation
@@ -32,16 +32,31 @@ const recipe = parseCooklang(`
32
32
  >> servings: 4
33
33
 
34
34
  Preheat #oven to 180C.
35
+
35
36
  Mix @flour{250%g} and @eggs{3} in a #bowl{}.
37
+
36
38
  Bake for ~{20%minutes}.
37
39
  `)
38
40
 
39
41
  recipe.metadata // { servings: 4 }
40
- recipe.ingredients // [{ type: "ingredient", name: "flour", quantity: 250, units: "g", fixed: false }, ...]
41
- recipe.cookware // [{ type: "cookware", name: "oven", quantity: 1, units: "" }, ...]
42
+ recipe.ingredients // [{ type: "ingredient", name: "flour", quantity: 250, units: "g", fixed: false, modifiers: {...}, relation: {...} }, ...]
43
+ recipe.cookware // [{ type: "cookware", name: "oven", quantity: 1, units: "", modifiers: {...}, relation: {...} }, ...]
42
44
  recipe.timers // [{ type: "timer", name: "", quantity: 20, units: "minutes" }]
43
- recipe.steps // [[{ type: "text", value: "Preheat " }, { type: "cookware", ... }, ...], ...]
45
+ recipe.inlineQuantities // [] in canonical mode
44
46
  recipe.errors // [] (parse errors and warnings)
47
+
48
+ // Steps are organized into sections:
49
+ recipe.sections[0].name // null (default unnamed section)
50
+ recipe.sections[0].content // array of { type: "step", items: [...] } and { type: "text", value: "..." }
51
+
52
+ // Each step contains ordered text + inline component tokens:
53
+ const step = recipe.sections[0].content[0] // { type: "step", items: [...] }
54
+ step.items
55
+ // [
56
+ // { type: "text", value: "Preheat " },
57
+ // { type: "cookware", name: "oven", quantity: 1, units: "", modifiers: {...}, relation: {...} },
58
+ // { type: "text", value: " to 180C." }
59
+ // ]
45
60
  ```
46
61
 
47
62
  ## Cooklang Syntax
@@ -61,31 +76,53 @@ recipe.errors // [] (parse errors and warnings)
61
76
  | `== Title ==` | Section header | `== For the sauce ==` |
62
77
  | `>> key: value` | Metadata directive | `>> servings: 4` |
63
78
  | `---` | YAML frontmatter block | See below |
64
- | `=@name{qty}` | Fixed quantity (won't scale) | `=@salt{1%tsp}` |
65
- | `@name{=qty}` | Fixed quantity (alternate) | `@salt{=1%tsp}` |
66
- | `@name{}(prep)` | Ingredient with preparation | `@flour{100%g}(sifted)` |
79
+ | `@name{=qty%unit}` | Fixed quantity (won't scale) | `@salt{=1%tsp}` |
80
+ | `@name{qty}(note)` | Ingredient with note | `@flour{100%g}(sifted)` |
81
+ | `#name(note)` | Cookware with note | `#pan(large)` |
67
82
  | `@name\|alias{}` | Pipe alias syntax | `@ground beef\|beef{}` |
68
83
 
69
84
  ## API
70
85
 
71
- ### `parseCooklang(source: string): CooklangRecipe`
86
+ ### `parseCooklang(source: string, options?: ParseCooklangOptions): CooklangRecipe`
72
87
 
73
88
  Parses a Cooklang source string into a structured recipe object.
74
89
 
90
+ ```typescript
91
+ interface ParseCooklangOptions {
92
+ extensions?: "canonical" | "all" // default: "canonical"
93
+ }
94
+ ```
95
+
96
+ - `"canonical"`: canonical/spec behavior (extensions off)
97
+ - `"all"`: cooklang-rs default behavior (modes + inline temperature quantities)
98
+
75
99
  ```typescript
76
100
  interface CooklangRecipe {
77
101
  metadata: Record<string, unknown>
78
- steps: RecipeStepItem[][]
79
- ingredients: RecipeIngredient[]
80
- cookware: RecipeCookware[]
81
- timers: RecipeTimer[]
82
- sections: string[]
83
- notes: string[]
102
+ sections: RecipeSection[] // Sections with interleaved steps and notes
103
+ ingredients: RecipeIngredient[] // Deduplicated across all steps
104
+ cookware: RecipeCookware[] // Deduplicated across all steps
105
+ timers: RecipeTimer[] // Deduplicated across all steps
106
+ inlineQuantities: Array<{ quantity: number | string; units: string }>
84
107
  errors: ParseError[]
108
+ warnings: ParseError[]
109
+ }
110
+
111
+ interface RecipeSection {
112
+ name: string | null // null for the default unnamed section
113
+ content: SectionContent[]
85
114
  }
115
+
116
+ type SectionContent =
117
+ | { type: "step"; items: RecipeStepItem[]; number?: number }
118
+ | { type: "text"; value: string } // Notes (> lines)
86
119
  ```
87
120
 
88
- **`steps`** is an array of steps, where each step is an array of items:
121
+ **`sections`** contains all recipe content. Each section has a `name` (null for the default section) and `content` — an interleaved array of steps and text (notes). Steps contain ordered `RecipeStepItem[]` arrays with text and typed tokens in document order.
122
+
123
+ **`ingredients`**, **`cookware`**, and **`timers`** are deduplicated across all steps.
124
+
125
+ ### Types
89
126
 
90
127
  ```typescript
91
128
  type RecipeStepItem =
@@ -93,27 +130,28 @@ type RecipeStepItem =
93
130
  | RecipeIngredient
94
131
  | RecipeCookware
95
132
  | RecipeTimer
96
- ```
97
-
98
- **`ingredients`**, **`cookware`**, and **`timers`** are deduplicated across all steps.
99
-
100
- ### Types
101
133
 
102
- ```typescript
103
134
  interface RecipeIngredient {
104
135
  type: "ingredient"
105
136
  name: string
137
+ alias?: string // from @name|alias{} syntax
106
138
  quantity: number | string
107
- units: string
139
+ units: string // only % separator: @name{qty%unit}
108
140
  fixed: boolean
109
- preparation?: string
141
+ note?: string // from @name{}(note) syntax
142
+ modifiers: RecipeModifiers
143
+ relation: IngredientRelation
110
144
  }
111
145
 
112
146
  interface RecipeCookware {
113
147
  type: "cookware"
114
148
  name: string
149
+ alias?: string
115
150
  quantity: number | string
116
- units: string
151
+ units: string // always ""
152
+ note?: string // from #name(note) syntax
153
+ modifiers: RecipeModifiers
154
+ relation: ComponentRelation
117
155
  }
118
156
 
119
157
  interface RecipeTimer {
@@ -148,10 +186,9 @@ const recipe = parseCooklang(`
148
186
  ---
149
187
  title: Sourdough Bread
150
188
  source: My grandmother
189
+ servings: 2
151
190
  ---
152
191
 
153
- >> servings: 2
154
-
155
192
  == Starter ==
156
193
  Mix @starter{100%g} with @water{100%g}
157
194
  Let ferment for ~{8%hours}
@@ -165,18 +202,20 @@ Knead in #mixing bowl{} for ~kneading{10%minutes}
165
202
  recipe.metadata
166
203
  // { title: "Sourdough Bread", source: "My grandmother", servings: 2 }
167
204
 
168
- recipe.sections
169
- // ["Starter", "Dough"]
205
+ recipe.sections.map(s => s.name)
206
+ // [null, "Starter", "Dough"]
170
207
 
171
208
  recipe.ingredients.map(i => `${i.quantity} ${i.units} ${i.name}`.trim())
172
209
  // ["100 g starter", "100 g water", "500 g flour", ...]
173
210
  ```
174
211
 
212
+ > **Note:** With YAML frontmatter (`---`), non-special `>> key: value` lines are treated as regular step text (matching [cooklang-rs](https://github.com/cooklang/cooklang-rs)). In `{ extensions: "all" }`, `[mode]/[define]/[duplicate]` directives still apply as configuration.
213
+
175
214
  ## Development
176
215
 
177
216
  ```bash
178
217
  bun install # Install dependencies
179
- bun test # Run all 188 tests
218
+ bun test # Run all 235 tests
180
219
  bun run build # Bundle + emit declarations
181
220
  bun run typecheck # Type-check without emitting
182
221
  bun run lint # Lint with Biome
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
- export { grammar, parseCooklang } from "./semantics";
1
+ export { parseCooklang } from "./parse-cooklang";
2
+ export { grammar } from "./parser/ohm-ast";
2
3
  export type * from "./types";
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AACpD,mBAAmB,SAAS,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAC1C,mBAAmB,SAAS,CAAA"}