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.
- checksums.yaml +4 -4
- data/README.md +23 -7
- data/lib/cooklang/formatter.rb +0 -46
- data/lib/cooklang/formatters/hash.rb +4 -2
- data/lib/cooklang/formatters/text.rb +46 -0
- data/lib/cooklang/ingredient.rb +12 -5
- data/lib/cooklang/lexer.rb +53 -4
- data/lib/cooklang/metadata.rb +0 -64
- data/lib/cooklang/parsers/ingredient_parser.rb +10 -3
- data/lib/cooklang/processors/metadata_processor.rb +15 -35
- data/lib/cooklang/processors/step_processor.rb +1 -1
- data/lib/cooklang/version.rb +1 -1
- data/spec/fixtures/comprehensive_recipe.cook +51 -0
- data/spec/formatters/formatter_spec.rb +29 -0
- data/spec/formatters/hash_spec.rb +75 -0
- data/spec/formatters/json_spec.rb +55 -0
- data/spec/formatters/text_spec.rb +30 -168
- data/spec/integration/metadata_canonical_spec.rb +169 -0
- data/spec/lexer_spec.rb +4 -4
- data/spec/models/ingredient_spec.rb +59 -3
- data/spec/models/metadata_spec.rb +19 -149
- data/spec/models/recipe_spec.rb +2 -2
- data/spec/parser_spec.rb +67 -0
- metadata +12 -2
|
@@ -81,161 +81,31 @@ RSpec.describe Cooklang::Metadata do
|
|
|
81
81
|
end
|
|
82
82
|
end
|
|
83
83
|
|
|
84
|
-
describe "
|
|
84
|
+
describe "direct access to metadata" do
|
|
85
85
|
let(:metadata) { described_class.new }
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
-
|
|
238
|
-
|
|
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
|
data/spec/models/recipe_spec.rb
CHANGED
|
@@ -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" =>
|
|
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" =>
|
|
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
|
|
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.
|
|
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
|