cooklang 1.0.3 → 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.
@@ -81,161 +81,31 @@ RSpec.describe Cooklang::Metadata do
81
81
  end
82
82
  end
83
83
 
84
- describe "recipe-specific accessors" do
84
+ describe "direct access to metadata" do
85
85
  let(:metadata) { described_class.new }
86
86
 
87
- describe "#servings" do
88
- it "returns integer servings" do
89
- metadata["servings"] = "4"
87
+ it "stores any data types" do
88
+ metadata["servings"] = 4
89
+ metadata["tags"] = ["pasta", "italian"]
90
+ metadata["rating"] = 4.5
91
+ metadata["published"] = true
92
+ metadata["source"] = { "name" => "Cookbook", "url" => "https://example.com" }
90
93
 
91
- expect(metadata.servings).to eq(4)
92
- end
93
-
94
- it "returns nil when not set" do
95
- expect(metadata.servings).to be_nil
96
- end
97
- end
98
-
99
- describe "#servings=" do
100
- it "sets servings" do
101
- metadata.servings = 6
102
-
103
- expect(metadata["servings"]).to eq(6)
104
- end
105
- end
106
-
107
- describe "#prep_time" do
108
- it "returns prep_time" do
109
- metadata["prep_time"] = "15 minutes"
110
-
111
- expect(metadata.prep_time).to eq("15 minutes")
112
- end
113
-
114
- it "returns prep-time as fallback" do
115
- metadata["prep-time"] = "15 minutes"
116
-
117
- expect(metadata.prep_time).to eq("15 minutes")
118
- end
119
-
120
- it "returns nil when not set" do
121
- expect(metadata.prep_time).to be_nil
122
- end
123
- end
124
-
125
- describe "#prep_time=" do
126
- it "sets prep_time" do
127
- metadata.prep_time = "20 minutes"
128
-
129
- expect(metadata["prep_time"]).to eq("20 minutes")
130
- end
131
- end
132
-
133
- describe "#cook_time" do
134
- it "returns cook_time" do
135
- metadata["cook_time"] = "30 minutes"
136
-
137
- expect(metadata.cook_time).to eq("30 minutes")
138
- end
139
-
140
- it "returns cook-time as fallback" do
141
- metadata["cook-time"] = "30 minutes"
142
-
143
- expect(metadata.cook_time).to eq("30 minutes")
144
- end
145
- end
146
-
147
- describe "#cook_time=" do
148
- it "sets cook_time" do
149
- metadata.cook_time = "25 minutes"
150
-
151
- expect(metadata["cook_time"]).to eq("25 minutes")
152
- end
153
- end
154
-
155
- describe "#total_time" do
156
- it "returns total_time" do
157
- metadata["total_time"] = "45 minutes"
158
-
159
- expect(metadata.total_time).to eq("45 minutes")
160
- end
161
-
162
- it "returns total-time as fallback" do
163
- metadata["total-time"] = "45 minutes"
164
-
165
- expect(metadata.total_time).to eq("45 minutes")
166
- end
167
- end
168
-
169
- describe "#total_time=" do
170
- it "sets total_time" do
171
- metadata.total_time = "50 minutes"
172
-
173
- expect(metadata["total_time"]).to eq("50 minutes")
174
- end
175
- end
176
-
177
- describe "#title" do
178
- it "returns title" do
179
- metadata["title"] = "Chocolate Cake"
180
-
181
- expect(metadata.title).to eq("Chocolate Cake")
182
- end
183
- end
184
-
185
- describe "#title=" do
186
- it "sets title" do
187
- metadata.title = "Vanilla Cake"
188
-
189
- expect(metadata["title"]).to eq("Vanilla Cake")
190
- end
191
- end
192
-
193
- describe "#source" do
194
- it "returns source" do
195
- metadata["source"] = "cookbook.com"
196
-
197
- expect(metadata.source).to eq("cookbook.com")
198
- end
199
- end
200
-
201
- describe "#source=" do
202
- it "sets source" do
203
- metadata.source = "my-blog.com"
204
-
205
- expect(metadata["source"]).to eq("my-blog.com")
206
- end
207
- end
208
-
209
- describe "#tags" do
210
- it "returns array when tags is array" do
211
- metadata["tags"] = ["dessert", "chocolate"]
212
-
213
- expect(metadata.tags).to eq(["dessert", "chocolate"])
214
- end
215
-
216
- it "splits string tags on comma" do
217
- metadata["tags"] = "dessert, chocolate, sweet"
218
-
219
- expect(metadata.tags).to eq(["dessert", "chocolate", "sweet"])
220
- end
221
-
222
- it "returns empty array when not set" do
223
- expect(metadata.tags).to eq([])
224
- end
225
-
226
- it "returns empty array for non-string, non-array values" do
227
- metadata["tags"] = 123
228
-
229
- expect(metadata.tags).to eq([])
230
- end
94
+ expect(metadata["servings"]).to eq(4)
95
+ expect(metadata["tags"]).to eq(["pasta", "italian"])
96
+ expect(metadata["rating"]).to eq(4.5)
97
+ expect(metadata["published"]).to eq(true)
98
+ expect(metadata["source"]).to eq({ "name" => "Cookbook", "url" => "https://example.com" })
231
99
  end
232
100
 
233
- describe "#tags=" do
234
- it "sets tags" do
235
- metadata.tags = ["breakfast", "quick"]
101
+ it "allows any custom fields" do
102
+ metadata["wine_pairing"] = "Pinot Noir"
103
+ metadata["my_custom_field"] = { "nested" => "data" }
104
+ metadata["notes"] = ["note1", "note2"]
236
105
 
237
- expect(metadata["tags"]).to eq(["breakfast", "quick"])
238
- end
106
+ expect(metadata["wine_pairing"]).to eq("Pinot Noir")
107
+ expect(metadata["my_custom_field"]).to eq({ "nested" => "data" })
108
+ expect(metadata["notes"]).to eq(["note1", "note2"])
239
109
  end
240
110
  end
241
111
  end
@@ -143,7 +143,7 @@ RSpec.describe Cooklang::Recipe do
143
143
 
144
144
  let(:expected_hash) do
145
145
  {
146
- metadata: { map: { "title" => "Test Recipe", "servings" => "4", "prep_time" => "15 minutes" } },
146
+ metadata: { map: { "title" => "Test Recipe", "servings" => 4, "prep_time" => "15 minutes" } },
147
147
  sections: [
148
148
  {
149
149
  name: nil,
@@ -174,7 +174,7 @@ RSpec.describe Cooklang::Recipe do
174
174
 
175
175
  let(:expected_json) do
176
176
  {
177
- "metadata" => { "map" => { "title" => "Test Recipe", "servings" => "4", "prep_time" => "15 minutes" } },
177
+ "metadata" => { "map" => { "title" => "Test Recipe", "servings" => 4, "prep_time" => "15 minutes" } },
178
178
  "sections" => [
179
179
  {
180
180
  "name" => nil,
data/spec/parser_spec.rb CHANGED
@@ -96,6 +96,73 @@ RSpec.describe Cooklang::Parser do
96
96
  end
97
97
  end
98
98
 
99
+ context "with fixed quantities" do
100
+ it "parses fixed quantity with unit" do
101
+ recipe = parser.parse("Season with @salt{=1%tsp}.")
102
+
103
+ expect(recipe.ingredients.size).to eq(1)
104
+ ingredient = recipe.ingredients.first
105
+ expect(ingredient.name).to eq("salt")
106
+ expect(ingredient.quantity).to eq(1)
107
+ expect(ingredient.unit).to eq("tsp")
108
+ expect(ingredient).to be_fixed
109
+ end
110
+
111
+ it "parses fixed quantity without unit" do
112
+ recipe = parser.parse("Add @eggs{=3} exactly.")
113
+
114
+ expect(recipe.ingredients.size).to eq(1)
115
+ ingredient = recipe.ingredients.first
116
+ expect(ingredient.name).to eq("eggs")
117
+ expect(ingredient.quantity).to eq(3)
118
+ expect(ingredient).to be_fixed
119
+ end
120
+
121
+ it "parses fixed quantity with space after equals" do
122
+ recipe = parser.parse("Add @salt{= 2%tsp}.")
123
+
124
+ ingredient = recipe.ingredients.first
125
+ expect(ingredient.quantity).to eq(2)
126
+ expect(ingredient.unit).to eq("tsp")
127
+ expect(ingredient).to be_fixed
128
+ end
129
+
130
+ it "parses fixed quantity with text value" do
131
+ recipe = parser.parse("Add @salt{=some%pinch}.")
132
+
133
+ ingredient = recipe.ingredients.first
134
+ expect(ingredient.quantity).to eq("some")
135
+ expect(ingredient.unit).to eq("pinch")
136
+ expect(ingredient).to be_fixed
137
+ end
138
+
139
+ it "parses fixed with no quantity as fixed with 'some'" do
140
+ recipe = parser.parse("Add @salt{=}.")
141
+
142
+ ingredient = recipe.ingredients.first
143
+ expect(ingredient.quantity).to eq("some")
144
+ expect(ingredient).to be_fixed
145
+ end
146
+
147
+ it "parses regular quantity as not fixed" do
148
+ recipe = parser.parse("Use @flour{500%g}.")
149
+
150
+ ingredient = recipe.ingredients.first
151
+ expect(ingredient).not_to be_fixed
152
+ end
153
+
154
+ it "parses mixed fixed and regular ingredients" do
155
+ recipe = parser.parse("Add @salt{=1%tsp} and @flour{500%g}.")
156
+
157
+ expect(recipe.ingredients.size).to eq(2)
158
+ salt = recipe.ingredients.find { |i| i.name == "salt" }
159
+ flour = recipe.ingredients.find { |i| i.name == "flour" }
160
+
161
+ expect(salt).to be_fixed
162
+ expect(flour).not_to be_fixed
163
+ end
164
+ end
165
+
99
166
  context "with cookware" do
100
167
  it "parses simple cookware" do
101
168
  recipe = parser.parse("Heat the #pan over medium heat.")
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cooklang
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Brooks
@@ -157,8 +157,13 @@ files:
157
157
  - spec/comprehensive_spec.rb
158
158
  - spec/cooklang_spec.rb
159
159
  - spec/fixtures/canonical.yaml
160
+ - spec/fixtures/comprehensive_recipe.cook
161
+ - spec/formatters/formatter_spec.rb
162
+ - spec/formatters/hash_spec.rb
163
+ - spec/formatters/json_spec.rb
160
164
  - spec/formatters/text_spec.rb
161
165
  - spec/integration/canonical_spec.rb
166
+ - spec/integration/metadata_canonical_spec.rb
162
167
  - spec/lexer_spec.rb
163
168
  - spec/models/cookware_spec.rb
164
169
  - spec/models/ingredient_spec.rb
@@ -192,15 +197,20 @@ required_rubygems_version: !ruby/object:Gem::Requirement
192
197
  - !ruby/object:Gem::Version
193
198
  version: '0'
194
199
  requirements: []
195
- rubygems_version: 3.7.1
200
+ rubygems_version: 3.7.2
196
201
  specification_version: 4
197
202
  summary: A Ruby parser for the Cooklang recipe markup language.
198
203
  test_files:
199
204
  - spec/comprehensive_spec.rb
200
205
  - spec/cooklang_spec.rb
201
206
  - spec/fixtures/canonical.yaml
207
+ - spec/fixtures/comprehensive_recipe.cook
208
+ - spec/formatters/formatter_spec.rb
209
+ - spec/formatters/hash_spec.rb
210
+ - spec/formatters/json_spec.rb
202
211
  - spec/formatters/text_spec.rb
203
212
  - spec/integration/canonical_spec.rb
213
+ - spec/integration/metadata_canonical_spec.rb
204
214
  - spec/lexer_spec.rb
205
215
  - spec/models/cookware_spec.rb
206
216
  - spec/models/ingredient_spec.rb